diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 28353e61..6652c494 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,20 +1,30 @@ # 🔁 Pull Request Template – BharatMLStack - > Please fill out the following sections to help us review your changes efficiently. ---- +## Context: +Give a brief overview of the motivation behind this change. Include any relevant discussion links (Slack, documents, tickets, etc.) that help reviewers understand the background and the issue being addressed. -## 📌 Summary - -> e.g., Adds optimizes Redis fetch latency in `online-feature-store`, or improves search UI responsiveness in `trufflebox-ui`. +## Describe your changes: +Mention the changes made in the codebase. ---- +## Testing: +Please describe how you tested the code. If manual tests were performed - please explain how. If automatic tests were added or existing ones cover the change - please explain how did you run them. -## 📂 Modules Affected +## Monitoring: +Explain how this change will be tracked after deployment. Indicate whether current dashboards, alerts, and logs are enough, or if additional instrumentation is required. - +## Rollback plan +Explain rollback plan in case of issues. +## Checklist before requesting a review +- [ ] I have reviewed my own changes? +- [ ] Relevant or critical functionality is covered by tests? +- [ ] Monitoring needs have been evaluated? +- [ ] Any necessary documentation updates have been considered? + +## 📂 Modules Affected + - [ ] `horizon` (Real-time systems / networking) - [ ] `online-feature-store` (Feature serving infra) - [ ] `trufflebox-ui` (Admin panel / UI) @@ -38,5 +48,4 @@ ## 📊 Benchmark / Metrics (if applicable) - - + \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 76a653ea..ffe4e44c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: Integration & Coordination CI on: pull_request: - branches: [master, develop] + branches: [main, develop] push: - branches: [master, develop] + branches: [main, develop] workflow_dispatch: # Allow manual triggering env: @@ -20,8 +20,8 @@ jobs: online-feature-store-changed: ${{ steps.changes.outputs.online-feature-store }} go-sdk-changed: ${{ steps.changes.outputs.go-sdk }} py-sdk-changed: ${{ steps.changes.outputs.py-sdk }} - helix-client-changed: ${{ steps.changes.outputs.helix-client }} inferflow-changed: ${{ steps.changes.outputs.inferflow }} + skye-changed: ${{ steps.changes.outputs.skye }} multiple-components: ${{ steps.check-multiple.outputs.multiple }} any-component: ${{ steps.check-any.outputs.any }} steps: @@ -47,10 +47,10 @@ jobs: - 'go-sdk/**' py-sdk: - 'py-sdk/**' - helix-client: - - 'helix-client/**' inferflow: - 'inferflow/**' + skye: + - 'skye/**' - name: Check if multiple components changed id: check-multiple @@ -74,10 +74,10 @@ jobs: if [ "${{ steps.changes.outputs.py-sdk }}" == "true" ]; then changed_count=$((changed_count + 1)) fi - if [ "${{ steps.changes.outputs.helix-client }}" == "true" ]; then + if [ "${{ steps.changes.outputs.inferflow }}" == "true" ]; then changed_count=$((changed_count + 1)) fi - if [ "${{ steps.changes.outputs.inferflow }}" == "true" ]; then + if [ "${{ steps.changes.outputs.skye }}" == "true" ]; then changed_count=$((changed_count + 1)) fi @@ -90,7 +90,7 @@ jobs: - name: Check if any component changed id: check-any run: | - if [ "${{ steps.changes.outputs.horizon }}" == "true" ] || [ "${{ steps.changes.outputs.trufflebox-ui }}" == "true" ] || [ "${{ steps.changes.outputs.numerix }}" == "true" ] || [ "${{ steps.changes.outputs.online-feature-store }}" == "true" ] || [ "${{ steps.changes.outputs.go-sdk }}" == "true" ] || [ "${{ steps.changes.outputs.py-sdk }}" == "true" ] || [ "${{ steps.changes.outputs.helix-client }}" == "true" ] || [ "${{ steps.changes.outputs.inferflow }}" == "true" ]; then + if [ "${{ steps.changes.outputs.horizon }}" == "true" ] || [ "${{ steps.changes.outputs.trufflebox-ui }}" == "true" ] || [ "${{ steps.changes.outputs.numerix }}" == "true" ] || [ "${{ steps.changes.outputs.online-feature-store }}" == "true" ] || [ "${{ steps.changes.outputs.go-sdk }}" == "true" ] || [ "${{ steps.changes.outputs.py-sdk }}" == "true" ] || [ "${{ steps.changes.outputs.inferflow }}" == "true" ] || [ "${{ steps.changes.outputs.skye }}" == "true" ]; then echo "any=true" >> $GITHUB_OUTPUT else echo "any=false" >> $GITHUB_OUTPUT @@ -110,8 +110,8 @@ jobs: echo "- Online Feature Store CI: ${{ needs.detect-changes.outputs.online-feature-store-changed }}" echo "- Go SDK CI: ${{ needs.detect-changes.outputs.go-sdk-changed }}" echo "- Python SDK CI: ${{ needs.detect-changes.outputs.py-sdk-changed }}" - echo "- Helix Client CI: ${{ needs.detect-changes.outputs.helix-client-changed }}" echo "- Inferflow CI: ${{ needs.detect-changes.outputs.inferflow-changed }}" + echo "- Skye CI: ${{ needs.detect-changes.outputs.skye-changed }}" echo "This workflow will proceed with integration tests..." integration-tests: @@ -130,7 +130,6 @@ jobs: echo "Testing Numerix matrix operations integration..." echo "Testing Go SDK compatibility with backend services..." echo "Testing Python SDK compatibility with backend services..." - echo "Testing Helix Client compatibility with backend services..." echo "Testing Inferflow compatibility with backend services..." # Add actual integration test commands here @@ -211,5 +210,5 @@ jobs: echo "- Online Feature Store: ${{ needs.detect-changes.outputs.online-feature-store-changed }}" >> $GITHUB_STEP_SUMMARY echo "- Go SDK: ${{ needs.detect-changes.outputs.go-sdk-changed }}" >> $GITHUB_STEP_SUMMARY echo "- Python SDK: ${{ needs.detect-changes.outputs.py-sdk-changed }}" >> $GITHUB_STEP_SUMMARY - echo "- Helix Client: ${{ needs.detect-changes.outputs.helix-client-changed }}" >> $GITHUB_STEP_SUMMARY - echo "- Inferflow: ${{ needs.detect-changes.outputs.inferflow-changed }}" >> $GITHUB_STEP_SUMMARY \ No newline at end of file + echo "- Inferflow: ${{ needs.detect-changes.outputs.inferflow-changed }}" >> $GITHUB_STEP_SUMMARY + echo "- Skye: ${{ needs.detect-changes.outputs.skye-changed }}" >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.github/workflows/go-sdk.yml b/.github/workflows/go-sdk.yml index 1f0bce69..d2356a14 100644 --- a/.github/workflows/go-sdk.yml +++ b/.github/workflows/go-sdk.yml @@ -2,10 +2,10 @@ name: Go SDK CI on: pull_request: - branches: [master, develop] + branches: [main, develop] paths: ['go-sdk/**'] push: - branches: [master, develop] + branches: [main, develop] paths: ['go-sdk/**'] jobs: diff --git a/.github/workflows/helix-client.yml b/.github/workflows/helix-client.yml deleted file mode 100644 index 9afb5ba5..00000000 --- a/.github/workflows/helix-client.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: Helix Client CI - -on: - pull_request: - branches: [master, develop] - paths: ['helix-client/**'] - push: - branches: [master, develop] - paths: ['helix-client/**'] - -jobs: - build-helix-client: - runs-on: ubuntu-latest - defaults: - run: - working-directory: ./helix-client - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: '1.24' - cache: false - - - name: Cache Go modules - uses: actions/cache@v3 - with: - path: | - ~/.cache/go-build - ~/go/pkg/mod - key: ${{ runner.os }}-helix-client-${{ hashFiles('helix-client/go.sum') }} - restore-keys: | - ${{ runner.os }}-helix-client- - - - name: Download dependencies - run: go mod download - - - name: Verify dependencies - run: go mod verify - - - name: Run tests - run: go test -v ./... - - - name: Run tests with coverage - run: go test -v -coverprofile=coverage.out ./... - - - name: Display coverage - run: go tool cover -func=coverage.out - - - name: Build SDK packages - run: go build -v ./... - - - name: Run go vet - run: go vet ./... - - - name: Install staticcheck - run: go install honnef.co/go/tools/cmd/staticcheck@latest - - - name: Run staticcheck - run: staticcheck ./... - - - name: Check for go mod tidy - run: | - go mod tidy - if [ -n "$(git status --porcelain go.mod go.sum)" ]; then - echo "go.mod or go.sum is not tidy" - git diff go.mod go.sum - exit 1 - fi \ No newline at end of file diff --git a/.github/workflows/horizon.yml b/.github/workflows/horizon.yml index 33b7497c..a958ed21 100644 --- a/.github/workflows/horizon.yml +++ b/.github/workflows/horizon.yml @@ -2,10 +2,10 @@ name: Horizon CI on: pull_request: - branches: [master, develop] + branches: [main, develop] paths: ['horizon/**'] push: - branches: [master, develop] + branches: [main, develop] paths: ['horizon/**'] jobs: @@ -21,7 +21,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: '1.23' + go-version: '1.24' cache: false - name: Cache Go modules diff --git a/.github/workflows/inferflow.yml b/.github/workflows/inferflow.yml index bb93e1be..f5c7f22a 100644 --- a/.github/workflows/inferflow.yml +++ b/.github/workflows/inferflow.yml @@ -2,10 +2,10 @@ name: Inferflow CI on: pull_request: - branches: [master, develop] + branches: [main, develop] paths: ['inferflow/**'] push: - branches: [master, develop] + branches: [main, develop] paths: ['inferflow/**'] jobs: diff --git a/.github/workflows/interaction-store.yml b/.github/workflows/interaction-store.yml new file mode 100644 index 00000000..eca96afd --- /dev/null +++ b/.github/workflows/interaction-store.yml @@ -0,0 +1,56 @@ +name: Interaction Store CI + +on: + pull_request: + branches: [main, develop] + paths: ['interaction-store/**'] + push: + branches: [main, develop] + paths: ['interaction-store/**'] + +jobs: + build-interaction-store: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./interaction-store + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.24' + cache: false + + - name: Cache Go modules + uses: actions/cache@v3 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: ${{ runner.os }}-go-is-${{ hashFiles('interaction-store/go.sum') }} + restore-keys: | + ${{ runner.os }}-go-is- + + - name: Download dependencies + run: go mod download + + - name: Run tests + run: go test -v ./... + + - name: Build server + run: go build -v ./cmd/server + + - name: Build consumer + run: go build -v ./cmd/consumer + + - name: Run go vet + run: go vet ./... + + - name: Install staticcheck + run: go install honnef.co/go/tools/cmd/staticcheck@latest + + - name: Run staticcheck + run: staticcheck ./... diff --git a/.github/workflows/numerix.yml b/.github/workflows/numerix.yml index b1c6ba89..972dbec3 100644 --- a/.github/workflows/numerix.yml +++ b/.github/workflows/numerix.yml @@ -2,10 +2,10 @@ name: Numerix CI on: pull_request: - branches: [master, develop] + branches: [main, develop] paths: ['numerix/**'] push: - branches: [master, develop] + branches: [main, develop] paths: ['numerix/**'] jobs: diff --git a/.github/workflows/online-feature-store.yml b/.github/workflows/online-feature-store.yml index 822baf6a..d5f33d50 100644 --- a/.github/workflows/online-feature-store.yml +++ b/.github/workflows/online-feature-store.yml @@ -2,10 +2,10 @@ name: Online Feature Store CI on: pull_request: - branches: [master, develop] + branches: [main, develop] paths: ['online-feature-store/**'] push: - branches: [master, develop] + branches: [main, develop] paths: ['online-feature-store/**'] jobs: diff --git a/.github/workflows/py-sdk.yml b/.github/workflows/py-sdk.yml index 202d3224..799450f2 100644 --- a/.github/workflows/py-sdk.yml +++ b/.github/workflows/py-sdk.yml @@ -2,10 +2,10 @@ name: Python SDK CI on: pull_request: - branches: [master, develop] + branches: [main, develop] paths: ['py-sdk/**'] push: - branches: [master, develop] + branches: [main, develop] paths: ['py-sdk/**'] jobs: diff --git a/.github/workflows/release-helix-client.yml b/.github/workflows/release-helix-client.yml deleted file mode 100644 index 5ed33890..00000000 --- a/.github/workflows/release-helix-client.yml +++ /dev/null @@ -1,151 +0,0 @@ -name: Release Helix Client - -on: - workflow_dispatch: - inputs: - version: - description: 'Version to release (e.g., v0.1.20 or v0.1.20-beta.1)' - required: true - type: string - is_beta: - description: 'Is this a beta release?' - required: false - type: boolean - default: false - is_alpha: - description: 'Is this an alpha release?' - required: false - type: boolean - default: false - branch: - description: 'Branch to release from' - required: true - type: string - default: 'main' - -jobs: - release: - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ inputs.branch }} - - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: '1.24' - - - name: Validate Helix Client - run: | - echo "Current branch: ${{ github.ref }}" - cd helix-client - go mod tidy - go test -v ./... - go build -v ./... - go vet ./... - - - name: Create Git tag - run: | - git config --global user.name "github-actions[bot]" - git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" - - TAG="helix-client/${{ inputs.version }}" - git tag -a "$TAG" -m "Helix Client release ${{ inputs.version }}" - git push origin "$TAG" - - - name: Create GitHub Release (Production) - if: ${{ !inputs.is_beta && !inputs.is_alpha }} - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: helix-client/${{ inputs.version }} - release_name: Helix Client ${{ inputs.version }} - body: | - ## Helix Client Release ${{ inputs.version }} - - ### Installation - ```bash - go get github.com/${{ github.repository }}/helix-client@${{ inputs.version }} - ``` - - ### Features - - Go client libraries for BharatMLStack services - - Numerix, Predator, and Skye client integrations - - Type-safe API bindings and gRPC support - - ### Usage - ```go - import "github.com/${{ github.repository }}/helix-client" - ``` - - ### Changes - Please see the commit history for detailed changes. - - ### Validation - - ✅ Tests passed - - ✅ Build successful - - ✅ Go vet clean - draft: false - prerelease: false - - - name: Create Beta Pre-release - if: ${{ inputs.is_beta }} - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: helix-client/${{ inputs.version }} - release_name: Helix Client ${{ inputs.version }} (Beta) - body: | - ## Helix Client Beta Release ${{ inputs.version }} - - ⚠️ **This is a beta release from the develop branch** - Use for testing purposes only. - - ### Installation - ```bash - go get github.com/${{ github.repository }}/helix-client@${{ inputs.version }} - ``` - - ### Changes - Please see the commit history for detailed changes. - - ### Validation - - ✅ Tests passed - - ✅ Build successful - - ✅ Go vet clean - draft: false - prerelease: true - - - name: Create Alpha Pre-release - if: ${{ inputs.is_alpha }} - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: helix-client/${{ inputs.version }} - release_name: Helix Client ${{ inputs.version }} (Alpha) - body: | - ## Helix Client Alpha Release ${{ inputs.version }} - - ⚠️ **This is an alpha release from a feature/fix branch** - Experimental build for development and testing. - - ### Installation - ```bash - go get github.com/${{ github.repository }}/helix-client@${{ inputs.version }} - ``` - - ### Changes - Please see the commit history for detailed changes. - - ### Validation - - ✅ Tests passed - - ✅ Build successful - - ✅ Go vet clean - draft: false - prerelease: true \ No newline at end of file diff --git a/.github/workflows/release-interaction-store.yml b/.github/workflows/release-interaction-store.yml new file mode 100644 index 00000000..b23a220f --- /dev/null +++ b/.github/workflows/release-interaction-store.yml @@ -0,0 +1,166 @@ +name: Release Interaction Store + +on: + workflow_dispatch: + inputs: + version: + description: 'Version to release (e.g., v0.1.20 or v0.1.20-beta.1)' + required: true + type: string + is_beta: + description: 'Is this a beta release?' + required: false + type: boolean + default: false + is_alpha: + description: 'Is this an alpha release?' + required: false + type: boolean + default: false + branch: + description: 'Branch to release from' + required: true + type: string + default: 'main' + +env: + REGISTRY: ghcr.io + IMAGE_PREFIX: ghcr.io/${{ github.repository_owner }} + +jobs: + release: + runs-on: ubuntu-latest + permissions: + contents: write + packages: write + strategy: + matrix: + component: [ + {name: "interaction-store-server", dockerfile: "cmd/server/Dockerfile"}, + {name: "interaction-store-consumer", dockerfile: "cmd/consumer/Dockerfile"} + ] + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ inputs.branch }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE_PREFIX }}/${{ matrix.component.name }} + tags: | + type=raw,value=${{ inputs.version }} + type=raw,value=latest,enable=${{ !inputs.is_beta && !inputs.is_alpha }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: ./interaction-store + file: ./interaction-store/${{ matrix.component.dockerfile }} + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + create-release: + needs: release + runs-on: ubuntu-latest + if: ${{ !inputs.is_beta && !inputs.is_alpha }} + steps: + - name: Create GitHub Release (Production) + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: interaction-store/${{ inputs.version }} + release_name: Interaction Store ${{ inputs.version }} + body: | + ## Interaction Store Release ${{ inputs.version }} + + ### Docker Images + - `${{ env.IMAGE_PREFIX }}/interaction-store-server:${{ inputs.version }}` + - `${{ env.IMAGE_PREFIX }}/interaction-store-consumer:${{ inputs.version }}` + + ### Components + - **Server**: gRPC API server for interaction store operations + - **Consumer**: Kafka consumer for real-time interaction ingestion + + ### Changes + Please see the commit history for detailed changes. + draft: false + prerelease: false + + create-beta-release: + needs: release + runs-on: ubuntu-latest + if: ${{ inputs.is_beta }} + steps: + - name: Create GitHub Release (Beta) + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: interaction-store/${{ inputs.version }} + release_name: Interaction Store ${{ inputs.version }} (Beta) + body: | + ## Interaction Store Beta Release ${{ inputs.version }} + + ⚠️ **This is a beta release from the develop branch** - Use for testing purposes only. + + ### Docker Images + - `${{ env.IMAGE_PREFIX }}/interaction-store-server:${{ inputs.version }}` + - `${{ env.IMAGE_PREFIX }}/interaction-store-consumer:${{ inputs.version }}` + + ### Components + - **Server**: gRPC API server for interaction store operations + - **Consumer**: Kafka consumer for real-time interaction ingestion + + ### Changes + Please see the commit history for detailed changes. + draft: false + prerelease: true + + create-alpha-release: + needs: release + runs-on: ubuntu-latest + if: ${{ inputs.is_alpha }} + steps: + - name: Create GitHub Release (Alpha) + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: interaction-store/${{ inputs.version }} + release_name: Interaction Store ${{ inputs.version }} (Alpha) + body: | + ## Interaction Store Alpha Release ${{ inputs.version }} + + ⚠️ **This is an alpha release from a feature/fix branch** - Experimental build for development and testing. + + ### Docker Images + - `${{ env.IMAGE_PREFIX }}/interaction-store-server:${{ inputs.version }}` + - `${{ env.IMAGE_PREFIX }}/interaction-store-consumer:${{ inputs.version }}` + + ### Components + - **Server**: gRPC API server for interaction store operations + - **Consumer**: Kafka consumer for real-time interaction ingestion + + ### Changes + Please see the commit history for detailed changes. + draft: false + prerelease: true diff --git a/.github/workflows/release-skye.yml b/.github/workflows/release-skye.yml new file mode 100644 index 00000000..49eee560 --- /dev/null +++ b/.github/workflows/release-skye.yml @@ -0,0 +1,186 @@ +name: Release Skye + +on: + workflow_dispatch: + inputs: + version: + description: 'Version to release (e.g., v0.1.0 or v0.1.0-beta.1)' + required: true + type: string + is_beta: + description: 'Is this a beta release?' + required: false + type: boolean + default: false + is_alpha: + description: 'Is this an alpha release?' + required: false + type: boolean + default: false + branch: + description: 'Branch to release from' + required: true + type: string + default: 'main' + +env: + REGISTRY: ghcr.io + +jobs: + release: + runs-on: ubuntu-latest + permissions: + contents: write + packages: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ inputs.branch }} + + - name: Set image base (lowercase for ghcr.io) + run: echo "IMAGE_BASE=ghcr.io/$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]')/skye" >> $GITHUB_ENV + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (skye-admin) + id: meta-admin + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE_BASE }}-admin + tags: | + type=raw,value=${{ inputs.version }} + type=raw,value=latest,enable=${{ !inputs.is_beta && !inputs.is_alpha }} + + - name: Extract metadata (skye-consumers) + id: meta-consumers + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE_BASE }}-consumers + tags: | + type=raw,value=${{ inputs.version }} + type=raw,value=latest,enable=${{ !inputs.is_beta && !inputs.is_alpha }} + + - name: Extract metadata (skye-serving) + id: meta-serving + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE_BASE }}-serving + tags: | + type=raw,value=${{ inputs.version }} + type=raw,value=latest,enable=${{ !inputs.is_beta && !inputs.is_alpha }} + + - name: Build and push skye-admin + uses: docker/build-push-action@v5 + with: + context: ./skye + file: ./skye/cmd/admin/Dockerfile + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta-admin.outputs.tags }} + labels: ${{ steps.meta-admin.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Build and push skye-consumers + uses: docker/build-push-action@v5 + with: + context: ./skye + file: ./skye/cmd/consumers/Dockerfile + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta-consumers.outputs.tags }} + labels: ${{ steps.meta-consumers.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Build and push skye-serving + uses: docker/build-push-action@v5 + with: + context: ./skye + file: ./skye/cmd/serving/Dockerfile + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta-serving.outputs.tags }} + labels: ${{ steps.meta-serving.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Create GitHub Release (Production) + if: ${{ !inputs.is_beta && !inputs.is_alpha }} + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: skye/${{ inputs.version }} + release_name: Skye ${{ inputs.version }} + body: | + ## Skye Release ${{ inputs.version }} + + ### Docker Images + - **skye-admin:** `${{ env.IMAGE_BASE }}-admin:${{ inputs.version }}` / `:latest` + - **skye-consumers:** `${{ env.IMAGE_BASE }}-consumers:${{ inputs.version }}` / `:latest` + - **skye-serving:** `${{ env.IMAGE_BASE }}-serving:${{ inputs.version }}` / `:latest` + + ### Quickstart + See [skye/docs/QUICKSTART.md](https://github.com/${{ github.repository }}/blob/${{ inputs.branch }}/skye/docs/QUICKSTART.md) for run instructions. + + ### Changes + Please see the commit history for detailed changes. + draft: false + prerelease: false + + - name: Create GitHub Release (Beta) + if: ${{ inputs.is_beta }} + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: skye/${{ inputs.version }} + release_name: Skye ${{ inputs.version }} (Beta) + body: | + ## Skye Beta Release ${{ inputs.version }} + + ⚠️ **This is a beta release** - Use for testing purposes only. + + ### Docker Images + - `${{ env.IMAGE_BASE }}-admin:${{ inputs.version }}` + - `${{ env.IMAGE_BASE }}-consumers:${{ inputs.version }}` + - `${{ env.IMAGE_BASE }}-serving:${{ inputs.version }}` + + ### Changes + Please see the commit history for detailed changes. + draft: false + prerelease: true + + - name: Create GitHub Release (Alpha) + if: ${{ inputs.is_alpha }} + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: skye/${{ inputs.version }} + release_name: Skye ${{ inputs.version }} (Alpha) + body: | + ## Skye Alpha Release ${{ inputs.version }} + + ⚠️ **This is an alpha release** - Experimental build for development and testing. + + ### Docker Images + - `${{ env.IMAGE_BASE }}-admin:${{ inputs.version }}` + - `${{ env.IMAGE_BASE }}-consumers:${{ inputs.version }}` + - `${{ env.IMAGE_BASE }}-serving:${{ inputs.version }}` + + ### Changes + Please see the commit history for detailed changes. + draft: false + prerelease: true diff --git a/.github/workflows/skye.yml b/.github/workflows/skye.yml new file mode 100644 index 00000000..45c31437 --- /dev/null +++ b/.github/workflows/skye.yml @@ -0,0 +1,56 @@ +name: Skye CI + +on: + pull_request: + branches: [master, develop] + paths: ['skye/**'] + push: + branches: [master, develop] + paths: ['skye/**'] + +jobs: + build-skye: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./skye + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.24' + cache: false + + - name: Cache Go modules + uses: actions/cache@v3 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: ${{ runner.os }}-go-skye-${{ hashFiles('skye/go.sum') }} + restore-keys: | + ${{ runner.os }}-go-skye- + + - name: Download dependencies + run: go mod download + + - name: Run tests + run: go test -v ./... + + - name: Build binaries + run: | + go build -o /dev/null ./cmd/admin + go build -o /dev/null ./cmd/consumers + go build -o /dev/null ./cmd/serving + + - name: Run go vet + run: go vet ./... + + - name: Install staticcheck + run: go install honnef.co/go/tools/cmd/staticcheck@latest + + - name: Run staticcheck + run: staticcheck ./... diff --git a/.github/workflows/trufflebox-ui.yml b/.github/workflows/trufflebox-ui.yml index 77442b00..e920678f 100644 --- a/.github/workflows/trufflebox-ui.yml +++ b/.github/workflows/trufflebox-ui.yml @@ -2,10 +2,10 @@ name: Trufflebox UI CI on: pull_request: - branches: [master, develop] + branches: [main, develop] paths: ['trufflebox-ui/**'] push: - branches: [master, develop] + branches: [main, develop] paths: ['trufflebox-ui/**'] jobs: diff --git a/.gitignore b/.gitignore index 93b602fd..e130cc1d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,10 +4,14 @@ trufflebox-ui/node_modules/ dist/ .env* .idea/ +.vscode/ workspace/ .vscode/ .cursor/ __pycache__/ +java-sdk/onfs/feature-store-client/target/ +java-sdk/onfs/feature-store-flink-connector-sdk-flink1x/target/ +java-sdk/onfs/feature-store-core/target/ # Dev toggle script artifacts .internal-configs/ @@ -19,3 +23,11 @@ flashring/performance_results.csv flashring/mem.prof flashring/flashring flashring/flashringtest + + + +horizon/pem/*.pem +horizon/pem/*.key +horizon/configs/*.pem +go.work +go.work.sum diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c721100c..57dfa835 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,6 +4,15 @@ repos: - id: trufflehog name: TruffleHog description: Detect secrets in your data. - entry: "trufflehog/trufflehog-hook.sh" + entry: "pre-commit-scripts/runner.sh" language: script stages: ["pre-commit", "pre-push"] + - repo: local + hooks: + - id: post-commit + name: Post Commit + description: Run post-commit scripts. + entry: "post-commit-scripts/runner.sh" + language: script + stages: ["post-commit"] + always_run: true diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 2decad3c..00000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - - { - "name": "Shard", - "type": "go", - "request": "launch", - "mode": "debug", - "program": "${workspaceFolder}/ssd-cache/cmd/shardtest/main.go" - }, - { - "name": "Cache", - "type": "go", - "request": "launch", - "mode": "debug", - "program": "${workspaceFolder}/ssd-cache/cmd/cachetest/main.go" - }, - { - "name": "Flashring", - "type": "go", - "request": "launch", - "mode": "debug", - "program": "${workspaceFolder}/flashring/cmd/flashringtest", - "env": { - "PLAN": "readthrough-batched" - } - } - - ] -} \ No newline at end of file diff --git a/README.md b/README.md index 508c6f8d..b0c0ff31 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ -# BharatMLStack -
- BharatMLStack Logo + + + + BharatMLStack +
@@ -20,121 +22,95 @@ ## What is BharatMLStack? -BharatMLStack is a comprehensive, production-ready machine learning infrastructure platform designed to democratize ML capabilities across India and beyond. Our mission is to provide a robust, scalable, and accessible ML stack that empowers organizations to build, deploy, and manage machine learning solutions at massive scale. +BharatMLStack is a production-ready, cloud-agnostic ML infrastructure platform that powers real-time feature serving, model inference, and embedding search at massive scale. Built and battle-tested at [Meesho](https://meesho.com), it is designed to help organizations ship ML to production faster, cheaper, and more reliably. ## Our Vision -- 🎯 **Democratize Machine Learning**: Make advanced ML infrastructure accessible to organizations of all sizes -- 🚀 **Scale Without Limits**: Built to handle millions of requests per second with enterprise-grade reliability -- 🇮🇳 **Bharat-First Approach**: Optimized for Indian market needs while maintaining global standards -- ⚡ **Real-Time Intelligence**: Enable instant decision-making with sub-millisecond feature serving -- 🔧 **Developer-Friendly**: Intuitive APIs and interfaces that accelerate ML development cycles +BharatMLStack is built around **four core tenets**: -## Star History +### Workflow Integration & Productivity +> Ship ML to production faster than ever. -[![Star History Chart](https://api.star-history.com/svg?repos=Meesho/BharatMLStack&type=Date)](https://www.star-history.com/#Meesho/BharatMLStack&Date) +- **3x faster** experiment-to-deployment cycles +- **95% reduction** in model onboarding time -## Running at Million Scale +### Cloud-Agnostic & Lock-In Free +> Run anywhere. Own your stack. -BharatMLStack is battle-tested in production environments, powering: -- **1M+ feature vector retrievals per second** across distributed deployments -- **Sub-10ms latency** for real-time feature retrieval -- **99.99% uptime** with auto-scaling and fault tolerance -- **Petabyte-scale** feature storage and processing -- **Multi-region deployments** with global load balancing +- Runs across **public cloud, on-prem, and edge** +- Kubernetes-native with zero vendor lock-in -## Document -- [Doc](https://meesho.github.io/BharatMLStack/) -- [Blogs](https://meesho.github.io/BharatMLStack/blog) -## Core Components +### Economic Efficiency +> Do more with less. -### 📋 Current Releases - -| Component | Version | Description | -|-----------|---------|-------------| -| 🚀 **Horizon** | `v1.0.0` | Control Plane & Backend | -| 🎨 **Trufflebox UI** | `v1.0.0` | ML Management Console | -| 🗄️ **Online Feature Store** | `v1.0.0` | Real-Time Features | -| 🐹 **Go SDK** | `v1.0.0` | Go Client Library | -| 🐍 **Python SDK** | `v1.0.1` | Python Client Library | -| 🚀 **Numerix** | `v1.0.0` | Mathematical Compute Engine | - -### 🚀 Horizon - Control Plane & Backend -The central control plane for BharatMLStack components, serving as the backend for Trufflebox UI. -- **Component orchestration**: Manages and coordinates all BharatMLStack services -- **API gateway**: Unified interface for all MLOps and workflows - -### 🎨 Trufflebox UI - ML Management Console -Modern web interface for managing ML models, features, and experiments. Currently it supports: -- **Feature Registry**: Centralized repository for feature definitions and metadata -- **Feature Cataloging**: Discovery and search capabilities for available features -- **Online Feature Store Control System**: Management interface for feature store operations -- **Approval Flows**: Workflow management for feature deployment and changes - -### 🗄️ Online Feature Store - Real-Time Features -High-performance feature store for real-time ML inference and training. -- **Real-time serving**: Sub-10ms feature retrieval at scale -- **Streaming ingestion**: Process millions of feature updates per second -- **Feature Backward Compatible Versioning**: Track and manage feature evolution -- **Multi-source integration**: Push from stream, batch and real-time sources - -### 🗄️ Numerix - Mathematical Compute Engine -High-performance feature store for real-time ML inference and training. -- **Matrix Operations**: High-performance matrix computations and transformations -- **gRPC API**: Fast binary protocol for efficient data transfer -- **Multi-format Support**: String and byte-based matrix formats -- **Optimized Performance**: Built with Rust for maximum efficiency -- **Scalable Architecture**: Designed for distributed processing - -## Key Differentiators - -- ✨ **Production-Ready**: Battle-tested components used in high-traffic production systems -- 🌐 **Cloud Agnostic**: Kubernetes-native, so deploy on the cloud you love -- 📊 **Observability**: Built-in monitoring, logging +- **60–70% lower** infrastructure costs vs hyperscaler managed services +- Optimized resource utilization across CPU and GPU workloads -## Quick Start +### Availability & Scalability +> Enterprise-grade reliability at internet scale. -🚀 **Get started with BharatMLStack in minutes!** +- **99.99% uptime** across clusters +- **1M+ QPS** with low latency -For comprehensive setup instructions, examples, and deployment guides, see our detailed Quick Start documentation: +## Designed Truly for Bharat Scale -📖 **[Quick Start Guide →](./quick-start/README.md)** +Built for the demands of one of the world's largest e-commerce platforms: -### What You'll Find: +| Metric | Performance | +|--------|-------------| +| **Feature Store** | 2.4M QPS (batch of 100 id lookups) | +| **Model Inference** | 1M+ QPS | +| **Embedding Search** | 500K QPS | +| **Feature Retrieval Latency** | Sub-10ms | -- **🐳 Docker Setup**: Complete stack deployment with Docker Compose -- **📊 Sample Data**: Pre-configured examples to get you started -- **🔍 Health Checks**: Verify your deployment is working -- **📝 Step-by-Step Tutorials**: From installation to first feature operations +## Core Components -### TL;DR - One Command Setup: +| Component | Description | Version | Docs | +|-----------|-------------|---------|------| +| **[TruffleBox UI](./trufflebox-ui/)** | Web console for feature registry, cataloging, and approval workflows | `v1.3.0` | [Docs](https://meesho.github.io/BharatMLStack/trufflebox-ui/v1.0.0/userguide) | +| **[Online Feature Store](./online-feature-store/)** | Sub-10ms feature retrieval at millions of QPS with streaming ingestion | `v1.2.0` | [Docs](https://meesho.github.io/BharatMLStack/category/online-feature-store) | +| **[Inferflow](./inferflow/)** | DAG-based real-time inference orchestration for composable ML pipelines | `v1.0.0` | [Docs](https://meesho.github.io/BharatMLStack/category/inferflow) | +| **[Numerix](./numerix/)** | Rust-powered math compute engine for high-performance matrix ops | `v1.0.0` | [Docs](https://meesho.github.io/BharatMLStack/category/numerix) | +| **[Skye](./skye/)** | Vector similarity search with pluggable backends | `v1.0.0` | [Docs](https://meesho.github.io/BharatMLStack/category/skye) | +| **[Go SDK](./go-sdk/)** | Go client for Feature Store, Interaction Store, and logging | `v1.3.0` | [Docs](https://meesho.github.io/BharatMLStack/category/go-sdk) | +| **[Python SDK](./py-sdk/)** | Python client libraries for Feature Store and inference logging | `v1.0.1` | [Docs](https://meesho.github.io/BharatMLStack/category/python-sdk) | +| **[Interaction Store](./interaction-store/)** | ScyllaDB-backed store for user interaction signals at sub-10ms | — | — | +| **[Horizon](./horizon/)** | Control plane that orchestrates all services and powers TruffleBox UI | `v1.3.0` | — | + +> Full documentation at [meesho.github.io/BharatMLStack](https://meesho.github.io/BharatMLStack/) | [Blogs](https://meesho.github.io/BharatMLStack/blog) +- [All Blog Posts](https://meesho.github.io/BharatMLStack/blog) + +## Quick Start ```bash -# Clone and start the complete stack git clone https://github.com/Meesho/BharatMLStack.git cd BharatMLStack/quick-start -ONFS_VERSION= HORIZON_VERSION= TRUFFLEBOX_VERSION= NUMERIX_VERSION= ./start.sh +#Set versions +ONFS_VERSION=v1.2.0 HORIZON_VERSION=v1.3.0 TRUFFLEBOX_VERSION=v1.3.0 NUMERIX_VERSION=v1.0.0 + +./start.sh ``` -Then follow the [Quick Start Guide](./quick-start/README.md) for detailed setup and usage instructions. +For step-by-step setup, Docker Compose details, sample data, and health checks, see the full **[Quick Start Guide →](./quick-start/README.md)**. ## Architecture -BharatMLStack follows a microservices architecture designed for scalability and maintainability. Several components are to be open-sourced -
- BharatMLStack Logo + BharatMLStack Architecture
-### 🚀 Quick Navigation +## Use-Cases + +BharatMLStack powers a wide range of ML-driven applications: -| Component | Documentation | Quick Start | -|-----------|--------------|-------------| -| **Online Feature Store** | [Docs](https://meesho.github.io/BharatMLStack/category/online-feature-store) | [Setup](./quick-start/README.md) | -| **Go SDK** | [Docs](./go-sdk/README.md) | [Examples](./go-sdk/README.md) | -| **Python SDK** | [Docs](./py-sdk/README.md) | [Quickstart](./py-sdk/README.md) | -| **User Guide** | [Docs](https://meesho.github.io/BharatMLStack/trufflebox-ui/v1.0.0/userguide) | [Setup](./quick-start/README.md) | -| **Numerix** | [Docs](https://meesho.github.io/BharatMLStack/category/numerix) | [Setup](./quick-start/README.md) | +| Use-Case | What BharatMLStack Enables | +|----------|---------------------------| +| **Personalized Candidate Generation** | Retrieve and rank millions of candidates in real time using feature vectors and embedding similarity | +| **Personalized Ranking** | Serve user, item, and context features at ultra-low latency to power real-time ranking models | +| **Fraud & Risk Detection** | Stream interaction signals and features to detect anomalies and fraudulent patterns in milliseconds | +| **Image Search** | Run embedding search at 500K QPS to match visual queries against massive product catalogs | +| **LLM Recommender Systems** | Orchestrate LLM inference pipelines with feature enrichment for next-gen recommendation engines | +| **DL & LLM Deployments at Scale** | Deploy and scale deep learning and large language models across GPU clusters with Inferflow orchestration | ## Contributing @@ -142,9 +118,9 @@ We welcome contributions from the community! Please see our [Contributing Guide] ## Community & Support -- 💬 **Discord**: Join our [community chat](https://discord.gg/XkT7XsV2AU) -- 🐛 **Issues**: Report bugs and request features on [GitHub Issues](https://github.com/Meesho/BharatMLStack/issues) -- 📧 **Email**: Contact us at [ml-oss@meesho.com](mailto:ml-oss@meesho.com ) +- **Discord**: Join our [community chat](https://discord.gg/XkT7XsV2AU) +- **Issues**: Report bugs and request features on [GitHub Issues](https://github.com/Meesho/BharatMLStack/issues) +- **Email**: Contact us at [ml-oss@meesho.com](mailto:ml-oss@meesho.com) ## License diff --git a/assets/bharatmlstack-architecture.png b/assets/bharatmlstack-architecture.png new file mode 100644 index 00000000..afa5b787 Binary files /dev/null and b/assets/bharatmlstack-architecture.png differ diff --git a/assets/bharatmlstack-logo.png b/assets/bharatmlstack-logo.png new file mode 100644 index 00000000..9756a1ec Binary files /dev/null and b/assets/bharatmlstack-logo.png differ diff --git a/cli-tools/go.mod b/cli-tools/go.mod index 860ddb30..7205fdd5 100644 --- a/cli-tools/go.mod +++ b/cli-tools/go.mod @@ -30,4 +30,4 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gorm.io/driver/mysql v1.5.6 // indirect gorm.io/gorm v1.25.10 // indirect -) +) \ No newline at end of file diff --git a/cli-tools/go.sum b/cli-tools/go.sum index 5b793032..20b99726 100644 --- a/cli-tools/go.sum +++ b/cli-tools/go.sum @@ -99,4 +99,4 @@ gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkD gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A= gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s= -gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= \ No newline at end of file diff --git a/core/activity-contracts/activity/activity.go b/core/activity-contracts/activity/activity.go new file mode 100644 index 00000000..5d26857e --- /dev/null +++ b/core/activity-contracts/activity/activity.go @@ -0,0 +1,35 @@ +package activity + +import "context" + +// GlobalActivityOutputs maps each action name to a list of its result maps. +// {"noop.log": [{"logged": "hello"}, {"logged": "world"}], "noop.echo": [...]} +type GlobalActivityOutputs map[string][]map[string]interface{} + +// ActivityInput is the generic input passed to all activities. +type ActivityInput struct { + Action string `json:"action"` + Params map[string]interface{} `json:"params"` + StepName string `json:"step_name"` + WorkflowExecutionId string `json:"workflow_execution_id"` + Async bool `json:"async"` + PluginVersion string `json:"plugin_version"` + StepId string `json:"step_id,omitempty"` + // Attempt is the 1-based Temporal activity attempt number for this invocation. + // Async plugins must include this in the payload they send to the external system + // so that callbacks can be routed back to the exact attempt that triggered the work. + Attempt int `json:"attempt,omitempty"` + // Config holds plugin-specific configuration injected from etcd at call time. + // Plugins should read environment-specific values from here instead of os.Getenv, + // so that config can be updated in etcd without redeploying the orchestrator. + Config map[string]string `json:"config,omitempty"` +} + +// ActivityOutput is the generic output returned by all activities. +type ActivityOutput struct { + Result map[string]interface{} `json:"result,omitempty"` + Error string `json:"error,omitempty"` +} + +// ActivityFunc is the function signature that every registered activity must implement. +type ActivityFunc func(ctx context.Context, globalOutputs GlobalActivityOutputs, input ActivityInput) (ActivityOutput, error) diff --git a/core/activity-contracts/go.mod b/core/activity-contracts/go.mod new file mode 100644 index 00000000..7ce73862 --- /dev/null +++ b/core/activity-contracts/go.mod @@ -0,0 +1,3 @@ +module github.com/Meesho/BharatMLStack/core/activity-contracts + +go 1.25.6 diff --git a/dev-toggle-go.sh b/dev-toggle-go.sh new file mode 100755 index 00000000..2ba9ecf9 --- /dev/null +++ b/dev-toggle-go.sh @@ -0,0 +1,842 @@ +#!/bin/bash + +set -e + +# Get the script directory (parent directory where this script lives) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +get_available_folders() { + local folders=() + for dir in "$SCRIPT_DIR"/*; do + if [ -d "$dir" ] && [ -f "$dir/go.mod" ]; then + local folder_name=$(basename "$dir") + folders+=("$folder_name") + fi + done + echo "${folders[@]}" +} + +interactive_select_folder() { + local available_folders=($(get_available_folders)) + + if [ ${#available_folders[@]} -eq 0 ]; then + log_error "No folders with go.mod found in $SCRIPT_DIR" + exit 1 + fi + + echo "" >&2 + echo "==========================================" >&2 + echo " Step 1: Select Folder(s)" >&2 + echo "==========================================" >&2 + echo "" >&2 + echo "Available folders:" >&2 + local index=1 + for folder in "${available_folders[@]}"; do + echo " [$index] $folder" >&2 + ((index++)) + done + echo "" >&2 + echo "You can select:" >&2 + echo " • Single folder: Enter a number (e.g., 1)" >&2 + echo " • Multiple folders: Enter numbers separated by comma or space (e.g., 1,2 or 1 2 3)" >&2 + echo "" >&2 + + while true; do + read -p "Select folder(s): " selection + + # Remove any extra spaces + selection=$(echo "$selection" | tr -s ' ' | tr ',' ' ') + + # Validate all selections + local valid=true + local selected_folders=() + local IFS=' ' + for num in $selection; do + if [[ "$num" =~ ^[0-9]+$ ]] && [ "$num" -ge 1 ] && [ "$num" -le ${#available_folders[@]} ]; then + selected_folders+=("${available_folders[$((num-1))]}") + else + valid=false + break + fi + done + + if [ "$valid" = true ] && [ ${#selected_folders[@]} -gt 0 ]; then + # Remove duplicates + local unique_folders=() + for folder in "${selected_folders[@]}"; do + local is_duplicate=false + for unique in "${unique_folders[@]}"; do + if [ "$folder" = "$unique" ]; then + is_duplicate=true + break + fi + done + if [ "$is_duplicate" = false ]; then + unique_folders+=("$folder") + fi + done + + # Output folders separated by space (will be captured as array) to stdout + echo "${unique_folders[@]}" + return 0 + else + echo "Invalid selection. Please enter number(s) between 1 and ${#available_folders[@]}." >&2 + echo " Examples: 1 or 1,2 or 1 2 3" >&2 + fi + done +} + +interactive_select_command() { + echo "" >&2 + echo "==========================================" >&2 + echo " Step 2: Select Command" >&2 + echo "==========================================" >&2 + echo "" >&2 + echo "Available commands:" >&2 + echo " [1] enable - Enable development mode (clone internal repo, copy files, update go.mod)" >&2 + echo " [2] disable - Disable development mode (remove copied files and go.mod changes)" >&2 + echo " [3] status - Show current development mode status" >&2 + echo " [4] update - Update internal configs (pull latest from internal repo)" >&2 + echo "" >&2 + + while true; do + read -p "Select command (1-4): " selection + case "$selection" in + 1) + echo "enable" + return 0 + ;; + 2) + echo "disable" + return 0 + ;; + 3) + echo "status" + return 0 + ;; + 4) + echo "update" + return 0 + ;; + *) + echo "Invalid selection. Please enter a number between 1 and 4." >&2 + ;; + esac + done +} + +select_internal_repo_branch() { + local provided_branch="${1:-}" + local branch="" + + # 1) explicit arg wins + if [ -n "$provided_branch" ]; then + branch="$provided_branch" + # 2) env var next + elif [ -n "${INTERNAL_REPO_BRANCH:-}" ]; then + branch="$INTERNAL_REPO_BRANCH" + # 3) interactive prompt if we have a TTY + elif [ -t 0 ]; then + echo "" >&2 + echo "==========================================" >&2 + echo " Step 3: Select Internal Configs Branch" >&2 + echo "==========================================" >&2 + echo "" >&2 + echo "Choose the branch to use from internal configs repo:" >&2 + echo " [1] develop (default)" >&2 + echo " [2] main" >&2 + echo " [3] enter a custom branch name (e.g., feature/my-change)" >&2 + echo "" >&2 + + while true; do + read -p "Select branch (1-2 or name): " selection + selection="$(echo "${selection:-}" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')" + case "$selection" in + ""|1|develop) + branch="develop" + break + ;; + 2|main) + branch="main" + break + ;; + 3) + read -p "Enter branch name: " custom + branch="${custom:-}" + break + ;; + *) + # Treat anything else as a branch name + branch="$selection" + break + ;; + esac + done + else + # 4) non-interactive default + branch="develop" + fi + + branch="$(echo "${branch:-}" | tr -d '\r' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')" + if [ -z "$branch" ]; then + log_error "Internal configs branch cannot be empty" + exit 1 + fi + + # Validate branch name as a proper git branch ref (safe for checkout/clone) + if command -v git >/dev/null 2>&1; then + if ! git check-ref-format --branch "$branch" >/dev/null 2>&1; then + log_error "Invalid internal configs branch name: '$branch'" + exit 1 + fi + fi + + echo "$branch" + return 0 +} + +print_usage() { + echo "Usage: $0 [branch]" + echo "" + echo "Parameters:" + echo " 1. folder-name - Name of the folder to operate on" + echo "" + + # Detect and show available folders + local available_folders=($(get_available_folders)) + if [ ${#available_folders[@]} -gt 0 ]; then + echo " Available folders:" + for folder in "${available_folders[@]}"; do + echo " • $folder" + done + else + echo " Available folders: (none found with go.mod)" + fi + + echo "" + echo " 2. command - Action to perform" + echo "" + echo " Available commands:" + echo " • enable - Enable development mode (clone internal repo, copy files, update go.mod)" + echo " • disable - Disable development mode (remove copied files and go.mod changes)" + echo " • status - Show current development mode status" + echo " • update - Update internal configs (pull latest from internal repo)" + echo "" + echo " 3. branch - (Optional) Internal configs repo branch to use (default: develop)" + echo " Examples: develop, main, feature/my-change" + echo " You can also set INTERNAL_REPO_BRANCH env var." + echo "" + echo "Examples:" + if [ ${#available_folders[@]} -gt 0 ]; then + local first_folder="${available_folders[0]}" + echo " $0 $first_folder enable" + echo " $0 $first_folder enable develop" + echo " $0 $first_folder enable main" + if [ ${#available_folders[@]} -gt 1 ]; then + local second_folder="${available_folders[1]}" + echo " $0 $second_folder status" + fi + echo " $0 $first_folder disable" + echo " $0 $first_folder update" + else + echo " $0 online-feature-store enable" + echo " $0 horizon status" + echo " $0 online-feature-store disable" + fi + echo "" + exit 1 +} + +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +log_debug() { + echo -e "${BLUE}[DEBUG]${NC} $1" +} + +# Validate folder name and set up paths +validate_and_setup() { + FOLDER_NAME="${1:-}" + COMMAND="${2:-}" + + if [ -z "$FOLDER_NAME" ]; then + log_error "Folder name is required" + print_usage + fi + + if [ -z "$COMMAND" ]; then + log_error "Command is required" + print_usage + fi + + # Set up paths based on folder name + TARGET_DIR="$SCRIPT_DIR/$FOLDER_NAME" + STATE_FILE="$TARGET_DIR/.dev-toggle-state" + GO_MOD_FILE="$TARGET_DIR/go.mod" + GO_MOD_APPEND_FILE="$TARGET_DIR/.go.mod.appended" + + INTERNAL_REPO_URL="https://github.com/Meesho/BharatMLStack-internal-configs" + INTERNAL_REPO_DIR="$TARGET_DIR/.internal-configs" + INTERNAL_FOLDER_DIR="$INTERNAL_REPO_DIR/$FOLDER_NAME" + + # Validate that target directory exists + if [ ! -d "$TARGET_DIR" ]; then + log_error "Target directory does not exist: $TARGET_DIR" + exit 1 + fi + + # Validate that go.mod exists in target directory + if [ ! -f "$GO_MOD_FILE" ]; then + log_error "go.mod not found in target directory: $TARGET_DIR" + log_error "This script is designed for Go projects with go.mod files" + exit 1 + fi + + log_debug "Target directory: $TARGET_DIR" + log_debug "State file: $STATE_FILE" + log_debug "Internal repo directory: $INTERNAL_REPO_DIR" + log_debug "Internal folder directory: $INTERNAL_FOLDER_DIR" +} + +check_status() { + log_info "==========================================" + log_info "Development Mode Status for: $FOLDER_NAME" + log_info "==========================================" + + if [ -f "$STATE_FILE" ]; then + echo "" + echo "Status: ENABLED" + echo "" + + # Count files + local file_count=$(grep -c "^FILE:" "$STATE_FILE" || echo "0") + echo "Files copied: $file_count" + + # Show files + if [ "$file_count" -gt 0 ]; then + echo "" + echo "Copied files:" + grep "^FILE:" "$STATE_FILE" | sed 's/^FILE:/ ✓ /' + fi + + echo "" + echo "go.mod modified: $([ -f "$GO_MOD_APPEND_FILE" ] && echo "YES" || echo "NO")" + + # Show replace directives if they exist + if [ -f "$GO_MOD_APPEND_FILE" ]; then + local replace_count=$(wc -l < "$GO_MOD_APPEND_FILE") + echo "Replace directives: $replace_count" + fi + + # Show internal repo status + if [ -d "$INTERNAL_REPO_DIR/.git" ]; then + echo "" + echo "Internal repo:" + local commit_hash=$(cd "$INTERNAL_REPO_DIR" && git rev-parse --short HEAD 2>/dev/null || echo "unknown") + local branch=$(cd "$INTERNAL_REPO_DIR" && git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown") + echo " Branch: $branch" + echo " Commit: $commit_hash" + fi + + echo "" + echo "State file: $STATE_FILE" + echo "" + + return 0 + else + echo "" + echo "Status: DISABLED" + echo "" + echo "No active development mode configuration found." + echo "Run '$0 $FOLDER_NAME enable' to enable development mode." + echo "" + return 1 + fi +} + +clone_or_update_internal_repo() { + local target_branch="${INTERNAL_REPO_BRANCH:-develop}" + + if [ -d "$INTERNAL_REPO_DIR/.git" ]; then + log_info "Internal configs repo already exists, updating..." + log_debug "Repository path: $INTERNAL_REPO_DIR" + cd "$INTERNAL_REPO_DIR" + + # Fetch latest changes + log_info "Fetching latest changes from origin..." + git fetch origin + log_debug "Fetch completed" + + log_info "Using branch: $target_branch" + + # Ensure the remote branch exists (and fetch it explicitly if needed) + if ! git show-ref --verify --quiet "refs/remotes/origin/$target_branch"; then + log_info "Remote branch origin/$target_branch not found locally; fetching it..." + git fetch origin "$target_branch" || true + fi + if ! git show-ref --verify --quiet "refs/remotes/origin/$target_branch"; then + log_error "Branch not found in internal configs repo: origin/$target_branch" + exit 1 + fi + + # Switch to the requested branch, tracking origin/ + local current_branch + current_branch=$(git rev-parse --abbrev-ref HEAD) + log_debug "Current branch: $current_branch" + if [ "$current_branch" != "$target_branch" ]; then + log_info "Switching from $current_branch to $target_branch" + fi + git checkout -B "$target_branch" "origin/$target_branch" + log_debug "Branch checkout completed" + + # Reset to latest + log_info "Resetting to origin/$target_branch..." + git reset --hard "origin/$target_branch" + local commit_hash=$(git rev-parse --short HEAD) + log_info "Updated to commit: $commit_hash" + + cd "$TARGET_DIR" + else + log_info "Internal configs repo not found, cloning..." + log_debug "Cloning from: $INTERNAL_REPO_URL" + log_debug "Destination: $INTERNAL_REPO_DIR" + log_debug "Branch: $target_branch" + rm -rf "$INTERNAL_REPO_DIR" + git clone -b "$target_branch" "$INTERNAL_REPO_URL" "$INTERNAL_REPO_DIR" + local commit_hash=$(cd "$INTERNAL_REPO_DIR" && git rev-parse --short HEAD) + log_info "Clone completed at commit: $commit_hash" + fi +} + +enable_dev_mode() { + log_info "==========================================" + log_info "Starting development mode enablement for: $FOLDER_NAME" + log_info "==========================================" + + if [ -f "$STATE_FILE" ]; then + log_warn "Development mode is already enabled!" + echo "Run '$0 $FOLDER_NAME disable' first to revert, or delete $STATE_FILE if state is corrupted." + exit 1 + fi + + log_debug "State file: $STATE_FILE" + log_debug "Working directory: $TARGET_DIR" + + # Clone or update the internal repo + log_info "Step 1: Fetching internal configurations..." + clone_or_update_internal_repo + + # Check if folder directory exists in internal repo + log_info "Step 2: Validating internal repository structure..." + log_debug "Looking for: $INTERNAL_FOLDER_DIR" + if [ ! -d "$INTERNAL_FOLDER_DIR" ]; then + log_error "$FOLDER_NAME directory not found in internal configs repo!" + log_error "Expected path: $INTERNAL_FOLDER_DIR" + exit 1 + fi + log_debug "✓ $FOLDER_NAME directory found" + + # Initialize state file + log_info "Step 3: Initializing state tracking..." + echo "# Dev toggle state - DO NOT EDIT MANUALLY" > "$STATE_FILE" + echo "# Generated on $(date)" >> "$STATE_FILE" + echo "# Folder: $FOLDER_NAME" >> "$STATE_FILE" + log_debug "Created state file: $STATE_FILE" + + # Find and copy all .go files and config files from internal repo + # Copy: .go sources and common config types (.yaml, .yml, .json, .env, .pbtxt) + log_info "Step 4: Searching for .go and config files to copy..." + log_debug "Scanning directory: $INTERNAL_FOLDER_DIR" + + local copied_count=0 + local find_patterns=(-name "*.go" -o -name "*.yaml" -o -name "*.yml" -o -name "*.json" -o -name "*.env" -o -name "*.pbtxt") + local file_count + file_count=$(find "$INTERNAL_FOLDER_DIR" -type f \( "${find_patterns[@]}" \) | wc -l) + log_info "Found $file_count file(s) to copy (.go and configs)" + + while IFS= read -r -d '' src_file; do + # Get relative path from INTERNAL_FOLDER_DIR + rel_path="${src_file#$INTERNAL_FOLDER_DIR/}" + dest_file="$TARGET_DIR/$rel_path" + dest_dir="$(dirname "$dest_file")" + + # Create destination directory if it doesn't exist + if [ ! -d "$dest_dir" ]; then + log_debug "Creating directory: $dest_dir" + mkdir -p "$dest_dir" + fi + + # Check if file already exists + if [ -f "$dest_file" ]; then + log_warn "File already exists, will be overwritten: $rel_path" + fi + + # Copy the file + log_debug "Copying: $src_file -> $dest_file" + cp "$src_file" "$dest_file" + log_info "✓ Copied: $rel_path" + + # Record in state file + echo "FILE:$rel_path" >> "$STATE_FILE" + ((copied_count++)) + done < <(find "$INTERNAL_FOLDER_DIR" -type f \( "${find_patterns[@]}" \) -print0) + + log_info "Successfully copied $copied_count file(s)" + + # Check if there's a go.mod file in internal repo with replace directives + log_info "Step 5: Processing go.mod modifications..." + if [ -f "$INTERNAL_FOLDER_DIR/go.mod" ]; then + log_debug "Found go.mod in internal configs: $INTERNAL_FOLDER_DIR/go.mod" + + # Extract require/replace directives from internal go.mod. + # Supports both single-line directives and block forms: + # require ( ... ) + # replace ( ... ) + # We normalize block entries into single-line statements to keep the appended section simple. + local extracted_lines + extracted_lines="$( + awk ' + function ltrim(s) { sub(/^[ \t]+/, "", s); return s } + BEGIN { in_req=0; in_rep=0 } + { + line=$0 + + if (line ~ /^require[ \t]*\(/) { in_req=1; next } + if (line ~ /^replace[ \t]*\(/) { in_rep=1; next } + + if (in_req) { + if (line ~ /^\)/) { in_req=0; next } + trimmed=ltrim(line) + if (trimmed=="" || trimmed ~ /^\/\//) next + print "require " trimmed + next + } + + if (in_rep) { + if (line ~ /^\)/) { in_rep=0; next } + trimmed=ltrim(line) + if (trimmed=="" || trimmed ~ /^\/\//) next + print "replace " trimmed + next + } + + if (line ~ /^require[ \t]+/ && line !~ /^require[ \t]*\(/) { print line; next } + if (line ~ /^replace[ \t]+/ && line !~ /^replace[ \t]*\(/) { print line; next } + } + ' "$INTERNAL_FOLDER_DIR/go.mod" | sed '/^[[:space:]]*$/d' + )" + + if [ -n "$extracted_lines" ]; then + printf "%s\n" "$extracted_lines" > "$GO_MOD_APPEND_FILE" + + local extracted_count + extracted_count=$(wc -l < "$GO_MOD_APPEND_FILE") + log_info "Found $extracted_count directive(s) (require/replace) to append" + log_debug "Directives to append:" + while IFS= read -r line; do + log_debug " $line" + done < "$GO_MOD_APPEND_FILE" + + # Append to current go.mod + log_info "Appending to $GO_MOD_FILE..." + echo "" >> "$GO_MOD_FILE" + echo "// Added by dev-toggle-go.sh - DO NOT EDIT" >> "$GO_MOD_FILE" + cat "$GO_MOD_APPEND_FILE" >> "$GO_MOD_FILE" + + log_info "✓ Successfully appended require/replace directives to go.mod" + else + log_warn "No require/replace directives found in internal go.mod" + rm -f "$GO_MOD_APPEND_FILE" + fi + else + log_warn "No go.mod found in internal configs at: $INTERNAL_FOLDER_DIR/go.mod" + fi + + # Run go mod tidy + log_info "Step 6: Running go mod tidy..." + log_debug "Changing to directory: $TARGET_DIR" + cd "$TARGET_DIR" + log_debug "Executing: go mod tidy" + go mod tidy + log_info "✓ go mod tidy completed successfully" + + log_info "==========================================" + log_info "✓ Development mode enabled successfully!" + log_info "==========================================" + echo "" + echo "Summary:" + echo " Folder: $FOLDER_NAME" + echo " Files copied: $copied_count" + echo " go.mod updated: $([ -f "$GO_MOD_APPEND_FILE" ] && echo "YES" || echo "NO")" + echo " State file: $STATE_FILE" + if [ "$FOLDER_NAME" = "horizon" ]; then + echo "" + echo "To run tests including internal (meesho) config tests, use:" + echo " cd $TARGET_DIR && go test -tags=meesho ./..." + echo "Without -tags=meesho, only the standard tests run (internal test files are skipped)." + fi +} + +disable_dev_mode() { + log_info "==========================================" + log_info "Starting development mode disablement for: $FOLDER_NAME" + log_info "==========================================" + + if [ ! -f "$STATE_FILE" ]; then + log_error "Development mode is not enabled or state file is missing!" + log_error "Expected state file: $STATE_FILE" + echo "Nothing to disable." + exit 1 + fi + + log_debug "State file found: $STATE_FILE" + log_debug "Working directory: $TARGET_DIR" + + # Remove copied files + log_info "Step 1: Removing copied files..." + local file_count=$(grep -c "^FILE:" "$STATE_FILE" || echo "0") + log_info "Found $file_count file(s) to remove" + + local removed_count=0 + while IFS= read -r line; do + if [[ "$line" =~ ^FILE:(.+)$ ]]; then + rel_path="${BASH_REMATCH[1]}" + file="$TARGET_DIR/$rel_path" + + log_debug "Attempting to remove: $file" + if [ -f "$file" ]; then + rm "$file" + log_info "✓ Removed: $rel_path" + ((removed_count++)) + else + log_warn "File not found (may have been manually deleted): $rel_path" + fi + fi + done < "$STATE_FILE" + + log_info "Successfully removed $removed_count file(s)" + + # Revert go.mod changes if they were made + log_info "Step 2: Reverting go.mod changes..." + if [ -f "$GO_MOD_APPEND_FILE" ]; then + log_debug "Found append file: $GO_MOD_APPEND_FILE" + + # Find the marker line and remove everything from that line onwards + if grep -q "// Added by dev-toggle-go.sh - DO NOT EDIT" "$GO_MOD_FILE"; then + log_debug "Found marker comment in go.mod" + local lines_before=$(wc -l < "$GO_MOD_FILE") + + # Use sed to delete from the marker line to end of file + log_debug "Removing lines from marker to end of file..." + sed -i.bak '/\/\/ Added by dev-toggle-go.sh - DO NOT EDIT/,$d' "$GO_MOD_FILE" + rm -f "${GO_MOD_FILE}.bak" + + # Remove any trailing empty lines that might be left + log_debug "Cleaning up trailing empty lines..." + while [ -s "$GO_MOD_FILE" ]; do + last_line=$(tail -n 1 "$GO_MOD_FILE") + if [ -z "$last_line" ] || [ "$last_line" = $'\n' ]; then + # Remove last line if empty + sed -i.bak '$ d' "$GO_MOD_FILE" 2>/dev/null || { + # Fallback for systems without sed -i + head -n -1 "$GO_MOD_FILE" > "${GO_MOD_FILE}.tmp" + mv "${GO_MOD_FILE}.tmp" "$GO_MOD_FILE" + } + rm -f "${GO_MOD_FILE}.bak" + else + break + fi + done + + local lines_after=$(wc -l < "$GO_MOD_FILE") + local lines_removed=$((lines_before - lines_after)) + log_info "✓ Removed $lines_removed line(s) from go.mod" + else + log_warn "Marker comment not found in go.mod (may have been manually edited)" + fi + + log_debug "Removing append file: $GO_MOD_APPEND_FILE" + rm -f "$GO_MOD_APPEND_FILE" + else + log_debug "No append file found, skipping go.mod revert" + fi + + # Run go mod tidy + log_info "Step 3: Running go mod tidy..." + log_debug "Changing to directory: $TARGET_DIR" + cd "$TARGET_DIR" + log_debug "Executing: go mod tidy" + go mod tidy + log_info "✓ go mod tidy completed successfully" + + # Clean up state file + log_info "Step 4: Cleaning up state file..." + log_debug "Removing: $STATE_FILE" + rm -f "$STATE_FILE" + log_info "✓ State file removed" + + # Remove internal configs directory + log_info "Step 5: Removing internal configs directory..." + if [ -d "$INTERNAL_REPO_DIR" ]; then + log_debug "Removing directory: $INTERNAL_REPO_DIR" + rm -rf "$INTERNAL_REPO_DIR" + log_info "✓ Internal configs directory removed" + else + log_debug "Internal configs directory not found, skipping" + fi + + log_info "==========================================" + log_info "✓ Development mode disabled successfully!" + log_info "==========================================" +} + +update_internal_configs() { + log_info "==========================================" + log_info "Updating internal configurations for: $FOLDER_NAME" + log_info "==========================================" + + if [ ! -f "$STATE_FILE" ]; then + log_error "Development mode is not enabled!" + log_error "Expected state file: $STATE_FILE" + echo "Run '$0 $FOLDER_NAME enable' first." + exit 1 + fi + + log_info "Current state: Development mode is ENABLED" + log_info "Update strategy: Disable -> Re-enable with latest configs" + echo "" + + # Disable current dev mode + log_info "Phase 1: Disabling current development mode..." + disable_dev_mode + + echo "" + log_info "Phase 2: Re-enabling development mode with latest configs..." + # Re-enable with fresh configs + enable_dev_mode + + echo "" + log_info "==========================================" + log_info "✓ Internal configs updated successfully!" + log_info "==========================================" +} + +# Main script logic +FOLDER_NAME_INPUT="${1:-}" +COMMAND="${2:-}" +BRANCH_INPUT="${3:-}" + +# Interactive mode: prompt for missing parameters +if [ -z "$FOLDER_NAME_INPUT" ]; then + echo "==========================================" + echo " Development Toggle for Go Projects" + echo "==========================================" + FOLDER_NAME_INPUT=$(interactive_select_folder) +fi + +if [ -z "$COMMAND" ]; then + COMMAND=$(interactive_select_command) +fi + +# If command needs internal configs, choose the branch once (applies to all selected folders) +if [ "$COMMAND" = "enable" ] || [ "$COMMAND" = "update" ]; then + INTERNAL_REPO_BRANCH="$(select_internal_repo_branch "$BRANCH_INPUT")" + log_debug "Internal configs branch: $INTERNAL_REPO_BRANCH" +fi + +# Parse folder names (support multiple folders) +FOLDER_NAMES=($FOLDER_NAME_INPUT) + +# If only one folder, process it directly +if [ ${#FOLDER_NAMES[@]} -eq 1 ]; then + FOLDER_NAME="${FOLDER_NAMES[0]}" + + # Validate and set up paths + validate_and_setup "$FOLDER_NAME" "$COMMAND" + + log_debug "Script started with folder: $FOLDER_NAME, command: $COMMAND" + log_debug "Script directory: $SCRIPT_DIR" + log_debug "Target directory: $TARGET_DIR" + echo "" + + case "$COMMAND" in + enable) + log_debug "Command: enable" + enable_dev_mode + ;; + disable) + log_debug "Command: disable" + disable_dev_mode + ;; + status) + log_debug "Command: status" + check_status + ;; + update) + log_debug "Command: update" + update_internal_configs + ;; + *) + log_error "Invalid command: $COMMAND" + print_usage + ;; + esac +else + # Multiple folders selected - process each one + echo "" + log_info "Processing ${#FOLDER_NAMES[@]} folder(s) with command: $COMMAND" + echo "" + + for FOLDER_NAME in "${FOLDER_NAMES[@]}"; do + echo "" + log_info "==========================================" + log_info "Processing: $FOLDER_NAME" + log_info "==========================================" + echo "" + + # Validate and set up paths for this folder + validate_and_setup "$FOLDER_NAME" "$COMMAND" + + case "$COMMAND" in + enable) + enable_dev_mode + ;; + disable) + disable_dev_mode + ;; + status) + check_status + ;; + update) + update_internal_configs + ;; + *) + log_error "Invalid command: $COMMAND" + print_usage + exit 1 + ;; + esac + done + + echo "" + log_info "==========================================" + log_info "✓ Completed processing all ${#FOLDER_NAMES[@]} folder(s)" + log_info "==========================================" +fi + diff --git a/docs-src/blog/authors.yml b/docs-src/blog/authors.yml index 2af81d4d..376741b0 100644 --- a/docs-src/blog/authors.yml +++ b/docs-src/blog/authors.yml @@ -5,22 +5,22 @@ adarsha: image_url: https://github.com/a0d00kc.png aditya: name: Aditya Kumar - title: SDE-III @ Meesho + title: Lead Software Engineer @ Meesho url: https://github.com/Adit2607 image_url: https://github.com/Adit2607.png jigar: name: Jigar Dave - title: SDE-IV @ Meesho + title: Lead Software Engineer @ Meesho url: https://github.com/jigarpatel26 image_url: https://github.com/jigarpatel26.png jaya: name: Jaya Kumar - title: MLE-III @ Meesho + title: Lead ML Engineer @ Meesho url: https://github.com/jayakommuru image_url: https://github.com/jayakommuru.png bhawani: name: Bhawani Singh - title: SDE-IV @ Meesho + title: Architect @ Meesho url: https://github.com/singh-bhawani image_url: https://github.com/singh-bhawani.png mohit: diff --git a/docs-src/blog/bharatmlstack-history/post-one/bharatmlstack.png b/docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-from-chaos-to-cutting-edge/bharatmlstack.png similarity index 100% rename from docs-src/blog/bharatmlstack-history/post-one/bharatmlstack.png rename to docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-from-chaos-to-cutting-edge/bharatmlstack.png diff --git a/docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-from-chaos-to-cutting-edge/bms.png b/docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-from-chaos-to-cutting-edge/bms.png new file mode 100644 index 00000000..b397fc88 Binary files /dev/null and b/docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-from-chaos-to-cutting-edge/bms.png differ diff --git a/docs-src/blog/bharatmlstack-history/post-one/first-gen-arch.png b/docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-from-chaos-to-cutting-edge/first-gen-arch.png similarity index 100% rename from docs-src/blog/bharatmlstack-history/post-one/first-gen-arch.png rename to docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-from-chaos-to-cutting-edge/first-gen-arch.png diff --git a/docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-from-chaos-to-cutting-edge/index.md b/docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-from-chaos-to-cutting-edge/index.md new file mode 100644 index 00000000..fd6dc4b9 --- /dev/null +++ b/docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-from-chaos-to-cutting-edge/index.md @@ -0,0 +1,238 @@ +--- +title: "Building Meesho’s ML Platform: From Chaos to Cutting-Edge (Part 1)" +description: "How Meesho transitioned from batch-based recommendations to a real-time ML platform—building an Online Feature Store, Interaction Store, and DAG execution framework that became BharatMLStack." +slug: building-meeshos-mlplatform +authors: [adarsha, aditya, bhawani, jigar] +date: 2022-11-15 +tags: [online-feature-store, interaction-store, mlplatform, meesho] +--- +![BharatMLStack](./bms.png) +It all started in early 2022, over a casual Friday evening catch-up. Like many great origin stories, this one began with friendly banter between a group of backend engineers and data scientists. As the conversations unfolded, so did the roasting—until one remark hit a little too close to home: + +*"Why are we still crunching data for Monthly Active Users (MAU) when the next day it’s all about Daily Active Users (DAU)?"* + +The laughter died down, and the question lingered. When we regrouped on Monday—clear-headed and slightly reflective—we decided to dig into the numbers. What they discovered was quite revealing: a large portion of compute resources wasn’t being put to good use. +Much of the system’s effort was spent supporting users who weren’t actively engaging, and even for new users, the experience wasn’t optimized to make a meaningful impact. + +At the same time, Meesho had just launched a company-wide initiative to reduce costs—and every team had to contribute. This realization sparked the journey that would eventually lead to the **Meesho ML Platform**, known today as **BharatMLStack**. + +![Alt Text](./old-batch-arch.png) + +Before the ML Platform, our recommendation and ranking pipelines followed a batch processing approach: +- **Data Ingestion**: The Data Platform team executed ETL jobs to ingest raw user data—including user profiles, interaction logs, and product impressions—into designated S3 buckets. +- **Layer 1**: Embedding Generation: On the Data Science side, Spark jobs pulled data from multiple S3 sources, cleaned and preprocessed it, and applied matrix factorization to generate user and item embeddings. The processed data and embeddings were then stored back in S3 in a structured format. +- **Layer 2**: Candidate Generation (CG): In this stage, Spark jobs leveraged embeddings and historical interaction data to generate candidate recommendations for users. These candidate lists were subsequently written to S3. +- **Layer 3**: Ranking and Merging – A final round of processing ranked the generated candidates using ML models, combined different candidate lists, and stored the final ranked recommendations in a caching system. +- **Serving**: A microservice retrieved ranked recommendations from an in-memory data store via exposed APIs, delivering personalized listings across key surfaces such as "For You" and Category Landing Pages (CLP). + +This approach held up well—until Meesho started seeing a significant surge in traffic. + +## The Turning Point: From Batch to Real-Time + +At this time, the team was iterating on new **Ranker models**, and real-time inference seemed like the next logical step. But Rankers needed **real-time feature retrieval**, which meant an **online feature store** had to be built first. + +Exploring open-source options led to **cost vs. performance trade-offs**, but Meesho’s surging traffic meant that **latency and stability were non-negotiable**. After multiple debates and stakeholder discussions, a bold decision was made: + +*We would build our own feature store.* + +Meanwhile, efforts began to bring **Candidate Generators (CGs)** to real-time. The challenge? **Storing and retrieving user interactions quickly enough** to power real-time recommendations. + +As the team dove deeper, a new roadblock emerged: +Our ML jobs were orchestrated using **Airflow DAGs**, giving data scientists flexibility in experimentation. But transitioning to real-time execution threatened this agility. Every change would now require backend engineering support, **slowing down iteration cycles**. + +That’s when the idea struck: +We needed a **framework for real-time DAG execution**—one that preserved the same flexibility as Airflow but worked for **streaming data**. + +This moment shaped the **next phase of our journey**. + +## First Generation Design + +![Alt Text](./first-gen-arch.png) + +# Laying the Groundwork: The First-Gen ML Platform + +To solve these challenges, the team built three foundational components: + + +### 1. IOP Framework: A Real-Time DAG Executor + +- **Reusable Nodes**: Each DAG node (e.g., an invocation to a CG service, a ranker, or a filter) had to be implemented only once. After that, it could be reused across any workflow by referencing it in config. +- **Config-driven Dynamic Graphs**: Execution graphs were defined as adjacency lists stored in **ZooKeeper**, allowing teams to modify the sequence or structure of operations without touching application code. +- **Plug-and-play CGs**: The Candidate Generator interface was preserved, so a single CG node could call any CG service by passing `cg_name` in the request. This drastically reduced the code surface area and improved maintainability. +- **Production-Grade DAGs**: DAGs were designed to execute in **low-latency real-time environments**, with support for **parallel execution, retries, and branching**. + +[More about IOP DAG](https://www.meesho.io/blog/rebuilding-meeshos-ranking-platform) + + +### 2. Online Feature Store - 0th Version + +- Used **Cassandra** and **Redis** for low-latency feature serving. +- Maintained feature consistency using **Feature Groups** with TTL-based expiry. +- A hybrid schema was used: feature keys stored in **ZooKeeper**, data stored in **compact arrays**. + + +### 3. Interaction Store - 0th Version + +- Captured real-time user interactions like clicks, orders, and add-to-cart events. +- Stored event data in **Redis ZSETs (sorted sets)** to enable fast lookups for recommendation engines. +- Provided an API to fetch a user's **last _k_ interactions** or **interactions within a time window**. + + +With these components in place, **real-time ML at Meesho became a reality**. + +This was just the beginning. + +## Building the Online Feature Store - 0th Version + +![Alt text](./online-feature-store-v0.png) + +### Choosing the Right Tech Stack + +We spent considerable time evaluating various databases, caches, and communication protocols for our **online feature store**. After carefully weighing **cost, latency, throughput**, and **operational stability**, we settled on a combination of: + +- **Cassandra** and **Redis** for storage +- **gRPC + Proto3** as our communication layer + + +### Streamlining the Data Flow + +To keep things simple in the initial version: + +- **Feature engineering jobs** wrote raw outputs to an **S3 bucket** +- A **daily feature push job**: + - Read from S3 + - Grouped related features into **Feature Groups** (ensuring consistency) + - Pushed them to **Kafka** + +For features requiring frequent updates: + +- **Ad-hoc jobs** computed features in higher frequency +- These jobs pushed to both **Kafka** and **S3** (S3 preserved historical data for future model training) + + +## The Challenges: Data Format and Storage + +One of the most critical design challenges was how to store feature data **efficiently and consistently**, especially in databases like **Cassandra** and **Redis**, which come with unique storage constraints. + +We had to solve for three key requirements: + +- ### Feature Consistency + When a feature group contains features like `order_count_1h` and `click_count_1h`, both must reflect the **same time window**. Inconsistent updates would lead to **unreliable model predictions**. + +- ### TTL Granularity + Each feature group required an **expiry timestamp**, so that **all features within it expired together**—preserving consistency during reads. + +- ### Extensibility Across Databases + We anticipated that infra needs would evolve. To future-proof our system, the data format was designed to be **decoupled from DB-specific layouts**, enabling portability to systems like **ScyllaDB**, **DynamoDB**, **HBase**, or **BigTable**. + + +--- + +## Overcoming Technical Constraints +At the time, we were using Cassandra, which not only imposed a soft limit of 75 columns per row, but also exhibited significant performance degradation as the number of columns increased further, particularly in memory constrained machines. Wide rows caused high memory usage during reads, unpredictable latencies due to heavy deserialization overhead, and inefficiencies during compactions and repairs. This ruled out the naive "one column per feature" approach. We needed a format that was compact, minimized the number of columns, and remained efficient and portable across different storage systems. + +## The Solution: Schema Separation + +We introduced the concept of Feature Groups—logical groupings of features that must remain consistent with one another. +To represent these groups efficiently, we adopted a layered storage approach: + +- **Feature Labels (Keys)** were stored in ZooKeeper, serving as the schema. +- **Feature Values** were stored as a comma-separated string array in Cassandra or Redis. +- **Expiry Timestamp and Schema Version** were appended using a semi-colon delimiter at the end of the string. + +Example: + +```bash +feature_1_value,feature_2_value,feature_3_value;expiry_ts +``` + +This format allowed: +- Consistent writes and reads at the group level +- Easy parsing of feature values using the schema lookup from ZooKeeper +- Efficient storage with minimal DB column usage +- Support for per-group TTLs and schema evolution + +## Tracking Changes in Feature Groups +Feature groups don’t stay static. As models evolve, features get added, renamed, or removed. But schema changes often go live before the data is ready—and stopping ingestion just to wait for everything to align isn't feasible. + +### Common Real-World Scenarios: +- A new feature is added to the schema, but ingestion jobs still use the older schema version. +- Ongoing writes don’t include the newly added feature, and stopping ingestion would break freshness for existing features. +- During serving, models request a mix of old and new features, depending on rollout stages. + +## The Solution: Schema Versioning +We solved this with versioned feature group schemas, which unlocked several capabilities: +- ### Backward Compatibility + Older ingestion jobs can continue writing using older schema versions. During reads, the system uses the schema version embedded in the value to interpret the data correctly. +- ### Partial Availability Handling + During inference, if some features in the request aren’t available (due to rollout delays or missing data), the system serves default values, ensuring the inference call doesn’t fail. +- ### Safe Writes Without Pipeline Pauses + With schema versioning, we no longer had to stop ingestion pipelines for schema updates. Writes using previous versions can continue safely, and downstream consumers evolve independently. +This design gave us the flexibility to move fast without breaking things—preserving data quality, enabling experimentation, and ensuring reliability at scale. + +![Alt Text](./schema.png) + +## Interaction Store - 0th Version + +![Alt Text](./interaction-store-v0.png) + +To power real-time Candidate Generators (CGs), we needed fast access to user behavior signals—like what a user recently clicked, ordered, or added to their cart. These interactions form the basis for many real-time recommendations, such as **Similar Products**, **People Also Viewed**, or **Recently Ordered Again**. +For the **0th version** of the Interaction Store, we focused on a design that was **simple, fast, and reliable** — optimized for high-throughput ingestion and low-latency lookups. + +## Event Ingestion +We instrumented our backend services to emit key user interaction events to Kafka in real time. These included: +- Click +- Order +- Add to Cart +- Wishlist +- Share + +Each event carried essential metadata: +- userId — uniquely identifies the user +- productId — the item being interacted with +- timestamp — the moment the interaction occurred + +This decoupled the interaction logging from storage, allowing ingestion and consumption to scale independently. + +## Storage Design +To store these events, we built Kafka consumers that processed the incoming streams and wrote the data into Redis, using sorted sets (ZSETs) as the primary data structure. + +### Why Redis? +Redis gave us: +- **Low-latency** reads and writes +- **Time-ordered data** using ZSETs (via score = timestamp) +- **Native TTL support**, if needed in later versions +- **In-memory performance** —ideal for real-time CGs + +### Storage Structure +Each user’s interactions were stored using a composite key format, uniquely identifying the user and interaction type. This structure allowed efficient organization and quick retrieval of recent activity for recommendation generation: + +```bash +userId_eventType → ZSET[...(pid, ts)...] +``` + +Within each ZSET: + +- The **timestamp** served as the score, maintaining temporal order +- The **productId** (optionally with metadata) was the **value** + +This allowed us to efficiently retrieve the interactions with HTTP-based API server with two query modes: +- Fetch the **last k interactions** of a specific type for a given user with `ZREVRANGE(userId_eventType, count)` +- Retrieve **all interactions within a time range** (e.g., last 24 hours) with `ZREVRANGEBYSCORE(userId_eventType, timeRange)` + +### Built-in Guardrails +Since Redis was the sole store, we implemented High Availability (HA) to prevent data loss. To optimize memory usage, we also enforced size limits per event type—only storing the last k interactions per user, with older entries getting truncated. + +## Conclusion: Laying the Foundation for Real-Time ML + +In this first phase, we tackled the **fundamentals**—shifting from batch-based recommendations to a **real-time Recommendation** using ML platform that could keep up with Meesho’s growth. + +With the **IOP Framework**, **Online Feature Store**, and **Interaction Store**, we built the core infrastructure to support real-time personalization at scale. These wins have already unlocked: +- ✅ Faster, more dynamic recommendations for millions of users. +- ✅ Better infrastructure efficiency, reducing wasted compute power. +- ✅ A flexible, modular system that allows for further experimentation. + +But this is just the beginning. While we've solved key challenges, **certain roadblocks remain** —from optimizing **cost-performance trade-offs** to **seamlessly evolving schemas**. + + +This foundational work laid the path for a reliable and scalable **real-time feature serving layer**. \ No newline at end of file diff --git a/docs-src/blog/bharatmlstack-history/post-one/interaction-store-v0.png b/docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-from-chaos-to-cutting-edge/interaction-store-v0.png similarity index 100% rename from docs-src/blog/bharatmlstack-history/post-one/interaction-store-v0.png rename to docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-from-chaos-to-cutting-edge/interaction-store-v0.png diff --git a/docs-src/blog/bharatmlstack-history/post-one/logo.jpg b/docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-from-chaos-to-cutting-edge/logo.jpg similarity index 100% rename from docs-src/blog/bharatmlstack-history/post-one/logo.jpg rename to docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-from-chaos-to-cutting-edge/logo.jpg diff --git a/docs-src/blog/bharatmlstack-history/post-one/old-batch-arch.png b/docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-from-chaos-to-cutting-edge/old-batch-arch.png similarity index 100% rename from docs-src/blog/bharatmlstack-history/post-one/old-batch-arch.png rename to docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-from-chaos-to-cutting-edge/old-batch-arch.png diff --git a/docs-src/blog/bharatmlstack-history/post-one/online-feature-store-v0.png b/docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-from-chaos-to-cutting-edge/online-feature-store-v0.png similarity index 100% rename from docs-src/blog/bharatmlstack-history/post-one/online-feature-store-v0.png rename to docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-from-chaos-to-cutting-edge/online-feature-store-v0.png diff --git a/docs-src/blog/bharatmlstack-history/post-one/schema.png b/docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-from-chaos-to-cutting-edge/schema.png similarity index 100% rename from docs-src/blog/bharatmlstack-history/post-one/schema.png rename to docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-from-chaos-to-cutting-edge/schema.png diff --git a/docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-lessons-from-first-gen/bms.png b/docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-lessons-from-first-gen/bms.png new file mode 100644 index 00000000..b397fc88 Binary files /dev/null and b/docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-lessons-from-first-gen/bms.png differ diff --git a/docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-lessons-from-first-gen/index.md b/docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-lessons-from-first-gen/index.md new file mode 100644 index 00000000..6bc7f513 --- /dev/null +++ b/docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-lessons-from-first-gen/index.md @@ -0,0 +1,149 @@ +--- +title: "Building Meesho’s ML Platform: Lessons from the First-Gen System (Part 2)" +description: "Lessons from scaling Meesho's first-gen ML platform—building Inferflow for no-code feature retrieval, migrating from Cassandra to ScyllaDB, optimizing the Interaction Store with tiered storage, and cutting infra costs by 60% while hitting 1M QPS." +authors: [bhawani, jigar, adarsha] +slug: building-meeshos-mlplatform-lessons-from-first-gen +date: 2023-4-10 +tags: [inferflow, interaction-store, mlplatform, meesho, bharatmlstack] +--- + +![BharatMLStack](./bms.png) +By late 2022, we had built something we were truly proud of—a real-time ML serving system with a DAG-based executor, a feature store, and an interaction store powering key ranking and personalization models. It was a major milestone, the culmination of months of effort from data scientists, ML engineers, and backend teams. Our system was live, and we were ready to push the boundaries of experimentation. +And it worked. Mostly. +But soon, cracks appeared. Every new model needed custom feature retrieval logic, DAGs became dense and unmanageable, and scaling turned into a constant firefight. Costs surged, and infra bottlenecks slowed experimentation. Our system worked, but it wasn’t built for scale. +This is the story of how we tackled these challenges—building Inferflow for seamless feature retrieval, optimizing real-time infra, and cutting costs while scaling to millions of QPS. + +### The Cost of Success +Every new Ranker model required its own feature set, often pulling from different entities. Each addition meant: + +- Adding new DAG nodes in IOP +- Writing custom logic to fetch features from multiple sources (e.g., user, product, user × category) +- Inferring intermediate features (e.g., extracting category from a product to fetch user × category data) +- Optimizing I/O and dealing with the inevitable bugs + +What began as clean DAGs soon turned into a tangled web of cross-dependent graphs. Every experimentation cycle meant new nodes, new dependencies, and slower iterations. + +### Scaling Pains (and Cassandra’s Limits) +At some point, we were hitting: + +- 250–300K reads/sec +- 1M writes/sec (during lean hours) + +All of this ran on Cassandra. While its distributed architecture had been proven in production, operating large-scale clusters came with considerable infrastructure overhead. Our proof-of-concept (POC) demonstrated throughput of around 100K ops/sec, but as we scaled further, the challenges grew. Ensuring node health, optimizing compaction, and maintaining storage balance became increasingly demanding. We also observed latency spikes under heavy load, alongside a sharp increase in total cost of ownership. + +### Interaction Store Woes +Our interaction store was another ticking time bomb: + +- 🚨 Clusters kept growing in size and cost +- 🚨 Latency spikes became increasingly frequent +- 🚨 The DMC proxy occasionally lost locality of nodes against shards, causing cross-node communication and degraded performance + +Each time this happened, we had to manually rebalance shards just to restore stable latency, making operations unsustainable at scale. + +### Silver Linings +Despite the chaos, the system was live and delivering value: + +- Real-time infrastructure was in production +- Costs dropped by 60–70% compared to offline personalization +- New experiments rolled out faster and more successfully +- User engagement metrics improved + +It wasn’t perfect. It was far from easy. But it worked—and that counted for a lot. + +### Round Two: Solving the Top 2 Bottlenecks +With the first-gen system stretched to its limits, we stepped back. Conversations with data scientists and backend engineers revealed three recurring pain points: + +1. Coding feature retrieval logic for every new model was becoming unsustainable +2. ML scale was exploding—bringing rising infra costs with it +3. Real-time embedding search was the next big unlock + +We tackled them one by one—starting with the biggest pain point. + +#### Problem 1: No-Code Feature Retrieval for Model Inference +We noticed a pattern: for personalized ranking, models needed features from: + +- ✅ Product +- ✅ User +- ✅ User × Category +- ✅ Region, cohort, sub-category, etc. + +A key insight emerged: Entities that contribute features for a model always map back to the context entities. + +![MP Dag](./mp-dag.png) + +With this, we designed Inferflow, a graph-driven feature retrieval and model orchestration system: + +- 1️⃣ Inferflow takes a modelId and context IDs (e.g., userId, productIds) +- 2️⃣ Loads a pre-defined feature retrieval graph from ZooKeeper +- 3️⃣ Executes the graph to resolve entity relationships dynamically +- 4️⃣ Outputs a 2D matrix of feature vectors + +💡 The impact? + +- 🚀 No more custom feature retrieval code—just graph updates in config +- 🚀 Feature consistency across experiments +- 🚀 Faster iteration cycles for ranking, fraud detection, and beyond + +Here’s a visual example that shows how this graph plays out during execution. We further extended the graph to call multiple models as needed: +![MP matrix](./mp-matrix.png) +We built Inferflow in GoLang, using gRPC and Proto3 serialization for efficiency. + +#### Problem 2: Scaling Without Breaking the Bank +With more ML use cases coming online, we needed to cut costs without compromising performance. We focused on: + +- 🔹 Online Feature Store +- 🔹 Interaction Store + +#### Optimizing the Online Feature Store +Our costs were concentrated in: + +- 📌 Database (Cassandra) +- 📌 Cache (Redis) +- 📌 Running Pods (Java services) + +1️⃣ Replacing Cassandra with ScyllaDB +As we hit the operational limits of large Cassandra clusters, we transitioned to ScyllaDB, which offered a seamless drop-in replacement without major code changes. The switch brought significant benefits: + +- Throughput: Matched or exceeded Cassandra's performance under identical workloads, even under high concurrency. +- Latency: Achieved consistently lower P99 latencies due to ScyllaDB's shard-per-core architecture and better I/O utilization. +- Cost Efficiency: Reduced infra footprint by ~70% through better CPU and memory efficiency, eliminating the need for over-provisioned nodes. + +2️⃣ Finding the Right Cache +To reduce backend load and improve response times, we benchmarked multiple caching solutions—Memcached, KeyDB, and Dragonfly—under real production traffic patterns. Dragonfly stood out due to its robust architecture and operational simplicity: + +- Data Skew Handling: Efficiently managed extreme key hotness and uneven access patterns without performance degradation. +- Throughput: Delivered consistently high throughput, even with large object sizes and concurrent access. +- Ease of Adoption: Acted as a drop-in Redis replacement with full protocol compatibility—no changes needed in application code or client libraries. + +3️⃣ Moving to GoLang for Cost-Efficient Serving +Java services were memory-heavy—so we rewrote core services in GoLang. The results? + +✅ Memory usage dropped by ~80% +✅ CPU utilization was significantly lower +✅ Faster, more efficient deployments + +#### Optimizing the Interaction Store +We realized that we only need a user’s interaction data in Redis when they open the app. So, we implemented a tiered storage approach: + +- 📌 Cold Tier (ScyllaDB)—Stores click, order, wishlist events +- 📌 Hot Tier (Redis)—Loads a user’s past interactions only when they open the app + +Smart Offloading: We introduced an inactivity tracker to detect when a user session ends. At that point, Redis data was flushed back to Scylla, reducing unnecessary writes. + +![InteractionStore](./interaction-str.png) +#### Results + +- Online Feature Store hit 1M QPS for the first time during the 2023 Mega Blockbuster Sale—without breaking a sweat +- Infra costs for Online Feature Store and Interaction Store dropped by ~60% + +#### The Catch: Our ML Hosting Hit a Hard Limit +While planning for 2023 MBS, we ran into a critical scalability bottleneck: + +- ❌ Insufficient compute availability in our region for ML instances +- ❌ Couldn’t provision enough nodes to handle real-time inference at scale + +This forced us to rethink where and how we hosted our models. The existing setup was great for prototyping—but it wasn’t built to handle the bursty, high-QPS demands of real-world production workloads. + +### Conclusion: From Firefighting to Future-Proofing +What started as an ambitious experiment turned into a real-time ML infrastructure that powered millions of requests per second. We battled scaling pains, rethought feature retrieval with Inferflow, and rebuilt our infra stack for efficiency—driving down costs while improving experimentation velocity. +But new challenges emerged. Our infrastructure could now handle scale, but our ML model hosting setup hit a hard limit. With compute availability bottlenecks threatening real-time inference, we faced a critical decision: how do we make model serving as scalable and cost-efficient as the rest of our stack? That’s the next piece of the puzzle—and the story of Part 3. diff --git a/docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-lessons-from-first-gen/interaction-str.png b/docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-lessons-from-first-gen/interaction-str.png new file mode 100644 index 00000000..99ddd4e9 Binary files /dev/null and b/docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-lessons-from-first-gen/interaction-str.png differ diff --git a/docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-lessons-from-first-gen/mp-dag.png b/docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-lessons-from-first-gen/mp-dag.png new file mode 100644 index 00000000..4a13af56 Binary files /dev/null and b/docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-lessons-from-first-gen/mp-dag.png differ diff --git a/docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-lessons-from-first-gen/mp-matrix.png b/docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-lessons-from-first-gen/mp-matrix.png new file mode 100644 index 00000000..44b21256 Binary files /dev/null and b/docs-src/blog/bharatmlstack-history/building-meeshos-mlplatform-lessons-from-first-gen/mp-matrix.png differ diff --git a/docs-src/blog/bharatmlstack-history/episodic-memory-for-agents/bms.png b/docs-src/blog/bharatmlstack-history/episodic-memory-for-agents/bms.png new file mode 100644 index 00000000..b397fc88 Binary files /dev/null and b/docs-src/blog/bharatmlstack-history/episodic-memory-for-agents/bms.png differ diff --git a/docs-src/blog/bharatmlstack-history/episodic-memory-for-agents/index.md b/docs-src/blog/bharatmlstack-history/episodic-memory-for-agents/index.md new file mode 100644 index 00000000..0cce0e1d --- /dev/null +++ b/docs-src/blog/bharatmlstack-history/episodic-memory-for-agents/index.md @@ -0,0 +1,219 @@ +--- +title: "Beyond Vector RAG: Building Agent Memory That Learns From Experience." +description: "Current agent memory is just search. We built an episodic memory system that tracks outcomes, forms causal links, extracts reasoning heuristics, and actually learns from failure — without retraining the model." +slug: episodic-memory-for-agents +authors: [adarsha] +date: 2026-02-19 +tags: [ai-agents, memory, architecture, llm, episodic-memory] +--- +![BharatMLStack](./bms.png) +Agent memory has come a long way. Persistent context, vector retrieval, knowledge graphs — the building blocks are real and getting better fast. + +But most of what we call "memory" today is still closer to search: chunk text, embed it, retrieve whatever looks similar at query time. That works well for recalling facts and preferences. It starts to break down when you need an agent to recall what happened last time, learn from a mistake, or avoid repeating a failed approach. + +We are trying to experiment something different. An episodic memory system where a frozen LLM — same weights, no retraining — produces increasingly better decisions over time because the memory feeding it context is continuously evolving. +Then we tested it. The results were interesting. + + + + +## The Gap Nobody Talks About + +Here's a scenario every engineering team has encountered: AI agent hits a Redis connection pool exhaustion issue. It misdiagnoses it as a database problem. You correct it. Next week, a different service has the exact same failure pattern. The agent makes the exact same mistake. + +Why? Because LLMs don't learn at inference time. Corrections adjust behavior within a conversation. Once the session ends, the lesson is gone. The model weights haven't changed. The next conversation starts from zero. + +Current "memory" systems don't fully address this. They store facts — user preferences, document chunks, conversation summaries. But facts aren't experience. Knowing that "Redis connection pools can exhaust under load" is different from remembering "last time I saw 500 errors under load, I assumed it was the database, I was wrong, it was actually the connection pool, and here's the correction I received." + +The first is a fact. The second is an episode. The difference matters. + +## What's Wrong With Vector RAG as Memory + +We identified five structural gaps in how current agent frameworks handle memory: + +**No concept of time.** Two events are either semantically similar or they're not. The system can't represent "this happened after that" without distorting similarity scores. An agent can't reason about sequence or causality. + +**No concept of situation.** A production incident and a design review might use the same technical vocabulary. Flat vector search can't distinguish them. Your agent retrieves planning notes when it should be retrieving incident postmortems. + +**No outcome tracking.** The system stores *what happened* but not *whether it worked*. A failed approach and a successful one are equally retrievable. The agent has no way to prefer strategies that worked over strategies that didn't. + +**Summaries destroy evidence.** Summarization-based memory compresses experience but discards the reasoning chain. The agent loses the ability to explain *how* it arrived at a conclusion. The audit trail is gone. + +**No causal links.** Each memory chunk is independent. There's no way to express that incident A caused decision B, which led to outcome C, which was corrected by approach D. Without this structure, the agent can't traverse chains of reasoning. + +These gaps compound. As an agent accumulates more experience, flat vector memory gets noisier, more contradictory, and less useful. The system degrades precisely when it should be improving. + +## The Architecture: Episodic Memory + +We are building a memory system modeled on how human episodic memory works — not as a metaphor, but as an engineering specification. + +The system has four layers: + +### Layer 1: Immutable Timeline + +Every piece of agent experience is recorded as an append-only timeline entry. Each entry carries a semantic embedding (what it means), a timestamp (when it happened), and a state label (what situation the agent was in — debugging, planning, code review, incident response). Entries are never modified, never deleted, never summarized. This is the source of truth. + +### Layer 2: Episode Segmentation + +The system watches the timeline and detects when one coherent unit of experience ends and another begins — via state transitions, semantic shifts, temporal gaps, or explicit signals. Each episode is a reference into the timeline (not a copy) with a generated summary, an outcome (SUCCESS, FAILURE, PARTIAL, UNKNOWN), decisions made, assumptions held, and corrections received. + +The outcome field is the most important thing that doesn't exist in any current memory system. Without it, you can't learn from mistakes. + +### Layer 3: Episodic Graph + +Episodes are connected through typed, weighted links: CAUSED_BY, LED_TO, RETRY_OF, LEARNED_FROM, CONTINUATION, CONTRADICTED. Over time, this forms a directed graph that enables traversal by meaning and causality. You can follow the chain: "this incident caused that investigation, which led to a failed fix, which was corrected by this approach." + +### Layer 4: Generalized Facts + +When multiple episodes exhibit consistent patterns, the system extracts reasoning heuristics: "When services fail immediately after deployment with no traffic change, investigate configuration errors before connection pool problems." Facts are versioned, never overwritten, and maintain links back to supporting and contradicting episodes. When contradicting evidence accumulates, confidence decreases. When confidence drops below a threshold, the fact is revised — but the old version is preserved. + +The LLM sits above all four layers. At query time, the system assembles structured context — relevant episodes with outcomes, applicable facts with confidence scores, causal narratives — and passes it to the LLM for reasoning. The model reasons over structured memory. It doesn't store or manage memory. + +### The Reinforcement Loop + +This is where it comes together: + +1. Agent reasons using retrieved episodes and facts +2. Outcome is detected (CI pass/fail, user correction, test result) +3. New episode is created with outcome tracking +4. Links are created between the retrieved episodes and the new episode +5. Facts are reinforced (if outcome aligned) or contradicted (if outcome conflicted) +6. If the decision was wrong and corrected, a LEARNED_FROM link is created + +The model weights never change. The memory structure evolves continuously. A frozen LLM produces better decisions over time because it receives better context from richer memory. + +## The Experiment + +We built the full system in Python (~1,000 lines) and tested it head-to-head against a baseline flat-vector RAG agent across a 9-round synthetic debugging scenario. Both agents used the identical LLM (Claude Sonnet 4) for reasoning. The only variable was the memory system. + +The scenario was designed to test five capabilities: + +| Round Type | What It Tests | Rounds | +|---|---|---| +| LEARN | Can the agent build experience from failures? | 1, 2, 4 | +| RED HERRING | Can the agent resist applying a pattern when it doesn't fit? | 3 | +| TEST | Can the agent apply learned patterns to new services? | 5, 6 | +| SUBTLE | Can the agent generalize to different symptoms, same root cause? | 7 | +| CORRECTION | After being corrected, does the agent adapt? | 8, 9 | + +Rounds 1-4 build experience: three connection pool failures across different services, plus one red herring (a deployment config error that *looks* like a connection pool issue). Rounds 5-7 test whether the agent applies the learned pattern to unfamiliar services and subtle symptom variations. Rounds 8-9 are the critical test: the agent is corrected after misdiagnosing a deployment-correlated error, then tested on a near-identical scenario to see if it adapts. + +## Results + +### Decision Accuracy + +| Round | Type | Episodic Agent | Baseline Agent | +|---|---|---|---| +| 1 | LEARN | ✗ | ✓ | +| 2 | LEARN | ✓ | ✓ | +| 3 | RED HERRING | ✗ | ✗ | +| 4 | LEARN | ✓ | ✓ | +| 5 | TEST | **✓** | ✗ | +| 6 | TEST | **✓** | ✗ | +| 7 | SUBTLE | **✓** | ✗ | +| 8 | CORRECTION | ✓ | ✓ | +| 9 | CORRECTION | ✓ | ✓ | +| **Total** | | **7/9 (78%)** | **5/9 (56%)** | + +The episodic agent won 7-5. A 40% relative improvement in decision accuracy using the exact same LLM. + +### Where the Gap Opened + +The episodic agent's advantage concentrated in exactly the rounds designed to test memory quality: + +**Rounds 5-6 (pattern application):** The episodic agent cited 4 past failure episodes with connection pool exhaustion as root cause, complete with correction annotations. It correctly identified pool exhaustion in new services. The baseline retrieved disconnected chunks and suggested checking timeout configurations — a pattern it picked up from the Round 3 red herring. + +**Round 7 (subtle symptoms — latency increase, no errors):** Both agents had the same evidence available. The episodic agent's retrieval surfaced a diverse set of episodes (thanks to MMR diversity filtering) including the Redis pool exhaustion from Round 6, which primed it to recognize that latency without errors can still be pool contention. The baseline defaulted to "check recent config changes." + +**Round 9 (adaptation after correction):** This is the result we're most proud of. Look at the episodic agent's reasoning: + +> *"Episode 1 directly parallels this situation — errors spiking immediately after a deployment (v2.4.1 then, v3.1.0 now) with no traffic change. In that case, the root cause was a database migration that dropped an index. The generalized fact confirms that deployment-related issues with immediate onset after version changes are more likely caused by configuration errors or missing dependencies than by connection pool problems."* + +It cited a specific past episode by analogy, quoted a generalized fact, and explained *why* this situation matches the deployment pattern rather than the connection pool pattern. The baseline gave a vaguer assessment. + +### Retrieval Quality + +This is where the structural difference is most visible: + +| Metric | Episodic Agent | Baseline Agent | +|---|---|---| +| Retrieved items with explicit outcome labels | **100%** | 25% | +| Correct pattern applications (Rounds 4-7) | **4/4** | 1/4 | +| False positives (Rounds 8-9) | **0** | 0 | + +Every item the episodic agent retrieved carried a structured outcome label (SUCCESS or FAILURE) with correction details. Only 25% of the baseline's chunks contained any outcome information — and those were incidental text mentions, not structured labels. + +The episodic agent correctly applied the connection pool pattern in all four rounds where it was the root cause, and correctly avoided it in both rounds where it wasn't. The baseline applied it correctly once. + +## What Didn't Work + +Two things didn't work as anticipated: + +**Round 3 (red herring):** Both agents failed. The symptoms looked like connection pool issues, but the root cause was a deployment config change. At this point, the episodic agent had only seen connection pool episodes — it had no counter-evidence for deployment-correlated errors. You can't distinguish patterns you've only seen one side of. After Round 8 introduced a correction, the agent successfully avoided this mistake in Round 9. + +**Fact quality variance.** Some extracted facts were specific and actionable ("Deployment-related issues with immediate onset are more likely configuration errors"). Others were vague ("Initial symptom-based diagnosis often leads to misidentifying the root cause"). A production system needs a usefulness filter, not just a confidence score. + +## What This Means + +The most important finding isn't the accuracy improvement. It's that the reinforcement loop closes without retraining. + +In the POC, we observed: + +- Rounds 1-4: Agent encounters failures, episodes recorded with outcomes and corrections +- After Round 4: Fact extracted — "Connection pool exhaustion is a common root cause under load" +- Rounds 5-7: Agent applies the pattern with increasing confidence (fact support count grows) +- Round 8: Agent encounters a deployment error, correctly identifies it as config, gets corrected +- After Round 8: New fact — "Deployment-related issues with immediate onset are more likely configuration errors" +- Round 9: Agent receives near-identical scenario, correctly avoids connection pool pattern, cites the Round 8 correction + +The model didn't change. The memory evolved. That's the whole point. + +## How It Compares to Existing Solutions + +Agent memory is a fast-moving space with several strong systems, each solving a different slice of the problem: + +**Mem0** excels at persistent personalization — extracting user preferences, managing session context, and reducing token costs through intelligent compression. It's the most production-ready memory layer available and integrates with nearly every agent framework. Its focus is on remembering about users and conversations rather than learning from task-level outcomes, which is a different problem than the one we're exploring here. + +**Zep/Graphiti** is doing some of the most interesting work in temporal knowledge graphs. Their bi-temporal model — tracking both when an event occurred and when it was ingested — addresses a real structural gap in how agent memory handles changing facts over time. Their episode and entity subgraphs share some philosophical DNA with our approach. Where our work diverges is in outcome tracking and reinforcement: we're specifically focused on whether a decision worked, and using that signal to update memory structure. + +**Letta (formerly MemGPT)** pioneered self-editing memory — giving the LLM tools to manage its own memory blocks. This is a powerful paradigm, and their recent work on "Context Repositories" and sleep-time compute suggests they're actively pushing toward agents that learn over time. Their team has been transparent that experiential learning is an unsolved problem, which is part of what motivated our exploration. + +**MemRL (Jan 2026 paper)** is the closest to our work academically. It shares the core insight of decoupling stable LLM reasoning from plastic, evolving memory. Their approach uses reinforcement learning to assign utility Q-values to memories, which is elegant but requires training a value function. Our approach is purely structural — no training step, no Q-values, just graph evolution and LLM-based reasoning over outcomes. + + +The common thread: most existing systems focus on knowledge persistence — remembering facts, preferences, and conversation history across sessions. The problem we're exploring is experiential learning — tracking whether past decisions worked, forming causal chains between episodes, and extracting reasoning heuristics that improve over time. These are complementary capabilities that would be needed by an ideal production system. + +## Try It Yourself + +The prototype is available in our experiments directory: + +``` +experiments/episodic-memory-prototype/ +├── memory/ # Timeline, encoder, episodes, graph, facts, retriever, reinforcer +├── agent/ # Episodic memory agent +├── baseline/ # Flat vector RAG agent (comparison) +├── simulator/ # 9-round debugging scenario +├── eval/ # Head-to-head comparison + scoring +└── tests/ +``` + +To run the comparison: + +```bash +cd experiments/episodic-memory-prototype +python -m venv .venv && source .venv/bin/activate +pip install -r requirements.txt +export ANTHROPIC_API_KEY=sk-ant-... +python -m eval.compare +``` + +Without an API key, it runs in heuristic mode (keyword-based decisions). With a key, both agents use Claude Sonnet for reasoning — that's where the quality gap becomes visible. + + +## Conclusion +This is a 9-round synthetic scenario we designed. It demonstrates the poc architecture works end-to-end and shows where episodic memory provides qualitatively different reasoning. It is not a peer-reviewed benchmark and should not be interpreted as a statistically rigorous claim. We're publishing the prototype so others can reproduce and extend the evaluation. +If this sparks interest do trigger github discussion. + +--- + +*The episodic memory prototype is available in `BharatMLStack` repo at `/experiments/episodic-memory-prototype`* diff --git a/docs-src/blog/bharatmlstack-history/llm-inference-optimization/bms.png b/docs-src/blog/bharatmlstack-history/llm-inference-optimization/bms.png new file mode 100644 index 00000000..b397fc88 Binary files /dev/null and b/docs-src/blog/bharatmlstack-history/llm-inference-optimization/bms.png differ diff --git a/docs-src/blog/bharatmlstack-history/llm-inference-optimization/index.md b/docs-src/blog/bharatmlstack-history/llm-inference-optimization/index.md new file mode 100644 index 00000000..a19550e8 --- /dev/null +++ b/docs-src/blog/bharatmlstack-history/llm-inference-optimization/index.md @@ -0,0 +1,120 @@ +--- +title: "LLM Inference Optimization Techniques: Engineering Sub-Second Latency at Scale" +description: "A practical guide to the optimization techniques behind sub-second LLM inference—covering paged KV caching, INT4 AWQ and FP8 quantization, kernel fusion, inflight batching, parallelism strategies, and speculative decoding, with production benchmarks on L4 and A100 GPUs." +authors: [jaya] +slug: llm-inference-optimization-sub-sec-latency +date: 2025-6-2 +tags: [llm, vllm, tensorrt-llm, mlplatform, meesho, bharatmlstack] +--- + +![BharatMLStack](./bms.png) +Raw execution of Large Language Models is inherently expensive and memory-intensive. To achieve sub-second latency and high throughput, we implement a multi-layered optimization strategy that targets the entire inference stack—from memory management to kernel execution. + +## 1. Advanced Memory Management: Paged & Prefix KV Caching + +The most significant bottleneck in LLM inference is not always compute, but memory bandwidth—specifically managing the Key-Value (KV) cache. + +### Paged KV caching + +Standard caching suffers from fragmentation. We use **Paged KV caching**, which operates similarly to an operating system's virtual memory: the KV cache is divided into non-contiguous blocks. This lets us serve larger batch sizes without running out of memory. + +### KV cache quantization + +To further maximize available memory, we implement **KV cache quantization** (e.g., FP8). By compressing stored attention keys and values from 16-bit to 8-bit, we nearly double the effective context window capacity of the GPU, allowing longer conversations or larger batches without materially degrading quality. + +### Prefix caching (the "voice bot" optimizer) + +For use cases like GenAI voice bots where the system prompt (e.g., "You are a helpful assistant...") is static across thousands of requests, we enable **prefix caching**. + +- **Impact**: By reusing pre-computed KV states for common prefixes, we achieve a cache hit rate of ~90%. This reduces **Time To First Token (TTFT)** by skipping redundant computation of the system prompt. + +## 2. Aggressive Quantization (INT4 AWQ & FP8) + +Running models in their native 16-bit precision (BF16) restricts maximum batch size and throughput. We use quantization to shrink model weights without sacrificing accuracy. + +### INT4 AWQ (Activation-aware Weight Quantization) + +For the Llama 3 family, we use **AWQ** to compress weights to 4 bits. This reduces model size by ~75%, allowing larger models to fit into L4 GPU memory and significantly improving token generation speed. + +### FP8 precision + +For NVIDIA Hopper (H100) architectures, we are exploring **FP8 quantization**, leveraging native FP8 tensor cores to accelerate matrix multiplications while maintaining a higher dynamic range than integer quantization. + +- **Verification**: We validate quantized models by comparing dot-product similarity of embeddings against the FP16 baseline, consistently achieving **>99% similarity**. + +## 3. Kernel Fusion & Custom Plugins + +To minimize overhead from launching thousands of small GPU operations, we fuse them into monolithic kernels using NVIDIA TensorRT plugins. + +- **Flash attention & FMHA**: We enable **Fused Multi-Head Attention (FMHA)** combined with flash attention to reduce memory reads/writes. +- **GEMM plugins**: We use specialized **GEMM** plugins to accelerate transformer linear layers. +- **Removing input padding**: Instead of padding short sequences to match the longest, we remove input padding so the GPU processes only valid tokens. + +## 4. Inflight (Continuous) Batching + +Traditional static batching waits for all requests in a batch to finish before returning results—so one long response delays everyone else. + +We implement **inflight batching**: as soon as one request completes, its slot is freed and filled by a new request from the queue. This keeps GPUs saturated and decouples latency of short queries from long ones. + +## 5. Parallelism Strategies: Scaling Beyond One GPU + +For large models (e.g., 70B+ parameters) that cannot fit into the VRAM of a single GPU, we use parallelism strategies. + +- **Tensor parallelism (TP)**: Split weight matrices across multiple GPUs (e.g., 4× L4 or 8× A100). Each GPU computes a shard and outputs are reduced at every layer. +- **Pipeline parallelism (PP)**: Split model layers across GPUs to pipeline compute (e.g., while one GPU computes later layers for Request A, another starts early layers for Request B). + +## 6. Speculative Decoding + +To reduce inter-token latency (ITL), we explore **speculative decoding**. + +- **Mechanism**: A smaller, faster "draft" model speculatively generates a short token sequence (e.g., 5 tokens). +- **Verification**: The larger target model verifies those tokens in one parallel forward pass. If correct, we effectively generate multiple tokens per large-model step; if not, we discard and regenerate. This is effective for predictable text, improving perceived generation speed. + +## Few Benchmarks + +Below are a couple of representative use cases and performance numbers. + +### Search query rewriting + +- **LLM**: Fine-tuned llama-3.2-1B +- **Input & output token length**: ~10–20 +- **Response type**: Non-streaming + +| Inference runtime | Hardware | Max requests/sec | Max p99 latency | +| --- | --- | ---: | ---: | +| TensorRT-LLM | 4 × L4 GPUs (multi-GPU) | 1000 | 95 ms | +| TensorRT-LLM | 1 × A100 40 GB GPU | 1000 | 69 ms | + +### Voice bot query + +- **LLM**: Llama-3.1-8B +- **Input token length**: ~1900–2000 +- **Output token length**: ~200 +- **Response type**: Streaming + +| Inference runtime | Concurrency | p99 TTFT (ms) | p99 ITL (ms) | Token throughput (tokens/sec) | Request throughput (req/sec) | Hardware | +| --- | ---: | ---: | ---: | ---: | ---: | --- | +| TensorRT-LLM | 1 | 36.27 | 22.78 | 45.66 | 0.23 | L4 | +| TensorRT-LLM | 2 | 49.81 | 23.21 | 89.37 | 0.45 | L4 | +| TensorRT-LLM | 4 | 55.33 | 36.62 | 153.39 | 0.78 | L4 | +| TensorRT-LLM | 8 | 66.5 | 39.11 | 279.88 | 1.47 | L4 | +| TensorRT-LLM | 16 | 131.8 | 30.39 | 547.8 | 2.77 | L4 | +| TensorRT-LLM | 32 | 277.22 | 48.02 | 925.7 | 4.78 | L4 | +| TensorRT-LLM | 64 | 498.52 | 71.62 | 1,164.40 | 6.2 | L4 | +| TensorRT-LLM | 128 | 677.31 | 120.37 | 1,445.18 | 7.69 | L4 | +| TensorRT-LLM | 256 | 1,926.31 | 216.88 | 1,600.81 | 8.52 | L4 | +| TensorRT-LLM | 1 | 21.17 | 9.24 | 130.05 | 0.68 | A100 | +| TensorRT-LLM | 2 | 25.78 | 9.21 | 264.5 | 1.35 | A100 | +| TensorRT-LLM | 4 | 28.52 | 10.99 | 437.69 | 2.27 | A100 | +| TensorRT-LLM | 8 | 34.4 | 12.61 | 760.49 | 3.96 | A100 | +| TensorRT-LLM | 16 | 68.03 | 14.32 | 1,343.80 | 7.01 | A100 | +| TensorRT-LLM | 32 | 185.96 | 16.82 | 2,287.30 | 11.92 | A100 | +| TensorRT-LLM | 64 | 136.87 | 21.17 | 3,625.22 | 18.89 | A100 | +| TensorRT-LLM | 128 | 463.78 | 34.15 | 4,456.51 | 23.24 | A100 | +| TensorRT-LLM | 256 | 890.12 | 59.18 | 5,188.24 | 27.05 | A100 | + +## Conclusion + +High-performance LLM inference is fundamentally a systems engineering problem: memory efficiency, kernel execution, batching strategy, and parallelism determine real-world latency and throughput. Techniques such as paged KV caching, aggressive quantization, kernel fusion, and inflight batching improve GPU utilization while reducing latency and memory pressure. + +These optimizations enable the platform to deliver sub-second responses, sustain high concurrency, and efficiently serve both lightweight and long-context workloads. By continuously optimizing across the full inference stack, we keep LLM serving scalable, cost-efficient, and production-ready for real-time AI applications. diff --git a/docs-src/blog/bharatmlstack-history/llm-inferencing-platform/bms.png b/docs-src/blog/bharatmlstack-history/llm-inferencing-platform/bms.png new file mode 100644 index 00000000..b397fc88 Binary files /dev/null and b/docs-src/blog/bharatmlstack-history/llm-inferencing-platform/bms.png differ diff --git a/docs-src/blog/bharatmlstack-history/llm-inferencing-platform/index.md b/docs-src/blog/bharatmlstack-history/llm-inferencing-platform/index.md new file mode 100644 index 00000000..ac16ef0f --- /dev/null +++ b/docs-src/blog/bharatmlstack-history/llm-inferencing-platform/index.md @@ -0,0 +1,201 @@ +--- +title: "Designing a Production-Grade LLM Inference Platform: From Model Weights to Scalable GPU Serving" +description: "A deep dive into building a production-grade LLM inference platform—covering the full LLMOps lifecycle from model onboarding and automated compilation to multi-engine serving with TensorRT-LLM, vLLM, and Dynamo, along with cold-start mitigation and LLM-specific observability." +authors: [jaya] +slug: multi-engine-llm-inferencing-platform +date: 2025-3-29 +tags: [llm, vllm, tensorrt-llm, mlplatform, meesho, bharatmlstack] +--- + +![BharatMLStack](./bms.png) +Serving large language models in production introduces new challenges across infrastructure, performance optimization, and operational lifecycle management. The LLM Inference Platform addresses these challenges by providing a unified system for deploying and managing open-source and fine-tuned LLMs at scale. + +The platform implements a complete LLMOps lifecycle — from model registration and automated compilation to deployment, runtime optimization, and monitoring. Designed as a self-service environment, users can onboard models directly from open repositories such as Hugging Face or upload custom fine-tuned models, and deploy them using a single-click workflow with no manual infrastructure or configuration steps required. + +In addition to fully automated deployment, the platform allows users to select and apply custom inference optimization techniques — such as quantization strategies, batching configurations, and runtime-specific performance enhancements — enabling teams to balance latency, throughput, and cost based on their use case. The goal is to reduce operational friction while enabling high-performance, production-grade LLM inference. + +## Why LLM Inference Is not just bigger ML model serving + +Large language model (LLM) inference introduces a fundamentally different set of challenges compared to traditional machine learning inference. While classical ML models typically perform a single forward pass to produce a fixed prediction, LLMs operate as autoregressive systems, generating outputs token by token based on previously generated context. This difference dramatically changes how inference systems must be designed, optimized, and scaled. + +### Autoregressive Generation and Sequential Computation: + +Unlike traditional models such as classifiers or recommenders — where inference cost is relatively constant — LLMs generate responses incrementally. Each new token depends on all previously generated tokens, making inference inherently sequential and dynamic. This means latency and compute requirements vary significantly depending on prompt length and output size, introducing complexity in scheduling and resource allocation. +Because tokens cannot be generated fully in parallel during decoding, GPUs may become underutilized without specialized batching and scheduling strategies. This has led to the development of dedicated LLM inference engines optimized for token-level execution. + +### Prefill and Decode Phases: + +LLM inference typically consists of two distinct stages: + +- Prefill phase — the model processes the input prompt and builds internal representations. This stage is compute-heavy and highly parallelizable. +- Decode phase — the model generates tokens sequentially, predicting one token at a time using previously generated context. + +The decode stage often becomes memory-bound rather than compute-bound, which creates new performance bottlenecks compared to traditional ML workloads. + +### Context Management and KV Caching: + +Another fundamental difference lies in how LLMs maintain context. Transformer-based models rely on attention mechanisms that require access to past token representations. To avoid recomputing these representations repeatedly, inference engines use key-value (KV) caching, which stores intermediate activations from previous tokens. +KV caching significantly improves performance by eliminating redundant computation, but it introduces new challenges: + +- Memory consumption grows with sequence length and batch size +- GPU memory becomes a critical bottleneck +- Efficient memory management becomes essential for scaling concurrent requests + +This tradeoff between compute efficiency and memory usage is unique to LLM inference workloads. + +### Dynamic and Irregular Workloads: + +Traditional ML inference typically operates on fixed-size inputs with predictable latency. In contrast, LLM requests vary widely in prompt length, output length, and runtime behavior. As a result: + +- Batch sizes must be dynamic rather than static +- Requests may enter and leave batches asynchronously +- Scheduling systems must continuously rebalance workloads to maximize GPU utilization + +These characteristics require specialized serving architectures that differ significantly from standard ML serving pipelines. + +### Streaming and User Experience Constraints: + +Another distinguishing factor is the expectation of real-time streaming responses. Instead of returning a single output, LLM systems often stream tokens to users as they are generated. +Because of these differences — sequential generation, growing memory requirements, dynamic workloads, and streaming constraints — LLM inference cannot be treated as a simple extension of existing ML serving systems. Production platforms must incorporate specialized runtime engines, advanced optimization techniques, and observability tailored specifically to LLM workloads. + +## LLMOps: High-Level Architecture + +![LLM Architecture](./llm-plat.png) + +The LLM Inference Framework is designed as a fully automated, end-to-end system for deploying and operating open-source and fine-tuned large language models at scale. The architecture abstracts the complexity of model optimization, hardware selection, deployment, and runtime management into a unified workflow that enables users to move from raw model weights to production-ready inference endpoints with minimal manual intervention. + +Our LLM Inference Framework is architected not just as a serving engine, but as a complete lifecycle management system. As illustrated in the high-level design below, the platform automates the journey of a model through seven distinct stages, ensuring reproducibility, performance, and scalability. + +1. Onboarding & Registration (The Source of Truth) + + The lifecycle begins with the Data Scientist or engineer. + + - Model Ingestion: Users onboard models—whether open-source (Hugging Face, NeMo) or internally fine-tuned—via the Truffle Box SDK/UI. + - LLM + Prompt Registry: Unlike traditional systems that only track model weights, our registry is a unified control plane. It stores both the Model Artifacts and the Prompt Templates. This allows Data Scientists to register and version-control prompts (e.g., "customer_support_v2") independently of the application code. + +2. The "Black Box" Build Engine + + Once a model is registered, the Automated LLM Compiler + Quantizer Module kicks off a background job on ephemeral GPU resources. + + - Transformation: The raw model is converted into a TRT-LLM Checkpoint. + - Quantization: The system automatically applies quantization algorithms (like INT4 AWQ or FP8) to reduce memory footprint. + - Engine Building: Finally, it compiles a highly optimized TRT Engine specifically tuned for the target hardware. + +3. Intelligent Profiling & Validation + + Before deployment, the new engine passes through the Hardware & Inference Runtime Profiler. + + - Benchmarking: This module empirically tests the engine against various hardware configurations (L4 vs. A100) and runtimes (TRT-LLM vs. vLLM). + - Optimization: It recommends the optimal configuration that meets latency SLAs (Time-To-First-Token) while minimizing cost. + +4. Smart Artifact Generation & Distribution + + To solve the Kubernetes "Cold Start" problem, the LLM Serving Artifacts Generation module packages the model using a bifurcated strategy: + + - Standard Models: Artifacts are uploaded to Cloud Storage (GCS) and downloaded by pods at startup. + - Very Large Models: For massive models (>8GB) where network downloads are too slow, the system pre-caches the model onto Secondary Boot Disks. These disks are attached directly to new GPU nodes during autoscaling, eliminating download wait times. + +5. Image Streaming & Deployment + + Simultaneously, the inference runtime container images are pulled from the Artifact Registry. + + - Image Streaming: We utilize container image streaming to allow pods to start initializing while the massive Triton/Dynamo container layers are still downloading, further shaving seconds off the startup time. link + +6. The Inference Runtime (Kubernetes) + + The workload lands on Kubernetes with Autoscaling. + + - Dynamic Backends: Depending on the profile generated in Stage 3, the pod initializes either TensorRT-LLM (for throughput) or vLLM (for flexibility), or spins up a Dynamo worker for distributed inference. + - Data Loading: The pod either downloads the model from Cloud Storage or mounts the pre-warmed Secondary Boot Disk ("Pull from Disk"). + +7. Client Interaction & Observability + + Finally, the LLM Inference Client executes the request. + + - Prompt Injection: The client pulls the specific prompt template ID from the Registry, ensuring the exact versioned instructions are used. + - Streaming Response: The request is sent via gRPC, and tokens are streamed back to the user in real-time. + +8. Observability: Monitoring the Pulse of GenAI + + In traditional microservices, success is measured by CPU utilization and request latency (p99). For Large Language Models, these metrics are insufficient. A user doesn't care if the GPU is at 80% utilization; they care about how fast the first word appears and how smoothly the rest of the sentence follows. + + To capture the true user experience, our platform instrumentation focuses on three critical LLM-specific metrics: + + 1. Time to First Token (TTFT) + - Definition: TTFT measures the time elapsed from the moment a request is received until the very first token is generated and streamed back to the user. + - Why it matters: This represents the "Prefill Phase" latency—the time the model takes to process the input prompt and load weights. A high TTFT makes the application feel unresponsive or "hung." + - Optimization: We closely monitor TTFT to ensure our Prefix Caching is effective (aiming for high cache hitrates), which drastically lowers this metric by skipping redundant prompt processing. + + 2. Inter-Token Latency (ITL) + - Definition: ITL measures the average time interval between the generation of consecutive tokens during the "Decode Phase". + - Why it matters: This defines the "perceived speed" of reading. Even if the first token is fast (low TTFT), high ITL makes the text generation look "jerky" or slow to the user. + - Benchmarks: In our testing with Llama 3.1, we track p99 ITL to ensure it stays below human reading speeds to maintain a natural conversational flow. + + 3. Token Throughput vs. Request Throughput + - We distinguish between two types of throughput to balance system efficiency with user load: + - Token Throughput (tokens/sec): The total number of tokens generated across all concurrent requests. This measures the raw compute efficiency of the GPU and the effectiveness of batching. + - Request Throughput (req/sec): The number of distinct user queries served per second. We use this to determine autoscaling thresholds, ensuring we scale out before the queue depth impacts ITL. + + 4. The Monitoring Stack + - Real-time Dashboards: We utilize Grafana to visualize these streaming metrics in real-time, allowing on-call engineers to spot "slow generation" incidents that generic "500 error" alerts would miss. + - Request Tracing: Since Triton Inference Server does not log request payloads by default, we integrate a Helix Client to asynchronously publish request logs to Log Tables. This allows us to trace a specific "slow" request back to its prompt to understand if a complex input caused the latency spike. + +## Supported Inference backends (TensorRT LLM, Dynamo & vLLM) + +Tailored for the Use Case: We do not believe in a "one-size-fits-all" approach to inference. Different use cases—whether a real-time voice bot requiring ultra-lowsub-second latency or a massive reasoning task requiring huge context windows—demand different runtime characteristics. Our platform is designed to be runtime-agnostic, allowing us to automatically select and tailor the best engine based on the specific requirements of the application: + +1. TensorRT-LLM: The High-Performance Standard + + Suitable for: High-throughput production workloads where latency is critical (e.g., customer support chat, real-time voice bots). + + TensorRT-LLM serves as our default backend for these scenarios. Our internal benchmarks on Llama 3.1 and 3.2 models demonstrated that a tuned TensorRT-LLM engine significantly outperforms standard runtimes, especially when utilizing INT4 AWQ and FP8 quantization . + + Key optimizations we tailor for these high-load cases include: + + - Optimized execution via TensorRT engine compilation + - Quantization-aware execution for reduced memory usage and improved throughput + - Inflight Batching: Allowing requests to be processed continuously without waiting for the entire batch to finish, drastically improving GPU utilization . + - Custom Plugins: Enabling specific NVIDIA plugins like the GEMM plugin and GPT Attention plugin to accelerate matrix multiplications and attention mechanisms . + +2. Dynamo: Distributed Inference for Reasoning Models + + Suitable for: Very large "reasoning" models (70B+) or scenarios requiring massive context windows where a single GPU's memory is insufficient. + + For these memory-bound tasks, we utilize Dynamo, a low-latency distributed inference framework . Unlike monolithic servers, Dynamo disaggregates the inference process to scale resources horizontally: + + - KV Aware Routing: A specialized router directs requests to workers that already hold the relevant Key-Value (KV) cache, minimizing redundant computation . + - Prefill vs. Decode Split: The workload is divided into Prefill Workers (processing the prompt) and Decode Workers (generating tokens), allowing us to scale the compute-heavy "reading" phase independently from the memory-heavy "writing" phase . + - Distributed execution across multiple GPU resources + +3. vLLM: The Flexible Baseline + + Suitable for: Rapid prototyping, testing new model architectures, or low-traffic internal tools where ease of deployment outweighs raw throughput. + + While TensorRT-LLM is optimized for maximum speed, vLLM provides a robust and flexible baseline . + + - High throughput through dynamic batching and efficient memory utilization + - Paged KV cache management for handling long contexts and concurrent requests + - Strong support for open-source model ecosystems + - Rapid Adoption: It allows us to onboard new model architectures immediately without waiting for a custom TensorRT build. + - Benchmarking Insight: In our internal tests, vLLM provided a strong baseline but often lacked the specific max-token optimizations present in our custom TRT engines . We use it strategically for initial testing before committing to a full TensorRT optimization pipeline. + +## Conclusion + +Large language model inference introduces a fundamentally new class of infrastructure challenges—where performance is governed not just by raw compute, but by memory efficiency, intelligent scheduling, runtime specialization, and lifecycle automation. Unlike traditional ML serving, LLM inference requires systems that understand token-level execution, manage rapidly growing context state, and continuously balance latency, throughput, and cost under highly dynamic workloads. + +The LLM Inference Framework addresses these challenges by transforming inference into a fully automated, reproducible lifecycle—from model onboarding and compilation to deployment, optimization, and observability. By integrating automated quantization and engine compilation, intelligent runtime selection, cold-start mitigation strategies, and LLM-specific observability metrics such as Time-to-First-Token and Inter-Token Latency, the platform ensures both high performance and operational simplicity. + +Equally important, the framework is designed with flexibility and future evolution in mind. Its runtime-agnostic architecture enables seamless adoption of emerging inference engines, hardware accelerators, and optimization techniques without requiring platform redesign. This ensures that teams can continuously leverage advancements in the rapidly evolving LLM ecosystem while maintaining consistent operational workflows. + +Ultimately, the goal of the platform is to make production-scale LLM deployment as seamless and reliable as traditional software deployment—allowing teams to focus on building intelligent applications rather than managing infrastructure complexity. By combining lifecycle automation, runtime optimization, and deep observability, the LLM Inference Framework provides a scalable foundation for delivering fast, cost-efficient, and production-ready LLM experiences. + +## Future Explorations + +While we have achieved significant milestones in latency and throughput, the landscape of GenAI is evolving rapidly. Our roadmap focuses on increasing flexibility, reducing costs, and enhancing reliability for enterprise-grade workloads. Here is what we are building next: + +- TPU Support: To diversify our hardware supply chain and further optimize cost-per-token, we are evaluating Google Cloud TPUs to bake it into our platform. By leveraging the JAX and PyTorch/XLA ecosystems, we aim to unlock the massive throughput potential of TPU v5e chips, particularly for our open-source Llama models. This will allow the hardware profiler to dynamically choose between NVIDIA GPUs and Google TPUs based on real-time availability and price-performance metrics. +- Multi-LoRA Serving (Serverless Experience): Currently, deploying a fine-tuned model requires a dedicated GPU. We are building support for Multi-LoRA serving, which will allow us to serve hundreds of unique, fine-tuned adapters on top of a single frozen base model. This will drastically reduce costs for multi-tenant applications, enabling a "serverless" experience where specific fine-tunes are hot-swapped instantly per request. +- Spot Instance Orchestration: To further optimize cloud costs, we are developing fault-tolerant mechanisms to run inference workloads on Spot Instances. By implementing aggressive checkpointing and seamless request draining, we aim to leverage cheaper, preemptible compute capacity without interrupting the user's streaming experience. +- Semantic Caching Layer: We plan to move beyond standard Prefix Caching to implement Semantic Caching. By using a vector database to fetch responses for semantically similar queries (e.g., "How do I reset my password?" vs. "Password reset steps"), we can bypass the GPU entirely for repetitive queries, reducing latency to near-zero. +- Context-Aware Autoscaling: Standard CPU/GPU utilization metrics are often insufficient signals for scaling LLMs. We are working on KV-cache pressure metrics for autoscaling. This ensures that we scale out before the memory fills up, preventing eviction-based slowdowns during traffic spikes. +- Online Evaluation & Guardrails: We are integrating a lightweight "Trust Layer" into the proxy. This will allow for low-latency input/output filtering (Guardrails) and asynchronous "LLM-as-a-Judge" evaluation pipelines to monitor response quality in production, not just system health. \ No newline at end of file diff --git a/docs-src/blog/bharatmlstack-history/llm-inferencing-platform/llm-plat.png b/docs-src/blog/bharatmlstack-history/llm-inferencing-platform/llm-plat.png new file mode 100644 index 00000000..1f0fa9f7 Binary files /dev/null and b/docs-src/blog/bharatmlstack-history/llm-inferencing-platform/llm-plat.png differ diff --git a/docs-src/blog/bharatmlstack-history/post-one/index.md b/docs-src/blog/bharatmlstack-history/post-one/index.md deleted file mode 100644 index aa933741..00000000 --- a/docs-src/blog/bharatmlstack-history/post-one/index.md +++ /dev/null @@ -1,240 +0,0 @@ ---- -slug: post-one -title: "Building Meesho’s ML Platform: From Chaos to Cutting-Edge (Part 1)" -authors: [adarsha, aditya, bhawani, jigar] -date: 2022-11-15 -tags: [online-feature-store, interaction-store, mlplatform, meesho] ---- - -![BharatMLStack](./bharatmlstack.png) -## The Genesis: How a Friday Night Roast Sparked Meesho’s ML Platform - -It all started in early 2022, over a casual Friday evening catch-up. Like many great origin stories, this one began with friendly banter between a group of backend engineers and data scientists. As the conversations unfolded, so did the roasting—until one remark hit a little too close to home: - -*"Why are we still crunching data for Monthly Active Users (MAU) when the next day it’s all about Daily Active Users (DAU)?"* - -The laughter died down, and the question lingered. When we regrouped on Monday—clear-headed and slightly reflective—we decided to dig into the numbers. What they discovered was quite revealing: a large portion of compute resources wasn’t being put to good use. -Much of the system’s effort was spent supporting users who weren’t actively engaging, and even for new users, the experience wasn’t optimized to make a meaningful impact. - -At the same time, Meesho had just launched a company-wide initiative to reduce costs—and every team had to contribute. This realization sparked the journey that would eventually lead to the **Meesho ML Platform**, known today as **BharatMLStack**. - -![Alt Text](./old-batch-arch.png) - -Before the ML Platform, our recommendation and ranking pipelines followed a batch processing approach: -- **Data Ingestion**: The Data Platform team executed ETL jobs to ingest raw user data—including user profiles, interaction logs, and product impressions—into designated S3 buckets. -- **Layer 1**: Embedding Generation: On the Data Science side, Spark jobs pulled data from multiple S3 sources, cleaned and preprocessed it, and applied matrix factorization to generate user and item embeddings. The processed data and embeddings were then stored back in S3 in a structured format. -- **Layer 2**: Candidate Generation (CG): In this stage, Spark jobs leveraged embeddings and historical interaction data to generate candidate recommendations for users. These candidate lists were subsequently written to S3. -- **Layer 3**: Ranking and Merging – A final round of processing ranked the generated candidates using ML models, combined different candidate lists, and stored the final ranked recommendations in a caching system. -- **Serving**: A microservice retrieved ranked recommendations from an in-memory data store via exposed APIs, delivering personalized listings across key surfaces such as "For You" and Category Landing Pages (CLP). - -This approach held up well—until Meesho started seeing a significant surge in traffic. - -## The Turning Point: From Batch to Real-Time - -At this time, the team was iterating on new **Ranker models**, and real-time inference seemed like the next logical step. But Rankers needed **real-time feature retrieval**, which meant an **online feature store** had to be built first. - -Exploring open-source options led to **cost vs. performance trade-offs**, but Meesho’s surging traffic meant that **latency and stability were non-negotiable**. After multiple debates and stakeholder discussions, a bold decision was made: - -*We would build our own feature store.* - -Meanwhile, efforts began to bring **Candidate Generators (CGs)** to real-time. The challenge? **Storing and retrieving user interactions quickly enough** to power real-time recommendations. - -As the team dove deeper, a new roadblock emerged: -Our ML jobs were orchestrated using **Airflow DAGs**, giving data scientists flexibility in experimentation. But transitioning to real-time execution threatened this agility. Every change would now require backend engineering support, **slowing down iteration cycles**. - -That’s when the idea struck: -We needed a **framework for real-time DAG execution**—one that preserved the same flexibility as Airflow but worked for **streaming data**. - -This moment shaped the **next phase of our journey**. - -## First Generation Design - -![Alt Text](./first-gen-arch.png) - -# Laying the Groundwork: The First-Gen ML Platform - -To solve these challenges, the team built three foundational components: - - -### 1. IOP Framework: A Real-Time DAG Executor - -- **Reusable Nodes**: Each DAG node (e.g., an invocation to a CG service, a ranker, or a filter) had to be implemented only once. After that, it could be reused across any workflow by referencing it in config. -- **Config-driven Dynamic Graphs**: Execution graphs were defined as adjacency lists stored in **ZooKeeper**, allowing teams to modify the sequence or structure of operations without touching application code. -- **Plug-and-play CGs**: The Candidate Generator interface was preserved, so a single CG node could call any CG service by passing `cg_name` in the request. This drastically reduced the code surface area and improved maintainability. -- **Production-Grade DAGs**: DAGs were designed to execute in **low-latency real-time environments**, with support for **parallel execution, retries, and branching**. - -[More about IOP DAG](https://www.meesho.io/blog/rebuilding-meeshos-ranking-platform) - - -### 2. Online Feature Store - 0th Version - -- Used **Cassandra** and **Redis** for low-latency feature serving. -- Maintained feature consistency using **Feature Groups** with TTL-based expiry. -- A hybrid schema was used: feature keys stored in **ZooKeeper**, data stored in **compact arrays**. - - -### 3. Interaction Store - 0th Version - -- Captured real-time user interactions like clicks, orders, and add-to-cart events. -- Stored event data in **Redis ZSETs (sorted sets)** to enable fast lookups for recommendation engines. -- Provided an API to fetch a user's **last _k_ interactions** or **interactions within a time window**. - - -With these components in place, **real-time ML at Meesho became a reality**. - -This was just the beginning. - -## Building the Online Feature Store - 0th Version - -![Alt text](./online-feature-store-v0.png) - -### Choosing the Right Tech Stack - -We spent considerable time evaluating various databases, caches, and communication protocols for our **online feature store**. After carefully weighing **cost, latency, throughput**, and **operational stability**, we settled on a combination of: - -- **Cassandra** and **Redis** for storage -- **gRPC + Proto3** as our communication layer - - -### Streamlining the Data Flow - -To keep things simple in the initial version: - -- **Feature engineering jobs** wrote raw outputs to an **S3 bucket** -- A **daily feature push job**: - - Read from S3 - - Grouped related features into **Feature Groups** (ensuring consistency) - - Pushed them to **Kafka** - -For features requiring frequent updates: - -- **Ad-hoc jobs** computed features in higher frequency -- These jobs pushed to both **Kafka** and **S3** (S3 preserved historical data for future model training) - - -## The Challenges: Data Format and Storage - -One of the most critical design challenges was how to store feature data **efficiently and consistently**, especially in databases like **Cassandra** and **Redis**, which come with unique storage constraints. - -We had to solve for three key requirements: - -- ### Feature Consistency - When a feature group contains features like `order_count_1h` and `click_count_1h`, both must reflect the **same time window**. Inconsistent updates would lead to **unreliable model predictions**. - -- ### TTL Granularity - Each feature group required an **expiry timestamp**, so that **all features within it expired together**—preserving consistency during reads. - -- ### Extensibility Across Databases - We anticipated that infra needs would evolve. To future-proof our system, the data format was designed to be **decoupled from DB-specific layouts**, enabling portability to systems like **ScyllaDB**, **DynamoDB**, **HBase**, or **BigTable**. - - ---- - -## Overcoming Technical Constraints -At the time, we were using Cassandra, which not only imposed a soft limit of 75 columns per row, but also exhibited significant performance degradation as the number of columns increased further, particularly in memory constrained machines. Wide rows caused high memory usage during reads, unpredictable latencies due to heavy deserialization overhead, and inefficiencies during compactions and repairs. This ruled out the naive "one column per feature" approach. We needed a format that was compact, minimized the number of columns, and remained efficient and portable across different storage systems. - -## The Solution: Schema Separation - -We introduced the concept of Feature Groups—logical groupings of features that must remain consistent with one another. -To represent these groups efficiently, we adopted a layered storage approach: - -- **Feature Labels (Keys)** were stored in ZooKeeper, serving as the schema. -- **Feature Values** were stored as a comma-separated string array in Cassandra or Redis. -- **Expiry Timestamp and Schema Version** were appended using a semi-colon delimiter at the end of the string. - -Example: - -```bash -feature_1_value,feature_2_value,feature_3_value;expiry_ts -``` - -This format allowed: -- Consistent writes and reads at the group level -- Easy parsing of feature values using the schema lookup from ZooKeeper -- Efficient storage with minimal DB column usage -- Support for per-group TTLs and schema evolution - -## Tracking Changes in Feature Groups -Feature groups don’t stay static. As models evolve, features get added, renamed, or removed. But schema changes often go live before the data is ready—and stopping ingestion just to wait for everything to align isn't feasible. - -### Common Real-World Scenarios: -- A new feature is added to the schema, but ingestion jobs still use the older schema version. -- Ongoing writes don’t include the newly added feature, and stopping ingestion would break freshness for existing features. -- During serving, models request a mix of old and new features, depending on rollout stages. - -## The Solution: Schema Versioning -We solved this with versioned feature group schemas, which unlocked several capabilities: -- ### Backward Compatibility - Older ingestion jobs can continue writing using older schema versions. During reads, the system uses the schema version embedded in the value to interpret the data correctly. -- ### Partial Availability Handling - During inference, if some features in the request aren’t available (due to rollout delays or missing data), the system serves default values, ensuring the inference call doesn’t fail. -- ### Safe Writes Without Pipeline Pauses - With schema versioning, we no longer had to stop ingestion pipelines for schema updates. Writes using previous versions can continue safely, and downstream consumers evolve independently. -This design gave us the flexibility to move fast without breaking things—preserving data quality, enabling experimentation, and ensuring reliability at scale. - -![Alt Text](./schema.png) - -## Interaction Store - 0th Version - -![Alt Text](./interaction-store-v0.png) - -To power real-time Candidate Generators (CGs), we needed fast access to user behavior signals—like what a user recently clicked, ordered, or added to their cart. These interactions form the basis for many real-time recommendations, such as **Similar Products**, **People Also Viewed**, or **Recently Ordered Again**. -For the **0th version** of the Interaction Store, we focused on a design that was **simple, fast, and reliable** — optimized for high-throughput ingestion and low-latency lookups. - -## Event Ingestion -We instrumented our backend services to emit key user interaction events to Kafka in real time. These included: -- Click -- Order -- Add to Cart -- Wishlist -- Share - -Each event carried essential metadata: -- userId — uniquely identifies the user -- productId — the item being interacted with -- timestamp — the moment the interaction occurred - -This decoupled the interaction logging from storage, allowing ingestion and consumption to scale independently. - -## Storage Design -To store these events, we built Kafka consumers that processed the incoming streams and wrote the data into Redis, using sorted sets (ZSETs) as the primary data structure. - -### Why Redis? -Redis gave us: -- **Low-latency** reads and writes -- **Time-ordered data** using ZSETs (via score = timestamp) -- **Native TTL support**, if needed in later versions -- **In-memory performance** —ideal for real-time CGs - -### Storage Structure -Each user’s interactions were stored using a composite key format, uniquely identifying the user and interaction type. This structure allowed efficient organization and quick retrieval of recent activity for recommendation generation: - -```bash -userId_eventType → ZSET[...(pid, ts)...] -``` - -Within each ZSET: - -- The **timestamp** served as the score, maintaining temporal order -- The **productId** (optionally with metadata) was the **value** - -This allowed us to efficiently retrieve the interactions with HTTP-based API server with two query modes: -- Fetch the **last k interactions** of a specific type for a given user with `ZREVRANGE(userId_eventType, count)` -- Retrieve **all interactions within a time range** (e.g., last 24 hours) with `ZREVRANGEBYSCORE(userId_eventType, timeRange)` - -### Built-in Guardrails -Since Redis was the sole store, we implemented High Availability (HA) to prevent data loss. To optimize memory usage, we also enforced size limits per event type—only storing the last k interactions per user, with older entries getting truncated. - -## Conclusion: Laying the Foundation for Real-Time ML - -In this first phase, we tackled the **fundamentals**—shifting from batch-based recommendations to a **real-time Recommendation** using ML platform that could keep up with Meesho’s growth. - -With the **IOP Framework**, **Online Feature Store**, and **Interaction Store**, we built the core infrastructure to support real-time personalization at scale. These wins have already unlocked: -- ✅ Faster, more dynamic recommendations for millions of users. -- ✅ Better infrastructure efficiency, reducing wasted compute power. -- ✅ A flexible, modular system that allows for further experimentation. - -But this is just the beginning. While we've solved key challenges, **certain roadblocks remain** —from optimizing **cost-performance trade-offs** to **seamlessly evolving schemas**. - - -This foundational work laid the path for a reliable and scalable **real-time feature serving layer**. \ No newline at end of file diff --git a/docs-src/blog/bharatmlstack-history/scaling-model-inference-and-embedding-search/bms.png b/docs-src/blog/bharatmlstack-history/scaling-model-inference-and-embedding-search/bms.png new file mode 100644 index 00000000..b397fc88 Binary files /dev/null and b/docs-src/blog/bharatmlstack-history/scaling-model-inference-and-embedding-search/bms.png differ diff --git a/docs-src/blog/bharatmlstack-history/scaling-model-inference-and-embedding-search/index.md b/docs-src/blog/bharatmlstack-history/scaling-model-inference-and-embedding-search/index.md new file mode 100644 index 00000000..933b76bf --- /dev/null +++ b/docs-src/blog/bharatmlstack-history/scaling-model-inference-and-embedding-search/index.md @@ -0,0 +1,102 @@ +--- +title: "Cracking the Code: Scaling Model Inference & Real-Time Embedding Search" +description: "How Meesho scaled model inference with self-hosted Triton on GKE—slashing latency and costs by 65%—and built a real-time embedding search system on Qdrant to power personalized recommendations at scale." +authors: [aditya, jaya, adarsha] +slug: scaling-model-inference-and-embedding-search +date: 2024-05-21 +tags: [model-inference, embedding-search, mlplatform, meesho, bharatmlstack] +--- + +![BharatMLStack](./bms.png) +By mid-2023, we had transformed our ML stack—building a real-time feature store, optimizing model retrieval, and fine-tuning ranking. But two critical gaps remained: + +- 🔹 Scaling model inference without hitting infrastructure roadblocks +- 🔹 Moving embedding search from batch to real-time for candidate generation + +Here’s how we tackled these last-mile challenges, broke free from infrastructure constraints, and built a cost-efficient, high-performance system. + +## Breaking Free from the Scalability Ceiling + +### The Model Serving Bottleneck—A Wake-Up Call + +July 2023. With just months left for the Mega Blockbuster Sale (MBS), we noticed a serious issue—scaling our model-serving infrastructure was taking 10–15 minutes. In real-time ML, that’s an eternity. +In one of our war rooms, we ran a quick experiment: + +- 🚀 We deployed an XGBoost model on a self-hosted Triton Inference Server running on a 16-core machine. +- 🚀 Fired requests and compared the outputs with our existing cloud-hosted setup. +- 🚀 The results matched—perfectly. + +That moment changed everything. We prepped a backup Triton setup on EKS, just in case our cloud provider couldn't allocate enough compute resources in time. Luckily, they did—but the seed was planted. +Then in October, just two weeks before MBS, we got an alarming response from our infrastructure team: + "Node availability may be an issue." +With no time to waste, we moved 30% of real-time ML traffic to our self-hosted Triton cluster. The results? + +- ✅ p99 latency dropped from 90–100ms to 30–40ms +- ✅ Triton handled significantly higher throughput on fewer resources +- ✅ No model changes were needed + +MBS ran without a hitch, proving that self-hosted inference was the way forward. + +### Scaling Triton on GKE + +This left us with two choices: + +- 1️⃣ Port models to a managed cloud inference service, investing time in learning a new deployment stack +- 2️⃣ Scale our existing Triton setup on GKE, optimizing for cost and performance + +We went with Option 2—and it slashed inference costs to 35% of what we previously paid, while giving us full control over scaling and optimizations. + +### Fixing the Cold Start Problem + +As we onboarded more deep learning (DL) models, we hit a new bottleneck, new inference pods took 7–9 minutes to spin up. + +After profiling, we found the culprits: + +- Triton’s base image—a massive 5GB +- Model binaries—often 1GB+ +- Startup delay—mostly due to downloading and initializing these assets + +To fix this, we built a lightweight Triton image, stripping unused components and shrinking the size to 900MB. This cut cold start times drastically, making auto-scaling faster and smoother. + +## Embedding Search: The Last Piece of the Puzzle + +By mid-2023, most of our ML stack had gone real-time—except for Candidate Generation (CG), which still ran in batch mode. To truly power real-time recommendations, we needed an online embedding search system. + +### Choosing the Right Vector Database + +We benchmarked three production-ready vector DBs across key parameters: + +- Milvus +- Qdrant +- Weaviate + +After extensive POCs, Qdrant stood out for its: + +- ✅ Blazing-fast search latency on high-dimensional vectors +- ✅ Efficient memory usage, crucial for in-memory workloads +- ✅ Support for upserts and soft deletes, vital for Ads use cases +- ✅ gRPC + REST APIs, making integration seamless +- ✅ Powerful filtering, allowing fine-tuned retrieval (e.g., filtering Ads by category, active status, etc.) + +At its core, Qdrant uses HNSW indexing, delivering both high recall and low-latency nearest-neighbor search—a perfect fit for our needs. + +### Embedding Freshness & Real-Time Updates + +To ensure embeddings stayed up to date, we built a dual ingestion pipeline: + +- 📌 Daily Refresh: A bulk pipeline updated embeddings overnight +- 📌 Real-Time Updates: Ads events triggered immediate upserts/deletes + +This setup powered real-time "Similar Products" recommendations on the product page and became the foundation for Ads Candidate Generation, ensuring the right ads surfaced in milliseconds. + +![Skye](./vss.png) + +## Final Takeaways: Scaling Smartly for Real-Time ML + +- 🚀 Self-hosted inference on Triton gave us lower cost, faster scaling, and better performance than managed services +- 🚀 Building a custom Triton image reduced cold starts, improving responsiveness +- 🚀 Qdrant-based embedding search enabled real-time personalization at scale +- 🚀 Real-time updates for embeddings unlocked dynamic, up-to-date recommendations + +By early 2024, Meesho’s ML stack had evolved into a fully real-time, scalable, and cost-efficient system, setting the foundation for even bigger leaps ahead. + diff --git a/docs-src/blog/bharatmlstack-history/scaling-model-inference-and-embedding-search/vss.png b/docs-src/blog/bharatmlstack-history/scaling-model-inference-and-embedding-search/vss.png new file mode 100644 index 00000000..c6b18475 Binary files /dev/null and b/docs-src/blog/bharatmlstack-history/scaling-model-inference-and-embedding-search/vss.png differ diff --git a/docs-src/docs/inferflow/_category_.json b/docs-src/docs/inferflow/_category_.json new file mode 100644 index 00000000..384c93bd --- /dev/null +++ b/docs-src/docs/inferflow/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Inferflow", + "position": 2, + "link": { + "type": "generated-index", + "description": "Inferflow is a graph-driven feature retrieval and model inference orchestration engine. It dynamically resolves entity relationships via configurable DAGs, retrieves features from the Online Feature Store, and orchestrates model scoring — all without custom code." + } +} diff --git a/docs-src/docs/inferflow/v1.0.0/_category_.json b/docs-src/docs/inferflow/v1.0.0/_category_.json new file mode 100644 index 00000000..3c72a212 --- /dev/null +++ b/docs-src/docs/inferflow/v1.0.0/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "v1.0.0", + "position": 1 +} diff --git a/docs-src/docs/inferflow/v1.0.0/architecture.md b/docs-src/docs/inferflow/v1.0.0/architecture.md new file mode 100644 index 00000000..789e9a4d --- /dev/null +++ b/docs-src/docs/inferflow/v1.0.0/architecture.md @@ -0,0 +1,267 @@ +--- +title: Architecture +sidebar_position: 1 +--- + +# BharatMLStack - Inferflow + +Inferflow is part of **BharatMLStack**, a graph-driven feature retrieval and model inference orchestration engine built in **Go**. It eliminates the need for custom feature retrieval code by using configurable DAG topologies to dynamically resolve entity relationships, fetch features from the Online Feature Store, and orchestrate model scoring — all driven by configuration stored in **etcd**. + +--- + +## Overview + +In a typical ML serving pipeline, every new model requires bespoke code to: +- Fetch features from multiple entities (user, product, user x category, etc.) +- Infer intermediate entity relationships (e.g., extract category from product to fetch user x category data) +- Orchestrate one or more model inference calls +- Handle I/O, batching, and error propagation + +Inferflow abstracts all of this behind a **config-driven DAG executor**. Given a `model_config_id` and context entities (e.g., `userId`, `productIds`), it: + +1. Loads a pre-defined feature retrieval and inference graph from etcd +2. Executes the graph to resolve entity relationships dynamically +3. Retrieves features from the Online Feature Store (OnFS) in parallel +4. Calls model serving endpoints (Predator) and compute services (Numerix) +5. Returns scored results as a structured response + +--- + +## High-Level Architecture + +![Inferflow Architecture - DAG Topology Executor](../../../static/img/v1.0.0-inferflow-arch.png) + +The diagram shows the internal DAG structure of Inferflow's topology executor. gRPC APIs (Pair, Point, Slate) feed into the DAG, where **Feature Init** bootstraps the ComponentMatrix. Feature components (FS User, FS Product, FS Region, FS User Cat, FS Region Scat) fetch features from **OnFS** in parallel and populate columns in the shared **2D Result Matrix**. Model components (Model A, Model B) call **Predator** for inference, and compute components call **Numerix** for operations like reranking. The entire DAG topology is driven by config loaded from **etcd**. + +--- + +## Core Components + +### 1. gRPC Server + +Inferflow exposes its APIs via a gRPC server, with HTTP health endpoints multiplexed on the same port using **cmux**. The server provides: + +- **Inferflow API** — `RetrieveModelScore`: entity-based feature retrieval and scoring +- **Predict API** — `InferPointWise`, `InferPairWise`, `InferSlateWise`: structured inference with targets, pairs, and slates + +### 2. DAG Topology Executor + +The heart of Inferflow. Each model configuration defines a `component_dependency` map that describes a Directed Acyclic Graph (DAG) of components. + +**Execution model:** +- Uses **Kahn's algorithm** for topological ordering +- Components at the same level run **concurrently** in goroutines +- All components share a mutable `ComponentMatrix` (rows = entity IDs, columns = features/scores) +- DAG topologies are **cached** using Murmur3 hashing with Ristretto cache + +**Validation:** +- Cycle detection via in-degree analysis +- Component existence verification against the `ComponentProvider` + +### 3. Component Types + +Inferflow defines four types of DAG components: + +| Component | Role | External Dependency | +|-----------|------|---------------------| +| **FeatureInitComponent** | Root node — initializes the `ComponentMatrix` with entity IDs and schema | None | +| **FeatureComponent** | Fetches features from the Online Feature Store for a specific entity type | OnFS (gRPC) | +| **PredatorComponent** | Calls model serving endpoints for inference scoring | Predator / Helix (gRPC) | +| **NumerixComponent** | Calls compute engine for operations like reranking | Numerix (gRPC) | + +### 4. ComponentMatrix — The 2D Result Matrix + +The ComponentMatrix is a shared, mutable 2D data structure that flows through the entire DAG. Every component reads from and writes to this matrix, progressively building a complete feature + score row for each entity. + +![DAG Execution & 2D Matrix Flow](../../../static/img/v1.0.0-inferflow-dag-matrix.png) + +#### How the matrix evolves through the DAG + +The diagram above illustrates the three execution phases and how the 2D matrix grows at each stage: + +**Phase 1 — Feature Retrieval** + +The **init** node creates an empty matrix with one row per target entity ID. Feature components then execute — first the top-level entities (entity A, entity B) fetch their features from OnFS and populate their columns (shown as colored blocks). Derived entities (entity C, D, E) resolve their keys from the already-populated columns and add more feature columns. At this point the matrix contains all feature data, with each color representing features from a different entity. + +The right side of the diagram shows the matrix being **decomposed** — feature columns from different entities are separated into per-model input groups, selecting only the features each model needs. + +**Phase 2 — Model Invocation** + +Model X and Model Y each receive their decomposed feature slices, call **Predator** for inference, and write score columns back into the matrix (shown as new colored columns appended to the right). Multiple models can run in parallel if they don't depend on each other's outputs. + +The scores are then decomposed again to prepare inputs for the compute stage. + +**Phase 3 — Numerix Compute** + +The **Score Comb** node takes score columns from both models, calls **Numerix** for a final compute operation (e.g., score combination, reranking), and writes the final score column (shown in dark red) into the matrix. The result is a complete row per entity with all features and all scores. + +#### Matrix structure + +| Property | Description | +|----------|-------------| +| **Rows** | One per target entity ID (e.g., each product being scored) | +| **String columns** | Human-readable values used in responses | +| **Byte columns** | Binary-encoded feature values used for model inputs | +| **Column naming** | `entity_label:feature_group:feature_name` | + +Each component only reads the columns it needs and writes to its own columns, enabling safe concurrent execution across independent branches of the DAG. + +For slate-based APIs, a companion `SlateData` structure holds per-slate matrices and scores, with `slate_target_indices` mapping slates to rows in the main matrix. + +### 5. Configuration Management (etcd) + +Model configurations are stored in etcd and hot-reloaded via watchers: + +- **Config paths**: `/config/inferflow/services/`, `/model-config` +- **Watch mechanism**: etcd watchers trigger `ReloadModelConfigMapAndRegisterComponents` on any change +- **On reload**: Updates `ConfigMap`, re-initializes feature schemas, and re-registers DAG components + +This means new models or configuration changes go live **without redeployment**. + +### 6. External Integrations + +#### Online Feature Store (OnFS) +- gRPC client calling `FeatureService.RetrieveFeatures` +- Batched retrieval with configurable batch size and deadline +- Auth via `CALLER_ID` and `CALLER_TOKEN` metadata + +#### Predator (Model Serving) +- Uses `go-sdk` for model inference +- Supports **percentage-based traffic routing** across multiple model endpoints +- Configurable calibration and batch sizing + +#### Numerix (Compute Engine) +- Uses `go-sdk` Numerix client +- RPC: `NumerixService.Compute` with entity score data +- Used for compute operations like reranking + +#### Kafka (Inference Logging) +- Async inference log publishing using `segmentio/kafka-go` +- Supports **Proto**, **Arrow**, and **Parquet** serialization formats +- Configurable sampling via `LoggingPerc` and user-based daily sampling + +--- + +## Request Flow + +``` +1. Client sends gRPC request with model_config_id + entity IDs + │ +2. Load ModelConfig from etcd-backed ConfigMap + │ +3. Adapt proto request → ComponentRequest + (build ComponentMatrix with entity schema) + │ +4. Resolve DAG topology from component_dependency config + │ +5. Execute DAG (Kahn's algorithm, concurrent): + │ + ├─ FeatureInitComponent: populate matrix with entity IDs + schema + │ + ├─ FeatureComponents (parallel): fetch features from OnFS → fill matrix columns + │ + ├─ PredatorComponent: build feature payloads from matrix → call model → write scores + │ + └─ NumerixComponent: read scores from matrix → call compute → write final scores + │ +6. Build response from matrix columns per ResponseConfig + │ +7. (Optional) Async Kafka logging of inference features and scores + │ +8. Return gRPC response to client +``` + +--- + +## Observability + +### Metrics (StatsD / Telegraf) + +| Metric | Description | +|--------|-------------| +| `inferflow.retrievemodelscore.request.total` | Total RetrieveModelScore requests | +| `inferflow.retrievemodelscore.latency` | End-to-end latency | +| `inferflow.retrievemodelscore.batch.size` | Batch size per request | +| `predict.infer.request.total` | Total Predict API requests | +| `predict.infer.latency` | Predict API latency | +| `inferflow.component.execution.total` | Per-component execution count | +| `inferflow.component.execution.latency` | Per-component latency | +| `inferflow.component.execution.error` | Component-level errors | +| `inferflow.component.feature.count` | Feature count per component | +| `inferflow.external.api.request.total` | External API call count | +| `inferflow.external.api.latency` | External API latency | +| `inferflow.component.inmemorycache.request.total` | Cache hit/miss total | +| `inferflow.component.inmemorycache.miss` | Cache misses | +| `inferflow.logging.kafka_sent` | Kafka log messages sent | + +### Logging +- Structured JSON logging via **zerolog** +- Configurable log levels + +--- + +## Deployment + +### Docker + +Inferflow ships as a multi-stage Docker image: + +- **Builder**: Go 1.19 Alpine with optional Kafka support (librdkafka) +- **Runtime**: Debian 10 slim +- **Build command**: `go build -tags musl -ldflags "-extldflags -static" -o server cmd/${module}/main.go` + +### Supported Environments +- Kubernetes (K8s) +- Google Kubernetes Engine (GKE) +- Amazon EKS + +### Configuration +All configuration is driven via environment variables (loaded by Viper) and etcd. No config files are required at deployment time. + +--- + +## Target Users + +| User | Role | +|------|------| +| Data Scientists | Define model configs and feature retrieval graphs via config — no code needed | +| ML Engineers | Onboard new models by updating etcd config; manage DAG topologies | +| Backend Developers | Integrate via gRPC SDKs for real-time scoring in application services | +| Platform Engineers | Deploy, scale, and monitor Inferflow clusters | + +--- + +## Benefits + +- **No-code feature retrieval** — new models need only a config change, not custom code +- **Feature consistency** — same graph-driven retrieval ensures identical features across experiments +- **Faster iteration** — experiment with new models in minutes, not days +- **Concurrent execution** — DAG components run in parallel for minimal latency +- **Hot reloading** — model config changes via etcd go live without redeployment +- **Multi-API support** — PointWise, PairWise, and SlateWise inference patterns out of the box +- **Production-grade** — built in Go with gRPC, designed for millions of QPS + +--- + +## Contributing + +We welcome contributions from the community! Please see our [Contributing Guide](https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTING.md) for details on how to get started. + +## Community & Support + +- **Discord**: Join our [community chat](https://discord.gg/XkT7XsV2AU) +- **Issues**: Report bugs and request features on [GitHub Issues](https://github.com/Meesho/BharatMLStack/issues) +- **Email**: Contact us at [ml-oss@meesho.com](mailto:ml-oss@meesho.com) + +## License + +BharatMLStack is open-source software licensed under the [BharatMLStack Business Source License 1.1](https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md). + +--- + +
+ Built with ❤️ for the ML community from Meesho +
+
+ If you find this useful, ⭐️ the repo — your support means the world to us! +
diff --git a/docs-src/docs/inferflow/v1.0.0/configuration.md b/docs-src/docs/inferflow/v1.0.0/configuration.md new file mode 100644 index 00000000..35b9b1bd --- /dev/null +++ b/docs-src/docs/inferflow/v1.0.0/configuration.md @@ -0,0 +1,366 @@ +--- +title: Configuration Guide +sidebar_position: 3 +--- + +# Inferflow - Configuration Guide + +Inferflow is fully config-driven. All model onboarding, feature retrieval logic, DAG topology, and inference behavior are controlled through configuration stored in **etcd** — with zero code changes required. + +--- + +## Configuration Overview + +Inferflow configuration is organized into two layers: + +1. **Static config** — Environment variables loaded at startup (via Viper) +2. **Dynamic config** — Model configurations stored in etcd, hot-reloaded on change + +--- + +## Static Configuration (Environment Variables) + +These are set at deployment time and require a restart to change. + +### Server + +| Variable | Description | Example | +|----------|-------------|---------| +| `APP_PORT` | gRPC/HTTP server port | `50051` | +| `APP_ENV` | Environment name | `production` | + +### etcd + +| Variable | Description | Example | +|----------|-------------|---------| +| `ETCD_ENDPOINTS` | Comma-separated etcd endpoints | `etcd-0:2379,etcd-1:2379` | +| `ETCD_DIAL_TIMEOUT` | Connection timeout | `5s` | + +### Online Feature Store (OnFS) + +| Variable | Description | Example | +|----------|-------------|---------| +| `externalServiceOnFs_host` | OnFS gRPC host | `onfs-api:50051` | +| `externalServiceOnFs_callerId` | Caller ID for auth | `inferflow` | +| `externalServiceOnFs_callerToken` | Caller token for auth | `` | +| `externalServiceOnFs_batchSize` | Batch size for feature retrieval | `100` | +| `externalServiceOnFs_deadline` | Request deadline | `200ms` | + +### Predator (Model Serving) + +| Variable | Description | Example | +|----------|-------------|---------| +| `externalServicePredator_defaultDeadline` | Default inference deadline | `100ms` | + +### Numerix (Compute Engine) + +| Variable | Description | Example | +|----------|-------------|---------| +| `numerixClientV1_host` | Numerix gRPC host | `numerix:50052` | +| `numerixClientV1_deadline` | Request deadline | `100ms` | + +### Kafka (Inference Logging) + +| Variable | Description | Example | +|----------|-------------|---------| +| `KafkaBootstrapServers` | Kafka broker addresses | `kafka-0:9092,kafka-1:9092` | +| `KafkaLoggingTopic` | Topic for inference logs | `inferflow-logs` | + +### Metrics (StatsD / Telegraf) + +| Variable | Description | Example | +|----------|-------------|---------| +| `TELEGRAF_HOST` | StatsD host | `telegraf` | +| `TELEGRAF_PORT` | StatsD port | `8125` | + +### In-Memory Cache + +| Variable | Description | Example | +|----------|-------------|---------| +| `CACHE_SIZE_MB` | Cache size in MB | `512` | +| `CACHE_TYPE` | Cache implementation | `freecache` | + +--- + +## Dynamic Configuration (etcd Model Config) + +Model configurations are stored in etcd and hot-reloaded. Each model is identified by a `model_config_id`. + +### Config Structure + +```json +{ + "model_config_id_example": { + "dag_execution_config": { + "component_dependency": { + "feature_initializer": ["fs_user", "fs_product"], + "fs_user": ["ranker_model"], + "fs_product": ["ranker_model"], + "ranker_model": [] + } + }, + "component_config": { + "feature_component_config": { + "fs_user": { ... }, + "fs_product": { ... } + }, + "predator_component_config": { + "ranker_model": { ... } + }, + "numerix_component_config": {}, + "cache_enabled": true, + "cache_version": "v1", + "cache_ttl": 300, + "error_logging_percent": 10 + }, + "response_config": { + "features": ["ranker_model:score"], + "model_schema_perc": 100, + "logging_perc": 5, + "log_features": ["fs_user:profile:age", "ranker_model:score"], + "log_batch_size": 100 + } + } +} +``` + +--- + +### DAG Execution Config + +Defines the component dependency graph. + +```json +{ + "component_dependency": { + "": ["", ""], + "": [""], + "": [""], + "": [] + } +} +``` + +**Rules:** +- The graph must be a valid DAG (no cycles) +- Components with no parents (zero in-degree) execute first +- Components with empty dependency arrays `[]` are leaf nodes +- All component names must match registered components in the `ComponentConfig` + +--- + +### Feature Component Config + +Configures how features are fetched from the Online Feature Store. + +```json +{ + "fs_user": { + "fs_keys": { + "schema": ["user_id"], + "col": "context:user:user_id" + }, + "fs_request": { + "entity_label": "user", + "feature_groups": [ + { + "label": "demographics", + "feature_labels": ["age", "location", "income_bracket"] + }, + { + "label": "behavior", + "feature_labels": ["click_rate", "purchase_freq"] + } + ] + }, + "fs_flatten_resp_keys": ["user_id"], + "col_name_prefix": "user", + "comp_cache_enabled": true, + "comp_cache_ttl": 600, + "composite_id": false + } +} +``` + +| Field | Description | +|-------|-------------| +| `fs_keys` | How to extract lookup keys from the matrix. `schema` defines key column names; `col` references a matrix column | +| `fs_request` | OnFS query: entity label + feature groups with specific features | +| `fs_flatten_resp_keys` | Keys to flatten in response mapping | +| `col_name_prefix` | Prefix for matrix column names (e.g., `user:demographics:age`) | +| `comp_cache_enabled` | Enable in-memory caching for this component | +| `comp_cache_ttl` | Cache TTL in seconds | +| `composite_id` | Whether entity keys are composite | + +--- + +### Predator Component Config + +Configures model inference endpoints. + +```json +{ + "ranker_model": { + "model_name": "product_ranker_v3", + "model_endpoint": "predator-ranker:8080", + "model_end_points": { + "predator-ranker-v3:8080": 80, + "predator-ranker-v4:8080": 20 + }, + "deadline": 100, + "batch_size": 50, + "calibration": { + "enabled": false + }, + "inputs": { + "feature_map": { + "user:demographics:age": "INT32", + "user:behavior:click_rate": "FP32", + "product:attributes:category_id": "INT32" + } + }, + "outputs": { + "score_columns": ["score", "confidence"] + }, + "slate_component": false + } +} +``` + +| Field | Description | +|-------|-------------| +| `model_name` | Model identifier on the serving platform | +| `model_endpoint` | Primary model serving endpoint | +| `model_end_points` | Multiple endpoints with percentage-based traffic routing | +| `deadline` | Inference timeout in milliseconds | +| `batch_size` | Max items per inference batch | +| `calibration` | Score calibration settings | +| `inputs.feature_map` | Map of matrix column → data type for model input | +| `outputs.score_columns` | Column names for model output scores | +| `slate_component` | If true, runs per-slate inference | + +--- + +### Numerix Component Config + +Configures compute operations (e.g., reranking). + +```json +{ + "reranker": { + "score_column": "final_score", + "data_type": "FP32", + "score_mapping": { + "ranker_model:score": "FP32", + "user:behavior:click_rate": "FP32" + }, + "compute_id": "diversity_rerank_v1", + "slate_component": false + } +} +``` + +| Field | Description | +|-------|-------------| +| `score_column` | Output column name for the computed score | +| `data_type` | Output data type | +| `score_mapping` | Map of matrix columns to include as compute inputs | +| `compute_id` | Identifies the compute operation on Numerix | +| `slate_component` | If true, runs per-slate compute | + +--- + +### Response Config + +Controls what data is returned to the client and what is logged. + +```json +{ + "features": ["ranker_model:score", "reranker:final_score"], + "model_schema_perc": 100, + "logging_perc": 5, + "log_features": [ + "user:demographics:age", + "ranker_model:score", + "reranker:final_score" + ], + "log_batch_size": 100 +} +``` + +| Field | Description | +|-------|-------------| +| `features` | Matrix columns to include in the gRPC response | +| `model_schema_perc` | Percentage of requests that include full schema in response | +| `logging_perc` | Percentage of requests to send to Kafka for logging | +| `log_features` | Specific features to include in log messages | +| `log_batch_size` | Batch size for grouped log messages | + +--- + +### Service-Level Config + +Global settings that apply across all models. + +```json +{ + "v2_logging_type": "proto", + "compression_enabled": false +} +``` + +| Field | Values | Description | +|-------|--------|-------------| +| `v2_logging_type` | `proto`, `arrow`, `parquet` | Serialization format for Kafka inference logs | +| `compression_enabled` | `true`, `false` | Enable compression for log messages | + +--- + +## Example: Onboarding a New Model + +To onboard a new ranking model, update the etcd config: + +**Step 1:** Define the feature retrieval graph + +```json +"component_dependency": { + "feature_initializer": ["fs_user", "fs_product", "fs_user_x_category"], + "fs_product": ["fs_user_x_category"], + "fs_user": ["new_ranker"], + "fs_user_x_category": ["new_ranker"], + "new_ranker": [] +} +``` + +Here `fs_user_x_category` depends on `fs_product` because it needs the category ID extracted from the product entity to resolve the user x category key. + +**Step 2:** Configure each component (feature groups, model endpoints, etc.) + +**Step 3:** Push the config to etcd — Inferflow picks it up automatically via watchers. + +No code changes. No redeployment. The new model is live. + +--- + +## Contributing + +We welcome contributions from the community! Please see our [Contributing Guide](https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTING.md) for details on how to get started. + +## Community & Support + +- **Discord**: Join our [community chat](https://discord.gg/XkT7XsV2AU) +- **Issues**: Report bugs and request features on [GitHub Issues](https://github.com/Meesho/BharatMLStack/issues) +- **Email**: Contact us at [ml-oss@meesho.com](mailto:ml-oss@meesho.com) + +## License + +BharatMLStack is open-source software licensed under the [BharatMLStack Business Source License 1.1](https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md). + +--- + +
+ Built with ❤️ for the ML community from Meesho +
+
+ If you find this useful, ⭐️ the repo — your support means the world to us! +
diff --git a/docs-src/docs/inferflow/v1.0.0/functionalities.md b/docs-src/docs/inferflow/v1.0.0/functionalities.md new file mode 100644 index 00000000..cb944b1b --- /dev/null +++ b/docs-src/docs/inferflow/v1.0.0/functionalities.md @@ -0,0 +1,309 @@ +--- +title: Key Functionalities +sidebar_position: 2 +--- + +# Inferflow - Key Functionalities + +## Overview + +Inferflow is a high-performance, config-driven ML inference orchestration engine built in **Go**. It provides **no-code feature retrieval**, **DAG-based execution**, and **multi-pattern model inference** — enabling ML teams to onboard new models through configuration changes alone. + +--- + +## Core Capabilities + +### Graph-Driven Feature Retrieval + +Inferflow's defining feature is its ability to resolve entity relationships and retrieve features through configurable DAG topologies — no custom code required. + +**How it works:** + +1. A `model_config_id` maps to a pre-defined DAG of components +2. Context entity IDs (e.g., `userId`, `productIds`) are provided at request time +3. The DAG resolves intermediate entity relationships (e.g., extracting `category` from `product` to fetch `user x category` features) +4. Features are fetched in parallel from the Online Feature Store +5. A 2D feature matrix is assembled and passed to model scoring + +**Impact:** +- New models require only a config update — no code changes +- Feature consistency is guaranteed across experiments +- Iteration cycles drop from days to minutes + +### DAG Topology Executor + +The execution engine uses **Kahn's algorithm** for topological ordering with **concurrent goroutine execution** at each level: + +``` +component_dependency: { + "feature_initializer": ["fs_user", "fs_product"], + "fs_user": ["ranker"], + "fs_product": ["ranker"], + "ranker": ["reranker"], + "reranker": [] +} +``` + +This config defines: +- `feature_initializer` runs first (zero in-degree) +- `fs_user` and `fs_product` run **in parallel** after init +- `ranker` runs after both feature components complete +- `reranker` runs after the ranker + +**Key properties:** +- Cycle detection via in-degree analysis +- DAG topologies cached using Murmur3 hashing (Ristretto cache) +- Components are registered and resolved via a `ComponentProvider` + +--- + +## Multi-Pattern Inference APIs + +Inferflow supports three inference patterns via the **Predict API**, each designed for different ML use cases: + +### PointWise Inference + +Score each target independently against context features. + +```protobuf +rpc InferPointWise(PredictRequest) returns (PredictResponse); +``` + +**Use cases:** Click-through rate prediction, fraud scoring, relevance ranking + +**Input:** Context features + list of targets (e.g., products) +**Output:** Per-target scores + +### PairWise Inference + +Score pairs of targets relative to each other. + +```protobuf +rpc InferPairWise(PredictRequest) returns (PredictResponse); +``` + +**Use cases:** Preference learning, comparison-based ranking + +**Input:** Context features + targets + pair indices (first/second) +**Output:** Per-pair scores + optional per-target scores + +### SlateWise Inference + +Score groups (slates) of targets together, capturing inter-item effects. + +```protobuf +rpc InferSlateWise(PredictRequest) returns (PredictResponse); +``` + +**Use cases:** Whole-page optimization, slate-level reranking, diversity-aware scoring + +**Input:** Context features + targets + slate definitions (target indices per slate) +**Output:** Per-slate scores + optional per-target scores + +--- + +## Entity & Legacy API + +### RetrieveModelScore + +The original Inferflow API for entity-based feature retrieval and scoring: + +```protobuf +service Inferflow { + rpc RetrieveModelScore(InferflowRequestProto) returns (InferflowResponseProto); +} +``` + +**Request structure:** + +| Field | Description | +|-------|-------------| +| `entities` | List of entity types with their IDs and optional inline features | +| `model_config_id` | Identifies the model configuration (DAG, components, response format) | +| `tracking_id` | Request-level tracing identifier | + +**Entity structure:** +- `entity`: Entity type label (e.g., `"user"`, `"product"`) +- `ids`: List of entity IDs +- `features`: Optional inline features (name + per-ID values) + +--- + +## Component Types + +### FeatureInitComponent + +**Role:** Root DAG node — initializes the shared `ComponentMatrix`. + +- Sets up rows from entity IDs +- Populates schema columns (string + byte) for all downstream components +- For slate APIs: initializes `SlateData` with `slate_target_indices` + +### FeatureComponent + +**Role:** Fetches features from the Online Feature Store (OnFS) for a specific entity type. + +- Reads `FSKeys` from config to extract lookup keys from the matrix +- Batches unique entities and calls OnFS via gRPC +- Optional **in-memory caching** keyed by `model_id:version:component:entity` +- Writes binary feature values into matrix byte columns + +**Column naming convention:** `entity_label:feature_group:feature_name` + +### PredatorComponent + +**Role:** Calls model serving endpoints for inference. + +- Builds feature payloads from matrix columns with type conversion +- Supports **percentage-based traffic routing** across multiple model endpoints +- Handles **slate-level inference**: per-slate matrix → separate inference → scores to `SlateData` +- Configurable **calibration** and **batch sizing** + +### NumerixComponent + +**Role:** Calls the Numerix compute engine for operations like reranking. + +- Uses `ScoreMapping` config to map matrix columns to compute inputs +- Writes a single score column back to the matrix +- Supports slate mode for per-slate compute operations + +--- + +## Feature Retrieval Pipeline + +### Key Resolution + +Feature components use `FSKeys` configuration to dynamically resolve entity keys: + +```json +{ + "FSKeys": { + "schema": ["user_id"], + "col": "user:profile:user_id" + } +} +``` + +The component reads key values from the existing matrix columns, enabling **chained entity resolution** — e.g., fetch product entity first, extract category, then fetch user x category features. + +### Batched Retrieval + +- Features are fetched via `FeatureService.RetrieveFeatures` gRPC call +- Requests are batched by unique entity keys +- Configurable batch size and deadline per component +- Auth via `CALLER_ID` and `CALLER_TOKEN` metadata + +### In-Memory Caching + +Optional per-component caching reduces OnFS load: + +- Cache key: `model_id:cache_version:component_name:entity_key` +- Configurable TTL per component +- Zero-GC-overhead cache implementation available +- Cache hit/miss metrics tracked via StatsD + +--- + +## Data Types + +Inferflow supports comprehensive ML data types for feature encoding and model input/output: + +| Data Type | Variants | Usage | +|-----------|----------|-------| +| **Integers** | int8, int16, int32, int64 | Categorical encodings, counts, IDs | +| **Floats** | float8 (e4m3, e5m2), float16, float32, float64 | Continuous features, embeddings, scores | +| **Strings** | Variable length | Categories, metadata | +| **Booleans** | Bit-packed | Binary indicators | +| **Vectors** | All scalar types | Embeddings, feature arrays | + +Type conversion is handled by the `datatypeconverter` package with optimized float8 implementations. + +--- + +## Inference Logging + +Inferflow supports async inference logging to Kafka for model monitoring and debugging: + +### Serialization Formats + +| Format | Use Case | +|--------|----------| +| **Proto** | Default, compact | +| **Arrow** | Columnar analytics | +| **Parquet** | Long-term storage, query-friendly | + +### Sampling Controls + +| Config | Description | +|--------|-------------| +| `LoggingPerc` | Percentage of requests to log (0-100) | +| `LogBatchSize` | Batch size for log message grouping | +| `LogFeatures` | Specific features to include in logs | + +### Log Content + +Each `InferflowLog` message includes: +- `user_id`, `tracking_id`, `model_config_id` +- Entity IDs and feature values +- Model scores and metadata + +--- + +## Configuration Hot-Reload + +Model configurations are stored in **etcd** and support **live updates without redeployment**: + +1. Inferflow registers watchers on etcd config paths +2. On config change, watchers trigger `ReloadModelConfigMapAndRegisterComponents` +3. `ConfigMap` is updated in memory +4. Feature schemas are re-initialized +5. DAG components are re-registered + +This enables: +- Adding new models in production without restarts +- A/B testing with different model configurations +- Instant rollback by reverting etcd config + +--- + +## Performance Characteristics + +### Concurrency Model +- DAG components at the same level execute concurrently in goroutines +- Feature retrieval is parallelized across entity types +- External gRPC calls use connection pooling + +### Memory Efficiency +- Built in Go — significantly lower memory footprint than Java equivalents (~80% reduction) +- Object pooling for `ComponentMatrix` and serialization buffers +- In-memory cache with zero-GC-overhead option (freecache) + +### Serialization +- gRPC with Proto3 for all external communication +- Binary feature encoding in the `ComponentMatrix` for minimal overhead +- Configurable compression for Kafka logging + +--- + +## Contributing + +We welcome contributions from the community! Please see our [Contributing Guide](https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTING.md) for details on how to get started. + +## Community & Support + +- **Discord**: Join our [community chat](https://discord.gg/XkT7XsV2AU) +- **Issues**: Report bugs and request features on [GitHub Issues](https://github.com/Meesho/BharatMLStack/issues) +- **Email**: Contact us at [ml-oss@meesho.com](mailto:ml-oss@meesho.com) + +## License + +BharatMLStack is open-source software licensed under the [BharatMLStack Business Source License 1.1](https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md). + +--- + +
+ Built with ❤️ for the ML community from Meesho +
+
+ If you find this useful, ⭐️ the repo — your support means the world to us! +
diff --git a/docs-src/docs/inferflow/v1.0.0/index.md b/docs-src/docs/inferflow/v1.0.0/index.md new file mode 100644 index 00000000..abb59d61 --- /dev/null +++ b/docs-src/docs/inferflow/v1.0.0/index.md @@ -0,0 +1,14 @@ +--- +title: v1.0.0 +description: Inferflow v1.0.0 +sidebar_position: 0 +slug: /inferflow/v1.0.0 +--- + +import DocCardList from '@theme/DocCardList'; + +# Inferflow v1.0.0 + +Inferflow is a graph-driven feature retrieval and model inference orchestration engine. It dynamically resolves entity relationships via configurable DAGs, retrieves features from the Online Feature Store, and orchestrates model scoring. + + diff --git a/docs-src/docs/inferflow/v1.0.0/release-notes.md b/docs-src/docs/inferflow/v1.0.0/release-notes.md new file mode 100644 index 00000000..2a73c843 --- /dev/null +++ b/docs-src/docs/inferflow/v1.0.0/release-notes.md @@ -0,0 +1,208 @@ +--- +title: Release Notes +sidebar_position: 4 +--- + +# Inferflow - Release Notes + +## Version 1.0.0 +**Release Date**: June 2025 +**Status**: General Availability (GA) + +We're excited to announce the first stable release of **Inferflow** — a graph-driven feature retrieval and model inference orchestration engine, part of BharatMLStack. + +--- + +## What's New + +### Config-Driven DAG Executor +- **No-code feature retrieval**: Onboard new models with config changes only — no custom code required +- **DAG topology execution**: Define component dependency graphs that are executed concurrently using Kahn's algorithm +- **Hot reload**: Model configurations stored in etcd are watched and reloaded live — no redeployment needed +- **DAG caching**: Topologies are cached using Murmur3 hashing with Ristretto for minimal overhead + +### Multi-Pattern Inference APIs +Three structured inference patterns via the Predict API: + +| API | Pattern | Use Case | +|-----|---------|----------| +| `InferPointWise` | Score each target independently | CTR prediction, fraud scoring | +| `InferPairWise` | Score pairs of targets | Preference learning, comparison ranking | +| `InferSlateWise` | Score groups of targets together | Whole-page optimization, diversity-aware ranking | + +Plus the entity-based `RetrieveModelScore` API for direct feature retrieval and scoring. + +### Component System +Four built-in component types: +- **FeatureInitComponent** — Initializes the shared ComponentMatrix +- **FeatureComponent** — Fetches features from the Online Feature Store (OnFS) +- **PredatorComponent** — Calls model serving endpoints with percentage-based traffic routing +- **NumerixComponent** — Calls compute engine for operations like reranking + +### Online Feature Store Integration +- gRPC-based feature retrieval via `FeatureService.RetrieveFeatures` +- Batched retrieval with configurable batch size and deadline +- Token-based authentication +- Dynamic key resolution from the ComponentMatrix + +### In-Memory Feature Caching +- Optional per-component caching to reduce OnFS load +- Configurable TTL per component +- Zero-GC-overhead cache option (freecache) +- Cache hit/miss metrics + +### Inference Logging +- Async logging to Kafka for model monitoring and debugging +- Three serialization formats: **Proto**, **Arrow**, **Parquet** +- Configurable sampling rate and feature selection +- Batched log message grouping + +--- + +## Performance + +### Built in Go +Inferflow is written entirely in Go, delivering: +- ~80% lower memory usage compared to equivalent Java services +- Lower CPU utilization +- Faster, more efficient deployments + +### Concurrency +- DAG components at the same level execute concurrently in goroutines +- Feature retrieval parallelized across entity types +- Connection pooling for all external gRPC calls + +### Serialization +- gRPC with Proto3 for all APIs +- Binary feature encoding in the ComponentMatrix +- Configurable compression for Kafka logging (ZSTD support) + +--- + +## APIs & Protocols + +### gRPC API + +**Inferflow Service:** +```protobuf +service Inferflow { + rpc RetrieveModelScore(InferflowRequestProto) returns (InferflowResponseProto); +} +``` + +**Predict Service:** +```protobuf +service PredictService { + rpc InferPointWise(PredictRequest) returns (PredictResponse); + rpc InferPairWise(PredictRequest) returns (PredictResponse); + rpc InferSlateWise(PredictRequest) returns (PredictResponse); +} +``` + +### Data Types Supported + +| Type | Variants | +|------|----------| +| Integers | int8, int16, int32, int64 | +| Floats | float8 (e4m3, e5m2), float16, float32, float64 | +| Strings | Variable length | +| Booleans | Bit-packed | +| Vectors | All scalar types | + +--- + +## Enterprise Features + +### Production Readiness +- **Health checks**: HTTP health endpoints via cmux +- **Graceful shutdown**: Clean resource cleanup +- **Structured logging**: JSON-formatted logs via zerolog +- **Signal handling**: SIGTERM/SIGINT support for container environments + +### Monitoring & Observability +- **StatsD / Telegraf integration**: Request rates, latencies, error rates +- **Per-component metrics**: Execution time, feature counts, cache hit rates +- **External API metrics**: OnFS, Predator, Numerix call tracking +- **Kafka logging metrics**: Messages sent, errors + +### Configuration Management +- **etcd-based**: All model configs stored in etcd +- **Watch & reload**: Live config updates without restart +- **Multi-model support**: Multiple `model_config_id` entries served concurrently + +--- + +## Deployment + +### Container Support +- **Docker image**: Multi-stage build (Go Alpine builder + Debian runtime) +- **Optional Kafka**: librdkafka support via build flag +- **Static binary**: Single binary deployment + +### Supported Environments +- Kubernetes (K8s) +- Google Kubernetes Engine (GKE) +- Amazon EKS + +--- + +## Compatibility + +### Supported Go Versions +- **Minimum**: Go 1.19 +- **Recommended**: Go 1.24+ + +### External Dependencies + +| Service | Version | Protocol | +|---------|---------|----------| +| etcd | 3.5+ | gRPC | +| Online Feature Store (OnFS) | 1.0+ | gRPC | +| Predator (Helix) | 1.0+ | gRPC | +| Numerix | 1.0+ | gRPC | +| Kafka | 2.0+ | TCP | + +--- + +## Download & Installation + +### Source Code +```bash +git clone https://github.com/Meesho/BharatMLStack.git +cd BharatMLStack/inferflow +``` + +### Build +```bash +go build -o inferflow-server cmd/inferflow/main.go +``` + +### Docker +```bash +docker build -t inferflow:latest . +``` + +--- + +## Contributing + +We welcome contributions from the community! Please see our [Contributing Guide](https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTING.md) for details on how to get started. + +## Community & Support + +- **Discord**: Join our [community chat](https://discord.gg/XkT7XsV2AU) +- **Issues**: Report bugs and request features on [GitHub Issues](https://github.com/Meesho/BharatMLStack/issues) +- **Email**: Contact us at [ml-oss@meesho.com](mailto:ml-oss@meesho.com) + +## License + +BharatMLStack is open-source software licensed under the [BharatMLStack Business Source License 1.1](https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md). + +--- + +
+ Built with ❤️ for the ML community from Meesho +
+
+ If you find this useful, ⭐️ the repo — your support means the world to us! +
diff --git a/docs-src/docs/intro.md b/docs-src/docs/intro.md new file mode 100644 index 00000000..4a56ea71 --- /dev/null +++ b/docs-src/docs/intro.md @@ -0,0 +1,57 @@ +--- +sidebar_position: 0 +title: BharatMLStack Documentation +slug: intro +--- + +# BharatMLStack Documentation + +Welcome to the BharatMLStack documentation. BharatMLStack is an open-source, end-to-end ML infrastructure stack built for scale, speed, and simplicity. Explore the components below to get started. + +--- + +## Quick Start + +Get up and running with BharatMLStack in minutes. Step-by-step instructions, sample data, and Docker Compose setup for local development and testing. + +**[Go to Quick Start →](/category/quick-start)** + +--- + +## Online Feature Store + +Sub-10ms, high-throughput access to machine learning features for real-time inference. Supports batch and streaming ingestion, schema validation, and compact versioned feature groups. + +**[Go to Online Feature Store →](/category/online-feature-store)** + +--- + +## Inferflow + +Graph-driven feature retrieval and model inference orchestration engine. Dynamically resolves entity relationships, retrieves features, and orchestrates model scoring — all without custom code. + +**[Go to Inferflow →](/category/inferflow)** + +--- + +## Trufflebox UI + +Modern, feature-rich UI framework for MLOps management. Supports feature catalog, user management, and admin operations with approval flows. + +**[Go to Trufflebox UI →](/category/trufflebox-ui)** + +--- + +## SDKs + +Client libraries for Go and Python to interact with the Online Feature Store and other platform components. Includes gRPC clients, REST APIs, and Apache Spark integration. + +**[Go to SDKs →](/category/sdks)** + +--- + +## Numerix + +High-performance compute engine for ultra-fast element-wise matrix operations. Built in Rust with SIMD acceleration for sub-5ms p99 latency. + +**[Go to Numerix →](/category/numerix)** diff --git a/docs-src/docs/numerix/_category_.json b/docs-src/docs/numerix/_category_.json index 7c2d4af0..2340ae40 100644 --- a/docs-src/docs/numerix/_category_.json +++ b/docs-src/docs/numerix/_category_.json @@ -1,6 +1,6 @@ { "label": "Numerix", - "position": 6, + "position": 7, "link": { "type": "generated-index", "description": "Numerix is a mathematical compute engine for BharatML Stack. It is used to perform mathematical operations on matrices and vectors." diff --git a/docs-src/docs/numerix/v1.0.0/_category_.json b/docs-src/docs/numerix/v1.0.0/_category_.json index 4748f653..66455a9e 100644 --- a/docs-src/docs/numerix/v1.0.0/_category_.json +++ b/docs-src/docs/numerix/v1.0.0/_category_.json @@ -1,10 +1,5 @@ { "label": "v1.0.0", - "position": 1, - "link": { - "type": "generated-index", - "description": "Numerix v1.0.0", - "slug": "/numerix/v1.0.0" - } + "position": 1 } diff --git a/docs-src/docs/numerix/v1.0.0/index.md b/docs-src/docs/numerix/v1.0.0/index.md new file mode 100644 index 00000000..1307fef7 --- /dev/null +++ b/docs-src/docs/numerix/v1.0.0/index.md @@ -0,0 +1,14 @@ +--- +title: v1.0.0 +description: Numerix v1.0.0 +sidebar_position: 0 +slug: /numerix/v1.0.0 +--- + +import DocCardList from '@theme/DocCardList'; + +# Numerix v1.0.0 + +Numerix is a mathematical compute engine for BharatML Stack. It is used to perform mathematical operations on matrices and vectors. + + diff --git a/docs-src/docs/online-feature-store/v1.0.0/_category_.json b/docs-src/docs/online-feature-store/v1.0.0/_category_.json index 4fec8dcc..4e1f685c 100644 --- a/docs-src/docs/online-feature-store/v1.0.0/_category_.json +++ b/docs-src/docs/online-feature-store/v1.0.0/_category_.json @@ -1,9 +1,4 @@ { - "label": "v1.0.0", - "position": 1, - "link": { - "type": "generated-index", - "description": "Online Feature Store v1.0.0", - "slug": "/online-feature-store/v1.0.0" - } + "label": "v1.0.0", + "position": 1 } \ No newline at end of file diff --git a/docs-src/docs/online-feature-store/v1.0.0/index.md b/docs-src/docs/online-feature-store/v1.0.0/index.md new file mode 100644 index 00000000..b790c081 --- /dev/null +++ b/docs-src/docs/online-feature-store/v1.0.0/index.md @@ -0,0 +1,14 @@ +--- +title: v1.0.0 +description: Online Feature Store v1.0.0 +sidebar_position: 0 +slug: /online-feature-store/v1.0.0 +--- + +import DocCardList from '@theme/DocCardList'; + +# Online Feature Store v1.0.0 + +A high-performance, scalable, and production-grade feature store built for modern machine learning systems. It supports both real-time and batch workflows, with low-latency feature retrieval. + + diff --git a/docs-src/docs/predator/_category_.json b/docs-src/docs/predator/_category_.json new file mode 100644 index 00000000..576eb122 --- /dev/null +++ b/docs-src/docs/predator/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Predator", + "position": 7, + "link": { + "type": "generated-index", + "description": "Predator is a scalable, high-performance model inference service built as a wrapper around NVIDIA Triton Inference Server, designed to serve ML models with low latency in Kubernetes, with OnFS and Interflow integration." + } +} diff --git a/docs-src/docs/predator/v1.0.0/_category_.json b/docs-src/docs/predator/v1.0.0/_category_.json new file mode 100644 index 00000000..3c72a212 --- /dev/null +++ b/docs-src/docs/predator/v1.0.0/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "v1.0.0", + "position": 1 +} diff --git a/docs-src/docs/predator/v1.0.0/architecture.md b/docs-src/docs/predator/v1.0.0/architecture.md new file mode 100644 index 00000000..e337ae4f --- /dev/null +++ b/docs-src/docs/predator/v1.0.0/architecture.md @@ -0,0 +1,201 @@ +--- +title: Architecture +sidebar_position: 1 +--- + +# BharatMLStack - Predator + +Predator is a scalable, high-performance model inference service built as a wrapper around the **NVIDIA Triton Inference Server**. It is designed to serve a variety of machine learning models (Deep Learning, Tree-based, etc.) with low latency in a **Kubernetes (K8s)** environment. + +The system integrates seamlessly with the **Online Feature Store (OnFS)** for real-time feature retrieval and uses **Horizon** as the deployment orchestration layer. Deployments follow a **GitOps** pipeline — Horizon generates Helm configurations, commits them to GitHub, and **Argo Sync** reconciles the desired state onto Kubernetes. + +--- + +## High-Level Design + +![Predator HLD - End-to-end deployment and inference architecture](../../../static/img/v1.0.0-predator-hld.png) + +### End-to-End Flow + +1. **Model Deployment Trigger**: An actor initiates deployment through **Trufflebox UI**, specifying the GCS path (`gcs://`) of the trained model. Separately, post-training pipelines write model artifacts to **GCS Artifactory**. + +2. **Orchestration via Horizon**: Trufflebox UI communicates with **Horizon**, the deployment orchestration layer. Horizon generates the appropriate **Helm** chart configuration for the inference service. + +3. **GitOps Pipeline**: Horizon commits the Helm values to a **GitHub** repository. **Argo Sync** watches the repo and reconciles the desired state onto the Kubernetes cluster, creating or updating deployable units. + +4. **Deployable Units (Deployable 1 … N)**: Each deployable is an independent Kubernetes deployment that: + - Downloads model artifacts from **GCS** at startup via an `init.sh` script. + - Launches a **Triton Inference Server** instance loaded with the model. + - Runs one or more pods, each containing the inference runtime and configured backends. + +5. **Triton Backends**: Each Triton instance supports pluggable backends based on the model type: + - **FIL** — GPU-accelerated tree-based models (XGBoost, LightGBM, Random Forest). + - **PyTorch** — Native PyTorch models via LibTorch. + - **Python** — Custom preprocessing/postprocessing or unsupported model formats. + - **TRT (TensorRT)** — GPU-optimized serialized TensorRT engines. + - **ONNX** — Framework-agnostic execution via ONNX Runtime. + - **DALI** — GPU-accelerated data preprocessing (image, audio, video). + +6. **Autoscaling with KEDA**: The cluster uses **KEDA** (Kubernetes Event-Driven Autoscaling) to scale deployable pods based on custom metrics (CPU utilization, GPU utilization via DCGM, queue depth, etc.). The underlying **Kubernetes** scheduler places pods across GPU/CPU node pools. + +### Key Design Principles + +- **GitOps-driven**: All deployment state is version-controlled in Git; Argo Sync ensures cluster state matches the declared configuration. +- **Isolation per deployable**: Each model or model group gets its own deployable unit, preventing noisy-neighbor interference. +- **Init-based model loading**: Models are materialized to local disk before Triton starts, ensuring deterministic startup and no runtime dependency on remote storage. +- **Pluggable backends**: The same infrastructure serves deep learning, tree-based, and custom models through Triton's backend abstraction. + +--- + +## Inference Engine: Triton Inference Server + +NVIDIA Triton Inference Server is a high-performance model serving system designed to deploy ML and deep learning models at scale across CPUs and GPUs. It provides a unified inference runtime that supports multiple frameworks, optimized execution, and production-grade scheduling. + +Triton operates as a standalone server that loads models from a model repository and exposes standardized HTTP/gRPC APIs. Predator uses **gRPC** for efficient request and response handling via the **helix client**. + +### Core Components + +- **Model Repository**: Central directory where models are stored. Predator typically materializes the model repository onto local disk via an init container, enabling fast model loading and eliminating runtime dependency on remote storage during inference. + +### Backends + +A backend is the runtime responsible for executing a model. Each model specifies which backend runs it via configuration. + +| Backend | Description | +|---------|-------------| +| **TensorRT** | GPU-optimized; executes serialized TensorRT engines (kernel fusion, FP16/INT8). | +| **PyTorch** | Serves native PyTorch models via LibTorch. | +| **ONNX Runtime** | Framework-agnostic ONNX execution with TensorRT and other accelerators. | +| **TensorFlow** | Runs TensorFlow SavedModel format. | +| **Python backend** | Custom Python code for preprocessing, postprocessing, or unsupported models. | +| **Custom backends** | C++/Python backends for specialized or proprietary runtimes. | +| **DALI** | GPU-accelerated data preprocessing (image, audio, video). | +| **FIL (Forest Inference Library)** | GPU-accelerated tree-based models (XGBoost, LightGBM, Random Forest). | + +### Key Features + +- **Dynamic batching**: Combines multiple requests into a single batch at runtime — higher GPU utilization, improved throughput, reduced latency variance. +- **Concurrent model execution**: Run multiple models or multiple instances of the same model; distribute load across GPUs. +- **Model versioning**: Support multiple versions per model. +- **Ensemble models**: Pipeline of models as an ensemble; eliminates intermediate network hops, reduces latency. +- **Model instance scaling**: Multiple copies of a model for parallel inference and load isolation. +- **Observability**: Prometheus metrics, granular latency, throughput, GPU utilization. +- **Warmup requests**: Preload kernels and avoid cold-start latency. + +--- + +## Model Repository Structure + +``` +model_repository/ +├── model_A/ +│ ├── config.pbtxt +│ ├── 1/ +│ │ └── model.plan +│ ├── 2/ +│ │ └── model.plan +├── model_B/ +│ ├── config.pbtxt +│ ├── 1/ +│ └── model.py +``` + +The `config.pbtxt` file defines how Triton loads and executes a model: input/output tensors, batch settings, hardware execution, backend runtime, and optimization parameters. At minimum it defines: `backend/platform`, `max_batch_size`, `inputs`, `outputs`. + +### Sample config.pbtxt + +```text +name: "product_ranking_model" +platform: "tensorrt_plan" +max_batch_size: 64 +input [ { name: "input_embeddings" data_type: TYPE_FP16 dims: [ 128 ] }, { name: "context_features" data_type: TYPE_FP32 dims: [ 32 ] } ] +output [ { name: "scores" data_type: TYPE_FP32 dims: [ 1 ] } ] +instance_group [ { kind: KIND_GPU count: 2 gpus: [0] } ] +dynamic_batching { preferred_batch_size: [8,16,32,64] max_queue_delay_microseconds: 2000 } +``` + +--- + +## Kubernetes Deployment Architecture + +Predator inference services are deployed on Kubernetes using **Helm-based** deployments for standardized, scalable, GPU-optimized model serving. Each deployment consists of Triton Inference Server wrapped within a Predator runtime, with autoscaling driven by CPU and GPU utilization. + +### Pod Architecture + +``` +Predator Pod +├── Init Container (Model Sync) +├── Triton Inference Server Container +``` + +Model artifacts and runtime are initialized before inference traffic is accepted. + +#### Init Container + +- Download model artifacts from cloud storage (GCS). +- Populate the Triton model repository directory. +- Example: `gcloud storage cp -r gs://.../model-path/* /models` + +Benefits: deterministic startup (Triton starts only after models are available), separation of concerns (image = runtime, repository = data). + +#### Triton Inference Server Container + +- Load model artifacts from local repository. +- Manage inference scheduling, request/response handling, and expose inference endpoints. + +### Triton Server Image Strategy + +The Helm chart uses the Triton container image from the internal **artifact registry**. Production uses **custom-built** images (only required backends, e.g. TensorRT, Python) to reduce size and startup time. Unnecessary components are excluded; images are built internally and pushed to the registry. + +**Response Caching**: Custom cache plugins can be added at image build time for optional inference response caching — reducing redundant execution and GPU use for repeated inputs. + +### Image Distribution Optimization + +- **Secondary boot disk image caching**: Images are pre-cached on GPU node pool secondary boot disks to avoid repeated pulls during scale-up and reduce pod startup time and cold-start latency. +- **Image streaming**: Can be used to progressively pull layers for faster time-to-readiness during scaling. + +### Health Probes + +Readiness and liveness use `/v2/health/ready`. Triton receives traffic only after model loading; failed instances are restarted automatically. + +### Resource Configuration + +Sample GPU resource config: + +```yaml +limits: + cpu: 7000m + memory: 28Gi + gpu: 1 +``` + +### Autoscaling Architecture + +Predator uses **KEDA** (Kubernetes Event-Driven Autoscaling) for scaling deployable pods. KEDA supports custom metric sources including: + +- **CPU / Memory utilization** for CPU-based deployments. +- **GPU utilization** via **DCGM** (Data Center GPU Manager) for GPU pods — covering utilization, memory, power, etc. +- **Custom Prometheus queries** for application-level scaling signals (e.g., inference queue depth, request latency). + +KEDA ScaledObjects are configured per deployable, enabling fine-grained, independent scaling for each model or model group. + +--- + +## Contributing + +We welcome contributions! See the [Contributing Guide](https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTING.md). + +## Community & Support + +- **Discord**: [community chat](https://discord.gg/XkT7XsV2AU) +- **Issues**: [GitHub Issues](https://github.com/Meesho/BharatMLStack/issues) +- **Email**: [ml-oss@meesho.com](mailto:ml-oss@meesho.com) + +## License + +BharatMLStack is open-source under the [BharatMLStack Business Source License 1.1](https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md). + +--- + +
Built with ❤️ for the ML community from Meesho
+
If you find this useful, ⭐️ the repo — your support means the world to us!
diff --git a/docs-src/docs/predator/v1.0.0/functionalities.md b/docs-src/docs/predator/v1.0.0/functionalities.md new file mode 100644 index 00000000..e5f90b7c --- /dev/null +++ b/docs-src/docs/predator/v1.0.0/functionalities.md @@ -0,0 +1,119 @@ +--- +title: Key Functionalities +sidebar_position: 2 +--- + +# Predator - Key Functionalities + +## Overview + +Predator is a scalable, high-performance model inference service built as a wrapper around **NVIDIA Triton Inference Server**. It serves Deep Learning and tree-based models with low latency in **Kubernetes**, integrates with the **Online Feature Store (OnFS)** and uses **Interflow** for orchestration between clients, feature store, and inference engine. Clients send inference requests via the **Helix client** over gRPC. + +--- + +## Core Capabilities + +### Multi-Backend Inference + +Predator leverages Triton's pluggable backends so you can serve a variety of model types from a single deployment: + +| Backend | Use Case | +|---------|----------| +| **TensorRT** | GPU-optimized DL; serialized engines (FP16/INT8) | +| **PyTorch** | Native PyTorch via LibTorch | +| **ONNX Runtime** | Framework-agnostic ONNX with TensorRT/GPU | +| **TensorFlow** | SavedModel format | +| **Python** | Custom preprocessing, postprocessing, or unsupported models | +| **FIL** | Tree-based models (XGBoost, LightGBM, Random Forest) on GPU | +| **DALI** | GPU-accelerated data preprocessing (image, audio, video) | +| **Custom** | C++/Python backends for proprietary or specialized runtimes | + +### Dynamic Batching + +Triton combines multiple incoming requests into a single batch at runtime. + +- Higher GPU utilization and improved throughput +- Reduced latency variance +- Configurable `preferred_batch_size` and `max_queue_delay_microseconds` in `config.pbtxt` + +### Concurrent Model Execution + +- Run multiple models simultaneously +- Run multiple instances of the same model +- Distribute load across GPUs via `instance_group` in model config + +### Model Versioning & Ensembles + +- **Versioning**: Multiple versions per model (e.g. `1/`, `2/` in the model repository) +- **Ensembles**: Define a pipeline of models as an ensemble; eliminates intermediate network hops and reduces latency + +### Model Instance Scaling + +- Deploy multiple copies of a model for parallel inference and load isolation +- Configured via `instance_group` + +--- + +## Inference & API + +### gRPC via Helix Client + +Predator uses **gRPC** for efficient request/response handling. Client applications (e.g. Realestate, IOP) send inference requests through the **Helix client**, which talks to the Triton Inference Server inside the Predator pod. + +### Model Repository + +Models are stored in a local model repository. Predator materializes this via an **Init Container** that downloads artifacts from cloud storage (e.g. GCS) so Triton has no runtime dependency on remote storage during inference. + +--- + +## Deployment & Operational Features + +### Custom Triton Images + +- Production uses **custom-built** Triton images (only required backends) for smaller size and faster startup +- Images built on GCP VM, pushed to **Artifact Registry**, and referenced in Helm deployments +- Optional **response caching** via custom cache plugins added at image build time + +### Image Distribution + +- **Secondary boot disk caching**: Triton image pre-cached on GPU node pool to reduce pod startup and scale-up latency +- **Image streaming**: Optionally used for faster time-to-readiness during scaling + +### Health Probes + +- Readiness and liveness use `/v2/health/ready` +- Triton receives traffic only after models are loaded; failed instances are restarted automatically + +### Autoscaling + +- CPU-based scaling for generic load +- GPU-based scaling using **DCGM** metrics (utilization, memory, power); custom queries drive scale-up/scale-down + +--- + +## Observability + +- **Prometheus metrics**: Latency, throughput, GPU utilization, and more +- Metrics emitted from the Triton Inference Container and visualized in **Grafana** +- **Warmup requests**: Configurable to preload kernels and avoid cold-start latency + +--- + +## Contributing + +We welcome contributions! See the [Contributing Guide](https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTING.md). + +## Community & Support + +- **Discord**: [community chat](https://discord.gg/XkT7XsV2AU) +- **Issues**: [GitHub Issues](https://github.com/Meesho/BharatMLStack/issues) +- **Email**: [ml-oss@meesho.com](mailto:ml-oss@meesho.com) + +## License + +BharatMLStack is open-source under the [BharatMLStack Business Source License 1.1](https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md). + +--- + +
Built with ❤️ for the ML community from Meesho
+
If you find this useful, ⭐️ the repo — your support means the world to us!
diff --git a/docs-src/docs/predator/v1.0.0/index.md b/docs-src/docs/predator/v1.0.0/index.md new file mode 100644 index 00000000..9de78cd9 --- /dev/null +++ b/docs-src/docs/predator/v1.0.0/index.md @@ -0,0 +1,14 @@ +--- +title: v1.0.0 +description: Predator v1.0.0 +sidebar_position: 0 +slug: /predator/v1.0.0 +--- + +import DocCardList from '@theme/DocCardList'; + +# Predator v1.0.0 + +Predator is a scalable, high-performance model inference service built as a wrapper around NVIDIA Triton Inference Server, designed to serve ML models with low latency in Kubernetes. + + diff --git a/docs-src/docs/predator/v1.0.0/release-notes.md b/docs-src/docs/predator/v1.0.0/release-notes.md new file mode 100644 index 00000000..a53f4ff3 --- /dev/null +++ b/docs-src/docs/predator/v1.0.0/release-notes.md @@ -0,0 +1,21 @@ +--- +title: Release Notes +sidebar_position: 3 +--- + +# Predator - Release Notes + +## Version 1.0.0 + +**Release Date**: June 2025 +**Status**: General Availability (GA) + +First stable release of **Predator** — scalable model inference service built around **NVIDIA Triton Inference Server**, part of BharatMLStack. Serves Deep Learning and tree-based models with low latency in **Kubernetes**; integrates with **OnFS** and **Interflow**; clients use the **Helix client** over gRPC. + +### What's New + +- **Triton inference engine**: Unified runtime for DL and tree-based models on CPU/GPU; model repository via Init Container from GCS; gRPC API via Helix client. +- **Multi-backend support**: TensorRT, PyTorch, ONNX Runtime, TensorFlow, Python, FIL, DALI, Custom. +- **Dynamic batching & concurrency**: Configurable via `config.pbtxt`; model versioning and ensembles. +- **Kubernetes deployment**: Helm-based; Init Container + Triton container; custom Triton images from Artifact Registry; health probes; CPU/GPU autoscaling. +- **Observability**: Prometheus metrics, Grafana; warmup requests for cold-start avoidance. diff --git a/docs-src/docs/quick-start/_category_.json b/docs-src/docs/quick-start/_category_.json index 2e50c7ae..ad53c8fa 100644 --- a/docs-src/docs/quick-start/_category_.json +++ b/docs-src/docs/quick-start/_category_.json @@ -1,6 +1,6 @@ { "label": "Quick Start", - "position": 2, + "position": 3, "link": { "type": "generated-index", "description": "Quick Start guide for BharatML Stack. Get up and running quickly with step-by-step instructions, sample data, and Docker Compose setup for local development and testing." diff --git a/docs-src/docs/quick-start/v1.0.0/index.md b/docs-src/docs/quick-start/v1.0.0/index.md new file mode 100644 index 00000000..adc1bb16 --- /dev/null +++ b/docs-src/docs/quick-start/v1.0.0/index.md @@ -0,0 +1,14 @@ +--- +title: v1.0.0 +description: Quick Start v1.0.0 +sidebar_position: 0 +slug: /quick-start/v1.0.0 +--- + +import DocCardList from '@theme/DocCardList'; + +# Quick Start v1.0.0 + +Get up and running quickly with step-by-step instructions, sample data, and Docker Compose setup for local development and testing. + + diff --git a/docs-src/docs/sdks/_category_.json b/docs-src/docs/sdks/_category_.json index 674a3f7e..ee44b06e 100644 --- a/docs-src/docs/sdks/_category_.json +++ b/docs-src/docs/sdks/_category_.json @@ -1,6 +1,6 @@ { "label": "SDKs", - "position": 3, + "position": 5, "link": { "type": "generated-index", "description": "Software Development Kits (SDKs) for BharatML Stack. Includes client libraries for Go and Python to interact with the online feature store and other platform components." diff --git a/docs-src/docs/sdks/go/v1.0.0/index.md b/docs-src/docs/sdks/go/v1.0.0/index.md new file mode 100644 index 00000000..72dbc2da --- /dev/null +++ b/docs-src/docs/sdks/go/v1.0.0/index.md @@ -0,0 +1,14 @@ +--- +title: v1.0.0 +description: Go SDK v1.0.0 +sidebar_position: 0 +slug: /sdks/go/v1.0.0 +--- + +import DocCardList from '@theme/DocCardList'; + +# Go SDK v1.0.0 + +Go client libraries and packages for interacting with the BharatML Stack online feature store, including gRPC clients and protocol buffer definitions. + + diff --git a/docs-src/docs/sdks/python/v1.0.0/_category_.json b/docs-src/docs/sdks/python/v1.0.0/_category_.json index 58516700..4e1f685c 100644 --- a/docs-src/docs/sdks/python/v1.0.0/_category_.json +++ b/docs-src/docs/sdks/python/v1.0.0/_category_.json @@ -1,8 +1,4 @@ { - "label": "v1.0.0", - "position": 1, - "link": { - "type": "generated-index", - "description": "Python SDK v1.0.0 documentation for BharatML Stack. Contains API reference, usage guides, and examples for the Python client libraries including gRPC feature client, Spark feature push client, and common utilities." - } + "label": "v1.0.0", + "position": 1 } \ No newline at end of file diff --git a/docs-src/docs/sdks/python/v1.0.0/index.md b/docs-src/docs/sdks/python/v1.0.0/index.md new file mode 100644 index 00000000..3d6f0e23 --- /dev/null +++ b/docs-src/docs/sdks/python/v1.0.0/index.md @@ -0,0 +1,14 @@ +--- +title: v1.0.0 +description: Python SDK v1.0.0 +sidebar_position: 0 +slug: /sdks/python/v1.0.0 +--- + +import DocCardList from '@theme/DocCardList'; + +# Python SDK v1.0.0 + +Python client libraries and utilities for interacting with the BharatML Stack online feature store, including gRPC clients, Spark integration, and common utilities. + + diff --git a/docs-src/docs/skye/_category_.json b/docs-src/docs/skye/_category_.json new file mode 100644 index 00000000..431fda78 --- /dev/null +++ b/docs-src/docs/skye/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Skye", + "position": 6, + "link": { + "type": "generated-index", + "description": "Skye is a high-performance vector similarity search platform that enables fast semantic retrieval by representing data as vectors and querying nearest matches in high-dimensional space. It supports pluggable vector databases, tenant-level index isolation, intelligent caching, and centralized cluster management." + } +} diff --git a/docs-src/docs/skye/v1.0.0/_category_.json b/docs-src/docs/skye/v1.0.0/_category_.json new file mode 100644 index 00000000..3c72a212 --- /dev/null +++ b/docs-src/docs/skye/v1.0.0/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "v1.0.0", + "position": 1 +} diff --git a/docs-src/docs/skye/v1.0.0/architecture.md b/docs-src/docs/skye/v1.0.0/architecture.md new file mode 100644 index 00000000..f08926a7 --- /dev/null +++ b/docs-src/docs/skye/v1.0.0/architecture.md @@ -0,0 +1,373 @@ +--- +title: Architecture +sidebar_position: 1 +--- + +# Skye - Vector Similarity Search Platform + +Skye is BharatMLStack's vector similarity search platform that enables fast semantic retrieval by representing data as vectors and querying nearest matches in high-dimensional space. It is composed of three runnable components: **skye-admin**, **skye-consumers**, and **skye-serving**. + +--- + +## System Overview + +![Skye System Architecture](../../../static/img/skye-system-overview.png) + +Skye provides a critical platform for managing data aggregation, model onboarding, and embedding support at production scale. The architecture is designed around three core pillars: + +- **Pluggable Vector Databases**: Support for multiple vector database backends (Qdrant and extensible to others) via a generic abstraction layer. +- **Tenant-Level Index Isolation with Shared Embeddings**: Models are stored once but can serve multiple tenants (variants), reducing data redundancy. +- **Event-Driven Administration**: Model lifecycle management is handled through Kafka-based event flows for resilience and fault tolerance. + +### Component Architecture + +| Component | Role | +|---|---| +| **skye-serving** | Handles real-time similarity search queries with in-memory caching and vector DB lookups | +| **skye-consumers** | Processes embedding ingestion (reset/delta jobs) and real-time aggregation events from Kafka | +| **skye-admin** | Manages model lifecycle, onboarding, variant registration, and coordinates Databricks jobs | + +--- + +## Data Model + +### Model and Variant Hierarchy + +Skye uses a **model-first** hierarchy rather than a tenant-first approach. Models sit at the base level with variants (formerly tenants) nested within each model. This eliminates embedding duplication across tenants. + +``` +model (e.g., intent_model) + ├── model_config (distance_function, vector_dimension, etc.) + ├── embedding_store (shared embeddings for all variants) + ├── variant_1 (e.g., organic) + │ ├── vss_filter (criteria for index inclusion) + │ ├── vectordb_type (QDRANT, etc.) + │ ├── vectordb_config (host, port, replication, sharding) + │ ├── read_version / write_version + │ └── job_frequency (FREQ_1D, FREQ_3H, etc.) + └── variant_2 (e.g., ad) + ├── vss_filter + ├── vectordb_type + └── ... +``` + +**Key benefit**: If a model consumes 30M embeddings and is used by two variants, the embeddings are stored once (30M) instead of duplicated (60M). + +### Entity-Based Data Split + +Data is split at the entity level (catalog, product, user) into separate tables for both embeddings and aggregator data: + +**Embedding Tables** (per entity): + +```sql +CREATE TABLE catalog_embeddings ( + model_name text, + version int, + id text, + embedding frozen>, + search_embedding frozen>, + to_be_indexed_variant_1 boolean, + to_be_indexed_variant_2 boolean, + PRIMARY KEY ((model_name, version), id) +); +``` + +**Aggregator Tables** (per entity): + +```sql +CREATE TABLE catalog_aggregator ( + id text, + is_live_ad text, + out_of_stock text, + PRIMARY KEY (id) +); +``` + +Each entity is mapped via a store configuration: + +```json +{ + "db_conf_id": "1", + "embeddings_table": "catalog_embeddings", + "aggregator_table": "catalog_aggregator" +} +``` + +--- + +## Serving Flow + +The serving path is optimized for low latency with multiple caching layers: + +1. **Request arrives** at skye-serving via gRPC +2. **ConfigRepo** resolves the model configuration, variant filters, and vector DB connection +3. **In-memory cache** is checked first to reduce load on distributed cache +4. **Distributed cache (Redis)** is checked next for cached similarity results +5. **Vector DB query** executes if cache misses, using `search_indexed_only` flag for optimal searches within indexed space +6. **Aggregator data** is fetched from ScyllaDB to apply variant-level filters +7. **Response** returns ranked similar candidates with scores + +### Configuration Bootstrap + +On startup, ConfigRepo creates: +- A map of each model with its configurations (embedding table, vector DB channel) +- A map of each entity to its aggregator table + +```json +{ + "intent_model": { + "db_conf_id": "1", + "index_embedding_table": "catalog_embeddings", + "vector_db_grpc_channel": "" + } +} +``` + +--- + +## Admin Flows + +Skye uses an **event-driven approach** for model lifecycle management: + +- All admin operations are processed through Kafka consumers asynchronously +- A SQL database behind the admin stores all model states +- Pod termination does not affect in-progress operations (events are re-consumed on failure) +- Databricks jobs are triggered and monitored via the admin API + +### API Contracts + +#### Register Model + +``` +POST /register-model +``` + +```json +{ + "entity": "catalog", + "ingestion_column_mapping": "{\"id_column\":\"id\",\"embedding_column\":\"features\",\"to_be_indexed_column\":\"to_be_indexed\"}", + "embedding_store_enabled": true, + "embedding_store_ttl": 604800, + "mq_id": 804, + "model_config": "{\"distance_function\":\"DOT\",\"vector_dimension\":32}", + "store_id": 1, + "training_data_path": "gcs_path" +} +``` + +#### Register Variant + +``` +POST /register-variant +``` + +```json +{ + "entity": "catalog", + "model_name": "intent_model", + "vss_filter": "{...filter criteria...}", + "vectordb_type": "QDRANT", + "vectordb_config": "{...connection config...}", + "job_frequency": "FREQ_1D" +} +``` + +#### Reset Model + +``` +POST /reset-model +``` + +```json +{ + "entity": "catalog", + "model_name": "intent_model", + "frequency": "FREQ_1D" +} +``` + +Response includes variant version mappings, MQ ID, and training data path for the Databricks job. + +#### Trigger Model Machine + +``` +POST /trigger-model-machine +``` + +```json +{ + "entity": "catalog", + "model_name": "intent_model", + "variant": "organic" +} +``` + +#### Promote Model / Variant to Scale-Up Cluster + +``` +POST /promote-model +POST /promote-variant +``` + +Used to transition successful experiments from experiment clusters to production clusters. + +--- + +## Consumer Flows + +![Skye Real-Time Consumer Flow](../../../static/img/skye-rt-consumer-flow.png) + +### Reset/Delta Ingestion + +Embedding ingestion occurs once per model and executes in parallel for each variant. The Kafka event contract supports: + +- **Multiple variants per event**: A single embedding event specifies which variants should index the data +- **Separate search and index embeddings**: Models can have different embeddings for search space vs index space +- **EOF handling**: EOF is sent to all partitions to ensure all data is consumed before completion + +```json +{ + "entity": "catalog", + "model_name": "intent_model", + "candidate_id": "48869419", + "version": "1", + "index_space": { + "variants_version_map": "{'organic':1,'ad':2}", + "embedding": [0.036, -0.048, ...], + "variants_index_map": "{'organic':true,'ad':false}", + "operation": "A", + "payload": "{'sscat_id':700}" + }, + "search_space": { + "embedding": [0.036, -0.048, ...] + } +} +``` + +### Real-Time Consumers + +A generic Kafka schema is used for all real-time consumers, simplifying new integrations: + +```json +{ + "timestamp": 1719308350, + "entity_label": "catalog", + "data": [ + { + "id": "125138466", + "label": "is_live_ad", + "value": "true" + } + ] +} +``` + +### Retry Topic + +Failed ingestion events are published to a retry topic for reprocessing, ensuring no data loss: + +```json +{ + "timestamp": 1719308350, + "entity_label": "catalog", + "model_name": "intent_model", + "variant": "organic", + "data": [ + { + "id": "125138466", + "label": "is_live_ad", + "value": "true" + } + ] +} +``` + +--- + +## Key Design Decisions + +### Pluggable Vector Database Support + +Skye introduces a generic `vector_db_type` configuration and converts vendor-specific configs to a generic `vector_config`, enabling support for multiple vector database backends beyond Qdrant. + +### Variant-Based Model Sharing + +By eliminating the tenant-based construct and introducing variants, Skye allows: +- Models to be shared across tenants without duplication +- Each variant to have its own filter criteria, vector DB config, and job frequency +- Independent read/write version tracking per variant + +### ScyllaDB for Real-Time Aggregation + +Replaced Delta Lake with self-hosted ScyllaDB for cost efficiency. The aggregator is entity-generic (not model/version-specific) since all real-time data is consistent across models. + +### Event-Driven State Management + +Model state transitions are handled via Kafka events with a SQL database backing store. This eliminates: +- Single points of failure in admin/ingestion flows +- Models getting stuck during pod restarts +- Manual intervention for consumer pause/resume + +--- + +## Resiliency + +| Mechanism | Description | +|---|---| +| **Retry Topics** | Failed ingestion messages are captured in a failure topic for reprocessing | +| **Circuit Breakers** | Applied to similarity search API calls to throttle RPS during failures | +| **Snapshot Backups** | Periodic collection snapshots enable quick restore during downtime | +| **Automated Cluster Setup** | Scripted provisioning eliminates configuration inconsistencies | +| **Databricks Job Retries** | Lambda functions with retry mechanisms for failed ingestion jobs | + +--- + +## Scalability + +- **Vector DB Scaling**: Generic scripts for adding nodes to existing clusters, enabling horizontal scaling based on load and RPS +- **Service Scaling**: Hosted on EKS with CPU-based autoscaling +- **Experiment Isolation**: Experiments run on separate EKS and vector DB clusters, reducing production cluster complexity +- **Indexed-Only Search**: The `search_indexed_only` flag ensures queries only search indexed space, avoiding latency from brute-force searches on partially built indexes + +--- + +## Observability + +### Metrics (per model + variant) + +| Metric | Description | +|---|---| +| `avg_similar_candidates` | Average number of similarity candidates returned | +| `avg_recall` | Score of the first similar catalog returned | +| Service Latency | P99.9 / P99 / P95 / P50 | +| Service 5xx Count | Error rate monitoring | +| Vector DB Latency | P99.9 / P99 / P95 / P50 | +| Vector DB QPS | Throughput monitoring | +| ScyllaDB Latency | P99.9 / P99 / P95 / P90 | +| Redis Latency | P99.9 / P99 / P95 / P90 | +| Redis Hit % | Cache effectiveness | + +### Alerts + +| Alert | Threshold | +|---|---| +| Indexed Vector Count | < 95% | +| Events to Failure Topic | Rate > 0 | +| Service 5xx | < 10 | +| Service Latency | Model-dependent SLA | + +--- + +## Technology Stack + +| Component | Technology | +|---|---| +| Language | Go | +| Vector Database | Qdrant (pluggable) | +| Embedding Storage | ScyllaDB | +| Real-Time Aggregation | ScyllaDB | +| Caching | Redis + In-Memory | +| Message Queue | Kafka | +| Configuration | ZooKeeper / etcd | +| Container Orchestration | Kubernetes (EKS) | +| Job Orchestration | Databricks | diff --git a/docs-src/docs/skye/v1.0.0/functionalities.md b/docs-src/docs/skye/v1.0.0/functionalities.md new file mode 100644 index 00000000..1ba92d30 --- /dev/null +++ b/docs-src/docs/skye/v1.0.0/functionalities.md @@ -0,0 +1,106 @@ +--- +title: Functionalities +sidebar_position: 2 +--- + +# Skye - Functionalities + +## Core Capabilities + +### 1. Vector Similarity Search + +Skye provides real-time nearest-neighbor search across high-dimensional vector spaces. It supports: + +- **Configurable distance functions**: DOT product, Cosine similarity, Euclidean distance +- **Configurable vector dimensions**: Per-model vector dimension settings +- **Indexed-only search**: Queries only search within fully indexed space, avoiding brute-force fallback on partially built indexes +- **Pagination support**: Service-level pagination for clients, even when the underlying vector DB does not natively support it + +### 2. Pluggable Vector Database Support + +The platform is designed to be vector DB agnostic: + +- **Generic vector config**: A `vector_db_type` field and generic `vectordb_config` replace vendor-specific configurations +- **Current support**: Qdrant with official Go client +- **Extensibility**: New vector databases can be integrated by implementing the vector DB interface + +### 3. Model and Variant Management + +#### Model Registration +- Models are registered via API with entity type, embedding configuration, distance function, vector dimension, and training data path +- Each model is associated with a store ID mapping to specific embedding and aggregator tables + +#### Variant Registration +- Variants represent different views/filters of the same model (e.g., organic, ad, commerce) +- Each variant has its own filter criteria, vector DB cluster, job frequency, and version tracking +- Variants share the same embeddings, eliminating data redundancy + +#### Model Promotion +- Successful experiments can be promoted from experiment clusters to production clusters via API + +### 4. Embedding Ingestion + +#### Batch Ingestion (Reset/Delta Jobs) +- Triggered via Databricks jobs that read from GCS paths +- Supports separate index-space and search-space embeddings +- Per-variant `to_be_indexed` flags control which embeddings are indexed for each variant +- EOF markers sent to all Kafka partitions ensure complete data consumption + +#### Real-Time Ingestion +- Generic Kafka schema for all real-time consumers +- Entity-based aggregation data (e.g., is_live_ad, out_of_stock) updates in real time +- During model resets, real-time consumers continue pushing data to the latest collection (no pausing) + +### 5. Real-Time Data Aggregation + +- Entity-wise (catalog, product, user) real-time aggregation via ScyllaDB +- Generic approach: aggregator tables are entity-level, not model/version-specific +- All real-time data is consistent across models sharing the same entity + +### 6. Intelligent Caching + +- **In-memory cache**: First layer, reduces load on distributed cache +- **Distributed cache (Redis)**: Second layer for cached similarity results +- Hit rate monitoring and cache effectiveness metrics per model + +### 7. Embedded Storage + +- Optional embedding storage with configurable TTL +- Enables embedding lookup APIs for downstream consumers +- Stored in ScyllaDB with efficient binary serialization + +### 8. Retry and Fault Tolerance + +- **Retry topic**: Failed ingestion events are published to a dedicated retry topic +- **Event-driven state management**: Model states persist in SQL DB, surviving pod restarts +- **Kafka-based admin**: Asynchronous processing with automatic re-consumption on failure + +### 9. Experiment Isolation + +- Dedicated EKS cluster (`skye-service-experiments`) for experiments +- Dedicated vector DB cluster for experiment workloads +- Clean separation from production: experiments do not impact production performance +- Promotion path from experiment to production after load analysis + +### 10. Centralized Cluster Management + +- Automated cluster provisioning via scripts (collaboration with DevOps) +- Consistent configurations across all clusters (eliminates consensus issues) +- Horizontal scaling support: generic scripts for adding nodes to existing clusters + +--- + +## Onboarding Flow + +### Step-by-step Process + +1. **Data Scientist** provides a base GCS path where model embeddings will be pushed +2. **Register Model** via `POST /register-model` with entity type, column mappings, model config +3. **Register Variant(s)** via `POST /register-variant` with filter criteria, vector DB config, job frequency +4. **Schedule Databricks Job** to read data from GCS path and ingest into Skye platform +5. **Reset Model** via `POST /reset-model` to trigger the first full ingestion +6. **Trigger Model Machine** via `POST /trigger-model-machine` to start the indexing pipeline + +### Extending to New Tenants + +With the variant system, extending a model to a new tenant only requires registering a new variant with appropriate filters -- no re-ingestion of embeddings is needed. diff --git a/docs-src/docs/skye/v1.0.0/index.md b/docs-src/docs/skye/v1.0.0/index.md new file mode 100644 index 00000000..0a5ee391 --- /dev/null +++ b/docs-src/docs/skye/v1.0.0/index.md @@ -0,0 +1,14 @@ +--- +title: v1.0.0 +description: Skye v1.0.0 +sidebar_position: 0 +slug: /skye/v1.0.0 +--- + +import DocCardList from '@theme/DocCardList'; + +# Skye v1.0.0 + +Skye is a high-performance vector similarity search platform that enables fast semantic retrieval by representing data as vectors and querying nearest matches in high-dimensional space. + + diff --git a/docs-src/docs/skye/v1.0.0/release-notes.md b/docs-src/docs/skye/v1.0.0/release-notes.md new file mode 100644 index 00000000..ac0b1f77 --- /dev/null +++ b/docs-src/docs/skye/v1.0.0/release-notes.md @@ -0,0 +1,67 @@ +--- +title: Release Notes +sidebar_position: 3 +--- + +# Skye - Release Notes + +## v1.0.0 + +### Overview + +Initial open-source release of Skye, BharatMLStack's vector similarity search platform. This release represents a complete re-architecture of the internal VSS (Vector Similarity Search) service, addressing scalability, resilience, and operational efficiency challenges from the previous generation. + +### What's New + +#### Architecture +- **Model-first hierarchy**: Models at the base level with variants nested within, eliminating embedding duplication across tenants +- **Entity-based data split**: Separate embedding and aggregator tables per entity type (catalog, product, user) +- **Event-driven admin flows**: Kafka-based model lifecycle management with SQL-backed state persistence +- **Pluggable vector DB support**: Generic vector database abstraction replacing vendor-specific tight coupling + +#### Serving +- **Multi-layer caching**: In-memory cache + Redis distributed cache for low-latency similarity search +- **Indexed-only search**: `search_indexed_only` flag prevents brute-force fallback on partially indexed collections +- **Pagination support**: Service-level pagination for clients +- **Separate search/index embeddings**: Models can use different embedding spaces for search and indexing + +#### Ingestion +- **Shared embeddings across variants**: Single ingestion per model with parallel variant processing +- **Generic RT consumer schema**: Simplified onboarding for new real-time data sources +- **Retry topic**: Automatic capture and reprocessing of failed ingestion events +- **EOF to all partitions**: Ensures complete data consumption before processing completion + +#### Operations +- **API-based model onboarding**: Register models and variants via REST API (replaces manual Databricks-only flow) +- **Automated cluster provisioning**: Scripted setup for consistent vector DB cluster configurations +- **Experiment isolation**: Dedicated EKS and vector DB clusters for experiments +- **Comprehensive observability**: Per-model + per-variant metrics for latency, throughput, error rates, and cache effectiveness + +### Improvements Over Previous Architecture + +| Area | Before | After | +|---|---|---| +| Embedding storage | Duplicated per tenant | Shared per model | +| Vector DB coupling | Tightly coupled to Qdrant | Pluggable via generic interface | +| State management | In-pod synchronous thread | Event-driven with SQL backing | +| Consumer handling | Paused during ingestion | No pausing; concurrent writes | +| Cluster setup | Manual, error-prone | Automated, consistent | +| Experiment infra | Shared with production | Isolated clusters | +| Failure recovery | Manual intervention | Retry topics + snapshots | +| Observability | Generic alerts | Model + variant level metrics | + +### Known Limitations + +- Snapshot restore is currently supported for smaller indexes only +- Pagination is handled at the service level (not natively by the vector DB) +- Horizontal scaling of vector DB clusters requires running provisioning scripts + +### Technology Stack + +- **Language**: Go +- **Vector Database**: Qdrant (pluggable) +- **Storage**: ScyllaDB +- **Cache**: Redis + In-Memory +- **Message Queue**: Kafka +- **Configuration**: ZooKeeper / etcd +- **Orchestration**: Kubernetes (EKS) diff --git a/docs-src/docs/trufflebox-ui/_category_.json b/docs-src/docs/trufflebox-ui/_category_.json index b06298f4..d44ae254 100644 --- a/docs-src/docs/trufflebox-ui/_category_.json +++ b/docs-src/docs/trufflebox-ui/_category_.json @@ -1,6 +1,6 @@ { "label": "Trufflebox UI", - "position": 2, + "position": 4, "link": { "type": "generated-index", "description": "Trufflebox UI is a modern, feature rich UI framework for supporting MLOps. It supports Feature catalog, management, user managemnet and other adminops" diff --git a/docs-src/docs/trufflebox-ui/v1.0.0/index.md b/docs-src/docs/trufflebox-ui/v1.0.0/index.md new file mode 100644 index 00000000..ee6a7212 --- /dev/null +++ b/docs-src/docs/trufflebox-ui/v1.0.0/index.md @@ -0,0 +1,14 @@ +--- +title: v1.0.0 +description: Trufflebox UI v1.0.0 +sidebar_position: 0 +slug: /trufflebox-ui/v1.0.0 +--- + +import DocCardList from '@theme/DocCardList'; + +# Trufflebox UI v1.0.0 + +Trufflebox UI is a modern, feature-rich UI framework for supporting MLOps. It supports feature catalog, management, user management, and other admin operations. + + diff --git a/docs-src/docusaurus.config.js b/docs-src/docusaurus.config.js index 229e0cb2..7d9bffb5 100644 --- a/docs-src/docusaurus.config.js +++ b/docs-src/docusaurus.config.js @@ -52,6 +52,7 @@ const config = { routeBasePath: '/', }, blog: { + blogSidebarCount: 'ALL', showReadingTime: true, feedOptions: { type: ['rss', 'atom'], @@ -78,6 +79,10 @@ const config = { ({ // Replace with your project's social card image: 'img/docusaurus-social-card.jpg', + colorMode: { + defaultMode: 'dark', + respectPrefersColorScheme: true, + }, navbar: { title: 'BharatMLStack', items: [ diff --git a/docs-src/package-lock.json b/docs-src/package-lock.json deleted file mode 100644 index c2617028..00000000 --- a/docs-src/package-lock.json +++ /dev/null @@ -1,17432 +0,0 @@ -{ - "name": "docs", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "docs", - "version": "0.0.0", - "dependencies": { - "@docusaurus/core": "3.8.1", - "@docusaurus/preset-classic": "3.8.1", - "@mdx-js/react": "^3.0.0", - "clsx": "^2.0.0", - "prism-react-renderer": "^2.3.0", - "react": "^19.0.0", - "react-dom": "^19.0.0" - }, - "devDependencies": { - "@docusaurus/module-type-aliases": "3.8.1", - "@docusaurus/types": "3.8.1" - }, - "engines": { - "node": ">=18.0" - } - }, - "node_modules/@algolia/autocomplete-core": { - "version": "1.17.9", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.9.tgz", - "integrity": "sha512-O7BxrpLDPJWWHv/DLA9DRFWs+iY1uOJZkqUwjS5HSZAGcl0hIVCQ97LTLewiZmZ402JYUrun+8NqFP+hCknlbQ==", - "license": "MIT", - "dependencies": { - "@algolia/autocomplete-plugin-algolia-insights": "1.17.9", - "@algolia/autocomplete-shared": "1.17.9" - } - }, - "node_modules/@algolia/autocomplete-plugin-algolia-insights": { - "version": "1.17.9", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.9.tgz", - "integrity": "sha512-u1fEHkCbWF92DBeB/KHeMacsjsoI0wFhjZtlCq2ddZbAehshbZST6Hs0Avkc0s+4UyBGbMDnSuXHLuvRWK5iDQ==", - "license": "MIT", - "dependencies": { - "@algolia/autocomplete-shared": "1.17.9" - }, - "peerDependencies": { - "search-insights": ">= 1 < 3" - } - }, - "node_modules/@algolia/autocomplete-preset-algolia": { - "version": "1.17.9", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.9.tgz", - "integrity": "sha512-Na1OuceSJeg8j7ZWn5ssMu/Ax3amtOwk76u4h5J4eK2Nx2KB5qt0Z4cOapCsxot9VcEN11ADV5aUSlQF4RhGjQ==", - "license": "MIT", - "dependencies": { - "@algolia/autocomplete-shared": "1.17.9" - }, - "peerDependencies": { - "@algolia/client-search": ">= 4.9.1 < 6", - "algoliasearch": ">= 4.9.1 < 6" - } - }, - "node_modules/@algolia/autocomplete-shared": { - "version": "1.17.9", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.9.tgz", - "integrity": "sha512-iDf05JDQ7I0b7JEA/9IektxN/80a2MZ1ToohfmNS3rfeuQnIKI3IJlIafD0xu4StbtQTghx9T3Maa97ytkXenQ==", - "license": "MIT", - "peerDependencies": { - "@algolia/client-search": ">= 4.9.1 < 6", - "algoliasearch": ">= 4.9.1 < 6" - } - }, - "node_modules/@algolia/client-abtesting": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.27.0.tgz", - "integrity": "sha512-SITU5umoknxETtw67TxJu9njyMkWiH8pM+Bvw4dzfuIrIAT6Y1rmwV4y0A0didWoT+6xVuammIykbtBMolBcmg==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.27.0", - "@algolia/requester-browser-xhr": "5.27.0", - "@algolia/requester-fetch": "5.27.0", - "@algolia/requester-node-http": "5.27.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-analytics": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.27.0.tgz", - "integrity": "sha512-go1b9qIZK5vYEQ7jD2bsfhhhVsoh9cFxQ5xF8TzTsg2WOCZR3O92oXCkq15SOK0ngJfqDU6a/k0oZ4KuEnih1Q==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.27.0", - "@algolia/requester-browser-xhr": "5.27.0", - "@algolia/requester-fetch": "5.27.0", - "@algolia/requester-node-http": "5.27.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-common": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.27.0.tgz", - "integrity": "sha512-tnFOzdNuMzsz93kOClj3fKfuYoF3oYaEB5bggULSj075GJ7HUNedBEm7a6ScrjtnOaOtipbnT7veUpHA4o4wEQ==", - "license": "MIT", - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-insights": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.27.0.tgz", - "integrity": "sha512-y1qgw39qZijjQBXrqZTiwK1cWgWGRiLpJNWBv9w36nVMKfl9kInrfsYmdBAfmlhVgF/+Woe0y1jQ7pa4HyShAw==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.27.0", - "@algolia/requester-browser-xhr": "5.27.0", - "@algolia/requester-fetch": "5.27.0", - "@algolia/requester-node-http": "5.27.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-personalization": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.27.0.tgz", - "integrity": "sha512-XluG9qPZKEbiLoIfXTKbABsWDNOMPx0t6T2ImJTTeuX+U/zBdmfcqqgcgkqXp+vbXof/XX/4of9Eqo1JaqEmKw==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.27.0", - "@algolia/requester-browser-xhr": "5.27.0", - "@algolia/requester-fetch": "5.27.0", - "@algolia/requester-node-http": "5.27.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-query-suggestions": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.27.0.tgz", - "integrity": "sha512-V8/To+SsAl2sdw2AAjeLJuCW1L+xpz+LAGerJK7HKqHzE5yQhWmIWZTzqYQcojkii4iBMYn0y3+uReWqT8XVSQ==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.27.0", - "@algolia/requester-browser-xhr": "5.27.0", - "@algolia/requester-fetch": "5.27.0", - "@algolia/requester-node-http": "5.27.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-search": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.27.0.tgz", - "integrity": "sha512-EJJ7WmvmUXZdchueKFCK8UZFyLqy4Hz64snNp0cTc7c0MKaSeDGYEDxVsIJKp15r7ORaoGxSyS4y6BGZMXYuCg==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.27.0", - "@algolia/requester-browser-xhr": "5.27.0", - "@algolia/requester-fetch": "5.27.0", - "@algolia/requester-node-http": "5.27.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/events": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@algolia/events/-/events-4.0.1.tgz", - "integrity": "sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==", - "license": "MIT" - }, - "node_modules/@algolia/ingestion": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.27.0.tgz", - "integrity": "sha512-xNCyWeqpmEo4EdmpG57Fs1fJIQcPwt5NnJ6MBdXnUdMVXF4f5PHgza+HQWQQcYpCsune96jfmR0v7us6gRIlCw==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.27.0", - "@algolia/requester-browser-xhr": "5.27.0", - "@algolia/requester-fetch": "5.27.0", - "@algolia/requester-node-http": "5.27.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/monitoring": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.27.0.tgz", - "integrity": "sha512-P0NDiEFyt9UYQLBI0IQocIT7xHpjMpoFN3UDeerbztlkH9HdqT0GGh1SHYmNWpbMWIGWhSJTtz6kSIWvFu4+pw==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.27.0", - "@algolia/requester-browser-xhr": "5.27.0", - "@algolia/requester-fetch": "5.27.0", - "@algolia/requester-node-http": "5.27.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/recommend": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.27.0.tgz", - "integrity": "sha512-cqfTMF1d1cc7hg0vITNAFxJZas7MJ4Obc36WwkKpY23NOtGb+4tH9X7UKlQa2PmTgbXIANoJ/DAQTeiVlD2I4Q==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.27.0", - "@algolia/requester-browser-xhr": "5.27.0", - "@algolia/requester-fetch": "5.27.0", - "@algolia/requester-node-http": "5.27.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/requester-browser-xhr": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.27.0.tgz", - "integrity": "sha512-ErenYTcXl16wYXtf0pxLl9KLVxIztuehqXHfW9nNsD8mz9OX42HbXuPzT7y6JcPiWJpc/UU/LY5wBTB65vsEUg==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.27.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/requester-fetch": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.27.0.tgz", - "integrity": "sha512-CNOvmXsVi+IvT7z1d+6X7FveVkgEQwTNgipjQCHTIbF9KSMfZR7tUsJC+NpELrm10ALdOMauah84ybs9rw1cKQ==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.27.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/requester-node-http": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.27.0.tgz", - "integrity": "sha512-Nx9EdLYZDsaYFTthqmc0XcVvsx6jqeEX8fNiYOB5i2HboQwl8pJPj1jFhGqoGd0KG7KFR+sdPO5/e0EDDAru2Q==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.27.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz", - "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", - "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.4", - "@babel/parser": "^7.27.4", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.27.4", - "@babel/types": "^7.27.3", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/generator": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", - "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.27.5", - "@babel/types": "^7.27.3", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", - "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.3" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", - "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-member-expression-to-functions": "^7.27.1", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.27.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", - "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "regexpu-core": "^6.2.0", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.4.tgz", - "integrity": "sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw==", - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", - "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", - "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", - "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", - "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-wrap-function": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", - "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", - "license": "MIT", - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.27.1", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", - "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.27.1.tgz", - "integrity": "sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ==", - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.1", - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", - "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", - "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.3" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", - "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", - "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", - "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", - "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.27.1.tgz", - "integrity": "sha512-6BpaYGDavZqkI6yT+KSPdpZFfpnd68UKXbcjI9pJ13pvHhPrCKWOOLp+ysvMeA+DxnhuPpgIaRpxRxo5A9t5jw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", - "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", - "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.27.1.tgz", - "integrity": "sha512-eST9RrwlpaoJBDHShc+DS2SG4ATTi2MYNb4OxYkf3n+7eb49LWpnS+HSpVfW4x927qQwgk8A2hGNVaajAEw0EA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", - "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-remap-async-to-generator": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", - "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.5.tgz", - "integrity": "sha512-JF6uE2s67f0y2RZcm2kpAUEbD50vH62TyWVebxwHAlbSdM49VqPz8t4a1uIjp4NIOIZ4xzLfjY5emt/RCyC7TQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", - "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.27.1.tgz", - "integrity": "sha512-s734HmYU78MVzZ++joYM+NkJusItbdRcbm+AGRgJCt3iA+yux0QpD9cBVdz3tKyrjVYWRl7j0mHSmv4lhV0aoA==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.27.1.tgz", - "integrity": "sha512-7iLhfFAubmpeJe/Wo2TVuDrykh/zlWXLzPNdL0Jqn/Xu8R3QQ8h9ff8FQoISZOsw74/HFqFI7NX63HN7QFIHKA==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-compilation-targets": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/traverse": "^7.27.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", - "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/template": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.27.3.tgz", - "integrity": "sha512-s4Jrok82JpiaIprtY2nHsYmrThKvvwgHwjgd7UMiYhZaN0asdXNLr0y+NjTfkA7SyQE5i2Fb7eawUOZmLvyqOA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", - "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", - "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", - "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz", - "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", - "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", - "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", - "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", - "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", - "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", - "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", - "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", - "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", - "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", - "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", - "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", - "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", - "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", - "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.3.tgz", - "integrity": "sha512-7ZZtznF9g4l2JCImCo5LNKFHB5eXnN39lLtLY5Tg+VkR0jwOt7TBciMckuiQIOIW7L5tkQOCh3bVGYeXgMx52Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.27.3", - "@babel/plugin-transform-parameters": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", - "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", - "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", - "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.1.tgz", - "integrity": "sha512-018KRk76HWKeZ5l4oTj2zPpSh+NbGdt0st5S6x0pga6HgrjBOJb24mMDHorFopOOd6YHkLgOZ+zaCjZGPO4aKg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", - "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", - "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", - "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-constant-elements": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.27.1.tgz", - "integrity": "sha512-edoidOjl/ZxvYo4lSBOQGDSyToYVkTAwyVoa2tkuYTSmjrB1+uAedoL5iROVLXkxH+vRgA7uP4tMg2pUJpZ3Ug==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.27.1.tgz", - "integrity": "sha512-p9+Vl3yuHPmkirRrg021XiP+EETmPMQTLr6Ayjj85RLNEbb3Eya/4VI0vAdzQG9SEAl2Lnt7fy5lZyMzjYoZQQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz", - "integrity": "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz", - "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==", - "license": "MIT", - "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz", - "integrity": "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.5.tgz", - "integrity": "sha512-uhB8yHerfe3MWnuLAhEbeQ4afVoqv8BQsPqrTv7e/jZ9y00kJL6l9a/f4OWaKxotmjzewfEyXE1vgDJenkQ2/Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", - "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", - "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.27.4.tgz", - "integrity": "sha512-D68nR5zxU64EUzV8i7T3R5XP0Xhrou/amNnddsRQssx6GrTLdZl1rLxyjtVZBd+v/NVX4AbTPOB5aU8thAZV1A==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.11.0", - "babel-plugin-polyfill-regenerator": "^0.6.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", - "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", - "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", - "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", - "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", - "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.1.tgz", - "integrity": "sha512-Q5sT5+O4QUebHdbwKedFBEwRLb02zJ7r4A5Gg2hUoLuU3FjdMcyqcywqUrLCaDsFCxzokf7u9kuy7qz51YUuAg==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", - "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", - "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", - "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", - "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.27.2.tgz", - "integrity": "sha512-Ma4zSuYSlGNRlCLO+EAzLnCmJK2vdstgv+n7aUP+/IKZrOfWHOJVdSJtuub8RzHTj3ahD37k5OKJWvzf16TQyQ==", - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.27.1", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.27.1", - "@babel/plugin-syntax-import-attributes": "^7.27.1", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-async-generator-functions": "^7.27.1", - "@babel/plugin-transform-async-to-generator": "^7.27.1", - "@babel/plugin-transform-block-scoped-functions": "^7.27.1", - "@babel/plugin-transform-block-scoping": "^7.27.1", - "@babel/plugin-transform-class-properties": "^7.27.1", - "@babel/plugin-transform-class-static-block": "^7.27.1", - "@babel/plugin-transform-classes": "^7.27.1", - "@babel/plugin-transform-computed-properties": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.27.1", - "@babel/plugin-transform-dotall-regex": "^7.27.1", - "@babel/plugin-transform-duplicate-keys": "^7.27.1", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", - "@babel/plugin-transform-dynamic-import": "^7.27.1", - "@babel/plugin-transform-exponentiation-operator": "^7.27.1", - "@babel/plugin-transform-export-namespace-from": "^7.27.1", - "@babel/plugin-transform-for-of": "^7.27.1", - "@babel/plugin-transform-function-name": "^7.27.1", - "@babel/plugin-transform-json-strings": "^7.27.1", - "@babel/plugin-transform-literals": "^7.27.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", - "@babel/plugin-transform-member-expression-literals": "^7.27.1", - "@babel/plugin-transform-modules-amd": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-modules-systemjs": "^7.27.1", - "@babel/plugin-transform-modules-umd": "^7.27.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", - "@babel/plugin-transform-new-target": "^7.27.1", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", - "@babel/plugin-transform-numeric-separator": "^7.27.1", - "@babel/plugin-transform-object-rest-spread": "^7.27.2", - "@babel/plugin-transform-object-super": "^7.27.1", - "@babel/plugin-transform-optional-catch-binding": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1", - "@babel/plugin-transform-parameters": "^7.27.1", - "@babel/plugin-transform-private-methods": "^7.27.1", - "@babel/plugin-transform-private-property-in-object": "^7.27.1", - "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.27.1", - "@babel/plugin-transform-regexp-modifiers": "^7.27.1", - "@babel/plugin-transform-reserved-words": "^7.27.1", - "@babel/plugin-transform-shorthand-properties": "^7.27.1", - "@babel/plugin-transform-spread": "^7.27.1", - "@babel/plugin-transform-sticky-regex": "^7.27.1", - "@babel/plugin-transform-template-literals": "^7.27.1", - "@babel/plugin-transform-typeof-symbol": "^7.27.1", - "@babel/plugin-transform-unicode-escapes": "^7.27.1", - "@babel/plugin-transform-unicode-property-regex": "^7.27.1", - "@babel/plugin-transform-unicode-regex": "^7.27.1", - "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.11.0", - "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.40.0", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", - "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/preset-react": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.27.1.tgz", - "integrity": "sha512-oJHWh2gLhU9dW9HHr42q0cI0/iHHXTLGe39qvpAZZzagHy0MzYLCnCVV0symeRvzmjHyVU7mw2K06E6u/JwbhA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-transform-react-display-name": "^7.27.1", - "@babel/plugin-transform-react-jsx": "^7.27.1", - "@babel/plugin-transform-react-jsx-development": "^7.27.1", - "@babel/plugin-transform-react-pure-annotations": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", - "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-typescript": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", - "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/runtime-corejs3": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.27.6.tgz", - "integrity": "sha512-vDVrlmRAY8z9Ul/HxT+8ceAru95LQgkSKiXkSYZvqtbkPSfhZJgpRp45Cldbh1GJ1kxzQkI70AqyrTI58KpaWQ==", - "license": "MIT", - "dependencies": { - "core-js-pure": "^3.30.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", - "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", - "@babel/parser": "^7.27.4", - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", - "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/@csstools/cascade-layer-name-parser": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-2.0.5.tgz", - "integrity": "sha512-p1ko5eHgV+MgXFVa4STPKpvPxr6ReS8oS2jzTukjR74i5zJNyWO1ZM1m8YKBXnzDKWfBN1ztLYlHxbVemDD88A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - } - }, - "node_modules/@csstools/color-helpers": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz", - "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - } - }, - "node_modules/@csstools/css-calc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", - "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - } - }, - "node_modules/@csstools/css-color-parser": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz", - "integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "dependencies": { - "@csstools/color-helpers": "^5.0.2", - "@csstools/css-calc": "^2.1.4" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - } - }, - "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", - "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.4" - } - }, - "node_modules/@csstools/css-tokenizer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", - "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@csstools/media-query-list-parser": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.3.tgz", - "integrity": "sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - } - }, - "node_modules/@csstools/postcss-cascade-layers": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-5.0.1.tgz", - "integrity": "sha512-XOfhI7GShVcKiKwmPAnWSqd2tBR0uxt+runAxttbSp/LY2U16yAVPmAf7e9q4JJ0d+xMNmpwNDLBXnmRCl3HMQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/selector-specificity": "^5.0.0", - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-cascade-layers/node_modules/@csstools/selector-specificity": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", - "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^7.0.0" - } - }, - "node_modules/@csstools/postcss-cascade-layers/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@csstools/postcss-color-function": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-4.0.10.tgz", - "integrity": "sha512-4dY0NBu7NVIpzxZRgh/Q/0GPSz/jLSw0i/u3LTUor0BkQcz/fNhN10mSWBDsL0p9nDb0Ky1PD6/dcGbhACuFTQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.0.10", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.1.0", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-color-mix-function": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.10.tgz", - "integrity": "sha512-P0lIbQW9I4ShE7uBgZRib/lMTf9XMjJkFl/d6w4EMNHu2qvQ6zljJGEcBkw/NsBtq/6q3WrmgxSS8kHtPMkK4Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.0.10", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.1.0", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-color-mix-variadic-function-arguments": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-variadic-function-arguments/-/postcss-color-mix-variadic-function-arguments-1.0.0.tgz", - "integrity": "sha512-Z5WhouTyD74dPFPrVE7KydgNS9VvnjB8qcdes9ARpCOItb4jTnm7cHp4FhxCRUoyhabD0WVv43wbkJ4p8hLAlQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.0.10", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.1.0", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-content-alt-text": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.6.tgz", - "integrity": "sha512-eRjLbOjblXq+byyaedQRSrAejKGNAFued+LcbzT+LCL78fabxHkxYjBbxkroONxHHYu2qxhFK2dBStTLPG3jpQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.1.0", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-exponential-functions": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-2.0.9.tgz", - "integrity": "sha512-abg2W/PI3HXwS/CZshSa79kNWNZHdJPMBXeZNyPQFbbj8sKO3jXxOt/wF7juJVjyDTc6JrvaUZYFcSBZBhaxjw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-font-format-keywords": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-4.0.0.tgz", - "integrity": "sha512-usBzw9aCRDvchpok6C+4TXC57btc4bJtmKQWOHQxOVKen1ZfVqBUuCZ/wuqdX5GHsD0NRSr9XTP+5ID1ZZQBXw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-gamut-mapping": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.10.tgz", - "integrity": "sha512-QDGqhJlvFnDlaPAfCYPsnwVA6ze+8hhrwevYWlnUeSjkkZfBpcCO42SaUD8jiLlq7niouyLgvup5lh+f1qessg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.0.10", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-gradients-interpolation-method": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.10.tgz", - "integrity": "sha512-HHPauB2k7Oits02tKFUeVFEU2ox/H3OQVrP3fSOKDxvloOikSal+3dzlyTZmYsb9FlY9p5EUpBtz0//XBmy+aw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.0.10", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.1.0", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-hwb-function": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.10.tgz", - "integrity": "sha512-nOKKfp14SWcdEQ++S9/4TgRKchooLZL0TUFdun3nI4KPwCjETmhjta1QT4ICQcGVWQTvrsgMM/aLB5We+kMHhQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.0.10", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.1.0", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-ic-unit": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-4.0.2.tgz", - "integrity": "sha512-lrK2jjyZwh7DbxaNnIUjkeDmU8Y6KyzRBk91ZkI5h8nb1ykEfZrtIVArdIjX4DHMIBGpdHrgP0n4qXDr7OHaKA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^4.1.0", - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-initial": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-initial/-/postcss-initial-2.0.1.tgz", - "integrity": "sha512-L1wLVMSAZ4wovznquK0xmC7QSctzO4D0Is590bxpGqhqjboLXYA16dWZpfwImkdOgACdQ9PqXsuRroW6qPlEsg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-is-pseudo-class": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-5.0.3.tgz", - "integrity": "sha512-jS/TY4SpG4gszAtIg7Qnf3AS2pjcUM5SzxpApOrlndMeGhIbaTzWBzzP/IApXoNWEW7OhcjkRT48jnAUIFXhAQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/selector-specificity": "^5.0.0", - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-is-pseudo-class/node_modules/@csstools/selector-specificity": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", - "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^7.0.0" - } - }, - "node_modules/@csstools/postcss-is-pseudo-class/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@csstools/postcss-light-dark-function": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.9.tgz", - "integrity": "sha512-1tCZH5bla0EAkFAI2r0H33CDnIBeLUaJh1p+hvvsylJ4svsv2wOmJjJn+OXwUZLXef37GYbRIVKX+X+g6m+3CQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.1.0", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-logical-float-and-clear": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-float-and-clear/-/postcss-logical-float-and-clear-3.0.0.tgz", - "integrity": "sha512-SEmaHMszwakI2rqKRJgE+8rpotFfne1ZS6bZqBoQIicFyV+xT1UF42eORPxJkVJVrH9C0ctUgwMSn3BLOIZldQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-logical-overflow": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overflow/-/postcss-logical-overflow-2.0.0.tgz", - "integrity": "sha512-spzR1MInxPuXKEX2csMamshR4LRaSZ3UXVaRGjeQxl70ySxOhMpP2252RAFsg8QyyBXBzuVOOdx1+bVO5bPIzA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-logical-overscroll-behavior": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overscroll-behavior/-/postcss-logical-overscroll-behavior-2.0.0.tgz", - "integrity": "sha512-e/webMjoGOSYfqLunyzByZj5KKe5oyVg/YSbie99VEaSDE2kimFm0q1f6t/6Jo+VVCQ/jbe2Xy+uX+C4xzWs4w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-logical-resize": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-resize/-/postcss-logical-resize-3.0.0.tgz", - "integrity": "sha512-DFbHQOFW/+I+MY4Ycd/QN6Dg4Hcbb50elIJCfnwkRTCX05G11SwViI5BbBlg9iHRl4ytB7pmY5ieAFk3ws7yyg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-logical-viewport-units": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-3.0.4.tgz", - "integrity": "sha512-q+eHV1haXA4w9xBwZLKjVKAWn3W2CMqmpNpZUk5kRprvSiBEGMgrNH3/sJZ8UA3JgyHaOt3jwT9uFa4wLX4EqQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-media-minmax": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-2.0.9.tgz", - "integrity": "sha512-af9Qw3uS3JhYLnCbqtZ9crTvvkR+0Se+bBqSr7ykAnl9yKhk6895z9rf+2F4dClIDJWxgn0iZZ1PSdkhrbs2ig==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/media-query-list-parser": "^4.0.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-media-queries-aspect-ratio-number-values": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-3.0.5.tgz", - "integrity": "sha512-zhAe31xaaXOY2Px8IYfoVTB3wglbJUVigGphFLj6exb7cjZRH9A6adyE22XfFK3P2PzwRk0VDeTJmaxpluyrDg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/media-query-list-parser": "^4.0.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-nested-calc": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-4.0.0.tgz", - "integrity": "sha512-jMYDdqrQQxE7k9+KjstC3NbsmC063n1FTPLCgCRS2/qHUbHM0mNy9pIn4QIiQGs9I/Bg98vMqw7mJXBxa0N88A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-normalize-display-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.0.tgz", - "integrity": "sha512-HlEoG0IDRoHXzXnkV4in47dzsxdsjdz6+j7MLjaACABX2NfvjFS6XVAnpaDyGesz9gK2SC7MbNwdCHusObKJ9Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-oklab-function": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.10.tgz", - "integrity": "sha512-ZzZUTDd0fgNdhv8UUjGCtObPD8LYxMH+MJsW9xlZaWTV8Ppr4PtxlHYNMmF4vVWGl0T6f8tyWAKjoI6vePSgAg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.0.10", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.1.0", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-progressive-custom-properties": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-4.1.0.tgz", - "integrity": "sha512-YrkI9dx8U4R8Sz2EJaoeD9fI7s7kmeEBfmO+UURNeL6lQI7VxF6sBE+rSqdCBn4onwqmxFdBU3lTwyYb/lCmxA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-random-function": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-random-function/-/postcss-random-function-2.0.1.tgz", - "integrity": "sha512-q+FQaNiRBhnoSNo+GzqGOIBKoHQ43lYz0ICrV+UudfWnEF6ksS6DsBIJSISKQT2Bvu3g4k6r7t0zYrk5pDlo8w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-relative-color-syntax": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.10.tgz", - "integrity": "sha512-8+0kQbQGg9yYG8hv0dtEpOMLwB9M+P7PhacgIzVzJpixxV4Eq9AUQtQw8adMmAJU1RBBmIlpmtmm3XTRd/T00g==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.0.10", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.1.0", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-scope-pseudo-class": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-4.0.1.tgz", - "integrity": "sha512-IMi9FwtH6LMNuLea1bjVMQAsUhFxJnyLSgOp/cpv5hrzWmrUYU5fm0EguNDIIOHUqzXode8F/1qkC/tEo/qN8Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-scope-pseudo-class/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@csstools/postcss-sign-functions": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@csstools/postcss-sign-functions/-/postcss-sign-functions-1.1.4.tgz", - "integrity": "sha512-P97h1XqRPcfcJndFdG95Gv/6ZzxUBBISem0IDqPZ7WMvc/wlO+yU0c5D/OCpZ5TJoTt63Ok3knGk64N+o6L2Pg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-stepped-value-functions": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-4.0.9.tgz", - "integrity": "sha512-h9btycWrsex4dNLeQfyU3y3w40LMQooJWFMm/SK9lrKguHDcFl4VMkncKKoXi2z5rM9YGWbUQABI8BT2UydIcA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-text-decoration-shorthand": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-4.0.2.tgz", - "integrity": "sha512-8XvCRrFNseBSAGxeaVTaNijAu+FzUvjwFXtcrynmazGb/9WUdsPCpBX+mHEHShVRq47Gy4peYAoxYs8ltUnmzA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/color-helpers": "^5.0.2", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-trigonometric-functions": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-4.0.9.tgz", - "integrity": "sha512-Hnh5zJUdpNrJqK9v1/E3BbrQhaDTj5YiX7P61TOvUhoDHnUmsNNxcDAgkQ32RrcWx9GVUvfUNPcUkn8R3vIX6A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-unset-value": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-4.0.0.tgz", - "integrity": "sha512-cBz3tOCI5Fw6NIFEwU3RiwK6mn3nKegjpJuzCndoGq3BZPkUjnsq7uQmIeMNeMbMk7YD2MfKcgCpZwX5jyXqCA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/utilities": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@csstools/utilities/-/utilities-2.0.0.tgz", - "integrity": "sha512-5VdOr0Z71u+Yp3ozOx8T11N703wIFGVRgOWbOZMKgglPJsWA54MRIoMNVMa7shUToIhx5J8vX4sOZgD2XiihiQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@docsearch/css": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.9.0.tgz", - "integrity": "sha512-cQbnVbq0rrBwNAKegIac/t6a8nWoUAn8frnkLFW6YARaRmAQr5/Eoe6Ln2fqkUCZ40KpdrKbpSAmgrkviOxuWA==", - "license": "MIT" - }, - "node_modules/@docsearch/react": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.9.0.tgz", - "integrity": "sha512-mb5FOZYZIkRQ6s/NWnM98k879vu5pscWqTLubLFBO87igYYT4VzVazh4h5o/zCvTIZgEt3PvsCOMOswOUo9yHQ==", - "license": "MIT", - "dependencies": { - "@algolia/autocomplete-core": "1.17.9", - "@algolia/autocomplete-preset-algolia": "1.17.9", - "@docsearch/css": "3.9.0", - "algoliasearch": "^5.14.2" - }, - "peerDependencies": { - "@types/react": ">= 16.8.0 < 20.0.0", - "react": ">= 16.8.0 < 20.0.0", - "react-dom": ">= 16.8.0 < 20.0.0", - "search-insights": ">= 1 < 3" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "search-insights": { - "optional": true - } - } - }, - "node_modules/@docusaurus/babel": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/@docusaurus/babel/-/babel-3.8.1.tgz", - "integrity": "sha512-3brkJrml8vUbn9aeoZUlJfsI/GqyFcDgQJwQkmBtclJgWDEQBKKeagZfOgx0WfUQhagL1sQLNW0iBdxnI863Uw==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.25.9", - "@babel/generator": "^7.25.9", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-transform-runtime": "^7.25.9", - "@babel/preset-env": "^7.25.9", - "@babel/preset-react": "^7.25.9", - "@babel/preset-typescript": "^7.25.9", - "@babel/runtime": "^7.25.9", - "@babel/runtime-corejs3": "^7.25.9", - "@babel/traverse": "^7.25.9", - "@docusaurus/logger": "3.8.1", - "@docusaurus/utils": "3.8.1", - "babel-plugin-dynamic-import-node": "^2.3.3", - "fs-extra": "^11.1.1", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=18.0" - } - }, - "node_modules/@docusaurus/bundler": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/@docusaurus/bundler/-/bundler-3.8.1.tgz", - "integrity": "sha512-/z4V0FRoQ0GuSLToNjOSGsk6m2lQUG4FRn8goOVoZSRsTrU8YR2aJacX5K3RG18EaX9b+52pN4m1sL3MQZVsQA==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.25.9", - "@docusaurus/babel": "3.8.1", - "@docusaurus/cssnano-preset": "3.8.1", - "@docusaurus/logger": "3.8.1", - "@docusaurus/types": "3.8.1", - "@docusaurus/utils": "3.8.1", - "babel-loader": "^9.2.1", - "clean-css": "^5.3.3", - "copy-webpack-plugin": "^11.0.0", - "css-loader": "^6.11.0", - "css-minimizer-webpack-plugin": "^5.0.1", - "cssnano": "^6.1.2", - "file-loader": "^6.2.0", - "html-minifier-terser": "^7.2.0", - "mini-css-extract-plugin": "^2.9.2", - "null-loader": "^4.0.1", - "postcss": "^8.5.4", - "postcss-loader": "^7.3.4", - "postcss-preset-env": "^10.2.1", - "terser-webpack-plugin": "^5.3.9", - "tslib": "^2.6.0", - "url-loader": "^4.1.1", - "webpack": "^5.95.0", - "webpackbar": "^6.0.1" - }, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "@docusaurus/faster": "*" - }, - "peerDependenciesMeta": { - "@docusaurus/faster": { - "optional": true - } - } - }, - "node_modules/@docusaurus/core": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.8.1.tgz", - "integrity": "sha512-ENB01IyQSqI2FLtOzqSI3qxG2B/jP4gQPahl2C3XReiLebcVh5B5cB9KYFvdoOqOWPyr5gXK4sjgTKv7peXCrA==", - "license": "MIT", - "dependencies": { - "@docusaurus/babel": "3.8.1", - "@docusaurus/bundler": "3.8.1", - "@docusaurus/logger": "3.8.1", - "@docusaurus/mdx-loader": "3.8.1", - "@docusaurus/utils": "3.8.1", - "@docusaurus/utils-common": "3.8.1", - "@docusaurus/utils-validation": "3.8.1", - "boxen": "^6.2.1", - "chalk": "^4.1.2", - "chokidar": "^3.5.3", - "cli-table3": "^0.6.3", - "combine-promises": "^1.1.0", - "commander": "^5.1.0", - "core-js": "^3.31.1", - "detect-port": "^1.5.1", - "escape-html": "^1.0.3", - "eta": "^2.2.0", - "eval": "^0.1.8", - "execa": "5.1.1", - "fs-extra": "^11.1.1", - "html-tags": "^3.3.1", - "html-webpack-plugin": "^5.6.0", - "leven": "^3.1.0", - "lodash": "^4.17.21", - "open": "^8.4.0", - "p-map": "^4.0.0", - "prompts": "^2.4.2", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "react-loadable": "npm:@docusaurus/react-loadable@6.0.0", - "react-loadable-ssr-addon-v5-slorber": "^1.0.1", - "react-router": "^5.3.4", - "react-router-config": "^5.1.1", - "react-router-dom": "^5.3.4", - "semver": "^7.5.4", - "serve-handler": "^6.1.6", - "tinypool": "^1.0.2", - "tslib": "^2.6.0", - "update-notifier": "^6.0.2", - "webpack": "^5.95.0", - "webpack-bundle-analyzer": "^4.10.2", - "webpack-dev-server": "^4.15.2", - "webpack-merge": "^6.0.1" - }, - "bin": { - "docusaurus": "bin/docusaurus.mjs" - }, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "@mdx-js/react": "^3.0.0", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/cssnano-preset": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.8.1.tgz", - "integrity": "sha512-G7WyR2N6SpyUotqhGznERBK+x84uyhfMQM2MmDLs88bw4Flom6TY46HzkRkSEzaP9j80MbTN8naiL1fR17WQug==", - "license": "MIT", - "dependencies": { - "cssnano-preset-advanced": "^6.1.2", - "postcss": "^8.5.4", - "postcss-sort-media-queries": "^5.2.0", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=18.0" - } - }, - "node_modules/@docusaurus/logger": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.8.1.tgz", - "integrity": "sha512-2wjeGDhKcExEmjX8k1N/MRDiPKXGF2Pg+df/bDDPnnJWHXnVEZxXj80d6jcxp1Gpnksl0hF8t/ZQw9elqj2+ww==", - "license": "MIT", - "dependencies": { - "chalk": "^4.1.2", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=18.0" - } - }, - "node_modules/@docusaurus/mdx-loader": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.8.1.tgz", - "integrity": "sha512-DZRhagSFRcEq1cUtBMo4TKxSNo/W6/s44yhr8X+eoXqCLycFQUylebOMPseHi5tc4fkGJqwqpWJLz6JStU9L4w==", - "license": "MIT", - "dependencies": { - "@docusaurus/logger": "3.8.1", - "@docusaurus/utils": "3.8.1", - "@docusaurus/utils-validation": "3.8.1", - "@mdx-js/mdx": "^3.0.0", - "@slorber/remark-comment": "^1.0.0", - "escape-html": "^1.0.3", - "estree-util-value-to-estree": "^3.0.1", - "file-loader": "^6.2.0", - "fs-extra": "^11.1.1", - "image-size": "^2.0.2", - "mdast-util-mdx": "^3.0.0", - "mdast-util-to-string": "^4.0.0", - "rehype-raw": "^7.0.0", - "remark-directive": "^3.0.0", - "remark-emoji": "^4.0.0", - "remark-frontmatter": "^5.0.0", - "remark-gfm": "^4.0.0", - "stringify-object": "^3.3.0", - "tslib": "^2.6.0", - "unified": "^11.0.3", - "unist-util-visit": "^5.0.0", - "url-loader": "^4.1.1", - "vfile": "^6.0.1", - "webpack": "^5.88.1" - }, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/module-type-aliases": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.8.1.tgz", - "integrity": "sha512-6xhvAJiXzsaq3JdosS7wbRt/PwEPWHr9eM4YNYqVlbgG1hSK3uQDXTVvQktasp3VO6BmfYWPozueLWuj4gB+vg==", - "license": "MIT", - "dependencies": { - "@docusaurus/types": "3.8.1", - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router-config": "*", - "@types/react-router-dom": "*", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "react-loadable": "npm:@docusaurus/react-loadable@6.0.0" - }, - "peerDependencies": { - "react": "*", - "react-dom": "*" - } - }, - "node_modules/@docusaurus/plugin-content-blog": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.8.1.tgz", - "integrity": "sha512-vNTpMmlvNP9n3hGEcgPaXyvTljanAKIUkuG9URQ1DeuDup0OR7Ltvoc8yrmH+iMZJbcQGhUJF+WjHLwuk8HSdw==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.8.1", - "@docusaurus/logger": "3.8.1", - "@docusaurus/mdx-loader": "3.8.1", - "@docusaurus/theme-common": "3.8.1", - "@docusaurus/types": "3.8.1", - "@docusaurus/utils": "3.8.1", - "@docusaurus/utils-common": "3.8.1", - "@docusaurus/utils-validation": "3.8.1", - "cheerio": "1.0.0-rc.12", - "feed": "^4.2.2", - "fs-extra": "^11.1.1", - "lodash": "^4.17.21", - "schema-dts": "^1.1.2", - "srcset": "^4.0.0", - "tslib": "^2.6.0", - "unist-util-visit": "^5.0.0", - "utility-types": "^3.10.0", - "webpack": "^5.88.1" - }, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "@docusaurus/plugin-content-docs": "*", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/plugin-content-docs": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.8.1.tgz", - "integrity": "sha512-oByRkSZzeGNQByCMaX+kif5Nl2vmtj2IHQI2fWjCfCootsdKZDPFLonhIp5s3IGJO7PLUfe0POyw0Xh/RrGXJA==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.8.1", - "@docusaurus/logger": "3.8.1", - "@docusaurus/mdx-loader": "3.8.1", - "@docusaurus/module-type-aliases": "3.8.1", - "@docusaurus/theme-common": "3.8.1", - "@docusaurus/types": "3.8.1", - "@docusaurus/utils": "3.8.1", - "@docusaurus/utils-common": "3.8.1", - "@docusaurus/utils-validation": "3.8.1", - "@types/react-router-config": "^5.0.7", - "combine-promises": "^1.1.0", - "fs-extra": "^11.1.1", - "js-yaml": "^4.1.0", - "lodash": "^4.17.21", - "schema-dts": "^1.1.2", - "tslib": "^2.6.0", - "utility-types": "^3.10.0", - "webpack": "^5.88.1" - }, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/plugin-content-pages": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.8.1.tgz", - "integrity": "sha512-a+V6MS2cIu37E/m7nDJn3dcxpvXb6TvgdNI22vJX8iUTp8eoMoPa0VArEbWvCxMY/xdC26WzNv4wZ6y0iIni/w==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.8.1", - "@docusaurus/mdx-loader": "3.8.1", - "@docusaurus/types": "3.8.1", - "@docusaurus/utils": "3.8.1", - "@docusaurus/utils-validation": "3.8.1", - "fs-extra": "^11.1.1", - "tslib": "^2.6.0", - "webpack": "^5.88.1" - }, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/plugin-css-cascade-layers": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-css-cascade-layers/-/plugin-css-cascade-layers-3.8.1.tgz", - "integrity": "sha512-VQ47xRxfNKjHS5ItzaVXpxeTm7/wJLFMOPo1BkmoMG4Cuz4nuI+Hs62+RMk1OqVog68Swz66xVPK8g9XTrBKRw==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.8.1", - "@docusaurus/types": "3.8.1", - "@docusaurus/utils": "3.8.1", - "@docusaurus/utils-validation": "3.8.1", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=18.0" - } - }, - "node_modules/@docusaurus/plugin-debug": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.8.1.tgz", - "integrity": "sha512-nT3lN7TV5bi5hKMB7FK8gCffFTBSsBsAfV84/v293qAmnHOyg1nr9okEw8AiwcO3bl9vije5nsUvP0aRl2lpaw==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.8.1", - "@docusaurus/types": "3.8.1", - "@docusaurus/utils": "3.8.1", - "fs-extra": "^11.1.1", - "react-json-view-lite": "^2.3.0", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/plugin-google-analytics": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.8.1.tgz", - "integrity": "sha512-Hrb/PurOJsmwHAsfMDH6oVpahkEGsx7F8CWMjyP/dw1qjqmdS9rcV1nYCGlM8nOtD3Wk/eaThzUB5TSZsGz+7Q==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.8.1", - "@docusaurus/types": "3.8.1", - "@docusaurus/utils-validation": "3.8.1", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/plugin-google-gtag": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.8.1.tgz", - "integrity": "sha512-tKE8j1cEZCh8KZa4aa80zpSTxsC2/ZYqjx6AAfd8uA8VHZVw79+7OTEP2PoWi0uL5/1Is0LF5Vwxd+1fz5HlKg==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.8.1", - "@docusaurus/types": "3.8.1", - "@docusaurus/utils-validation": "3.8.1", - "@types/gtag.js": "^0.0.12", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/plugin-google-tag-manager": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.8.1.tgz", - "integrity": "sha512-iqe3XKITBquZq+6UAXdb1vI0fPY5iIOitVjPQ581R1ZKpHr0qe+V6gVOrrcOHixPDD/BUKdYwkxFjpNiEN+vBw==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.8.1", - "@docusaurus/types": "3.8.1", - "@docusaurus/utils-validation": "3.8.1", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/plugin-sitemap": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.8.1.tgz", - "integrity": "sha512-+9YV/7VLbGTq8qNkjiugIelmfUEVkTyLe6X8bWq7K5qPvGXAjno27QAfFq63mYfFFbJc7z+pudL63acprbqGzw==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.8.1", - "@docusaurus/logger": "3.8.1", - "@docusaurus/types": "3.8.1", - "@docusaurus/utils": "3.8.1", - "@docusaurus/utils-common": "3.8.1", - "@docusaurus/utils-validation": "3.8.1", - "fs-extra": "^11.1.1", - "sitemap": "^7.1.1", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/plugin-svgr": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-svgr/-/plugin-svgr-3.8.1.tgz", - "integrity": "sha512-rW0LWMDsdlsgowVwqiMb/7tANDodpy1wWPwCcamvhY7OECReN3feoFwLjd/U4tKjNY3encj0AJSTxJA+Fpe+Gw==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.8.1", - "@docusaurus/types": "3.8.1", - "@docusaurus/utils": "3.8.1", - "@docusaurus/utils-validation": "3.8.1", - "@svgr/core": "8.1.0", - "@svgr/webpack": "^8.1.0", - "tslib": "^2.6.0", - "webpack": "^5.88.1" - }, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/preset-classic": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.8.1.tgz", - "integrity": "sha512-yJSjYNHXD8POMGc2mKQuj3ApPrN+eG0rO1UPgSx7jySpYU+n4WjBikbrA2ue5ad9A7aouEtMWUoiSRXTH/g7KQ==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.8.1", - "@docusaurus/plugin-content-blog": "3.8.1", - "@docusaurus/plugin-content-docs": "3.8.1", - "@docusaurus/plugin-content-pages": "3.8.1", - "@docusaurus/plugin-css-cascade-layers": "3.8.1", - "@docusaurus/plugin-debug": "3.8.1", - "@docusaurus/plugin-google-analytics": "3.8.1", - "@docusaurus/plugin-google-gtag": "3.8.1", - "@docusaurus/plugin-google-tag-manager": "3.8.1", - "@docusaurus/plugin-sitemap": "3.8.1", - "@docusaurus/plugin-svgr": "3.8.1", - "@docusaurus/theme-classic": "3.8.1", - "@docusaurus/theme-common": "3.8.1", - "@docusaurus/theme-search-algolia": "3.8.1", - "@docusaurus/types": "3.8.1" - }, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/theme-classic": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.8.1.tgz", - "integrity": "sha512-bqDUCNqXeYypMCsE1VcTXSI1QuO4KXfx8Cvl6rYfY0bhhqN6d2WZlRkyLg/p6pm+DzvanqHOyYlqdPyP0iz+iw==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.8.1", - "@docusaurus/logger": "3.8.1", - "@docusaurus/mdx-loader": "3.8.1", - "@docusaurus/module-type-aliases": "3.8.1", - "@docusaurus/plugin-content-blog": "3.8.1", - "@docusaurus/plugin-content-docs": "3.8.1", - "@docusaurus/plugin-content-pages": "3.8.1", - "@docusaurus/theme-common": "3.8.1", - "@docusaurus/theme-translations": "3.8.1", - "@docusaurus/types": "3.8.1", - "@docusaurus/utils": "3.8.1", - "@docusaurus/utils-common": "3.8.1", - "@docusaurus/utils-validation": "3.8.1", - "@mdx-js/react": "^3.0.0", - "clsx": "^2.0.0", - "copy-text-to-clipboard": "^3.2.0", - "infima": "0.2.0-alpha.45", - "lodash": "^4.17.21", - "nprogress": "^0.2.0", - "postcss": "^8.5.4", - "prism-react-renderer": "^2.3.0", - "prismjs": "^1.29.0", - "react-router-dom": "^5.3.4", - "rtlcss": "^4.1.0", - "tslib": "^2.6.0", - "utility-types": "^3.10.0" - }, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/theme-common": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.8.1.tgz", - "integrity": "sha512-UswMOyTnPEVRvN5Qzbo+l8k4xrd5fTFu2VPPfD6FcW/6qUtVLmJTQCktbAL3KJ0BVXGm5aJXz/ZrzqFuZERGPw==", - "license": "MIT", - "dependencies": { - "@docusaurus/mdx-loader": "3.8.1", - "@docusaurus/module-type-aliases": "3.8.1", - "@docusaurus/utils": "3.8.1", - "@docusaurus/utils-common": "3.8.1", - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router-config": "*", - "clsx": "^2.0.0", - "parse-numeric-range": "^1.3.0", - "prism-react-renderer": "^2.3.0", - "tslib": "^2.6.0", - "utility-types": "^3.10.0" - }, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "@docusaurus/plugin-content-docs": "*", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/theme-search-algolia": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.8.1.tgz", - "integrity": "sha512-NBFH5rZVQRAQM087aYSRKQ9yGEK9eHd+xOxQjqNpxMiV85OhJDD4ZGz6YJIod26Fbooy54UWVdzNU0TFeUUUzQ==", - "license": "MIT", - "dependencies": { - "@docsearch/react": "^3.9.0", - "@docusaurus/core": "3.8.1", - "@docusaurus/logger": "3.8.1", - "@docusaurus/plugin-content-docs": "3.8.1", - "@docusaurus/theme-common": "3.8.1", - "@docusaurus/theme-translations": "3.8.1", - "@docusaurus/utils": "3.8.1", - "@docusaurus/utils-validation": "3.8.1", - "algoliasearch": "^5.17.1", - "algoliasearch-helper": "^3.22.6", - "clsx": "^2.0.0", - "eta": "^2.2.0", - "fs-extra": "^11.1.1", - "lodash": "^4.17.21", - "tslib": "^2.6.0", - "utility-types": "^3.10.0" - }, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/theme-translations": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.8.1.tgz", - "integrity": "sha512-OTp6eebuMcf2rJt4bqnvuwmm3NVXfzfYejL+u/Y1qwKhZPrjPoKWfk1CbOP5xH5ZOPkiAsx4dHdQBRJszK3z2g==", - "license": "MIT", - "dependencies": { - "fs-extra": "^11.1.1", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=18.0" - } - }, - "node_modules/@docusaurus/types": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.8.1.tgz", - "integrity": "sha512-ZPdW5AB+pBjiVrcLuw3dOS6BFlrG0XkS2lDGsj8TizcnREQg3J8cjsgfDviszOk4CweNfwo1AEELJkYaMUuOPg==", - "license": "MIT", - "dependencies": { - "@mdx-js/mdx": "^3.0.0", - "@types/history": "^4.7.11", - "@types/react": "*", - "commander": "^5.1.0", - "joi": "^17.9.2", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "utility-types": "^3.10.0", - "webpack": "^5.95.0", - "webpack-merge": "^5.9.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/types/node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@docusaurus/utils": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.8.1.tgz", - "integrity": "sha512-P1ml0nvOmEFdmu0smSXOqTS1sxU5tqvnc0dA4MTKV39kye+bhQnjkIKEE18fNOvxjyB86k8esoCIFM3x4RykOQ==", - "license": "MIT", - "dependencies": { - "@docusaurus/logger": "3.8.1", - "@docusaurus/types": "3.8.1", - "@docusaurus/utils-common": "3.8.1", - "escape-string-regexp": "^4.0.0", - "execa": "5.1.1", - "file-loader": "^6.2.0", - "fs-extra": "^11.1.1", - "github-slugger": "^1.5.0", - "globby": "^11.1.0", - "gray-matter": "^4.0.3", - "jiti": "^1.20.0", - "js-yaml": "^4.1.0", - "lodash": "^4.17.21", - "micromatch": "^4.0.5", - "p-queue": "^6.6.2", - "prompts": "^2.4.2", - "resolve-pathname": "^3.0.0", - "tslib": "^2.6.0", - "url-loader": "^4.1.1", - "utility-types": "^3.10.0", - "webpack": "^5.88.1" - }, - "engines": { - "node": ">=18.0" - } - }, - "node_modules/@docusaurus/utils-common": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.8.1.tgz", - "integrity": "sha512-zTZiDlvpvoJIrQEEd71c154DkcriBecm4z94OzEE9kz7ikS3J+iSlABhFXM45mZ0eN5pVqqr7cs60+ZlYLewtg==", - "license": "MIT", - "dependencies": { - "@docusaurus/types": "3.8.1", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=18.0" - } - }, - "node_modules/@docusaurus/utils-validation": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.8.1.tgz", - "integrity": "sha512-gs5bXIccxzEbyVecvxg6upTwaUbfa0KMmTj7HhHzc016AGyxH2o73k1/aOD0IFrdCsfJNt37MqNI47s2MgRZMA==", - "license": "MIT", - "dependencies": { - "@docusaurus/logger": "3.8.1", - "@docusaurus/utils": "3.8.1", - "@docusaurus/utils-common": "3.8.1", - "fs-extra": "^11.2.0", - "joi": "^17.9.2", - "js-yaml": "^4.1.0", - "lodash": "^4.17.21", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=18.0" - } - }, - "node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", - "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", - "license": "MIT" - }, - "node_modules/@mdx-js/mdx": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.0.tgz", - "integrity": "sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdx": "^2.0.0", - "collapse-white-space": "^2.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "estree-util-scope": "^1.0.0", - "estree-walker": "^3.0.0", - "hast-util-to-jsx-runtime": "^2.0.0", - "markdown-extensions": "^2.0.0", - "recma-build-jsx": "^1.0.0", - "recma-jsx": "^1.0.0", - "recma-stringify": "^1.0.0", - "rehype-recma": "^1.0.0", - "remark-mdx": "^3.0.0", - "remark-parse": "^11.0.0", - "remark-rehype": "^11.0.0", - "source-map": "^0.7.0", - "unified": "^11.0.0", - "unist-util-position-from-estree": "^2.0.0", - "unist-util-stringify-position": "^4.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/@mdx-js/react": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.0.tgz", - "integrity": "sha512-QjHtSaoameoalGnKDT3FoIl4+9RwyTmo9ZJGBdLOks/YOiWHoRDI3PUwEzOE7kEmGcV3AFcp9K6dYu9rEuKLAQ==", - "license": "MIT", - "dependencies": { - "@types/mdx": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - }, - "peerDependencies": { - "@types/react": ">=16", - "react": ">=16" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@pnpm/config.env-replace": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", - "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", - "license": "MIT", - "engines": { - "node": ">=12.22.0" - } - }, - "node_modules/@pnpm/network.ca-file": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", - "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", - "license": "MIT", - "dependencies": { - "graceful-fs": "4.2.10" - }, - "engines": { - "node": ">=12.22.0" - } - }, - "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "license": "ISC" - }, - "node_modules/@pnpm/npm-conf": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-2.3.1.tgz", - "integrity": "sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==", - "license": "MIT", - "dependencies": { - "@pnpm/config.env-replace": "^1.1.0", - "@pnpm/network.ca-file": "^1.0.1", - "config-chain": "^1.1.11" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@polka/url": { - "version": "1.0.0-next.29", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", - "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", - "license": "MIT" - }, - "node_modules/@sideway/address": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", - "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@sideway/formula": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", - "license": "BSD-3-Clause" - }, - "node_modules/@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "license": "MIT" - }, - "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/@slorber/remark-comment": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@slorber/remark-comment/-/remark-comment-1.0.0.tgz", - "integrity": "sha512-RCE24n7jsOj1M0UPvIQCHTe7fI0sFL4S2nwKVWwHyVr/wI/H8GosgsJGyhnsZoGFnD/P2hLf1mSbrrgSLN93NA==", - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.1.0", - "micromark-util-symbol": "^1.0.1" - } - }, - "node_modules/@svgr/babel-plugin-add-jsx-attribute": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", - "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", - "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", - "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", - "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-svg-dynamic-title": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", - "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-svg-em-dimensions": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", - "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-transform-react-native-svg": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", - "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-transform-svg-component": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", - "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-preset": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", - "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", - "license": "MIT", - "dependencies": { - "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", - "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", - "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", - "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", - "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", - "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", - "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", - "@svgr/babel-plugin-transform-svg-component": "8.0.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/core": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", - "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.21.3", - "@svgr/babel-preset": "8.1.0", - "camelcase": "^6.2.0", - "cosmiconfig": "^8.1.3", - "snake-case": "^3.0.4" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/hast-util-to-babel-ast": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", - "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.21.3", - "entities": "^4.4.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/plugin-jsx": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", - "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.21.3", - "@svgr/babel-preset": "8.1.0", - "@svgr/hast-util-to-babel-ast": "8.0.0", - "svg-parser": "^2.0.4" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@svgr/core": "*" - } - }, - "node_modules/@svgr/plugin-svgo": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz", - "integrity": "sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==", - "license": "MIT", - "dependencies": { - "cosmiconfig": "^8.1.3", - "deepmerge": "^4.3.1", - "svgo": "^3.0.2" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@svgr/core": "*" - } - }, - "node_modules/@svgr/webpack": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-8.1.0.tgz", - "integrity": "sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.21.3", - "@babel/plugin-transform-react-constant-elements": "^7.21.3", - "@babel/preset-env": "^7.20.2", - "@babel/preset-react": "^7.18.6", - "@babel/preset-typescript": "^7.21.0", - "@svgr/core": "8.1.0", - "@svgr/plugin-jsx": "8.1.0", - "@svgr/plugin-svgo": "8.1.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@szmarczak/http-timer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", - "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", - "license": "MIT", - "dependencies": { - "defer-to-connect": "^2.0.1" - }, - "engines": { - "node": ">=14.16" - } - }, - "node_modules/@trysound/sax": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", - "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", - "license": "ISC", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/@types/body-parser": { - "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", - "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/bonjour": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", - "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect-history-api-fallback": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", - "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", - "license": "MIT", - "dependencies": { - "@types/express-serve-static-core": "*", - "@types/node": "*" - } - }, - "node_modules/@types/debug": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", - "license": "MIT", - "dependencies": { - "@types/ms": "*" - } - }, - "node_modules/@types/eslint": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", - "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", - "license": "MIT", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "license": "MIT", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT" - }, - "node_modules/@types/estree-jsx": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", - "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", - "license": "MIT", - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/@types/express": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", - "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", - "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/express/node_modules/@types/express-serve-static-core": { - "version": "4.19.6", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", - "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/gtag.js": { - "version": "0.0.12", - "resolved": "https://registry.npmjs.org/@types/gtag.js/-/gtag.js-0.0.12.tgz", - "integrity": "sha512-YQV9bUsemkzG81Ea295/nF/5GijnD2Af7QhEofh7xu+kvCN6RdodgNwwGWXB5GMI3NoyvQo0odNctoH/qLMIpg==", - "license": "MIT" - }, - "node_modules/@types/hast": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/history": { - "version": "4.7.11", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", - "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", - "license": "MIT" - }, - "node_modules/@types/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", - "license": "MIT" - }, - "node_modules/@types/http-cache-semantics": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", - "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", - "license": "MIT" - }, - "node_modules/@types/http-errors": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "license": "MIT" - }, - "node_modules/@types/http-proxy": { - "version": "1.17.16", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.16.tgz", - "integrity": "sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "license": "MIT" - }, - "node_modules/@types/mdast": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", - "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/mdx": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", - "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==", - "license": "MIT" - }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "license": "MIT" - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "24.0.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.1.tgz", - "integrity": "sha512-MX4Zioh39chHlDJbKmEgydJDS3tspMP/lnQC67G3SWsTnb9NeYVWOjkxpOSy4oMfPs4StcWHwBrvUb4ybfnuaw==", - "license": "MIT", - "dependencies": { - "undici-types": "~7.8.0" - } - }, - "node_modules/@types/node-forge": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", - "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/prismjs": { - "version": "1.26.5", - "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.5.tgz", - "integrity": "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==", - "license": "MIT" - }, - "node_modules/@types/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", - "license": "MIT" - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "19.1.8", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", - "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", - "license": "MIT", - "dependencies": { - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-router": { - "version": "5.1.20", - "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", - "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", - "license": "MIT", - "dependencies": { - "@types/history": "^4.7.11", - "@types/react": "*" - } - }, - "node_modules/@types/react-router-config": { - "version": "5.0.11", - "resolved": "https://registry.npmjs.org/@types/react-router-config/-/react-router-config-5.0.11.tgz", - "integrity": "sha512-WmSAg7WgqW7m4x8Mt4N6ZyKz0BubSj/2tVUMsAHp+Yd2AMwcSbeFq9WympT19p5heCFmF97R9eD5uUR/t4HEqw==", - "license": "MIT", - "dependencies": { - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router": "^5.1.0" - } - }, - "node_modules/@types/react-router-dom": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", - "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", - "license": "MIT", - "dependencies": { - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router": "*" - } - }, - "node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", - "license": "MIT" - }, - "node_modules/@types/sax": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", - "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/send": { - "version": "0.17.5", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", - "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", - "license": "MIT", - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/serve-index": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", - "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", - "license": "MIT", - "dependencies": { - "@types/express": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", - "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "*" - } - }, - "node_modules/@types/sockjs": { - "version": "0.3.36", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", - "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/unist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", - "license": "MIT" - }, - "node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "license": "MIT" - }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "license": "ISC" - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", - "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/helper-numbers": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", - "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", - "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", - "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", - "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.13.2", - "@webassemblyjs/helper-api-error": "1.13.2", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", - "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", - "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/wasm-gen": "1.14.1" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", - "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", - "license": "MIT", - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", - "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", - "license": "Apache-2.0", - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", - "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", - "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/helper-wasm-section": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-opt": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1", - "@webassemblyjs/wast-printer": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", - "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", - "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", - "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-api-error": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", - "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "license": "BSD-3-Clause" - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "license": "Apache-2.0" - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/accepts/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/accepts/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/accepts/node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/address": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", - "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "license": "MIT", - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/algoliasearch": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.27.0.tgz", - "integrity": "sha512-2PvAgvxxJzA3+dB+ERfS2JPdvUsxNf89Cc2GF5iCcFupTULOwmbfinvqrC4Qj9nHJJDNf494NqEN/1f9177ZTQ==", - "license": "MIT", - "dependencies": { - "@algolia/client-abtesting": "5.27.0", - "@algolia/client-analytics": "5.27.0", - "@algolia/client-common": "5.27.0", - "@algolia/client-insights": "5.27.0", - "@algolia/client-personalization": "5.27.0", - "@algolia/client-query-suggestions": "5.27.0", - "@algolia/client-search": "5.27.0", - "@algolia/ingestion": "1.27.0", - "@algolia/monitoring": "1.27.0", - "@algolia/recommend": "5.27.0", - "@algolia/requester-browser-xhr": "5.27.0", - "@algolia/requester-fetch": "5.27.0", - "@algolia/requester-node-http": "5.27.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/algoliasearch-helper": { - "version": "3.25.0", - "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.25.0.tgz", - "integrity": "sha512-vQoK43U6HXA9/euCqLjvyNdM4G2Fiu/VFp4ae0Gau9sZeIKBPvUPnXfLYAe65Bg7PFuw03coeu5K6lTPSXRObw==", - "license": "MIT", - "dependencies": { - "@algolia/events": "^4.0.1" - }, - "peerDependencies": { - "algoliasearch": ">= 3.1 < 6" - } - }, - "node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "license": "ISC", - "dependencies": { - "string-width": "^4.1.0" - } - }, - "node_modules/ansi-align/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/ansi-align/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-html-community": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", - "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", - "engines": [ - "node >= 0.8.0" - ], - "license": "Apache-2.0", - "bin": { - "ansi-html": "bin/ansi-html" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "license": "MIT" - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/astring": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", - "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", - "license": "MIT", - "bin": { - "astring": "bin/astring" - } - }, - "node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/babel-loader": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", - "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", - "license": "MIT", - "dependencies": { - "find-cache-dir": "^4.0.0", - "schema-utils": "^4.0.0" - }, - "engines": { - "node": ">= 14.15.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0", - "webpack": ">=5" - } - }, - "node_modules/babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "license": "MIT", - "dependencies": { - "object.assign": "^4.1.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.13", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.13.tgz", - "integrity": "sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==", - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.4", - "semver": "^6.3.1" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz", - "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.3", - "core-js-compat": "^3.40.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.4.tgz", - "integrity": "sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw==", - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.4" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/bail": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", - "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" - }, - "node_modules/batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", - "license": "MIT" - }, - "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7numerixpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/bonjour-service": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", - "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "multicast-dns": "^7.2.5" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "license": "ISC" - }, - "node_modules/boxen": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-6.2.1.tgz", - "integrity": "sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw==", - "license": "MIT", - "dependencies": { - "ansi-align": "^3.0.1", - "camelcase": "^6.2.0", - "chalk": "^4.1.2", - "cli-boxes": "^3.0.0", - "string-width": "^5.0.1", - "type-fest": "^2.5.0", - "widest-line": "^4.0.1", - "wrap-ansi": "^8.0.1" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.25.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", - "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001718", - "electron-to-chromium": "^1.5.160", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "license": "MIT" - }, - "node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/cacheable-lookup": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", - "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", - "license": "MIT", - "engines": { - "node": ">=14.16" - } - }, - "node_modules/cacheable-request": { - "version": "10.2.14", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", - "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", - "license": "MIT", - "dependencies": { - "@types/http-cache-semantics": "^4.0.2", - "get-stream": "^6.0.1", - "http-cache-semantics": "^4.1.1", - "keyv": "^4.5.3", - "mimic-response": "^4.0.0", - "normalize-url": "^8.0.0", - "responselike": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - } - }, - "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "license": "MIT", - "dependencies": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001723", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001723.tgz", - "integrity": "sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/ccount": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/character-entities": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", - "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-html4": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", - "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-legacy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", - "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-reference-invalid": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", - "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/cheerio": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", - "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", - "license": "MIT", - "dependencies": { - "cheerio-select": "^2.1.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "htmlparser2": "^8.0.1", - "parse5": "^7.0.0", - "parse5-htmlparser2-tree-adapter": "^7.0.0" - }, - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/cheeriojs/cheerio?sponsor=1" - } - }, - "node_modules/cheerio-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", - "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-select": "^5.1.0", - "css-what": "^6.1.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", - "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", - "license": "MIT", - "engines": { - "node": ">=6.0" - } - }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/clean-css": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", - "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", - "license": "MIT", - "dependencies": { - "source-map": "~0.6.0" - }, - "engines": { - "node": ">= 10.0" - } - }, - "node_modules/clean-css/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/cli-boxes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", - "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-table3": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", - "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", - "license": "MIT", - "dependencies": { - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" - } - }, - "node_modules/cli-table3/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/cli-table3/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/collapse-white-space": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", - "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, - "node_modules/colord": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", - "license": "MIT" - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "license": "MIT" - }, - "node_modules/combine-promises": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/combine-promises/-/combine-promises-1.2.0.tgz", - "integrity": "sha512-VcQB1ziGD0NXrhKxiwyNbCDmRzs/OShMs2GqW2DlU2A/Sd0nQxE1oWDAE5O0ygSx5mgQOn9eIFh7yKPgFRVkPQ==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/comma-separated-tokens": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", - "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/common-path-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", - "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", - "license": "ISC" - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "license": "MIT", - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compressible/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz", - "integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "compressible": "~2.0.18", - "debug": "2.6.9", - "negotiator": "~0.6.4", - "on-headers": "~1.0.2", - "safe-buffer": "5.2.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7numerixpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "license": "MIT" - }, - "node_modules/config-chain": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", - "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", - "license": "MIT", - "dependencies": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "node_modules/config-chain/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" - }, - "node_modules/configstore": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-6.0.0.tgz", - "integrity": "sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA==", - "license": "BSD-2-Clause", - "dependencies": { - "dot-prop": "^6.0.1", - "graceful-fs": "^4.2.6", - "unique-string": "^3.0.0", - "write-file-atomic": "^3.0.3", - "xdg-basedir": "^5.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/yeoman/configstore?sponsor=1" - } - }, - "node_modules/connect-history-api-fallback": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", - "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/consola": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", - "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", - "license": "MIT", - "engines": { - "node": "^14.18.0 || >=16.10.0" - } - }, - "node_modules/content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "license": "MIT" - }, - "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" - }, - "node_modules/copy-text-to-clipboard": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.0.tgz", - "integrity": "sha512-RnJFp1XR/LOBDckxTib5Qjr/PMfkatD0MUCQgdpqS8MdKiNUzBjAQBEN6oUy+jW7LI93BBG3DtMB2KOOKpGs2Q==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/copy-webpack-plugin": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", - "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", - "license": "MIT", - "dependencies": { - "fast-glob": "^3.2.11", - "glob-parent": "^6.0.1", - "globby": "^13.1.1", - "normalize-path": "^3.0.0", - "schema-utils": "^4.0.0", - "serialize-javascript": "^6.0.0" - }, - "engines": { - "node": ">= 14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - } - }, - "node_modules/copy-webpack-plugin/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/copy-webpack-plugin/node_modules/globby": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", - "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", - "license": "MIT", - "dependencies": { - "dir-glob": "^3.0.1", - "fast-glob": "^3.3.0", - "ignore": "^5.2.4", - "merge2": "^1.4.1", - "slash": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/copy-webpack-plugin/node_modules/slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/core-js": { - "version": "3.43.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.43.0.tgz", - "integrity": "sha512-N6wEbTTZSYOY2rYAn85CuvWWkCK6QweMn7/4Nr3w+gDBeBhk/x4EJeY6FPo4QzDoJZxVTv8U7CMvgWk6pOHHqA==", - "hasInstallScript": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-js-compat": { - "version": "3.43.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.43.0.tgz", - "integrity": "sha512-2GML2ZsCc5LR7hZYz4AXmjQw8zuy2T//2QntwdnpuYI7jteT6GVYJL7F6C2C57R7gSYrcqVW3lAALefdbhBLDA==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.25.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-js-pure": { - "version": "3.43.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.43.0.tgz", - "integrity": "sha512-i/AgxU2+A+BbJdMxh3v7/vxi2SbFqxiFmg6VsDwYB4jkucrd1BZNA9a9gphC0fYMG5IBSgQcbQnk865VCLe7xA==", - "hasInstallScript": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "license": "MIT" - }, - "node_modules/cosmiconfig": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", - "license": "MIT", - "dependencies": { - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/crypto-random-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", - "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", - "license": "MIT", - "dependencies": { - "type-fest": "^1.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/crypto-random-string/node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/css-blank-pseudo": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-7.0.1.tgz", - "integrity": "sha512-jf+twWGDf6LDoXDUode+nc7ZlrqfaNphrBIBrcmeP3D8yw1uPaix1gCC8LUQUGQ6CycuK2opkbFFWFuq/a94ag==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/css-blank-pseudo/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/css-declaration-sorter": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz", - "integrity": "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==", - "license": "ISC", - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.0.9" - } - }, - "node_modules/css-has-pseudo": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-7.0.2.tgz", - "integrity": "sha512-nzol/h+E0bId46Kn2dQH5VElaknX2Sr0hFuB/1EomdC7j+OISt2ZzK7EHX9DZDY53WbIVAR7FYKSO2XnSf07MQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/selector-specificity": "^5.0.0", - "postcss-selector-parser": "^7.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/css-has-pseudo/node_modules/@csstools/selector-specificity": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", - "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^7.0.0" - } - }, - "node_modules/css-has-pseudo/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/css-loader": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", - "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", - "license": "MIT", - "dependencies": { - "icss-utils": "^5.1.0", - "postcss": "^8.4.33", - "postcss-modules-extract-imports": "^3.1.0", - "postcss-modules-local-by-default": "^4.0.5", - "postcss-modules-scope": "^3.2.0", - "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } - } - }, - "node_modules/css-minimizer-webpack-plugin": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz", - "integrity": "sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg==", - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "cssnano": "^6.0.1", - "jest-worker": "^29.4.3", - "postcss": "^8.4.24", - "schema-utils": "^4.0.1", - "serialize-javascript": "^6.0.1" - }, - "engines": { - "node": ">= 14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "@parcel/css": { - "optional": true - }, - "@swc/css": { - "optional": true - }, - "clean-css": { - "optional": true - }, - "csso": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "lightningcss": { - "optional": true - } - } - }, - "node_modules/css-prefers-color-scheme": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-10.0.0.tgz", - "integrity": "sha512-VCtXZAWivRglTZditUfB4StnsWr6YVZ2PRtuxQLKTNRdtAf8tpzaVPE9zXIF3VaSc7O70iK/j1+NXxyQCqdPjQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", - "license": "MIT", - "dependencies": { - "mdn-data": "2.0.30", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, - "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/cssdb": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.3.0.tgz", - "integrity": "sha512-c7bmItIg38DgGjSwDPZOYF/2o0QU/sSgkWOMyl8votOfgFuyiFKWPesmCGEsrGLxEA9uL540cp8LdaGEjUGsZQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - } - ], - "license": "MIT-0" - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cssnano": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz", - "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", - "license": "MIT", - "dependencies": { - "cssnano-preset-default": "^6.1.2", - "lilconfig": "^3.1.1" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/cssnano" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/cssnano-preset-advanced": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-6.1.2.tgz", - "integrity": "sha512-Nhao7eD8ph2DoHolEzQs5CfRpiEP0xa1HBdnFZ82kvqdmbwVBUr2r1QuQ4t1pi+D1ZpqpcO4T+wy/7RxzJ/WPQ==", - "license": "MIT", - "dependencies": { - "autoprefixer": "^10.4.19", - "browserslist": "^4.23.0", - "cssnano-preset-default": "^6.1.2", - "postcss-discard-unused": "^6.0.5", - "postcss-merge-idents": "^6.0.3", - "postcss-reduce-idents": "^6.0.3", - "postcss-zindex": "^6.0.2" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/cssnano-preset-default": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", - "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.0", - "css-declaration-sorter": "^7.2.0", - "cssnano-utils": "^4.0.2", - "postcss-calc": "^9.0.1", - "postcss-colormin": "^6.1.0", - "postcss-convert-values": "^6.1.0", - "postcss-discard-comments": "^6.0.2", - "postcss-discard-duplicates": "^6.0.3", - "postcss-discard-empty": "^6.0.3", - "postcss-discard-overridden": "^6.0.2", - "postcss-merge-longhand": "^6.0.5", - "postcss-merge-rules": "^6.1.1", - "postcss-minify-font-values": "^6.1.0", - "postcss-minify-gradients": "^6.0.3", - "postcss-minify-params": "^6.1.0", - "postcss-minify-selectors": "^6.0.4", - "postcss-normalize-charset": "^6.0.2", - "postcss-normalize-display-values": "^6.0.2", - "postcss-normalize-positions": "^6.0.2", - "postcss-normalize-repeat-style": "^6.0.2", - "postcss-normalize-string": "^6.0.2", - "postcss-normalize-timing-functions": "^6.0.2", - "postcss-normalize-unicode": "^6.1.0", - "postcss-normalize-url": "^6.0.2", - "postcss-normalize-whitespace": "^6.0.2", - "postcss-ordered-values": "^6.0.2", - "postcss-reduce-initial": "^6.1.0", - "postcss-reduce-transforms": "^6.0.2", - "postcss-svgo": "^6.0.3", - "postcss-unique-selectors": "^6.0.4" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/cssnano-utils": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", - "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", - "license": "MIT", - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/csso": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", - "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", - "license": "MIT", - "dependencies": { - "css-tree": "~2.2.0" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/csso/node_modules/css-tree": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", - "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", - "license": "MIT", - "dependencies": { - "mdn-data": "2.0.28", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/csso/node_modules/mdn-data": { - "version": "2.0.28", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", - "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", - "license": "CC0-1.0" - }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT" - }, - "node_modules/debounce": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", - "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decode-named-character-reference": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", - "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", - "license": "MIT", - "dependencies": { - "character-entities": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decompress-response/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-gateway": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", - "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", - "license": "BSD-2-Clause", - "dependencies": { - "execa": "^5.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "license": "MIT" - }, - "node_modules/detect-port": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.6.1.tgz", - "integrity": "sha512-CmnVc+Hek2egPx1PeTFVta2W78xy2K/9Rkf6cC4T59S50tVnzKj+tnx5mmx5lwvCkujZ4uRrpRSuV+IVs3f90Q==", - "license": "MIT", - "dependencies": { - "address": "^1.0.1", - "debug": "4" - }, - "bin": { - "detect": "bin/detect-port.js", - "detect-port": "bin/detect-port.js" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/devlop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", - "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", - "license": "MIT", - "dependencies": { - "dequal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dns-packet": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", - "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", - "license": "MIT", - "dependencies": { - "@leichtgewicht/ip-codec": "^2.0.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/dom-converter": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", - "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", - "license": "MIT", - "dependencies": { - "utila": "~0.4" - } - }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "license": "MIT", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/dot-prop": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", - "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", - "license": "MIT", - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/dot-prop/node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "license": "MIT" - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "license": "MIT" - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.167", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.167.tgz", - "integrity": "sha512-LxcRvnYO5ez2bMOFpbuuVuAI5QNeY1ncVytE/KXaL6ZNfzX1yPlAO0nSOyIHx2fVAuUprMqPs/TdVhUFZy7SIQ==", - "license": "ISC" - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "license": "MIT" - }, - "node_modules/emojilib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", - "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", - "license": "MIT" - }, - "node_modules/emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/emoticon": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/emoticon/-/emoticon-4.1.0.tgz", - "integrity": "sha512-VWZfnxqwNcc51hIy/sbOdEem6D+cVtpPzEEtVAFdaas30+1dgkyaOQ4sQ6Bp0tOMqWO1v+HQfYaoodOkdhK6SQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.18.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", - "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "license": "MIT" - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/esast-util-from-estree": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", - "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "devlop": "^1.0.0", - "estree-util-visit": "^2.0.0", - "unist-util-position-from-estree": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/esast-util-from-js": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", - "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "acorn": "^8.0.0", - "esast-util-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-goat": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz", - "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-util-attach-comments": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", - "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-build-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", - "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "estree-walker": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-is-identifier-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", - "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-scope": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz", - "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-to-js": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", - "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "astring": "^1.8.0", - "source-map": "^0.7.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-value-to-estree": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.4.0.tgz", - "integrity": "sha512-Zlp+gxis+gCfK12d3Srl2PdX2ybsEA8ZYy6vQGVQTNNYLEGRQQ56XB64bjemN8kxIKXP1nC9ip4Z+ILy9LGzvQ==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/remcohaszing" - } - }, - "node_modules/estree-util-visit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", - "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eta": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/eta/-/eta-2.2.0.tgz", - "integrity": "sha512-UVQ72Rqjy/ZKQalzV5dCCJP80GrmPrMxh6NlNf+erV6ObL0ZFkhCstWRawS85z3smdr3d2wXPsZEY7rDPfGd2g==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - }, - "funding": { - "url": "https://github.com/eta-dev/eta?sponsor=1" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eval": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/eval/-/eval-0.1.8.tgz", - "integrity": "sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==", - "dependencies": { - "@types/node": "*", - "require-like": ">= 0.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "license": "MIT" - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express/node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7numerixpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/express/node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" - }, - "node_modules/express/node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "license": "MIT" - }, - "node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "license": "MIT", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", - "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fault": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz", - "integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==", - "license": "MIT", - "dependencies": { - "format": "^0.2.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "license": "Apache-2.0", - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/feed": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/feed/-/feed-4.2.2.tgz", - "integrity": "sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==", - "license": "MIT", - "dependencies": { - "xml-js": "^1.6.11" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/figures/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/file-loader": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", - "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", - "license": "MIT", - "dependencies": { - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/file-loader/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/file-loader/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "license": "MIT", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/file-loader/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "license": "MIT" - }, - "node_modules/file-loader/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7numerixpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/find-cache-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", - "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", - "license": "MIT", - "dependencies": { - "common-path-prefix": "^3.0.0", - "pkg-dir": "^7.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/find-up": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", - "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", - "license": "MIT", - "dependencies": { - "locate-path": "^7.1.0", - "path-exists": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data-encoder": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", - "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", - "license": "MIT", - "engines": { - "node": ">= 14.17" - } - }, - "node_modules/format": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", - "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs-extra": { - "version": "11.3.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", - "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/fs-monkey": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz", - "integrity": "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==", - "license": "Unlicense" - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-own-enumerable-property-symbols": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", - "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", - "license": "ISC" - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/github-slugger": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.5.0.tgz", - "integrity": "sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==", - "license": "ISC" - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "license": "BSD-2-Clause" - }, - "node_modules/global-dirs": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", - "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", - "license": "MIT", - "dependencies": { - "ini": "2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/got": { - "version": "12.6.1", - "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", - "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", - "license": "MIT", - "dependencies": { - "@sindresorhus/is": "^5.2.0", - "@szmarczak/http-timer": "^5.0.1", - "cacheable-lookup": "^7.0.0", - "cacheable-request": "^10.2.8", - "decompress-response": "^6.0.0", - "form-data-encoder": "^2.1.2", - "get-stream": "^6.0.1", - "http2-wrapper": "^2.1.10", - "lowercase-keys": "^3.0.0", - "p-cancelable": "^3.0.0", - "responselike": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, - "node_modules/got/node_modules/@sindresorhus/is": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", - "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/gray-matter": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", - "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", - "license": "MIT", - "dependencies": { - "js-yaml": "^3.13.1", - "kind-of": "^6.0.2", - "section-matter": "^1.0.0", - "strip-bom-string": "^1.0.0" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/gray-matter/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/gray-matter/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/gzip-size": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", - "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", - "license": "MIT", - "dependencies": { - "duplexer": "^0.1.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/handle-thing": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", - "license": "MIT" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-yarn": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-3.0.0.tgz", - "integrity": "sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hast-util-from-parse5": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", - "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "devlop": "^1.0.0", - "hastscript": "^9.0.0", - "property-information": "^7.0.0", - "vfile": "^6.0.0", - "vfile-location": "^5.0.0", - "web-namespaces": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-parse-selector": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", - "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-raw": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", - "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "@ungap/structured-clone": "^1.0.0", - "hast-util-from-parse5": "^8.0.0", - "hast-util-to-parse5": "^8.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "parse5": "^7.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-estree": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz", - "integrity": "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "estree-util-attach-comments": "^3.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "mdast-util-mdx-expression": "^2.0.0", - "mdast-util-mdx-jsx": "^3.0.0", - "mdast-util-mdxjs-esm": "^2.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "style-to-js": "^1.0.0", - "unist-util-position": "^5.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-jsx-runtime": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", - "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "mdast-util-mdx-expression": "^2.0.0", - "mdast-util-mdx-jsx": "^3.0.0", - "mdast-util-mdxjs-esm": "^2.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "style-to-js": "^1.0.0", - "unist-util-position": "^5.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-parse5": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", - "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-parse5/node_modules/property-information": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", - "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/hast-util-whitespace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", - "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hastscript": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", - "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-parse-selector": "^4.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, - "node_modules/history": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", - "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.1.2", - "loose-envify": "^1.2.0", - "resolve-pathname": "^3.0.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0", - "value-equal": "^1.0.1" - } - }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "license": "BSD-3-Clause", - "dependencies": { - "react-is": "^16.7.0" - } - }, - "node_modules/hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - } - }, - "node_modules/hpack.js/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" - }, - "node_modules/hpack.js/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/hpack.js/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/hpack.js/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/html-entities": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", - "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/mdevils" - }, - { - "type": "patreon", - "url": "https://patreon.com/mdevils" - } - ], - "license": "MIT" - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "license": "MIT" - }, - "node_modules/html-minifier-terser": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz", - "integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==", - "license": "MIT", - "dependencies": { - "camel-case": "^4.1.2", - "clean-css": "~5.3.2", - "commander": "^10.0.0", - "entities": "^4.4.0", - "param-case": "^3.0.4", - "relateurl": "^0.2.7", - "terser": "^5.15.1" - }, - "bin": { - "html-minifier-terser": "cli.js" - }, - "engines": { - "node": "^14.13.1 || >=16.0.0" - } - }, - "node_modules/html-minifier-terser/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/html-tags": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", - "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/html-void-elements": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", - "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/html-webpack-plugin": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.3.tgz", - "integrity": "sha512-QSf1yjtSAsmf7rYBV7XX86uua4W/vkhIt0xNXKbsi2foEeW7vjJQz4bhnpL3xH+l1ryl1680uNv968Z+X6jSYg==", - "license": "MIT", - "dependencies": { - "@types/html-minifier-terser": "^6.0.0", - "html-minifier-terser": "^6.0.2", - "lodash": "^4.17.21", - "pretty-error": "^4.0.0", - "tapable": "^2.0.0" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/html-webpack-plugin" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "webpack": "^5.20.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } - } - }, - "node_modules/html-webpack-plugin/node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/html-webpack-plugin/node_modules/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", - "license": "MIT", - "dependencies": { - "camel-case": "^4.1.2", - "clean-css": "^5.2.2", - "commander": "^8.3.0", - "he": "^1.2.0", - "param-case": "^3.0.4", - "relateurl": "^0.2.7", - "terser": "^5.10.0" - }, - "bin": { - "html-minifier-terser": "cli.js" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/htmlparser2": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", - "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "entities": "^4.4.0" - } - }, - "node_modules/http-cache-semantics": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", - "license": "BSD-2-Clause" - }, - "node_modules/http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", - "license": "MIT" - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-parser-js": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", - "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", - "license": "MIT" - }, - "node_modules/http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "license": "MIT", - "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/http-proxy-middleware": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", - "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", - "license": "MIT", - "dependencies": { - "@types/http-proxy": "^1.17.8", - "http-proxy": "^1.18.1", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "@types/express": "^4.17.13" - }, - "peerDependenciesMeta": { - "@types/express": { - "optional": true - } - } - }, - "node_modules/http-proxy-middleware/node_modules/is-plain-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", - "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/http2-wrapper": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", - "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", - "license": "MIT", - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.2.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/icss-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "license": "ISC", - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/image-size": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-2.0.2.tgz", - "integrity": "sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==", - "license": "MIT", - "bin": { - "image-size": "bin/image-size.js" - }, - "engines": { - "node": ">=16.x" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-lazy": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", - "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/infima": { - "version": "0.2.0-alpha.45", - "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.45.tgz", - "integrity": "sha512-uyH0zfr1erU1OohLk0fT4Rrb94AOhguWNOcD9uGrSpRvNB+6gZXUoJX5J0NtvzBO10YZ9PgvA4NFgt+fYg8ojw==", - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/inline-style-parser": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", - "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", - "license": "MIT" - }, - "node_modules/invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, - "node_modules/ipaddr.js": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", - "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/is-alphabetical": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", - "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-alphanumerical": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", - "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", - "license": "MIT", - "dependencies": { - "is-alphabetical": "^2.0.0", - "is-decimal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "license": "MIT" - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", - "license": "MIT", - "dependencies": { - "ci-info": "^3.2.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-decimal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", - "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-hexadecimal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", - "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "license": "MIT", - "dependencies": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-npm": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-6.0.0.tgz", - "integrity": "sha512-JEjxbSmtPSt1c8XTkVrlujcXdKV1/tvuQ7GwKcAlyiVLeYFQ2VHat8xfrDJsIkhCdF/tZ7CiIR3sy141c6+gPQ==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "license": "MIT" - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-yarn-global": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.4.1.tgz", - "integrity": "sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ==", - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/jiti": { - "version": "1.21.7", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", - "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", - "license": "MIT", - "bin": { - "jiti": "bin/jiti.js" - } - }, - "node_modules/joi": { - "version": "17.13.3", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", - "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.3.0", - "@hapi/topo": "^5.1.0", - "@sideway/address": "^4.1.5", - "@sideway/formula": "^3.0.1", - "@sideway/pinpoint": "^2.0.0" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "license": "MIT" - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/latest-version": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-7.0.0.tgz", - "integrity": "sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==", - "license": "MIT", - "dependencies": { - "package-json": "^8.1.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/launch-editor": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.10.0.tgz", - "integrity": "sha512-D7dBRJo/qcGX9xlvt/6wUYzQxjh5G1RvZPgPv8vi4KRU99DVQL/oW7tnVOCCTm2HGeo3C5HvGE5Yrh6UBoZ0vA==", - "license": "MIT", - "dependencies": { - "picocolors": "^1.0.0", - "shell-quote": "^1.8.1" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "license": "MIT" - }, - "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "license": "MIT", - "engines": { - "node": ">=6.11.5" - } - }, - "node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "license": "MIT", - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, - "node_modules/locate-path": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", - "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", - "license": "MIT", - "dependencies": { - "p-locate": "^6.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT" - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "license": "MIT" - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "license": "MIT" - }, - "node_modules/lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", - "license": "MIT" - }, - "node_modules/longest-streak": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", - "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.3" - } - }, - "node_modules/lowercase-keys": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", - "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/markdown-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", - "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/markdown-table": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", - "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mdast-util-directive": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz", - "integrity": "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "parse-entities": "^4.0.0", - "stringify-entities": "^4.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-find-and-replace": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", - "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "escape-string-regexp": "^5.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mdast-util-from-markdown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", - "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark": "^4.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-from-markdown/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/mdast-util-frontmatter": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz", - "integrity": "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "escape-string-regexp": "^5.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "micromark-extension-frontmatter": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-frontmatter/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mdast-util-gfm": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", - "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", - "license": "MIT", - "dependencies": { - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-gfm-autolink-literal": "^2.0.0", - "mdast-util-gfm-footnote": "^2.0.0", - "mdast-util-gfm-strikethrough": "^2.0.0", - "mdast-util-gfm-table": "^2.0.0", - "mdast-util-gfm-task-list-item": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-autolink-literal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", - "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "ccount": "^2.0.0", - "devlop": "^1.0.0", - "mdast-util-find-and-replace": "^3.0.0", - "micromark-util-character": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/mdast-util-gfm-footnote": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.1.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-strikethrough": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", - "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-table": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", - "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "markdown-table": "^3.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-task-list-item": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", - "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", - "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", - "license": "MIT", - "dependencies": { - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-mdx-expression": "^2.0.0", - "mdast-util-mdx-jsx": "^3.0.0", - "mdast-util-mdxjs-esm": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx-expression": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", - "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx-jsx": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", - "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "devlop": "^1.1.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "parse-entities": "^4.0.0", - "stringify-entities": "^4.0.0", - "unist-util-stringify-position": "^4.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdxjs-esm": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", - "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-phrasing": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", - "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-hast": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", - "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@ungap/structured-clone": "^1.0.0", - "devlop": "^1.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "trim-lines": "^3.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-markdown": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", - "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "longest-streak": "^3.0.0", - "mdast-util-phrasing": "^4.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "unist-util-visit": "^5.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", - "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", - "license": "CC0-1.0" - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memfs": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", - "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", - "license": "Unlicense", - "dependencies": { - "fs-monkey": "^1.0.4" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "license": "MIT" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/micromark": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", - "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/debug": "^4.0.0", - "debug": "^4.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-core-commonmark": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", - "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-destination": "^2.0.0", - "micromark-factory-label": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-factory-title": "^2.0.0", - "micromark-factory-whitespace": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-html-tag-name": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-core-commonmark/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-core-commonmark/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-core-commonmark/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-extension-directive": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", - "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-factory-whitespace": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "parse-entities": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-directive/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-directive/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-directive/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-extension-frontmatter": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz", - "integrity": "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==", - "license": "MIT", - "dependencies": { - "fault": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-extension-gfm": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", - "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", - "license": "MIT", - "dependencies": { - "micromark-extension-gfm-autolink-literal": "^2.0.0", - "micromark-extension-gfm-footnote": "^2.0.0", - "micromark-extension-gfm-strikethrough": "^2.0.0", - "micromark-extension-gfm-table": "^2.0.0", - "micromark-extension-gfm-tagfilter": "^2.0.0", - "micromark-extension-gfm-task-list-item": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", - "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-extension-gfm-footnote": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-extension-gfm-strikethrough": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", - "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-strikethrough/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-extension-gfm-table": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", - "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-table/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-extension-gfm-tagfilter": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", - "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-task-list-item": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", - "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-extension-mdx-expression": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", - "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-mdx-expression": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-mdx-expression/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-extension-mdx-jsx": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", - "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "micromark-factory-mdx-expression": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-extension-mdx-md": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", - "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdxjs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", - "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", - "license": "MIT", - "dependencies": { - "acorn": "^8.0.0", - "acorn-jsx": "^5.0.0", - "micromark-extension-mdx-expression": "^3.0.0", - "micromark-extension-mdx-jsx": "^3.0.0", - "micromark-extension-mdx-md": "^2.0.0", - "micromark-extension-mdxjs-esm": "^3.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdxjs-esm": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", - "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-position-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-factory-destination": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", - "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-destination/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-destination/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-factory-label": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", - "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-label/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-label/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-factory-mdx-expression": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", - "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-position-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" - } - }, - "node_modules/micromark-factory-mdx-expression/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-factory-space": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", - "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-factory-space/node_modules/micromark-util-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", - "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-factory-title": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", - "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-title/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-title/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-title/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-factory-whitespace": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", - "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-whitespace/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-whitespace/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-whitespace/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-character": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", - "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-util-character/node_modules/micromark-util-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", - "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-chunked": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", - "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-chunked/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-classify-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", - "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-classify-character/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-classify-character/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-combine-extensions": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", - "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-chunked": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", - "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-numeric-character-reference/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-decode-string": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", - "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-string/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-string/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-encode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", - "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-events-to-acorn": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", - "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/unist": "^3.0.0", - "devlop": "^1.0.0", - "estree-util-visit": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "vfile-message": "^4.0.0" - } - }, - "node_modules/micromark-util-events-to-acorn/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-html-tag-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", - "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-normalize-identifier": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", - "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-normalize-identifier/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-resolve-all": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", - "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-sanitize-uri": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", - "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-sanitize-uri/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-sanitize-uri/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-subtokenize": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", - "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-subtokenize/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-symbol": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", - "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", - "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", - "license": "MIT", - "dependencies": { - "mime-db": "~1.33.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/mimic-response": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", - "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mini-css-extract-plugin": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.2.tgz", - "integrity": "sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==", - "license": "MIT", - "dependencies": { - "schema-utils": "^4.0.0", - "tapable": "^2.2.1" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - } - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "license": "ISC" - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/mrmime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", - "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/multicast-dns": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", - "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", - "license": "MIT", - "dependencies": { - "dns-packet": "^5.2.2", - "thunky": "^1.0.2" - }, - "bin": { - "multicast-dns": "cli.js" - } - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "license": "MIT" - }, - "node_modules/no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "license": "MIT", - "dependencies": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "node_modules/node-emoji": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", - "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", - "license": "MIT", - "dependencies": { - "@sindresorhus/is": "^4.6.0", - "char-regex": "^1.0.2", - "emojilib": "^2.4.0", - "skin-tone": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", - "license": "(BSD-3-Clause OR GPL-2.0)", - "engines": { - "node": ">= 6.13.0" - } - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-url": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.2.tgz", - "integrity": "sha512-Ee/R3SyN4BuynXcnTaekmaVdbDAEiNrHqjQIA37mHU8G9pf7aaAD4ZX3XjBLo6rsdcxA/gtkcNYZLt30ACgynw==", - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nprogress": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", - "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==", - "license": "MIT" - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/null-loader": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/null-loader/-/null-loader-4.0.1.tgz", - "integrity": "sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg==", - "license": "MIT", - "dependencies": { - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/null-loader/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/null-loader/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "license": "MIT", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/null-loader/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "license": "MIT" - }, - "node_modules/null-loader/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", - "license": "MIT" - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", - "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", - "license": "MIT", - "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/opener": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", - "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", - "license": "(WTFPL OR MIT)", - "bin": { - "opener": "bin/opener-bin.js" - } - }, - "node_modules/p-cancelable": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", - "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", - "license": "MIT", - "engines": { - "node": ">=12.20" - } - }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", - "license": "MIT", - "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", - "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", - "license": "MIT", - "dependencies": { - "p-limit": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-queue": { - "version": "6.6.2", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", - "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", - "license": "MIT", - "dependencies": { - "eventemitter3": "^4.0.4", - "p-timeout": "^3.2.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", - "license": "MIT", - "dependencies": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-timeout": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", - "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", - "license": "MIT", - "dependencies": { - "p-finally": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/package-json": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-8.1.1.tgz", - "integrity": "sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==", - "license": "MIT", - "dependencies": { - "got": "^12.1.0", - "registry-auth-token": "^5.0.1", - "registry-url": "^6.0.0", - "semver": "^7.3.7" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/param-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", - "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", - "license": "MIT", - "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-entities": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", - "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "character-entities-legacy": "^3.0.0", - "character-reference-invalid": "^2.0.0", - "decode-named-character-reference": "^1.0.0", - "is-alphanumerical": "^2.0.0", - "is-decimal": "^2.0.0", - "is-hexadecimal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/parse-entities/node_modules/@types/unist": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", - "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", - "license": "MIT" - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse-numeric-range": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", - "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==", - "license": "ISC" - }, - "node_modules/parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "license": "MIT", - "dependencies": { - "entities": "^6.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", - "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", - "license": "MIT", - "dependencies": { - "domhandler": "^5.0.3", - "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5/node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/pascal-case": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", - "license": "MIT", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", - "license": "(WTFPL OR MIT)" - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "license": "MIT" - }, - "node_modules/path-to-regexp": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", - "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", - "license": "MIT", - "dependencies": { - "isarray": "0.0.1" - } - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pkg-dir": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", - "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", - "license": "MIT", - "dependencies": { - "find-up": "^6.3.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/postcss": { - "version": "8.5.5", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.5.tgz", - "integrity": "sha512-d/jtm+rdNT8tpXuHY5MMtcbJFBkhXE6593XVR9UoGCH8jSFGci7jGvMGH5RYd5PBJW+00NZQt6gf7CbagJCrhg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-attribute-case-insensitive": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-7.0.1.tgz", - "integrity": "sha512-Uai+SupNSqzlschRyNx3kbCTWgY/2hcwtHEI/ej2LJWc9JJ77qKgGptd8DHwY1mXtZ7Aoh4z4yxfwMBue9eNgw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-attribute-case-insensitive/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-calc": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", - "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.0.11", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.2.2" - } - }, - "node_modules/postcss-clamp": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", - "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=7.6.0" - }, - "peerDependencies": { - "postcss": "^8.4.6" - } - }, - "node_modules/postcss-color-functional-notation": { - "version": "7.0.10", - "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.10.tgz", - "integrity": "sha512-k9qX+aXHBiLTRrWoCJuUFI6F1iF6QJQUXNVWJVSbqZgj57jDhBlOvD8gNUGl35tgqDivbGLhZeW3Ongz4feuKA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.0.10", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.1.0", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-color-hex-alpha": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-10.0.0.tgz", - "integrity": "sha512-1kervM2cnlgPs2a8Vt/Qbe5cQ++N7rkYo/2rz2BkqJZIHQwaVuJgQH38REHrAi4uM0b1fqxMkWYmese94iMp3w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "dependencies": { - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-color-rebeccapurple": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-10.0.0.tgz", - "integrity": "sha512-JFta737jSP+hdAIEhk1Vs0q0YF5P8fFcj+09pweS8ktuGuZ8pPlykHsk6mPxZ8awDl4TrcxUqJo9l1IhVr/OjQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-colormin": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", - "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.0", - "caniuse-api": "^3.0.0", - "colord": "^2.9.3", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-convert-values": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", - "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-custom-media": { - "version": "11.0.6", - "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-11.0.6.tgz", - "integrity": "sha512-C4lD4b7mUIw+RZhtY7qUbf4eADmb7Ey8BFA2px9jUbwg7pjTZDl4KY4bvlUV+/vXQvzQRfiGEVJyAbtOsCMInw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "dependencies": { - "@csstools/cascade-layer-name-parser": "^2.0.5", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/media-query-list-parser": "^4.0.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-custom-properties": { - "version": "14.0.6", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-14.0.6.tgz", - "integrity": "sha512-fTYSp3xuk4BUeVhxCSJdIPhDLpJfNakZKoiTDx7yRGCdlZrSJR7mWKVOBS4sBF+5poPQFMj2YdXx1VHItBGihQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "dependencies": { - "@csstools/cascade-layer-name-parser": "^2.0.5", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-custom-selectors": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-8.0.5.tgz", - "integrity": "sha512-9PGmckHQswiB2usSO6XMSswO2yFWVoCAuih1yl9FVcwkscLjRKjwsjM3t+NIWpSU2Jx3eOiK2+t4vVTQaoCHHg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "dependencies": { - "@csstools/cascade-layer-name-parser": "^2.0.5", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-custom-selectors/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-dir-pseudo-class": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-9.0.1.tgz", - "integrity": "sha512-tRBEK0MHYvcMUrAuYMEOa0zg9APqirBcgzi6P21OhxtJyJADo/SWBwY1CAwEohQ/6HDaa9jCjLRG7K3PVQYHEA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-dir-pseudo-class/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-discard-comments": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", - "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", - "license": "MIT", - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-discard-duplicates": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", - "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", - "license": "MIT", - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-discard-empty": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", - "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", - "license": "MIT", - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-discard-overridden": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", - "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", - "license": "MIT", - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-discard-unused": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-6.0.5.tgz", - "integrity": "sha512-wHalBlRHkaNnNwfC8z+ppX57VhvS+HWgjW508esjdaEYr3Mx7Gnn2xA4R/CKf5+Z9S5qsqC+Uzh4ueENWwCVUA==", - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.0.16" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-double-position-gradients": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-6.0.2.tgz", - "integrity": "sha512-7qTqnL7nfLRyJK/AHSVrrXOuvDDzettC+wGoienURV8v2svNbu6zJC52ruZtHaO6mfcagFmuTGFdzRsJKB3k5Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^4.1.0", - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-focus-visible": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-10.0.1.tgz", - "integrity": "sha512-U58wyjS/I1GZgjRok33aE8juW9qQgQUNwTSdxQGuShHzwuYdcklnvK/+qOWX1Q9kr7ysbraQ6ht6r+udansalA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-focus-visible/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-focus-within": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-9.0.1.tgz", - "integrity": "sha512-fzNUyS1yOYa7mOjpci/bR+u+ESvdar6hk8XNK/TRR0fiGTp2QT5N+ducP0n3rfH/m9I7H/EQU6lsa2BrgxkEjw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-focus-within/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-font-variant": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", - "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", - "license": "MIT", - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-gap-properties": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-6.0.0.tgz", - "integrity": "sha512-Om0WPjEwiM9Ru+VhfEDPZJAKWUd0mV1HmNXqp2C29z80aQ2uP9UVhLc7e3aYMIor/S5cVhoPgYQ7RtfeZpYTRw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-image-set-function": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-7.0.0.tgz", - "integrity": "sha512-QL7W7QNlZuzOwBTeXEmbVckNt1FSmhQtbMRvGGqqU4Nf4xk6KUEQhAoWuMzwbSv5jxnumerixZ5Tv7eiDB9U87znA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-lab-function": { - "version": "7.0.10", - "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-7.0.10.tgz", - "integrity": "sha512-tqs6TCEv9tC1Riq6fOzHuHcZyhg4k3gIAMB8GGY/zA1ssGdm6puHMVE7t75aOSoFg7UD2wyrFFhbldiCMyyFTQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.0.10", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.1.0", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-loader": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.4.tgz", - "integrity": "sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A==", - "license": "MIT", - "dependencies": { - "cosmiconfig": "^8.3.5", - "jiti": "^1.20.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">= 14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "postcss": "^7.0.0 || ^8.0.1", - "webpack": "^5.0.0" - } - }, - "node_modules/postcss-logical": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-8.1.0.tgz", - "integrity": "sha512-pL1hXFQ2fEXNKiNiAgtfA005T9FBxky5zkX6s4GZM2D8RkVgRqz3f4g1JUoq925zXv495qk8UNldDwh8uGEDoA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-merge-idents": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-6.0.3.tgz", - "integrity": "sha512-1oIoAsODUs6IHQZkLQGO15uGEbK3EAl5wi9SS8hs45VgsxQfMnxvt+L+zIr7ifZFIH14cfAeVe2uCTa+SPRa3g==", - "license": "MIT", - "dependencies": { - "cssnano-utils": "^4.0.2", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-merge-longhand": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", - "integrity": "sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0", - "stylehacks": "^6.1.1" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-merge-rules": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz", - "integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.0", - "caniuse-api": "^3.0.0", - "cssnano-utils": "^4.0.2", - "postcss-selector-parser": "^6.0.16" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-minify-font-values": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz", - "integrity": "sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-minify-gradients": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz", - "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==", - "license": "MIT", - "dependencies": { - "colord": "^2.9.3", - "cssnano-utils": "^4.0.2", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-minify-params": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", - "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.0", - "cssnano-utils": "^4.0.2", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-minify-selectors": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz", - "integrity": "sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==", - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.0.16" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-modules-extract-imports": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", - "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", - "license": "ISC", - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-local-by-default": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", - "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", - "license": "MIT", - "dependencies": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^7.0.0", - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-modules-scope": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", - "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", - "license": "ISC", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-modules-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", - "license": "ISC", - "dependencies": { - "icss-utils": "^5.0.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-nesting": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.2.tgz", - "integrity": "sha512-1YCI290TX+VP0U/K/aFxzHzQWHWURL+CtHMSbex1lCdpXD1SoR2sYuxDu5aNI9lPoXpKTCggFZiDJbwylU0LEQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/selector-resolve-nested": "^3.1.0", - "@csstools/selector-specificity": "^5.0.0", - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-nesting/node_modules/@csstools/selector-resolve-nested": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.1.0.tgz", - "integrity": "sha512-mf1LEW0tJLKfWyvn5KdDrhpxHyuxpbNwTIwOYLIvsTffeyOf85j5oIzfG0yosxDgx/sswlqBnESYUcQH0vgZ0g==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^7.0.0" - } - }, - "node_modules/postcss-nesting/node_modules/@csstools/selector-specificity": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", - "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^7.0.0" - } - }, - "node_modules/postcss-nesting/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-normalize-charset": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", - "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==", - "license": "MIT", - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-normalize-display-values": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz", - "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-normalize-positions": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz", - "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-normalize-repeat-style": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz", - "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-normalize-string": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", - "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-normalize-timing-functions": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz", - "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-normalize-unicode": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", - "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-normalize-url": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", - "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-normalize-whitespace": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", - "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-opacity-percentage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-3.0.0.tgz", - "integrity": "sha512-K6HGVzyxUxd/VgZdX04DCtdwWJ4NGLG212US4/LA1TLAbHgmAsTWVR86o+gGIbFtnTkfOpb9sCRBx8K7HO66qQ==", - "funding": [ - { - "type": "kofi", - "url": "https://ko-fi.com/mrcgrtz" - }, - { - "type": "liberapay", - "url": "https://liberapay.com/mrcgrtz" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-ordered-values": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", - "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", - "license": "MIT", - "dependencies": { - "cssnano-utils": "^4.0.2", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-overflow-shorthand": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-6.0.0.tgz", - "integrity": "sha512-BdDl/AbVkDjoTofzDQnwDdm/Ym6oS9KgmO7Gr+LHYjNWJ6ExORe4+3pcLQsLA9gIROMkiGVjjwZNoL/mpXHd5Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-page-break": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", - "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", - "license": "MIT", - "peerDependencies": { - "postcss": "^8" - } - }, - "node_modules/postcss-place": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-10.0.0.tgz", - "integrity": "sha512-5EBrMzat2pPAxQNWYavwAfoKfYcTADJ8AXGVPcUZ2UkNloUTWzJQExgrzrDkh3EKzmAx1evfTAzF9I8NGcc+qw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-preset-env": { - "version": "10.2.3", - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.2.3.tgz", - "integrity": "sha512-zlQN1yYmA7lFeM1wzQI14z97mKoM8qGng+198w1+h6sCud/XxOjcKtApY9jWr7pXNS3yHDEafPlClSsWnkY8ow==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/postcss-cascade-layers": "^5.0.1", - "@csstools/postcss-color-function": "^4.0.10", - "@csstools/postcss-color-mix-function": "^3.0.10", - "@csstools/postcss-color-mix-variadic-function-arguments": "^1.0.0", - "@csstools/postcss-content-alt-text": "^2.0.6", - "@csstools/postcss-exponential-functions": "^2.0.9", - "@csstools/postcss-font-format-keywords": "^4.0.0", - "@csstools/postcss-gamut-mapping": "^2.0.10", - "@csstools/postcss-gradients-interpolation-method": "^5.0.10", - "@csstools/postcss-hwb-function": "^4.0.10", - "@csstools/postcss-ic-unit": "^4.0.2", - "@csstools/postcss-initial": "^2.0.1", - "@csstools/postcss-is-pseudo-class": "^5.0.3", - "@csstools/postcss-light-dark-function": "^2.0.9", - "@csstools/postcss-logical-float-and-clear": "^3.0.0", - "@csstools/postcss-logical-overflow": "^2.0.0", - "@csstools/postcss-logical-overscroll-behavior": "^2.0.0", - "@csstools/postcss-logical-resize": "^3.0.0", - "@csstools/postcss-logical-viewport-units": "^3.0.4", - "@csstools/postcss-media-minmax": "^2.0.9", - "@csstools/postcss-media-queries-aspect-ratio-number-values": "^3.0.5", - "@csstools/postcss-nested-calc": "^4.0.0", - "@csstools/postcss-normalize-display-values": "^4.0.0", - "@csstools/postcss-oklab-function": "^4.0.10", - "@csstools/postcss-progressive-custom-properties": "^4.1.0", - "@csstools/postcss-random-function": "^2.0.1", - "@csstools/postcss-relative-color-syntax": "^3.0.10", - "@csstools/postcss-scope-pseudo-class": "^4.0.1", - "@csstools/postcss-sign-functions": "^1.1.4", - "@csstools/postcss-stepped-value-functions": "^4.0.9", - "@csstools/postcss-text-decoration-shorthand": "^4.0.2", - "@csstools/postcss-trigonometric-functions": "^4.0.9", - "@csstools/postcss-unset-value": "^4.0.0", - "autoprefixer": "^10.4.21", - "browserslist": "^4.25.0", - "css-blank-pseudo": "^7.0.1", - "css-has-pseudo": "^7.0.2", - "css-prefers-color-scheme": "^10.0.0", - "cssdb": "^8.3.0", - "postcss-attribute-case-insensitive": "^7.0.1", - "postcss-clamp": "^4.1.0", - "postcss-color-functional-notation": "^7.0.10", - "postcss-color-hex-alpha": "^10.0.0", - "postcss-color-rebeccapurple": "^10.0.0", - "postcss-custom-media": "^11.0.6", - "postcss-custom-properties": "^14.0.6", - "postcss-custom-selectors": "^8.0.5", - "postcss-dir-pseudo-class": "^9.0.1", - "postcss-double-position-gradients": "^6.0.2", - "postcss-focus-visible": "^10.0.1", - "postcss-focus-within": "^9.0.1", - "postcss-font-variant": "^5.0.0", - "postcss-gap-properties": "^6.0.0", - "postcss-image-set-function": "^7.0.0", - "postcss-lab-function": "^7.0.10", - "postcss-logical": "^8.1.0", - "postcss-nesting": "^13.0.2", - "postcss-opacity-percentage": "^3.0.0", - "postcss-overflow-shorthand": "^6.0.0", - "postcss-page-break": "^3.0.4", - "postcss-place": "^10.0.0", - "postcss-pseudo-class-any-link": "^10.0.1", - "postcss-replace-overflow-wrap": "^4.0.0", - "postcss-selector-not": "^8.0.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-pseudo-class-any-link": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-10.0.1.tgz", - "integrity": "sha512-3el9rXlBOqTFaMFkWDOkHUTQekFIYnaQY55Rsp8As8QQkpiSgIYEcF/6Ond93oHiDsGb4kad8zjt+NPlOC1H0Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-pseudo-class-any-link/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-reduce-idents": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-6.0.3.tgz", - "integrity": "sha512-G3yCqZDpsNPoQgbDUy3T0E6hqOQ5xigUtBQyrmq3tn2GxlyiL0yyl7H+T8ulQR6kOcHJ9t7/9H4/R2tv8tJbMA==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-reduce-initial": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", - "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.0", - "caniuse-api": "^3.0.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-reduce-transforms": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz", - "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-replace-overflow-wrap": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", - "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", - "license": "MIT", - "peerDependencies": { - "postcss": "^8.0.3" - } - }, - "node_modules/postcss-selector-not": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-8.0.1.tgz", - "integrity": "sha512-kmVy/5PYVb2UOhy0+LqUYAhKj7DUGDpSWa5LZqlkWJaaAV+dxxsOG3+St0yNLu6vsKD7Dmqx+nWQt0iil89+WA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-selector-not/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-sort-media-queries": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/postcss-sort-media-queries/-/postcss-sort-media-queries-5.2.0.tgz", - "integrity": "sha512-AZ5fDMLD8SldlAYlvi8NIqo0+Z8xnXU2ia0jxmuhxAU+Lqt9K+AlmLNJ/zWEnE9x+Zx3qL3+1K20ATgNOr3fAA==", - "license": "MIT", - "dependencies": { - "sort-css-media-queries": "2.2.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "postcss": "^8.4.23" - } - }, - "node_modules/postcss-svgo": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz", - "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0", - "svgo": "^3.2.0" - }, - "engines": { - "node": "^14 || ^16 || >= 18" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-unique-selectors": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz", - "integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==", - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.0.16" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "license": "MIT" - }, - "node_modules/postcss-zindex": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-6.0.2.tgz", - "integrity": "sha512-5BxW9l1evPB/4ZIc+2GobEBoKC+h8gPGCMi+jxsYvd2x0mjq7wazk6DrP71pStqxE9Foxh5TVnonbWpFZzXaYg==", - "license": "MIT", - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/pretty-error": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", - "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", - "license": "MIT", - "dependencies": { - "lodash": "^4.17.20", - "renderkid": "^3.0.0" - } - }, - "node_modules/pretty-time": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz", - "integrity": "sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/prism-react-renderer": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz", - "integrity": "sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==", - "license": "MIT", - "dependencies": { - "@types/prismjs": "^1.26.0", - "clsx": "^2.0.0" - }, - "peerDependencies": { - "react": ">=16.0.0" - } - }, - "node_modules/prismjs": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", - "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "license": "MIT" - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/property-information": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", - "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/proto-list": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", - "license": "ISC" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxy-addr/node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/pupa": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.1.0.tgz", - "integrity": "sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==", - "license": "MIT", - "dependencies": { - "escape-goat": "^4.0.0" - }, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/raw-body/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", - "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", - "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", - "license": "MIT", - "dependencies": { - "scheduler": "^0.26.0" - }, - "peerDependencies": { - "react": "^19.1.0" - } - }, - "node_modules/react-fast-compare": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", - "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", - "license": "MIT" - }, - "node_modules/react-helmet-async": { - "name": "@slorber/react-helmet-async", - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@slorber/react-helmet-async/-/react-helmet-async-1.3.0.tgz", - "integrity": "sha512-e9/OK8VhwUSc67diWI8Rb3I0YgI9/SBQtnhe9aEuK6MhZm7ntZZimXgwXnd8W96YTmSOb9M4d8LwhRZyhWr/1A==", - "license": "Apache-2.0", - "dependencies": { - "@babel/runtime": "^7.12.5", - "invariant": "^2.2.4", - "prop-types": "^15.7.2", - "react-fast-compare": "^3.2.0", - "shallowequal": "^1.1.0" - }, - "peerDependencies": { - "react": "^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" - }, - "node_modules/react-json-view-lite": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/react-json-view-lite/-/react-json-view-lite-2.4.1.tgz", - "integrity": "sha512-fwFYknRIBxjbFm0kBDrzgBy1xa5tDg2LyXXBepC5f1b+MY3BUClMCsvanMPn089JbV1Eg3nZcrp0VCuH43aXnA==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/react-loadable": { - "name": "@docusaurus/react-loadable", - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-6.0.0.tgz", - "integrity": "sha512-YMMxTUQV/QFSnbgrP3tjDzLHRg7vsbMn8e9HAa8o/1iXoiomo48b7sk/kkmWEuWNDPJVlKSJRB6Y2fHqdJk+SQ==", - "license": "MIT", - "dependencies": { - "@types/react": "*" - }, - "peerDependencies": { - "react": "*" - } - }, - "node_modules/react-loadable-ssr-addon-v5-slorber": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz", - "integrity": "sha512-lq3Lyw1lGku8zUEJPDxsNm1AfYHBrO9Y1+olAYwpUJ2IGFBskM0DMKok97A6LWUpHm+o7IvQBOWu9MLenp9Z+A==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.10.3" - }, - "engines": { - "node": ">=10.13.0" - }, - "peerDependencies": { - "react-loadable": "*", - "webpack": ">=4.41.1 || 5.x" - } - }, - "node_modules/react-router": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz", - "integrity": "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.13", - "history": "^4.9.0", - "hoist-non-react-statics": "^3.1.0", - "loose-envify": "^1.3.1", - "path-to-regexp": "^1.7.0", - "prop-types": "^15.6.2", - "react-is": "^16.6.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" - }, - "peerDependencies": { - "react": ">=15" - } - }, - "node_modules/react-router-config": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/react-router-config/-/react-router-config-5.1.1.tgz", - "integrity": "sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.1.2" - }, - "peerDependencies": { - "react": ">=15", - "react-router": ">=5" - } - }, - "node_modules/react-router-dom": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz", - "integrity": "sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.13", - "history": "^4.9.0", - "loose-envify": "^1.3.1", - "prop-types": "^15.6.2", - "react-router": "5.3.4", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" - }, - "peerDependencies": { - "react": ">=15" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/recma-build-jsx": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", - "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-util-build-jsx": "^3.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/recma-jsx": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.0.tgz", - "integrity": "sha512-5vwkv65qWwYxg+Atz95acp8DMu1JDSqdGkA2Of1j6rCreyFUE/gp15fC8MnGEuG1W68UKjM6x6+YTWIh7hZM/Q==", - "license": "MIT", - "dependencies": { - "acorn-jsx": "^5.0.0", - "estree-util-to-js": "^2.0.0", - "recma-parse": "^1.0.0", - "recma-stringify": "^1.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/recma-parse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz", - "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "esast-util-from-js": "^2.0.0", - "unified": "^11.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/recma-stringify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz", - "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-util-to-js": "^2.0.0", - "unified": "^11.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "license": "MIT" - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", - "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regexpu-core": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", - "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.0", - "regjsgen": "^0.8.0", - "regjsparser": "^0.12.0", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/registry-auth-token": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.0.tgz", - "integrity": "sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==", - "license": "MIT", - "dependencies": { - "@pnpm/npm-conf": "^2.1.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/registry-url": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz", - "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", - "license": "MIT", - "dependencies": { - "rc": "1.2.8" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", - "license": "MIT" - }, - "node_modules/regjsparser": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", - "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", - "license": "BSD-2-Clause", - "dependencies": { - "jsesc": "~3.0.2" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/rehype-raw": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", - "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-raw": "^9.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/rehype-recma": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz", - "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/hast": "^3.0.0", - "hast-util-to-estree": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/remark-directive": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-3.0.1.tgz", - "integrity": "sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-directive": "^3.0.0", - "micromark-extension-directive": "^3.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-emoji": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/remark-emoji/-/remark-emoji-4.0.1.tgz", - "integrity": "sha512-fHdvsTR1dHkWKev9eNyhTo4EFwbUvJ8ka9SgeWkMPYFX4WoI7ViVBms3PjlQYgw5TLvNQso3GUB/b/8t3yo+dg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.2", - "emoticon": "^4.0.1", - "mdast-util-find-and-replace": "^3.0.1", - "node-emoji": "^2.1.0", - "unified": "^11.0.4" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/remark-frontmatter": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/remark-frontmatter/-/remark-frontmatter-5.0.0.tgz", - "integrity": "sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-frontmatter": "^2.0.0", - "micromark-extension-frontmatter": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-gfm": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", - "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-gfm": "^3.0.0", - "micromark-extension-gfm": "^3.0.0", - "remark-parse": "^11.0.0", - "remark-stringify": "^11.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-mdx": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.0.tgz", - "integrity": "sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA==", - "license": "MIT", - "dependencies": { - "mdast-util-mdx": "^3.0.0", - "micromark-extension-mdxjs": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-parse": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", - "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-rehype": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", - "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "mdast-util-to-hast": "^13.0.0", - "unified": "^11.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-stringify": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", - "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-to-markdown": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/renderkid": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", - "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", - "license": "MIT", - "dependencies": { - "css-select": "^4.1.3", - "dom-converter": "^0.2.0", - "htmlparser2": "^6.1.0", - "lodash": "^4.17.21", - "strip-ansi": "^6.0.1" - } - }, - "node_modules/renderkid/node_modules/css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/renderkid/node_modules/dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "license": "MIT", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/renderkid/node_modules/domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.2.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/renderkid/node_modules/domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/renderkid/node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "license": "BSD-2-Clause", - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/renderkid/node_modules/htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-like": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", - "integrity": "sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==", - "engines": { - "node": "*" - } - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "license": "MIT" - }, - "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", - "license": "MIT" - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-pathname": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", - "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==", - "license": "MIT" - }, - "node_modules/responselike": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", - "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", - "license": "MIT", - "dependencies": { - "lowercase-keys": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rtlcss": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", - "integrity": "sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==", - "license": "MIT", - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0", - "postcss": "^8.4.21", - "strip-json-comments": "^3.1.1" - }, - "bin": { - "rtlcss": "bin/rtlcss.js" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/sax": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", - "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", - "license": "ISC" - }, - "node_modules/scheduler": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", - "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", - "license": "MIT" - }, - "node_modules/schema-dts": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/schema-dts/-/schema-dts-1.1.5.tgz", - "integrity": "sha512-RJr9EaCmsLzBX2NDiO5Z3ux2BVosNZN5jo0gWgsyKvxKIUL5R3swNvoorulAeL9kLB0iTSX7V6aokhla2m7xbg==", - "license": "Apache-2.0" - }, - "node_modules/schema-utils": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", - "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/search-insights": { - "version": "2.17.3", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", - "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", - "license": "MIT", - "peer": true - }, - "node_modules/section-matter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", - "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", - "license": "MIT", - "dependencies": { - "extend-shallow": "^2.0.1", - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", - "license": "MIT" - }, - "node_modules/selfsigned": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", - "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", - "license": "MIT", - "dependencies": { - "@types/node-forge": "^1.3.0", - "node-forge": "^1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz", - "integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==", - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7numerixpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-handler": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz", - "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==", - "license": "MIT", - "dependencies": { - "bytes": "3.0.0", - "content-disposition": "0.5.2", - "mime-types": "2.1.18", - "minimatch": "3.1.2", - "path-is-inside": "1.0.2", - "path-to-regexp": "3.3.0", - "range-parser": "1.2.0" - } - }, - "node_modules/serve-handler/node_modules/path-to-regexp": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", - "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", - "license": "MIT" - }, - "node_modules/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-index/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-index/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", - "license": "MIT", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "license": "ISC" - }, - "node_modules/serve-index/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7numerixpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/serve-index/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "license": "ISC" - }, - "node_modules/serve-index/node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "license": "MIT", - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", - "license": "MIT" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/shell-quote": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", - "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "license": "ISC" - }, - "node_modules/sirv": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", - "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", - "license": "MIT", - "dependencies": { - "@polka/url": "^1.0.0-next.24", - "mrmime": "^2.0.0", - "totalist": "^3.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "license": "MIT" - }, - "node_modules/sitemap": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-7.1.2.tgz", - "integrity": "sha512-ARCqzHJ0p4gWt+j7NlU5eDlIO9+Rkr/JhPFZKKQ1l5GCus7rJH4UdrlVAh0xC/gDS/Qir2UMxqYNHtsKr2rpCw==", - "license": "MIT", - "dependencies": { - "@types/node": "^17.0.5", - "@types/sax": "^1.2.1", - "arg": "^5.0.0", - "sax": "^1.2.4" - }, - "bin": { - "sitemap": "dist/cli.js" - }, - "engines": { - "node": ">=12.0.0", - "npm": ">=5.6.0" - } - }, - "node_modules/sitemap/node_modules/@types/node": { - "version": "17.0.45", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", - "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", - "license": "MIT" - }, - "node_modules/skin-tone": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", - "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", - "license": "MIT", - "dependencies": { - "unicode-emoji-modifier-base": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/snake-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", - "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", - "license": "MIT", - "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/sockjs": { - "version": "0.3.24", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", - "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", - "license": "MIT", - "dependencies": { - "faye-websocket": "^0.11.3", - "uuid": "^8.3.2", - "websocket-driver": "^0.7.4" - } - }, - "node_modules/sort-css-media-queries": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/sort-css-media-queries/-/sort-css-media-queries-2.2.0.tgz", - "integrity": "sha512-0xtkGhWCC9MGt/EzgnvbbbKhqWjl1+/rncmhTh5qCpbYguXh6S/qwePfv/JQ8jePXXmqingylxoC49pCkSPIbA==", - "license": "MIT", - "engines": { - "node": ">= 6.3.0" - } - }, - "node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">= 8" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/space-separated-tokens": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", - "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/spdy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", - "license": "MIT", - "dependencies": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "license": "MIT", - "dependencies": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "license": "BSD-3-Clause" - }, - "node_modules/srcset": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/srcset/-/srcset-4.0.0.tgz", - "integrity": "sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/std-env": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", - "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", - "license": "MIT" - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/stringify-entities": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", - "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", - "license": "MIT", - "dependencies": { - "character-entities-html4": "^2.0.0", - "character-entities-legacy": "^3.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/stringify-object": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", - "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", - "license": "BSD-2-Clause", - "dependencies": { - "get-own-enumerable-property-symbols": "^3.0.0", - "is-obj": "^1.0.1", - "is-regexp": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", - "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/style-to-js": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.16.tgz", - "integrity": "sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==", - "license": "MIT", - "dependencies": { - "style-to-object": "1.0.8" - } - }, - "node_modules/style-to-object": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", - "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", - "license": "MIT", - "dependencies": { - "inline-style-parser": "0.2.4" - } - }, - "node_modules/stylehacks": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz", - "integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.0", - "postcss-selector-parser": "^6.0.16" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/svg-parser": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", - "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", - "license": "MIT" - }, - "node_modules/svgo": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", - "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", - "license": "MIT", - "dependencies": { - "@trysound/sax": "0.2.0", - "commander": "^7.2.0", - "css-select": "^5.1.0", - "css-tree": "^2.3.1", - "css-what": "^6.1.0", - "csso": "^5.0.5", - "picocolors": "^1.0.0" - }, - "bin": { - "svgo": "bin/svgo" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/svgo" - } - }, - "node_modules/svgo/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/tapable": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", - "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/terser": { - "version": "5.42.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.42.0.tgz", - "integrity": "sha512-UYCvU9YQW2f/Vwl+P0GfhxJxbUGLwd+5QrrGgLajzWAtC/23AX0vcise32kkP7Eu0Wu9VlzzHAXkLObgjQfFlQ==", - "license": "BSD-2-Clause", - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.14.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", - "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "jest-worker": "^27.4.5", - "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", - "terser": "^5.31.1" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/terser-webpack-plugin/node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "license": "MIT" - }, - "node_modules/thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", - "license": "MIT" - }, - "node_modules/tiny-invariant": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", - "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", - "license": "MIT" - }, - "node_modules/tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", - "license": "MIT" - }, - "node_modules/tinypool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.0.tgz", - "integrity": "sha512-7CotroY9a8DKsKprEy/a14aCCm8jYVmR7aFy4fpkZM8sdpNJbKkixuNjgM50yCmip2ezc8z4N7k3oe2+rfRJCQ==", - "license": "MIT", - "engines": { - "node": "^18.0.0 || >=20.0.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/totalist": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", - "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/trim-lines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", - "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/trough": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", - "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/type-is/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/type-is/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "license": "MIT", - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, - "node_modules/undici-types": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", - "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", - "license": "MIT" - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", - "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-emoji-modifier-base": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", - "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "license": "MIT", - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", - "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unified": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", - "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "bail": "^2.0.0", - "devlop": "^1.0.0", - "extend": "^3.0.0", - "is-plain-obj": "^4.0.0", - "trough": "^2.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unique-string": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", - "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", - "license": "MIT", - "dependencies": { - "crypto-random-string": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/unist-util-is": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", - "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-position": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", - "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-position-from-estree": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", - "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", - "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit-parents": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", - "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/update-notifier": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-6.0.2.tgz", - "integrity": "sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og==", - "license": "BSD-2-Clause", - "dependencies": { - "boxen": "^7.0.0", - "chalk": "^5.0.1", - "configstore": "^6.0.0", - "has-yarn": "^3.0.0", - "import-lazy": "^4.0.0", - "is-ci": "^3.0.1", - "is-installed-globally": "^0.4.0", - "is-npm": "^6.0.0", - "is-yarn-global": "^0.4.0", - "latest-version": "^7.0.0", - "pupa": "^3.1.0", - "semver": "^7.3.7", - "semver-diff": "^4.0.0", - "xdg-basedir": "^5.1.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/yeoman/update-notifier?sponsor=1" - } - }, - "node_modules/update-notifier/node_modules/boxen": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.1.1.tgz", - "integrity": "sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==", - "license": "MIT", - "dependencies": { - "ansi-align": "^3.0.1", - "camelcase": "^7.0.1", - "chalk": "^5.2.0", - "cli-boxes": "^3.0.0", - "string-width": "^5.1.2", - "type-fest": "^2.13.0", - "widest-line": "^4.0.1", - "wrap-ansi": "^8.1.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/update-notifier/node_modules/camelcase": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", - "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/update-notifier/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/url-loader": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", - "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==", - "license": "MIT", - "dependencies": { - "loader-utils": "^2.0.0", - "mime-types": "^2.1.27", - "schema-utils": "^3.0.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "file-loader": "*", - "webpack": "^4.0.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "file-loader": { - "optional": true - } - } - }, - "node_modules/url-loader/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/url-loader/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "license": "MIT", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/url-loader/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "license": "MIT" - }, - "node_modules/url-loader/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/url-loader/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/url-loader/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, - "node_modules/utila": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", - "license": "MIT" - }, - "node_modules/utility-types": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz", - "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/value-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", - "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==", - "license": "MIT" - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/vfile": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", - "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-location": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", - "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-message": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", - "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/watchpack": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", - "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", - "license": "MIT", - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "license": "MIT", - "dependencies": { - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/web-namespaces": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", - "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/webpack": { - "version": "5.99.9", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.9.tgz", - "integrity": "sha512-brOPwM3JnmOa+7kd3NsmOUOwbDAj8FT9xDsG3IW0MgbN9yZV7Oi/s/+MNQ/EcSMqw7qfoRyXPoeEWT8zLVdVGg==", - "license": "MIT", - "dependencies": { - "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "@webassemblyjs/ast": "^1.14.1", - "@webassemblyjs/wasm-edit": "^1.14.1", - "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.14.0", - "browserslist": "^4.24.0", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.1", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^4.3.2", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.11", - "watchpack": "^2.4.1", - "webpack-sources": "^3.2.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-bundle-analyzer": { - "version": "4.10.2", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz", - "integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==", - "license": "MIT", - "dependencies": { - "@discoveryjs/json-ext": "0.5.7", - "acorn": "^8.0.4", - "acorn-walk": "^8.0.0", - "commander": "^7.2.0", - "debounce": "^1.2.1", - "escape-string-regexp": "^4.0.0", - "gzip-size": "^6.0.0", - "html-escaper": "^2.0.2", - "opener": "^1.5.2", - "picocolors": "^1.0.0", - "sirv": "^2.0.3", - "ws": "^7.3.1" - }, - "bin": { - "webpack-bundle-analyzer": "lib/bin/analyzer.js" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/webpack-dev-middleware": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", - "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", - "license": "MIT", - "dependencies": { - "colorette": "^2.0.10", - "memfs": "^3.4.3", - "mime-types": "^2.1.31", - "range-parser": "^1.2.1", - "schema-utils": "^4.0.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/webpack-dev-middleware/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack-dev-middleware/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack-dev-middleware/node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack-dev-server": { - "version": "4.15.2", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", - "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", - "license": "MIT", - "dependencies": { - "@types/bonjour": "^3.5.9", - "@types/connect-history-api-fallback": "^1.3.5", - "@types/express": "^4.17.13", - "@types/serve-index": "^1.9.1", - "@types/serve-static": "^1.13.10", - "@types/sockjs": "^0.3.33", - "@types/ws": "^8.5.5", - "ansi-html-community": "^0.0.8", - "bonjour-service": "^1.0.11", - "chokidar": "^3.5.3", - "colorette": "^2.0.10", - "compression": "^1.7.4", - "connect-history-api-fallback": "^2.0.0", - "default-gateway": "^6.0.3", - "express": "^4.17.3", - "graceful-fs": "^4.2.6", - "html-entities": "^2.3.2", - "http-proxy-middleware": "^2.0.3", - "ipaddr.js": "^2.0.1", - "launch-editor": "^2.6.0", - "open": "^8.0.9", - "p-retry": "^4.5.0", - "rimraf": "^3.0.2", - "schema-utils": "^4.0.0", - "selfsigned": "^2.1.1", - "serve-index": "^1.9.1", - "sockjs": "^0.3.24", - "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.4", - "ws": "^8.13.0" - }, - "bin": { - "webpack-dev-server": "bin/webpack-dev-server.js" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.37.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "webpack": { - "optional": true - }, - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", - "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/webpack-merge": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", - "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/webpack-sources": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.2.tgz", - "integrity": "sha512-ykKKus8lqlgXX/1WjudpIEjqsafjOTcOJqxnAbMLAu/KCsDCJ6GBtvscewvTkrn24HsnvFwrSCbenFrhtcCsAA==", - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpackbar": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-6.0.1.tgz", - "integrity": "sha512-TnErZpmuKdwWBdMoexjio3KKX6ZtoKHRVvLIU0A47R0VVBDtx3ZyOJDktgYixhoJokZTYTt1Z37OkO9pnGJa9Q==", - "license": "MIT", - "dependencies": { - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "consola": "^3.2.3", - "figures": "^3.2.0", - "markdown-table": "^2.0.0", - "pretty-time": "^1.1.0", - "std-env": "^3.7.0", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=14.21.3" - }, - "peerDependencies": { - "webpack": "3 || 4 || 5" - } - }, - "node_modules/webpackbar/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/webpackbar/node_modules/markdown-table": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", - "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", - "license": "MIT", - "dependencies": { - "repeat-string": "^1.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/webpackbar/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/webpackbar/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "license": "Apache-2.0", - "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/widest-line": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", - "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", - "license": "MIT", - "dependencies": { - "string-width": "^5.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/wildcard": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", - "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", - "license": "MIT" - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "license": "MIT", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xdg-basedir": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", - "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/xml-js": { - "version": "1.6.11", - "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", - "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", - "license": "MIT", - "dependencies": { - "sax": "^1.2.4" - }, - "bin": { - "xml-js": "bin/cli.js" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "license": "ISC" - }, - "node_modules/yocto-queue": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", - "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", - "license": "MIT", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zwitch": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", - "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - } - } -} diff --git a/docs-src/package.json b/docs-src/package.json index 3b2c4d32..b470544c 100644 --- a/docs-src/package.json +++ b/docs-src/package.json @@ -24,7 +24,8 @@ }, "devDependencies": { "@docusaurus/module-type-aliases": "3.8.1", - "@docusaurus/types": "3.8.1" + "@docusaurus/types": "3.8.1", + "yarn": "1.22.22" }, "browserslist": { "production": [ diff --git a/docs-src/src/css/custom.css b/docs-src/src/css/custom.css index ff94defd..b66bc7db 100644 --- a/docs-src/src/css/custom.css +++ b/docs-src/src/css/custom.css @@ -1,143 +1,636 @@ /** - * Any CSS included here will be global. The classic template - * bundles Infima by default. Infima is a CSS framework designed to - * work well for content-centric websites. + * Global theme for BharatMLStack docs site. + * Overrides Infima variables to match the homepage's indigo/purple dark theme. + * Supports both dark (primary) and light modes. */ -/* You can override the default Infima variables here. */ +/* ======================================== + 1. Infima Variable Overrides + ======================================== */ + :root { - /* BharatMLStack brand colors - purple/burgundy theme */ - --ifm-color-primary: #450839; - --ifm-color-primary-dark: #3d0732; - --ifm-color-primary-darker: #39062f; - --ifm-color-primary-darkest: #2f0527; - --ifm-color-primary-light: #4d0940; - --ifm-color-primary-lighter: #510a43; - --ifm-color-primary-lightest: #5d0c4d; + /* Primary palette – gold/amber */ + --ifm-color-primary: #f59e0b; + --ifm-color-primary-dark: #d97706; + --ifm-color-primary-darker: #b45309; + --ifm-color-primary-darkest: #92400e; + --ifm-color-primary-light: #fbbf24; + --ifm-color-primary-lighter: #fcd34d; + --ifm-color-primary-lightest: #fde68a; + + /* Light mode backgrounds and text */ + --ifm-background-color: #f8fafc; + --ifm-background-surface-color: #ffffff; + --ifm-font-color-base: #1e293b; + --ifm-font-color-secondary: #64748b; + --ifm-heading-color: #0f172a; + --ifm-link-color: #f59e0b; + --ifm-link-hover-color: #d97706; + + /* Code */ --ifm-code-font-size: 95%; - --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); - - /* Custom BharatMLStack variables with better contrast */ - --bharatml-primary: #450839; - --bharatml-primary-hover: #6a0c59; - --bharatml-secondary: #f9f9f9; - --bharatml-text: #1c1e21; /* Much darker for better contrast */ - --bharatml-text-light: #606770; /* Darker gray for better readability */ + --ifm-code-background: #f1f5f9; + --ifm-code-border-radius: 6px; + --ifm-code-padding-horizontal: 0.4rem; + --ifm-code-padding-vertical: 0.15rem; + --docusaurus-highlighted-code-line-bg: rgba(245, 158, 11, 0.08); + + /* Cards, borders, shadows */ + --ifm-card-background-color: #ffffff; + --ifm-global-shadow-lw: 0 2px 8px rgba(0, 0, 0, 0.06); + --ifm-global-shadow-md: 0 4px 16px rgba(0, 0, 0, 0.08); + --ifm-global-shadow-tl: 0 8px 32px rgba(0, 0, 0, 0.1); + --ifm-global-radius: 8px; + + /* Table of contents */ + --ifm-toc-border-color: rgba(0, 0, 0, 0.08); + + /* Navbar height for padding */ + --ifm-navbar-height: 3.75rem; } -/* For readability concerns, you should choose a lighter palette in dark mode. */ +/* Dark mode */ [data-theme='dark'] { - --ifm-color-primary: #8b4582; - --ifm-color-primary-dark: #7d3f75; - --ifm-color-primary-darker: #763c6e; - --ifm-color-primary-darkest: #62315a; - --ifm-color-primary-light: #994b8f; - --ifm-color-primary-lighter: #a04e96; - --ifm-color-primary-lightest: #b657a9; - --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); - - /* Dark mode BharatMLStack colors */ - --bharatml-primary: #8b4582; - --bharatml-primary-hover: #a04e96; - --bharatml-secondary: #1e1e1e; - --bharatml-text: #e3e3e3; /* Light text for dark mode */ - --bharatml-text-light: #b4b4b4; /* Lighter gray for dark mode */ + --ifm-color-primary: #fbbf24; + --ifm-color-primary-dark: #f59e0b; + --ifm-color-primary-darker: #d97706; + --ifm-color-primary-darkest: #b45309; + --ifm-color-primary-light: #fcd34d; + --ifm-color-primary-lighter: #fde68a; + --ifm-color-primary-lightest: #fef3c7; + + --ifm-background-color: #27001D; + --ifm-background-surface-color: #3d0029; + --ifm-font-color-base: #e2e8f0; + --ifm-font-color-secondary: #94a3b8; + --ifm-heading-color: #f1f5f9; + --ifm-link-color: #fbbf24; + --ifm-link-hover-color: #fcd34d; + + --ifm-code-background: rgba(255, 255, 255, 0.06); + --docusaurus-highlighted-code-line-bg: rgba(251, 191, 36, 0.15); + + --ifm-card-background-color: rgba(255, 255, 255, 0.03); + --ifm-global-shadow-lw: 0 2px 8px rgba(0, 0, 0, 0.3); + --ifm-global-shadow-md: 0 4px 16px rgba(0, 0, 0, 0.4); + --ifm-global-shadow-tl: 0 8px 32px rgba(0, 0, 0, 0.5); + + --ifm-toc-border-color: rgba(255, 255, 255, 0.06); } -/* Custom BharatMLStack styles */ -.bharatml-hero { - background: linear-gradient(135deg, var(--bharatml-primary) 0%, var(--bharatml-primary-hover) 100%); - color: white; + +/* ======================================== + 2. Global Gradient Orb Background + ======================================== */ + +.gradient-bg-global { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 0; + pointer-events: none; } -/* Hero button styling - both buttons should have white borders and proper text colors */ -.bharatml-hero .bharatml-button { - background-color: var(--bharatml-primary); - border: 2px solid white !important; - color: white !important; - transition: all 0.3s ease; +.gradient-orb-global { + position: absolute; + border-radius: 50%; + filter: blur(100px); + opacity: 0.25; + animation: globalOrbFloat 25s ease-in-out infinite; } -.bharatml-hero .bharatml-button:hover { - background-color: white !important; - border-color: white !important; - color: var(--bharatml-primary) !important; +[data-theme='light'] .gradient-orb-global { + opacity: 0.10; } -.bharatml-hero .button--outline { - background-color: transparent !important; - border: 2px solid white !important; - color: white !important; - transition: all 0.3s ease; +.orb-global-1 { + width: 600px; + height: 600px; + background: radial-gradient(circle, #fbbf24, transparent); + top: -10%; + left: -10%; } -.bharatml-hero .button--outline:hover { - background-color: white !important; - border-color: white !important; - color: var(--bharatml-primary) !important; +.orb-global-2 { + width: 500px; + height: 500px; + background: radial-gradient(circle, #f59e0b, transparent); + top: 50%; + right: -10%; + animation-delay: 8s; } -/* Dark mode hero buttons */ -[data-theme='dark'] .bharatml-hero .bharatml-button { - background-color: var(--bharatml-primary); - border: 2px solid white !important; - color: white !important; +.orb-global-3 { + width: 700px; + height: 700px; + background: radial-gradient(circle, #06b6d4, transparent); + bottom: -20%; + left: 30%; + animation-delay: 15s; } -[data-theme='dark'] .bharatml-hero .bharatml-button:hover { - background-color: white !important; - border-color: white !important; - color: var(--bharatml-primary) !important; +@keyframes globalOrbFloat { + 0%, 100% { + transform: translate(0, 0) scale(1); + } + 33% { + transform: translate(60px, -60px) scale(1.1); + } + 66% { + transform: translate(-40px, 40px) scale(0.9); + } } -[data-theme='dark'] .bharatml-hero .button--outline { - background-color: transparent !important; - border: 2px solid white !important; - color: white !important; + +/* ======================================== + 3. Navbar – Glass Morphism + ======================================== */ + +.navbar { + background: rgba(39, 0, 29, 0.8) !important; + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border-bottom: 1px solid rgba(255, 255, 255, 0.05); + box-shadow: none; + position: sticky; + z-index: 100; +} + +[data-theme='light'] .navbar { + background: rgba(255, 255, 255, 0.85) !important; + border-bottom: 1px solid rgba(0, 0, 0, 0.08); +} + +.navbar__title { + font-weight: 800; + background: linear-gradient(135deg, #fbbf24, #f59e0b, #06b6d4); + background-size: 200% 200%; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + animation: navGradientShift 3s ease infinite; +} + +@keyframes navGradientShift { + 0%, 100% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } +} + +.navbar__link { + font-weight: 500; } -[data-theme='dark'] .bharatml-hero .button--outline:hover { - background-color: white !important; - border-color: white !important; - color: var(--bharatml-primary) !important; +[data-theme='dark'] .navbar__link { + color: #e2e8f0; } -/* General button styling for other parts of the site */ -.bharatml-button { - background-color: var(--bharatml-primary); - border-color: var(--bharatml-primary); - transition: all 0.3s ease; +[data-theme='dark'] .navbar__link:hover, +[data-theme='dark'] .navbar__link--active { + color: #fbbf24; +} + +.navbar__toggle { + color: var(--ifm-font-color-base); +} + +/* Navbar sidebar (mobile) */ +.navbar-sidebar { + background: var(--ifm-background-color); +} + + +/* ======================================== + 4. Footer – Dark Theme + ======================================== */ + +.footer { + background: #3d0029 !important; + border-top: 1px solid rgba(255, 255, 255, 0.05); +} + +[data-theme='light'] .footer { + background: #f1f5f9 !important; + border-top: 1px solid rgba(0, 0, 0, 0.08); +} + +.footer__title { + color: #e2e8f0; + font-weight: 700; +} + +[data-theme='light'] .footer__title { + color: #1e293b; +} + +.footer__link-item { + color: #94a3b8; + transition: color 0.3s; +} + +.footer__link-item:hover { + color: #fbbf24; + text-decoration: none; +} + +[data-theme='light'] .footer__link-item { + color: #64748b; +} + +[data-theme='light'] .footer__link-item:hover { + color: #f59e0b; +} + +.footer__copyright { + color: #64748b; +} + + +/* ======================================== + 5. Sidebar – Glass Effect + ======================================== */ + +[data-theme='dark'] .theme-doc-sidebar-container { + border-right: 1px solid rgba(255, 255, 255, 0.05) !important; } -.bharatml-button:hover { - background-color: var(--bharatml-primary-hover); - border-color: var(--bharatml-primary-hover); - color: white; +[data-theme='dark'] .menu { + background: transparent; } -.bharatml-card { - border: 1px solid rgba(69, 8, 57, 0.1); +[data-theme='dark'] .menu__link { + color: #cbd5e1; border-radius: 8px; - padding: 2rem; - transition: all 0.3s ease; - background: white; + transition: all 0.2s; +} + +[data-theme='dark'] .menu__link:hover { + background: rgba(251, 191, 36, 0.1); + color: #e2e8f0; +} + +[data-theme='dark'] .menu__link--active:not(.menu__link--sublist) { + background: rgba(251, 191, 36, 0.15); + color: #fbbf24; + font-weight: 600; +} + +[data-theme='dark'] .menu__list-item-collapsible:hover { + background: rgba(251, 191, 36, 0.08); +} + +[data-theme='dark'] .theme-doc-sidebar-item-category > .menu__list-item-collapsible > .menu__link { + color: #e2e8f0; + font-weight: 600; +} + + +/* ======================================== + 6. Doc / Blog Content + ======================================== */ + +/* Ensure proper z-index for content above gradient orbs */ +[class*='docMainContainer'], +[class*='mainWrapper'], +.main-wrapper { + position: relative; + z-index: 1; +} + +/* Markdown content */ +.markdown h1, +.markdown h2, +.markdown h3, +.markdown h4, +.markdown h5, +.markdown h6 { + color: var(--ifm-heading-color); +} + +/* Tables */ +[data-theme='dark'] table { + border-color: rgba(255, 255, 255, 0.08); +} + +[data-theme='dark'] table thead tr { + background: rgba(255, 255, 255, 0.04); + border-bottom: 1px solid rgba(255, 255, 255, 0.08); +} + +[data-theme='dark'] table tbody tr { + border-bottom: 1px solid rgba(255, 255, 255, 0.04); +} + +[data-theme='dark'] table tbody tr:nth-child(2n) { + background: rgba(255, 255, 255, 0.02); +} + +[data-theme='dark'] th, +[data-theme='dark'] td { + border-color: rgba(255, 255, 255, 0.06); +} + +/* Blockquotes */ +[data-theme='dark'] blockquote { + border-left-color: #fbbf24; + background: rgba(251, 191, 36, 0.05); + color: #cbd5e1; +} + +/* Horizontal rules */ +[data-theme='dark'] hr { + border-color: rgba(255, 255, 255, 0.06); +} + + +/* ======================================== + 7. Code Blocks + ======================================== */ + +[data-theme='dark'] .prism-code { + background: rgba(255, 255, 255, 0.04) !important; + border: 1px solid rgba(255, 255, 255, 0.06); +} + +[data-theme='dark'] code { + background: rgba(255, 255, 255, 0.06); + border: 1px solid rgba(255, 255, 255, 0.08); + color: #e2e8f0; +} + +[data-theme='dark'] a code { + color: var(--ifm-link-color); +} + +/* Code block title bar */ +[data-theme='dark'] .codeBlockTitle_node_modules-\@docusaurus-theme-classic-lib-theme-CodeBlock-Content-styles-module { + background: rgba(255, 255, 255, 0.06) !important; + border-bottom: 1px solid rgba(255, 255, 255, 0.06); +} + + +/* ======================================== + 8. Admonitions + ======================================== */ + +[data-theme='dark'] .alert { + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.06); + color: #e2e8f0; +} + +[data-theme='dark'] .alert--info { + border-left: 4px solid #06b6d4; + background: rgba(6, 182, 212, 0.06); +} + +[data-theme='dark'] .alert--warning { + border-left: 4px solid #f59e0b; + background: rgba(245, 158, 11, 0.06); +} + +[data-theme='dark'] .alert--danger { + border-left: 4px solid #ef4444; + background: rgba(239, 68, 68, 0.06); } -.bharatml-card:hover { - border-color: var(--bharatml-primary); - box-shadow: 0 4px 20px rgba(69, 8, 57, 0.1); - transform: translateY(-2px); +[data-theme='dark'] .alert--success { + border-left: 4px solid #10b981; + background: rgba(16, 185, 129, 0.06); } -.bharatml-icon { - width: 64px; - height: 64px; - background: linear-gradient(135deg, var(--bharatml-primary), var(--bharatml-primary-hover)); +[data-theme='dark'] .alert--secondary { + border-left: 4px solid #fbbf24; + background: rgba(251, 191, 36, 0.06); +} + +[data-theme='dark'] .admonitionHeading_node_modules-\@docusaurus-theme-classic-lib-theme-Admonition-Layout-styles-module { + color: inherit; +} + + +/* ======================================== + 9. Table of Contents (right sidebar) + ======================================== */ + +[data-theme='dark'] .table-of-contents__link { + color: #94a3b8; +} + +[data-theme='dark'] .table-of-contents__link:hover, +[data-theme='dark'] .table-of-contents__link--active { + color: #fbbf24; +} + +[data-theme='dark'] .table-of-contents { + border-left: 1px solid rgba(255, 255, 255, 0.06); +} + + +/* ======================================== + 10. Pagination / Doc navigation + ======================================== */ + +[data-theme='dark'] .pagination-nav__link { + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 12px; - display: flex; - align-items: center; - justify-content: center; - margin: 0 auto 1rem; - font-size: 1.5rem; - color: white; + transition: all 0.3s; +} + +[data-theme='dark'] .pagination-nav__link:hover { + border-color: rgba(251, 191, 36, 0.3); + background: rgba(251, 191, 36, 0.06); +} + +[data-theme='dark'] .pagination-nav__sublabel { + color: #94a3b8; +} + +[data-theme='dark'] .pagination-nav__label { + color: #e2e8f0; +} + + +/* ======================================== + 11. Blog-specific + ======================================== */ + +[data-theme='dark'] .blog-post-page article header h1 { + color: #f1f5f9; +} + +[data-theme='dark'] article .avatar__name a { + color: #fbbf24; +} + +[data-theme='dark'] .blog-tags a { + background: rgba(251, 191, 36, 0.1); + border: 1px solid rgba(251, 191, 36, 0.2); + color: #fbbf24; +} + +[data-theme='dark'] .blog-tags a:hover { + background: rgba(251, 191, 36, 0.2); + border-color: rgba(251, 191, 36, 0.4); + text-decoration: none; +} + + +/* ======================================== + 12. Search and misc inputs + ======================================== */ + +[data-theme='dark'] .navbar__search-input { + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + color: #e2e8f0; +} + +[data-theme='dark'] .navbar__search-input::placeholder { + color: #64748b; +} + + +/* ======================================== + 13. Breadcrumbs + ======================================== */ + +[data-theme='dark'] .breadcrumbs__link { + background: rgba(255, 255, 255, 0.04); + color: #94a3b8; + border-radius: 6px; +} + +[data-theme='dark'] .breadcrumbs__link:hover { + background: rgba(251, 191, 36, 0.1); + color: #e2e8f0; +} + +[data-theme='dark'] .breadcrumbs__item--active .breadcrumbs__link { + background: rgba(251, 191, 36, 0.12); + color: #fbbf24; +} + + +/* ======================================== + 14. Tabs + ======================================== */ + +[data-theme='dark'] .tabs__item { + color: #94a3b8; + border-bottom-color: transparent; +} + +[data-theme='dark'] .tabs__item:hover { + color: #e2e8f0; +} + +[data-theme='dark'] .tabs__item--active { + color: #fbbf24; + border-bottom-color: #fbbf24; +} + + +/* ======================================== + 15. Scrollbar (dark mode) + ======================================== */ + +[data-theme='dark'] ::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +[data-theme='dark'] ::-webkit-scrollbar-track { + background: transparent; +} + +[data-theme='dark'] ::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.12); + border-radius: 4px; +} + +[data-theme='dark'] ::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.2); +} + + +/* ======================================== + 16. Version / Dropdown badges + ======================================== */ + +[data-theme='dark'] .dropdown__menu { + background: #3d0029; + border: 1px solid rgba(255, 255, 255, 0.08); +} + +[data-theme='dark'] .dropdown__link { + color: #cbd5e1; +} + +[data-theme='dark'] .dropdown__link:hover { + background: rgba(251, 191, 36, 0.1); + color: #e2e8f0; +} + +[data-theme='dark'] .dropdown__link--active { + color: #fbbf24; + background: rgba(251, 191, 36, 0.12); +} + + +/* ======================================== + 17. Homepage Isolation + (hide Docusaurus navbar/footer on homepage) + ======================================== */ + +html.homepage-active .navbar { + display: none !important; +} + +html.homepage-active .footer { + display: none !important; +} + +html.homepage-active main { + margin-top: 0; +} + +html.homepage-active [class*='docMainContainer'], +html.homepage-active [class*='mainWrapper'] { + padding-top: 0; +} + + +/* ======================================== + 18. Light mode refinements + ======================================== */ + +[data-theme='light'] .theme-doc-sidebar-container { + border-right: 1px solid rgba(0, 0, 0, 0.06); +} + +[data-theme='light'] .menu__link--active:not(.menu__link--sublist) { + background: rgba(245, 158, 11, 0.08); + color: #f59e0b; + font-weight: 600; +} + +[data-theme='light'] .menu__link:hover { + background: rgba(245, 158, 11, 0.05); +} + +[data-theme='light'] .pagination-nav__link { + border-radius: 12px; + transition: all 0.3s; +} + +[data-theme='light'] .pagination-nav__link:hover { + border-color: rgba(245, 158, 11, 0.3); + box-shadow: 0 4px 16px rgba(245, 158, 11, 0.08); +} + +[data-theme='light'] blockquote { + border-left-color: #f59e0b; } diff --git a/docs-src/src/pages/index.js b/docs-src/src/pages/index.js index d2d965fd..495cce0f 100644 --- a/docs-src/src/pages/index.js +++ b/docs-src/src/pages/index.js @@ -1,165 +1,590 @@ -import clsx from 'clsx'; -import Link from '@docusaurus/Link'; +import React, { useEffect, useLayoutEffect, useRef, useState, useCallback } from 'react'; +import Layout from '@theme/Layout'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import useBaseUrl from '@docusaurus/useBaseUrl'; -import Layout from '@theme/Layout'; -import { OnlineFeatureStoreFeatures, TruffleboxUIFeatures, SDKsFeatures } from '@site/src/components/HomepageFeatures'; - -import Heading from '@theme/Heading'; import styles from './index.module.css'; -function HomepageHeader() { - const {siteConfig} = useDocusaurusContext(); +// ─── Data ────────────────────────────────────────────── + +const BARRIERS = [ + { + icon: '\u{1F9E0}', + title: 'Focus on building intelligence, not infrastructure', + questions: [ + 'Does every model deployment require a full-stack integration effort?', + 'Do engineers have to rebuild feature retrieval, endpoint integrations, and logging for each new model?', + 'Does changing a simple expression like 0.2\u00D7s\u2081 + 0.8\u00D7s\u2082 to 0.3\u00D7s\u2081 + 0.7\u00D7s\u2082 really need code reviews and redeployments?', + 'Why does deploying intelligence require the devops team to provision infra?', + ], + answer: + 'Machine learning teams should be iterating on models, not systems. Yet today, infrastructure complexity turns simple improvements into weeks of engineering effort, slowing experimentation and innovation.', + }, + { + icon: '\u{1F4B0}', + title: 'Built for scale without exponential cost growth', + questions: [ + 'Do your infrastructure costs scale faster than your ML impact?', + 'Are you recomputing the same features, reloading the same data, and moving the same bytes across systems repeatedly?', + 'Are expensive GPUs and compute sitting underutilized while workloads wait on data or inefficient pipelines?', + 'Why does scaling ML often mean scaling cost linearly\u2014or worse?', + ], + answer: + 'A modern ML platform should eliminate redundant computation, reuse features intelligently, and optimize data access across memory, NVMe, and object storage. Compute should be pooled, scheduled efficiently, and fully utilized\u2014ensuring that scale drives impact, not runaway infrastructure costs.', + }, + { + icon: '\u{1F30D}', + title: 'Freedom to deploy anywhere, without lock-in', + questions: [ + 'Are your models tied to a single cloud, making migration costly and complex?', + 'Does adopting managed services today limit your ability to optimize cost or move infrastructure tomorrow?', + 'Can you deploy the same ML stack across public cloud, private cloud, or sovereign environments without redesigning everything?', + 'Why should infrastructure choices dictate the future of your ML systems?', + ], + answer: + 'A modern ML platform should be built on open standards and cloud-neutral abstractions, allowing you to deploy anywhere\u2014public cloud, private infrastructure, or sovereign environments. This ensures complete control over your data, freedom from vendor lock-in, and the ability to optimize for cost, performance, and compliance without architectural constraints.', + }, +]; + +const COMPONENTS = [ + { + icon: '\u{26A1}', + title: 'Online Feature Store', + description: + 'BharatMLStack Online Feature Store delivers sub-10ms, high-throughput access to machine learning features for real-time inference. It seamlessly ingests batch and streaming data, validates schemas, and persists compact, versioned feature groups optimized for low latency and efficiency. With scalable storage backends, gRPC APIs, and binary-optimized formats, it ensures consistent, reliable feature serving across ML pipelines.', + cta: '/online-feature-store/v1.0.0', + }, + { + icon: '\u{1F500}', + title: 'Inferflow', + description: + "Inferflow is BharatMLStack's intelligent inference gateway that dynamically retrieves and assembles features required by ML models using a graph-based configuration called Inferpipes. It automatically resolves entity relationships, fetches features from the Online Feature Store, and constructs feature vectors without custom code.", + cta: '/inferflow/v1.0.0', + }, + { + icon: '\u{1F50D}', + title: 'Skye', + description: + 'Skye enables fast similarity retrieval by representing data as vectors and querying nearest matches in high-dimensional space. It supports pluggable vector databases, ensuring flexibility across infrastructure. The system provides tenant-level index isolation while allowing single embedding ingestion even when shared across tenants, reducing redundancy.', + cta: '/skye/v1.0.0', + }, + { + icon: '\u{1F9EE}', + title: 'Numerix', + description: + 'Numerix is a high-performance compute engine designed for ultra-fast element-wise matrix operations. Built in Rust and accelerated using SIMD, it delivers exceptional efficiency and predictable performance. Optimized for real-time inference workloads, it achieves strict sub-5ms p99 latency on matrices up to 1000\u00D710.', + cta: '/numerix/v1.0.0', + }, + { + icon: '\u{1F680}', + title: 'Predator', + description: + 'Predator streamlines infrastructure and model lifecycle management. It enables the creation of deployables with specific Triton Server versions and supports seamless model rollouts. Leveraging Helm charts and Argo CD, Predator automates Kubernetes-based deployments while integrating with KEDA for auto-scaling and performance tuning.', + cta: '/predator/v1.0.0', + }, +]; + +const STATS = [ + { target: 4.5, suffix: 'M+', decimals: 1, label: 'Daily Orders', description: 'Daily orders processed via ML pipelines' }, + { target: 2.4, suffix: 'M', decimals: 1, label: 'QPS on FS', description: 'QPS on Feature Store with batch size of 100 id lookups' }, + { target: 1, suffix: 'M+', decimals: 0, label: 'QPS Inference', description: 'QPS on Model Inference' }, + { target: 500, suffix: 'K', decimals: 0, label: 'QPS Embedding', description: 'QPS Embedding Search' }, +]; + +const DEMO_VIDEOS = [ + { + title: 'Feature Store', + description: 'Learn how to onboard and manage features using the self-serve UI for the Online Feature Store.', + url: 'https://videos.meesho.com/reels/feature_store.mp4', + }, + { + title: 'Embedding Platform', + description: 'Walkthrough of onboarding and managing embedding models via the Skye self-serve UI.', + url: 'https://videos.meesho.com/reels/embedding_platform.mp4', + }, + { + title: 'Numerix', + description: 'Step-by-step guide to configuring and running matrix operations through the Numerix self-serve UI.', + url: 'https://videos.meesho.com/reels/numerix.mp4', + }, + { + title: 'Predator', + description: 'How to deploy and manage ML models on Kubernetes using the Predator self-serve UI.', + url: 'https://videos.meesho.com/reels/predator.mp4', + }, + { + title: 'Inferflow', + description: 'Setting up inferpipes and feature retrieval graphs through the Inferflow self-serve UI.', + url: 'https://videos.meesho.com/reels/inferflow.mp4', + }, +]; + +const BLOG_POSTS = [ + { + title: "Building Meesho's ML Platform: From Chaos to Cutting-Edge (Part 1)", + category: 'ML Platform', + icon: '\u{1F680}', + link: '/blog/post-one', + }, + { + title: "Building Meesho's ML Platform: Lessons from the First-Gen System (Part 2)", + category: 'ML Platform', + icon: '\u{1F9E9}', + link: '/blog/post-two', + }, + { + title: 'Cracking the Code: Scaling Model Inference & Real-Time Embedding Search', + category: 'Inference', + icon: '\u{26A1}', + link: '/blog/post-three', + }, + { + title: 'Designing a Production-Grade LLM Inference Platform: From Model Weights to Scalable GPU Serving', + category: 'LLM', + icon: '\u{1F9E0}', + link: '/blog/post-four', + }, + { + title: 'LLM Inference Optimization Techniques: Engineering Sub-Second Latency at Scale', + category: 'Optimization', + icon: '\u{1F52C}', + link: '/blog/post-five', + }, + { + title: "Beyond Vector RAG: Building Agent Memory That Learns From Experience.", + category: 'AI Agents', + icon: '\u{1F9E0}', + link: '/blog/episodic-memory-for-agents', + }, +]; + +// ─── Components ──────────────────────────────────────── + +function CustomNav() { + const docsUrl = useBaseUrl('/'); + const blogUrl = useBaseUrl('/blog'); return ( -
-
-
- BharatMLStack Logo -
- - Welcome to {siteConfig.title} - -

+

+ ); +} + +function NetworkBackground() { + const canvasRef = useRef(null); + + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return; + const ctx = canvas.getContext('2d'); + let animationId; + let nodes = []; + const NODE_COUNT = 50; + const CONNECTION_DIST = 150; + + function resize() { + const parent = canvas.parentElement; + canvas.width = parent.offsetWidth; + canvas.height = parent.offsetHeight; + } + + function initNodes() { + nodes = []; + for (let i = 0; i < NODE_COUNT; i++) { + nodes.push({ + x: Math.random() * canvas.width, + y: Math.random() * canvas.height, + vx: (Math.random() - 0.5) * 0.4, + vy: (Math.random() - 0.5) * 0.4, + radius: Math.random() * 2 + 1, + }); + } + } + + function draw() { + ctx.clearRect(0, 0, canvas.width, canvas.height); + + // Draw connections + for (let i = 0; i < nodes.length; i++) { + for (let j = i + 1; j < nodes.length; j++) { + const dx = nodes[i].x - nodes[j].x; + const dy = nodes[i].y - nodes[j].y; + const dist = Math.sqrt(dx * dx + dy * dy); + if (dist < CONNECTION_DIST) { + const opacity = (1 - dist / CONNECTION_DIST) * 0.25; + ctx.beginPath(); + ctx.moveTo(nodes[i].x, nodes[i].y); + ctx.lineTo(nodes[j].x, nodes[j].y); + ctx.strokeStyle = `rgba(99, 102, 241, ${opacity})`; + ctx.lineWidth = 0.8; + ctx.stroke(); + } + } + } + + // Draw nodes + for (const node of nodes) { + ctx.beginPath(); + ctx.arc(node.x, node.y, node.radius, 0, Math.PI * 2); + ctx.fillStyle = 'rgba(139, 92, 246, 0.5)'; + ctx.fill(); + } + } + + function update() { + for (const node of nodes) { + node.x += node.vx; + node.y += node.vy; + // Bounce off edges + if (node.x < 0 || node.x > canvas.width) node.vx *= -1; + if (node.y < 0 || node.y > canvas.height) node.vy *= -1; + // Keep in bounds + node.x = Math.max(0, Math.min(canvas.width, node.x)); + node.y = Math.max(0, Math.min(canvas.height, node.y)); + } + } + + function animate() { + update(); + draw(); + animationId = requestAnimationFrame(animate); + } + + resize(); + initNodes(); + animate(); + + const resizeObserver = new ResizeObserver(() => { + resize(); + }); + resizeObserver.observe(canvas.parentElement); + + return () => { + cancelAnimationFrame(animationId); + resizeObserver.disconnect(); + }; + }, []); + + return ( +
+
+ BharatML Stack Logo +
+ ); } -function OnlineFeatureStoreAbout() { +function BarriersSection() { return ( -
-
-
-
- Built for India's Scale -

- BharatMLStack is a comprehensive, production-ready machine learning infrastructure - platform designed to democratize ML capabilities across India and beyond. Our mission - is to provide a robust, scalable, and accessible ML stack that empowers organizations - to build, deploy, and manage machine learning solutions at massive scale. -

- - Explore Online Feature Store → - -
-
-
-

🏆 Key Achievements

-
    -
  • ✅ Sub-10ms P99 latency for real-time inference
  • -
  • ✅ 1M+ RPS tested with 100 IDs per request
  • -
  • ✅ PSDB format outperforms Proto3 & Arrow
  • -
  • ✅ Multi-database: Scylla, Dragonfly, Redis
  • -
  • ✅ Production-ready with comprehensive monitoring
  • +
    +
    +
    +

    Why BharatMLStack

    +

    The Real Barriers to Scaling Machine Learning

    +

    + ML teams spend more time fighting infrastructure than building intelligence. + BharatMLStack removes those barriers. +

    +
    +
    + {BARRIERS.map((barrier, idx) => ( +
    +
    {barrier.icon}
    +

    {barrier.title}

    +
      + {barrier.questions.map((q, i) => ( +
    • {q}
    • + ))}
    +

    {barrier.answer}

    -
    + ))}
); } -function TruffleboxAbout() { +function ComponentsSection() { + const cardsRef = useRef([]); + const baseUrl = useBaseUrl('/'); + + useEffect(() => { + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + entry.target.classList.add(styles.componentCardVisible); + } + }); + }, + { threshold: 0.1, rootMargin: '0px 0px -80px 0px' } + ); + + cardsRef.current.forEach((card) => { + if (card) observer.observe(card); + }); + + return () => observer.disconnect(); + }, []); + return ( -
-
-
-
- Modern MLOps Management -

- Trufflebox UI provides a comprehensive, modern web interface for managing your entire - ML infrastructure. Built with cutting-edge web technologies, it delivers an intuitive - experience for feature management, user administration, and operational oversight. - Streamline your MLOps workflows with enterprise-grade UI components. -

- - Explore Trufflebox UI → - -
-
-
-

🎨 UI Features

-
    -
  • ✅ Comprehensive feature catalog & discovery
  • -
  • ✅ Role-based access control & user management
  • -
  • ✅ Job, Store, Admin Ops management
  • -
  • ✅ Approval flow for everything
  • -
  • ✅ Responsive design for desktop & mobile
  • -
+
+
+
+

Platform Components

+

BharatMLStack Components

+

+ Purpose-built components for every stage of the ML lifecycle, from feature + serving to model deployment. +

+
+
+ {COMPONENTS.map((comp, idx) => ( +
(cardsRef.current[idx] = el)} + > +
{comp.icon}
+
+

{comp.title}

+

{comp.description}

+ + Learn more → + +
-
+ ))}
); } -function SDKsAbout() { +function AnimatedCounter({ target, suffix, decimals, duration = 1500 }) { + const [count, setCount] = useState(0); + const [hasStarted, setHasStarted] = useState(false); + const ref = useRef(null); + + const startAnimation = useCallback(() => { + if (hasStarted) return; + setHasStarted(true); + + const startTime = performance.now(); + const step = (now) => { + const elapsed = now - startTime; + const progress = Math.min(elapsed / duration, 1); + // Ease-out cubic for a fast start that decelerates + const eased = 1 - Math.pow(1 - progress, 3); + setCount(eased * target); + if (progress < 1) { + requestAnimationFrame(step); + } else { + setCount(target); + } + }; + requestAnimationFrame(step); + }, [target, duration, hasStarted]); + + useEffect(() => { + const el = ref.current; + if (!el) return; + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + startAnimation(); + } + }, + { threshold: 0.3 } + ); + observer.observe(el); + return () => observer.disconnect(); + }, [startAnimation]); + + const display = decimals > 0 + ? count.toFixed(decimals) + : Math.round(count).toLocaleString(); + return ( -
-
-
-
- Developer-First Integration -

- Our SDKs are designed with developers in mind, providing idiomatic APIs for Go and Python - that feel natural in your existing codebase. Whether you're building microservices, - data pipelines, or ML applications, our SDKs provide the tools you need for seamless - integration with BharatMLStack's powerful infrastructure. -

- - Explore SDKs → - -
-
-
-

🛠️ Developer Tools

-
    -
  • ✅ Native Go & Python SDKs with type safety
  • -
  • ✅ High-performance gRPC
  • -
  • ✅ Apache Spark integration for publishing features
  • -
+
+ {display}{suffix} +
+ ); +} + +function StatsSection() { + return ( +
+
+
+

Proven at scale

+

Scaling Numbers

+
+
+ {STATS.map((stat, idx) => ( +
+

{stat.label}

+ +

{stat.description}

+
+ ))} +
+
+
+ ); +} + +function DemoVideosSection() { + return ( +
+
+
+

See it in action

+

Demo Videos

+

+ Watch short demos of each BharatMLStack component in action. +

+
+
+ {DEMO_VIDEOS.map((video, idx) => ( +
+
+ +
+
+

{video.title}

+

{video.description}

+
+ ))} +
+
+
+ ); +} + +function BlogSection() { + const baseUrl = useBaseUrl('/'); + return ( +
+
+
+

From our blog

+

View Our Blogs

+

+ Technical articles, architecture deep-dives, and the story behind BharatMLStack. +

+
+ +
+
+ ); +} + +function CTASection() { + const getStartedUrl = useBaseUrl('/intro'); + return ( +
+
+
+

Deploy ML models with confidence

+

+ Comprehensive stack for business-ready ML. Integrates seamlessly with enterprise + systems. Robust security and regulatory compliance. +

+
@@ -167,21 +592,96 @@ function SDKsAbout() { ); } +function CustomFooter() { + const docsUrl = useBaseUrl('/'); + const blogUrl = useBaseUrl('/blog'); + return ( + + ); +} + +// ─── Page ────────────────────────────────────────────── + export default function Home() { - const {siteConfig} = useDocusaurusContext(); + const { siteConfig } = useDocusaurusContext(); + + // Hide Docusaurus navbar/footer on homepage (client-side, before paint) + useLayoutEffect(() => { + document.documentElement.classList.add('homepage-active'); + return () => { + document.documentElement.classList.remove('homepage-active'); + }; + }, []); + return ( - -
- - - - - - -
+ description="Open source, end-to-end ML infrastructure stack built for scale, speed, and simplicity." + > + {/* Inline style ensures Docusaurus navbar/footer are hidden during SSR and before JS hydration */} + +
+ + + + + + + + + +
); } diff --git a/docs-src/src/pages/index.module.css b/docs-src/src/pages/index.module.css index 30770d52..3dc200fc 100644 --- a/docs-src/src/pages/index.module.css +++ b/docs-src/src/pages/index.module.css @@ -1,144 +1,1005 @@ /** - * CSS files with the .module.css suffix will be treated as CSS modules - * and scoped locally. + * Homepage CSS Module + * Dark-themed design (primary), with light mode variant. + * Based on reference HTML design for BharatMLStack. */ -.heroBanner { - padding: 4rem 0; - text-align: center; - position: relative; - overflow: hidden; +/* ======================================== + CSS Variables (scoped via data-theme) + ======================================== */ + +:root { + --hp-primary: #fbbf24; + --hp-primary-dark: #f59e0b; + --hp-secondary: #8b5cf6; + --hp-accent: #06b6d4; + --hp-success: #10b981; + --hp-dark: #27001D; + --hp-dark-light: #3d0029; + --hp-text: #e2e8f0; + --hp-text-muted: #94a3b8; + --hp-bg-card: rgba(255, 255, 255, 0.03); + --hp-bg-page: #27001D; } -@media screen and (max-width: 996px) { - .heroBanner { - padding: 2rem; - } +[data-theme='light'] { + --hp-dark: #f8fafc; + --hp-dark-light: #f1f5f9; + --hp-text: #1e293b; + --hp-text-muted: #64748b; + --hp-bg-card: rgba(0, 0, 0, 0.02); + --hp-bg-page: #f8fafc; } -.logoContainer { - margin-bottom: 2rem; +/* ======================================== + Page wrapper + ======================================== */ + +.homepageWrapper { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; + background: var(--hp-bg-page); + color: var(--hp-text); + line-height: 1.6; + overflow-x: hidden; +} + +/* ======================================== + Custom Navigation + ======================================== */ + +.customNav { + position: fixed; + top: 0; + width: 100%; + background: rgba(39, 0, 29, 0.85); + backdrop-filter: blur(20px); + border-bottom: 1px solid rgba(255, 255, 255, 0.15); + z-index: 1000; + padding: 1.2rem 0; + transition: transform 0.3s ease; +} + +[data-theme='light'] .customNav { + background: rgba(255, 255, 255, 0.85); + border-bottom: 1px solid rgba(0, 0, 0, 0.08); +} + +.navContainer { + max-width: 1400px; + margin: 0 auto; + padding: 0 2rem; display: flex; - justify-content: center; + justify-content: space-between; align-items: center; } -.heroLogo { - width: 180px; - height: 180px; - filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.1)); - transition: transform 0.3s ease; +.logo { + font-size: 1.6rem; + font-weight: 800; + background: linear-gradient(135deg, #fbbf24, #f59e0b, #06b6d4); + background-size: 200% 200%; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + animation: hpGradientShift 3s ease infinite; + text-decoration: none; +} + +@keyframes hpGradientShift { + 0%, 100% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } +} + +.navLinks { + display: flex; + gap: 2.5rem; + align-items: center; +} + +.navLink { + color: var(--hp-text); + text-decoration: none; + transition: color 0.3s; + font-weight: 500; +} + +.navLink:hover { + color: var(--hp-primary); + text-decoration: none; +} + +/* ======================================== + Buttons + ======================================== */ + +.btn { + padding: 0.75rem 2rem; + border-radius: 50px; + text-decoration: none; + font-weight: 600; + transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); + display: inline-block; + cursor: pointer; + border: none; + font-size: 1rem; +} + +.btn:hover { + text-decoration: none; +} + +.btnPrimary { + background: linear-gradient(135deg, #fbbf24, #f59e0b); + color: white; + box-shadow: 0 10px 30px rgba(251, 191, 36, 0.3); +} + +.btnPrimary:hover { + transform: translateY(-3px); + box-shadow: 0 15px 40px rgba(251, 191, 36, 0.5); + color: white; +} + +.btnSecondary { + background: rgba(255, 255, 255, 0.05); + color: var(--hp-text); + border: 2px solid rgba(251, 191, 36, 0.5); +} + +[data-theme='light'] .btnSecondary { + background: rgba(251, 191, 36, 0.05); + border-color: rgba(251, 191, 36, 0.4); +} + +.btnSecondary:hover { + background: rgba(251, 191, 36, 0.2); + border-color: var(--hp-primary); + transform: translateY(-3px); + color: var(--hp-text); +} + +.btnWhite { + background: white; + color: var(--hp-primary); +} + +.btnWhite:hover { + background: #f8fafc; + transform: translateY(-3px) scale(1.05); + color: var(--hp-primary); +} + +.btnOutlineWhite { + background: transparent; + border: 2px solid white; + color: white; +} + +.btnOutlineWhite:hover { + background: rgba(255, 255, 255, 0.15); + color: white; + transform: translateY(-3px); } -.heroLogo:hover { - transform: scale(1.05); +/* ======================================== + Hero Section + ======================================== */ + +.hero { + min-height: 100vh; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 4rem; + align-items: center; + padding: 10rem 2rem 5rem; + max-width: 1400px; + margin: 0 auto; + position: relative; + z-index: 1; + overflow: hidden; } -@media screen and (max-width: 768px) { - .heroLogo { - width: 120px; - height: 120px; +.networkCanvas { + position: absolute; + top: 0; + left: -2rem; + width: calc(100% + 4rem); + height: 100%; + z-index: 0; + pointer-events: none; +} + +.heroContent { + animation: hpFadeInUp 1s ease-out; + position: relative; + z-index: 1; +} + +@keyframes hpFadeInUp { + from { + opacity: 0; + transform: translateY(40px); } - - .logoContainer { - margin-bottom: 1.5rem; + to { + opacity: 1; + transform: translateY(0); } } -.buttons { - display: flex; - align-items: center; - justify-content: center; - gap: 1rem; +.heroBadge { + display: inline-block; + padding: 0.5rem 1.5rem; + background: rgba(251, 191, 36, 0.1); + border: 1px solid rgba(251, 191, 36, 0.3); + border-radius: 50px; + color: var(--hp-primary); + font-size: 0.9rem; + font-weight: 600; margin-bottom: 2rem; + backdrop-filter: blur(10px); } -@media screen and (max-width: 768px) { - .buttons { - flex-direction: column; - gap: 0.5rem; - } +.heroTitle { + font-size: 4.5rem; + font-weight: 900; + margin-bottom: 1.5rem; + line-height: 1.1; + background: linear-gradient(135deg, #fff 0%, #a5b4fc 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; } -.statsContainer { +[data-theme='light'] .heroTitle { + background: linear-gradient(135deg, #1e293b 0%, #fbbf24 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.heroSubtitle { + font-size: 1.25rem; + color: var(--hp-text-muted); + margin-bottom: 2.5rem; + line-height: 1.8; +} + +.heroButtons { display: flex; - justify-content: center; + gap: 1.5rem; + flex-wrap: wrap; +} + +.heroImage { + position: relative; + z-index: 1; + animation: hpFadeInUp 1s ease-out 0.3s both; +} + +.heroImage img { + width: 100%; + border-radius: 20px; + box-shadow: 0 40px 80px rgba(0, 0, 0, 0.5); +} + +[data-theme='light'] .heroImage img { + box-shadow: 0 40px 80px rgba(0, 0, 0, 0.15); +} + +.adoptionBadge { + text-align: center; + margin-top: 3rem; + animation: hpFadeInUp 1s ease-out 0.6s both; +} + +.adoptionBadge p { + color: var(--hp-text-muted); + font-size: 0.95rem; +} + +/* ======================================== + Section (generic) + ======================================== */ + +.section { + padding: 8rem 2rem; + position: relative; + z-index: 1; +} + +.container { + max-width: 1400px; + margin: 0 auto; +} + +.sectionHeader { + text-align: center; + margin-bottom: 5rem; +} + +.sectionSubtitle { + font-size: 0.95rem; + color: var(--hp-primary); + font-weight: 700; + text-transform: uppercase; + letter-spacing: 2px; + margin-bottom: 1rem; +} + +.sectionTitle { + font-size: 3.5rem; + font-weight: 900; + margin-bottom: 1.5rem; + background: linear-gradient(135deg, #fff, #a5b4fc); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +[data-theme='light'] .sectionTitle { + background: linear-gradient(135deg, #1e293b, #fbbf24); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.sectionDescription { + font-size: 1.2rem; + color: var(--hp-text-muted); + max-width: 800px; + margin: 0 auto; +} + +/* ======================================== + Barriers Section (3-panel) + ======================================== */ + +.barriersGrid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 2.5rem; + margin-top: 4rem; +} + +.barrierCard { + background: var(--hp-bg-card); + backdrop-filter: blur(20px); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 24px; + padding: 2.5rem; + transition: all 0.4s; +} + +[data-theme='light'] .barrierCard { + background: white; + border-color: rgba(0, 0, 0, 0.08); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05); +} + +.barrierCard:hover { + transform: translateY(-8px); + border-color: rgba(251, 191, 36, 0.3); + box-shadow: 0 20px 50px rgba(0, 0, 0, 0.4); +} + +[data-theme='light'] .barrierCard:hover { + box-shadow: 0 20px 50px rgba(251, 191, 36, 0.12); + border-color: rgba(251, 191, 36, 0.3); +} + +.barrierIcon { + font-size: 2.5rem; + margin-bottom: 1.5rem; +} + +.barrierCard h3 { + font-size: 1.4rem; + font-weight: 700; + margin-bottom: 1rem; + color: var(--hp-text); +} + +.barrierCard p { + color: var(--hp-text-muted); + line-height: 1.8; + font-size: 0.95rem; +} + +.barrierQuestions { + list-style: none; + padding: 0; + margin: 1rem 0; +} + +.barrierQuestions li { + color: var(--hp-text-muted); + padding: 0.4rem 0; + font-size: 0.92rem; + line-height: 1.6; + position: relative; + padding-left: 1.2rem; +} + +.barrierQuestions li::before { + content: '?'; + position: absolute; + left: 0; + color: var(--hp-primary); + font-weight: 700; +} + +.barrierAnswer { + margin-top: 1rem; + color: var(--hp-text-muted); + font-size: 0.92rem; + line-height: 1.8; + border-top: 1px solid rgba(255, 255, 255, 0.06); + padding-top: 1rem; +} + +[data-theme='light'] .barrierAnswer { + border-top-color: rgba(0, 0, 0, 0.06); +} + +/* ======================================== + Component Cards (5 cards) + ======================================== */ + +.componentsGrid { + display: grid; + grid-template-columns: repeat(3, 1fr); gap: 3rem; - margin-top: 2rem; - opacity: 0.9; + margin-top: 4rem; } -@media screen and (max-width: 768px) { - .statsContainer { - flex-direction: column; - gap: 1rem; - align-items: center; - } +.componentCard { + background: var(--hp-bg-card); + backdrop-filter: blur(20px); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 24px; + overflow: hidden; + transition: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275); + opacity: 0; + transform: translateY(50px); +} + +.componentCardVisible { + opacity: 1; + transform: translateY(0); +} + +[data-theme='light'] .componentCard { + background: white; + border-color: rgba(0, 0, 0, 0.08); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05); +} + +.componentCard:hover { + transform: translateY(-10px); + border-color: rgba(251, 191, 36, 0.3); + box-shadow: 0 30px 60px rgba(0, 0, 0, 0.5); +} + +[data-theme='light'] .componentCard:hover { + box-shadow: 0 30px 60px rgba(251, 191, 36, 0.1); +} + +.componentCardVisible:hover { + transform: translateY(-10px); +} + +.componentContent { + padding: 2.5rem; +} + +.componentContent h3 { + font-size: 1.6rem; + margin-bottom: 1rem; + font-weight: 700; + color: var(--hp-text); +} + +.componentContent p { + color: var(--hp-text-muted); + margin-bottom: 1.5rem; + line-height: 1.7; +} + +.componentLink { + color: var(--hp-primary); + text-decoration: none; + font-weight: 600; + display: inline-flex; + align-items: center; + gap: 0.5rem; + transition: gap 0.3s; +} + +.componentLink:hover { + gap: 1rem; + text-decoration: none; + color: var(--hp-primary); } -.statItem { +.componentIcon { + width: 100%; + height: 180px; display: flex; - flex-direction: column; align-items: center; + justify-content: center; + font-size: 4rem; + background: linear-gradient(135deg, rgba(251, 191, 36, 0.1), rgba(245, 158, 11, 0.1)); +} + +[data-theme='light'] .componentIcon { + background: linear-gradient(135deg, rgba(251, 191, 36, 0.06), rgba(245, 158, 11, 0.06)); +} + +/* ======================================== + Stats Grid + ======================================== */ + +.statsSection { + background: rgba(0, 0, 0, 0.2); +} + +[data-theme='light'] .statsSection { + background: rgba(251, 191, 36, 0.03); +} + +.statsGrid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 2.5rem; + margin-top: 4rem; +} + +.statCard { + background: var(--hp-bg-card); + backdrop-filter: blur(20px); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 20px; + padding: 2.5rem; text-align: center; - color: white; + transition: all 0.4s; +} + +[data-theme='light'] .statCard { + background: white; + border-color: rgba(0, 0, 0, 0.08); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05); +} + +.statCard:hover { + transform: translateY(-5px); + border-color: rgba(251, 191, 36, 0.3); +} + +.statLabel { + font-size: 0.9rem; + color: var(--hp-text-muted); + text-transform: uppercase; + letter-spacing: 1.5px; + margin-bottom: 0.5rem; +} + +.statValue { + font-size: 2.5rem; + font-weight: 900; + background: linear-gradient(135deg, #fbbf24, #f59e0b); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.statDescription { + color: var(--hp-text-muted); + font-size: 0.95rem; + margin-top: 0.5rem; +} + +/* ======================================== + Demo Videos Grid + ======================================== */ + +.videosGrid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 2.5rem; + margin-top: 4rem; +} + +.videoCard { + background: var(--hp-bg-card); + backdrop-filter: blur(20px); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 24px; + overflow: hidden; + transition: all 0.4s; +} + +[data-theme='light'] .videoCard { + background: white; + border-color: rgba(0, 0, 0, 0.08); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05); +} + +.videoCard:hover { + transform: translateY(-8px); + border-color: rgba(251, 191, 36, 0.3); + box-shadow: 0 20px 50px rgba(0, 0, 0, 0.4); +} + +[data-theme='light'] .videoCard:hover { + box-shadow: 0 20px 50px rgba(251, 191, 36, 0.12); +} + +.videoWrapper { + position: relative; + width: 100%; + aspect-ratio: 16 / 9; + background: #000; + overflow: hidden; +} + +.videoPlayer { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} + +.videoContent { + padding: 1.5rem 2rem 2rem; } -.statItem strong { - font-size: 1.5rem; +.videoContent h3 { + font-size: 1.3rem; font-weight: 700; - margin-bottom: 0.25rem; + margin-bottom: 0.5rem; + color: var(--hp-text); +} + +.videoContent p { + color: var(--hp-text-muted); + font-size: 0.92rem; + line-height: 1.6; + margin: 0; +} + +/* ======================================== + Blog Grid + ======================================== */ + +.blogGrid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); + gap: 2.5rem; + margin-top: 4rem; +} + +.blogCard { + background: var(--hp-bg-card); + backdrop-filter: blur(20px); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 20px; + overflow: hidden; + transition: all 0.4s; + text-decoration: none; + color: inherit; display: block; } -.statItem span { - font-size: 0.875rem; - opacity: 0.8; - text-transform: uppercase; - letter-spacing: 0.5px; +[data-theme='light'] .blogCard { + background: white; + border-color: rgba(0, 0, 0, 0.08); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05); } -.aboutSection { - padding: 4rem 0; - background-color: var(--ifm-background-surface-color); +.blogCard:hover { + transform: translateY(-5px); + border-color: rgba(251, 191, 36, 0.3); + text-decoration: none; + color: inherit; } -.highlightBox { - background: linear-gradient(135deg, #f8f9ff 0%, #e8f0ff 100%); - border: 1px solid rgba(69, 8, 57, 0.1); - border-radius: 12px; +.blogCardIcon { + width: 100%; + height: 160px; + display: flex; + align-items: center; + justify-content: center; + font-size: 3rem; + background: linear-gradient(135deg, rgba(251, 191, 36, 0.15), rgba(6, 182, 212, 0.15)); +} + +[data-theme='light'] .blogCardIcon { + background: linear-gradient(135deg, rgba(251, 191, 36, 0.08), rgba(6, 182, 212, 0.08)); +} + +.blogContent { padding: 2rem; - height: 100%; } -.highlightBox h3 { - color: var(--bharatml-primary); +.blogCategory { + display: inline-block; + padding: 0.25rem 0.75rem; + background: rgba(251, 191, 36, 0.2); + border-radius: 12px; + font-size: 0.75rem; + color: var(--hp-primary); + font-weight: 700; + text-transform: uppercase; margin-bottom: 1rem; - font-size: 1.25rem; } -.highlightBox ul { +.blogCard h3 { + font-size: 1.3rem; + margin-bottom: 0.75rem; + font-weight: 700; + color: var(--hp-text); +} + +.blogMeta { + display: flex; + align-items: center; + gap: 0.5rem; + color: var(--hp-text-muted); + font-size: 0.85rem; +} + +/* ======================================== + CTA Section + ======================================== */ + +.ctaSection { + background: linear-gradient(135deg, rgba(139, 0, 77, 0.3), rgba(99, 0, 54, 0.4)); + border: 2px solid rgba(139, 0, 77, 0.5); + border-radius: 40px; + padding: 6rem 4rem; + text-align: center; + margin: 2rem 0; + position: relative; + overflow: hidden; +} + +.ctaSection::before { + content: ''; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, transparent 70%); + animation: hpRotate 20s linear infinite; +} + +@keyframes hpRotate { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +.ctaTitle { + font-size: 3.5rem; + font-weight: 900; + margin-bottom: 1.5rem; + position: relative; + z-index: 1; + color: white; + background: none; + -webkit-text-fill-color: white; +} + +.ctaDescription { + font-size: 1.3rem; + margin-bottom: 3rem; + position: relative; + z-index: 1; + color: rgba(255, 255, 255, 0.9); +} + +.ctaButtons { + display: flex; + gap: 1.5rem; + justify-content: center; + flex-wrap: wrap; + position: relative; + z-index: 1; +} + +/* ======================================== + Custom Footer + ======================================== */ + +.customFooter { + background: var(--hp-dark-light); + border-top: 1px solid rgba(255, 255, 255, 0.05); + padding: 5rem 2rem 2rem; + position: relative; + z-index: 1; +} + +[data-theme='light'] .customFooter { + background: #f1f5f9; + border-top-color: rgba(0, 0, 0, 0.08); +} + +.footerContent { + max-width: 1400px; + margin: 0 auto; + display: grid; + grid-template-columns: 2fr 1fr 1fr 1fr; + gap: 4rem; + margin-bottom: 3rem; +} + +.footerSection h4 { + font-size: 1.2rem; + margin-bottom: 1.5rem; + font-weight: 700; + color: var(--hp-text); +} + +.footerSection p { + color: var(--hp-text-muted); + line-height: 1.8; +} + +.footerList { list-style: none; padding: 0; margin: 0; } -.highlightBox li { - padding: 0.5rem 0; - font-size: 0.95rem; - color: var(--bharatml-text); +.footerList li { + margin-bottom: 0.75rem; +} + +.footerList a { + color: var(--hp-text-muted); + text-decoration: none; + transition: all 0.3s; +} + +.footerList a:hover { + color: var(--hp-primary); + text-decoration: none; +} + +.footerBottom { + max-width: 1400px; + margin: 0 auto; + padding-top: 2rem; + border-top: 1px solid rgba(255, 255, 255, 0.05); + display: flex; + justify-content: space-between; + align-items: center; + color: var(--hp-text-muted); + flex-wrap: wrap; + gap: 1rem; +} + +[data-theme='light'] .footerBottom { + border-top-color: rgba(0, 0, 0, 0.08); +} + +.footerLinks { + display: flex; + gap: 2rem; +} + +.footerLinks a { + color: var(--hp-text-muted); + text-decoration: none; + transition: color 0.3s; +} + +.footerLinks a:hover { + color: var(--hp-primary); + text-decoration: none; } -.highlightBox li:not(:last-child) { - border-bottom: 1px solid rgba(69, 8, 57, 0.05); +/* ======================================== + Responsive + ======================================== */ + +@media (max-width: 1024px) { + .hero { + grid-template-columns: 1fr; + text-align: center; + padding-top: 8rem; + } + + .heroImage { + order: -1; + margin: 2rem 0 0 0; + } + + .heroContent { + order: 1; + } + + .heroButtons { + justify-content: center; + } + + .componentsGrid { + grid-template-columns: 1fr 1fr; + } + + .barriersGrid { + grid-template-columns: 1fr; + } + + .videosGrid { + grid-template-columns: 1fr 1fr; + } + + .statsGrid { + grid-template-columns: 1fr 1fr; + } + + .footerContent { + grid-template-columns: 1fr 1fr; + } } -/* Dark mode adjustments */ -[data-theme='dark'] .highlightBox { - background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); - border-color: rgba(139, 69, 130, 0.2); +@media (max-width: 768px) { + .heroTitle { + font-size: 3rem; + } + + .sectionTitle { + font-size: 2.5rem; + } + + .navLinks a:not(.btn):not(.btnPrimary) { + display: none; + } + + .componentsGrid, + .blogGrid, + .videosGrid { + grid-template-columns: 1fr; + } + + .statsGrid { + grid-template-columns: 1fr; + } + + .footerContent { + grid-template-columns: 1fr; + } + + .ctaTitle { + font-size: 2.5rem; + } + + .ctaSection { + padding: 4rem 2rem; + border-radius: 20px; + } + + .section { + padding: 4rem 1.5rem; + } + + .hero { + padding: 7rem 1.5rem 3rem; + } } -[data-theme='dark'] .highlightBox li { - color: var(--bharatml-text); +@media (max-width: 480px) { + .heroTitle { + font-size: 2.2rem; + } + + .sectionTitle { + font-size: 2rem; + } + + .heroButtons { + flex-direction: column; + align-items: center; + } } diff --git a/docs-src/src/theme/Root.js b/docs-src/src/theme/Root.js new file mode 100644 index 00000000..3ceb9c99 --- /dev/null +++ b/docs-src/src/theme/Root.js @@ -0,0 +1,14 @@ +import React from 'react'; + +export default function Root({ children }) { + return ( + <> +
+
+
+
+
+ {children} + + ); +} diff --git a/docs-src/static/img/bharatml-stack-logo.jpg b/docs-src/static/img/bharatml-stack-logo.jpg new file mode 100644 index 00000000..46ecdfa5 Binary files /dev/null and b/docs-src/static/img/bharatml-stack-logo.jpg differ diff --git a/docs-src/static/img/skye-rt-consumer-flow.png b/docs-src/static/img/skye-rt-consumer-flow.png new file mode 100644 index 00000000..11e40769 Binary files /dev/null and b/docs-src/static/img/skye-rt-consumer-flow.png differ diff --git a/docs-src/static/img/skye-system-overview.png b/docs-src/static/img/skye-system-overview.png new file mode 100644 index 00000000..2f992dbf Binary files /dev/null and b/docs-src/static/img/skye-system-overview.png differ diff --git a/docs-src/static/img/v1.0.0-inferflow-arch.png b/docs-src/static/img/v1.0.0-inferflow-arch.png new file mode 100644 index 00000000..acad4888 Binary files /dev/null and b/docs-src/static/img/v1.0.0-inferflow-arch.png differ diff --git a/docs-src/static/img/v1.0.0-inferflow-dag-matrix.png b/docs-src/static/img/v1.0.0-inferflow-dag-matrix.png new file mode 100644 index 00000000..2345d69f Binary files /dev/null and b/docs-src/static/img/v1.0.0-inferflow-dag-matrix.png differ diff --git a/docs-src/static/img/v1.0.0-predator-hld.png b/docs-src/static/img/v1.0.0-predator-hld.png new file mode 100644 index 00000000..3e8a21ad Binary files /dev/null and b/docs-src/static/img/v1.0.0-predator-hld.png differ diff --git a/docs-src/yarn.lock b/docs-src/yarn.lock index a0cac9b6..c1e7229f 100644 --- a/docs-src/yarn.lock +++ b/docs-src/yarn.lock @@ -6185,7 +6185,7 @@ mrmime@^2.0.0: ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7numerixpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== ms@2.1.3, ms@^2.1.3: version "2.1.3" @@ -6729,7 +6729,7 @@ postcss-gap-properties@^6.0.0: postcss-image-set-function@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/postcss-image-set-function/-/postcss-image-set-function-7.0.0.tgz#538e94e16716be47f9df0573b56bbaca86e1da53" - integrity sha512-QL7W7QNlZuzOwBTeXEmbVckNt1FSmhQtbMRvGGqqU4Nf4xk6KUEQhAoWuMzwbSv5jxnumerixZ5Tv7eiDB9U87znA== + integrity sha512-QL7W7QNlZuzOwBTeXEmbVckNt1FSmhQtbMRvGGqqU4Nf4xk6KUEQhAoWuMzwbSv5jxiRiSZ5Tv7eiDB9U87znA== dependencies: "@csstools/utilities" "^2.0.0" postcss-value-parser "^4.2.0" diff --git a/docs/.nojekyll b/docs/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/docs/404.html b/docs/404.html index 4d1bd7ff..29793efd 100644 --- a/docs/404.html +++ b/docs/404.html @@ -4,14 +4,14 @@ BharatMLStack - - - + + + -

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

+

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

\ No newline at end of file diff --git a/docs/assets/css/styles.8dc7ce64.css b/docs/assets/css/styles.8dc7ce64.css deleted file mode 100644 index 088734c6..00000000 --- a/docs/assets/css/styles.8dc7ce64.css +++ /dev/null @@ -1 +0,0 @@ -@layer docusaurus.infima,docusaurus.theme-common,docusaurus.theme-classic,docusaurus.core,docusaurus.plugin-debug,docusaurus.theme-mermaid,docusaurus.theme-live-codeblock,docusaurus.theme-search-algolia.docsearch,docusaurus.theme-search-algolia;@layer docusaurus.infima{.col,.container{padding:0 var(--ifm-spacing-horizontal);width:100%}.markdown>h2,.markdown>h3,.markdown>h4,.markdown>h5,.markdown>h6{margin-bottom:calc(var(--ifm-heading-vertical-rhythm-bottom)*var(--ifm-leading))}.markdown li,body{word-wrap:break-word}body,ol ol,ol ul,ul ol,ul ul{margin:0}pre,table{overflow:auto}blockquote,pre{margin:0 0 var(--ifm-spacing-vertical)}.breadcrumbs__link,.button{transition-timing-function:var(--ifm-transition-timing-default)}.button,code{vertical-align:middle}.button--outline.button--active,.button--outline:active,.button--outline:hover,:root{--ifm-button-color:var(--ifm-font-color-base-inverse)}.menu__link:hover,a{transition:color var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.navbar--dark,:root{--ifm-navbar-link-hover-color:var(--ifm-color-primary)}.menu,.navbar-sidebar{overflow-x:hidden}:root,html[data-theme=dark]{--ifm-color-emphasis-500:var(--ifm-color-gray-500)}:root{--ifm-color-scheme:light;--ifm-dark-value:10%;--ifm-darker-value:15%;--ifm-darkest-value:30%;--ifm-light-value:15%;--ifm-lighter-value:30%;--ifm-lightest-value:50%;--ifm-contrast-background-value:90%;--ifm-contrast-foreground-value:70%;--ifm-contrast-background-dark-value:70%;--ifm-contrast-foreground-dark-value:90%;--ifm-color-primary:#3578e5;--ifm-color-secondary:#ebedf0;--ifm-color-success:#00a400;--ifm-color-info:#54c7ec;--ifm-color-warning:#ffba00;--ifm-color-danger:#fa383e;--ifm-color-primary-dark:#306cce;--ifm-color-primary-darker:#2d66c3;--ifm-color-primary-darkest:#2554a0;--ifm-color-primary-light:#538ce9;--ifm-color-primary-lighter:#72a1ed;--ifm-color-primary-lightest:#9abcf2;--ifm-color-primary-contrast-background:#ebf2fc;--ifm-color-primary-contrast-foreground:#102445;--ifm-color-secondary-dark:#d4d5d8;--ifm-color-secondary-darker:#c8c9cc;--ifm-color-secondary-darkest:#a4a6a8;--ifm-color-secondary-light:#eef0f2;--ifm-color-secondary-lighter:#f1f2f5;--ifm-color-secondary-lightest:#f5f6f8;--ifm-color-secondary-contrast-background:#fdfdfe;--ifm-color-secondary-contrast-foreground:#474748;--ifm-color-success-dark:#009400;--ifm-color-success-darker:#008b00;--ifm-color-success-darkest:#007300;--ifm-color-success-light:#26b226;--ifm-color-success-lighter:#4dbf4d;--ifm-color-success-lightest:#80d280;--ifm-color-success-contrast-background:#e6f6e6;--ifm-color-success-contrast-foreground:#003100;--ifm-color-info-dark:#4cb3d4;--ifm-color-info-darker:#47a9c9;--ifm-color-info-darkest:#3b8ba5;--ifm-color-info-light:#6ecfef;--ifm-color-info-lighter:#87d8f2;--ifm-color-info-lightest:#aae3f6;--ifm-color-info-contrast-background:#eef9fd;--ifm-color-info-contrast-foreground:#193c47;--ifm-color-warning-dark:#e6a700;--ifm-color-warning-darker:#d99e00;--ifm-color-warning-darkest:#b38200;--ifm-color-warning-light:#ffc426;--ifm-color-warning-lighter:#ffcf4d;--ifm-color-warning-lightest:#ffdd80;--ifm-color-warning-contrast-background:#fff8e6;--ifm-color-warning-contrast-foreground:#4d3800;--ifm-color-danger-dark:#e13238;--ifm-color-danger-darker:#d53035;--ifm-color-danger-darkest:#af272b;--ifm-color-danger-light:#fb565b;--ifm-color-danger-lighter:#fb7478;--ifm-color-danger-lightest:#fd9c9f;--ifm-color-danger-contrast-background:#ffebec;--ifm-color-danger-contrast-foreground:#4b1113;--ifm-color-white:#fff;--ifm-color-black:#000;--ifm-color-gray-0:var(--ifm-color-white);--ifm-color-gray-100:#f5f6f7;--ifm-color-gray-200:#ebedf0;--ifm-color-gray-300:#dadde1;--ifm-color-gray-400:#ccd0d5;--ifm-color-gray-500:#bec3c9;--ifm-color-gray-600:#8d949e;--ifm-color-gray-700:#606770;--ifm-color-gray-800:#444950;--ifm-color-gray-900:#1c1e21;--ifm-color-gray-1000:var(--ifm-color-black);--ifm-color-emphasis-0:var(--ifm-color-gray-0);--ifm-color-emphasis-100:var(--ifm-color-gray-100);--ifm-color-emphasis-200:var(--ifm-color-gray-200);--ifm-color-emphasis-300:var(--ifm-color-gray-300);--ifm-color-emphasis-400:var(--ifm-color-gray-400);--ifm-color-emphasis-600:var(--ifm-color-gray-600);--ifm-color-emphasis-700:var(--ifm-color-gray-700);--ifm-color-emphasis-800:var(--ifm-color-gray-800);--ifm-color-emphasis-900:var(--ifm-color-gray-900);--ifm-color-emphasis-1000:var(--ifm-color-gray-1000);--ifm-color-content:var(--ifm-color-emphasis-900);--ifm-color-content-inverse:var(--ifm-color-emphasis-0);--ifm-color-content-secondary:#525860;--ifm-background-color:#0000;--ifm-background-surface-color:var(--ifm-color-content-inverse);--ifm-global-border-width:1px;--ifm-global-radius:0.4rem;--ifm-hover-overlay:#0000000d;--ifm-font-color-base:var(--ifm-color-content);--ifm-font-color-base-inverse:var(--ifm-color-content-inverse);--ifm-font-color-secondary:var(--ifm-color-content-secondary);--ifm-font-family-base:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--ifm-font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--ifm-font-size-base:100%;--ifm-font-weight-light:300;--ifm-font-weight-normal:400;--ifm-font-weight-semibold:500;--ifm-font-weight-bold:700;--ifm-font-weight-base:var(--ifm-font-weight-normal);--ifm-line-height-base:1.65;--ifm-global-spacing:1rem;--ifm-spacing-vertical:var(--ifm-global-spacing);--ifm-spacing-horizontal:var(--ifm-global-spacing);--ifm-transition-fast:200ms;--ifm-transition-slow:400ms;--ifm-transition-timing-default:cubic-bezier(0.08,0.52,0.52,1);--ifm-global-shadow-lw:0 1px 2px 0 #0000001a;--ifm-global-shadow-md:0 5px 40px #0003;--ifm-global-shadow-tl:0 12px 28px 0 #0003,0 2px 4px 0 #0000001a;--ifm-z-index-dropdown:100;--ifm-z-index-fixed:200;--ifm-z-index-overlay:400;--ifm-container-width:1140px;--ifm-container-width-xl:1320px;--ifm-code-background:#f6f7f8;--ifm-code-border-radius:var(--ifm-global-radius);--ifm-code-font-size:90%;--ifm-code-padding-horizontal:0.1rem;--ifm-code-padding-vertical:0.1rem;--ifm-pre-background:var(--ifm-code-background);--ifm-pre-border-radius:var(--ifm-code-border-radius);--ifm-pre-color:inherit;--ifm-pre-line-height:1.45;--ifm-pre-padding:1rem;--ifm-heading-color:inherit;--ifm-heading-margin-top:0;--ifm-heading-margin-bottom:var(--ifm-spacing-vertical);--ifm-heading-font-family:var(--ifm-font-family-base);--ifm-heading-font-weight:var(--ifm-font-weight-bold);--ifm-heading-line-height:1.25;--ifm-h1-font-size:2rem;--ifm-h2-font-size:1.5rem;--ifm-h3-font-size:1.25rem;--ifm-h4-font-size:1rem;--ifm-h5-font-size:0.875rem;--ifm-h6-font-size:0.85rem;--ifm-image-alignment-padding:1.25rem;--ifm-leading-desktop:1.25;--ifm-leading:calc(var(--ifm-leading-desktop)*1rem);--ifm-list-left-padding:2rem;--ifm-list-margin:1rem;--ifm-list-item-margin:0.25rem;--ifm-list-paragraph-margin:1rem;--ifm-table-cell-padding:0.75rem;--ifm-table-background:#0000;--ifm-table-stripe-background:#00000008;--ifm-table-border-width:1px;--ifm-table-border-color:var(--ifm-color-emphasis-300);--ifm-table-head-background:inherit;--ifm-table-head-color:inherit;--ifm-table-head-font-weight:var(--ifm-font-weight-bold);--ifm-table-cell-color:inherit;--ifm-link-color:var(--ifm-color-primary);--ifm-link-decoration:none;--ifm-link-hover-color:var(--ifm-link-color);--ifm-link-hover-decoration:underline;--ifm-paragraph-margin-bottom:var(--ifm-leading);--ifm-blockquote-font-size:var(--ifm-font-size-base);--ifm-blockquote-border-left-width:2px;--ifm-blockquote-padding-horizontal:var(--ifm-spacing-horizontal);--ifm-blockquote-padding-vertical:0;--ifm-blockquote-shadow:none;--ifm-blockquote-color:var(--ifm-color-emphasis-800);--ifm-blockquote-border-color:var(--ifm-color-emphasis-300);--ifm-hr-background-color:var(--ifm-color-emphasis-500);--ifm-hr-height:1px;--ifm-hr-margin-vertical:1.5rem;--ifm-scrollbar-size:7px;--ifm-scrollbar-track-background-color:#f1f1f1;--ifm-scrollbar-thumb-background-color:silver;--ifm-scrollbar-thumb-hover-background-color:#a7a7a7;--ifm-alert-background-color:inherit;--ifm-alert-border-color:inherit;--ifm-alert-border-radius:var(--ifm-global-radius);--ifm-alert-border-width:0px;--ifm-alert-border-left-width:5px;--ifm-alert-color:var(--ifm-font-color-base);--ifm-alert-padding-horizontal:var(--ifm-spacing-horizontal);--ifm-alert-padding-vertical:var(--ifm-spacing-vertical);--ifm-alert-shadow:var(--ifm-global-shadow-lw);--ifm-avatar-intro-margin:1rem;--ifm-avatar-intro-alignment:inherit;--ifm-avatar-photo-size:3rem;--ifm-badge-background-color:inherit;--ifm-badge-border-color:inherit;--ifm-badge-border-radius:var(--ifm-global-radius);--ifm-badge-border-width:var(--ifm-global-border-width);--ifm-badge-color:var(--ifm-color-white);--ifm-badge-padding-horizontal:calc(var(--ifm-spacing-horizontal)*0.5);--ifm-badge-padding-vertical:calc(var(--ifm-spacing-vertical)*0.25);--ifm-breadcrumb-border-radius:1.5rem;--ifm-breadcrumb-spacing:0.5rem;--ifm-breadcrumb-color-active:var(--ifm-color-primary);--ifm-breadcrumb-item-background-active:var(--ifm-hover-overlay);--ifm-breadcrumb-padding-horizontal:0.8rem;--ifm-breadcrumb-padding-vertical:0.4rem;--ifm-breadcrumb-size-multiplier:1;--ifm-breadcrumb-separator:url('data:image/svg+xml;utf8,');--ifm-breadcrumb-separator-filter:none;--ifm-breadcrumb-separator-size:0.5rem;--ifm-breadcrumb-separator-size-multiplier:1.25;--ifm-button-background-color:inherit;--ifm-button-border-color:var(--ifm-button-background-color);--ifm-button-border-width:var(--ifm-global-border-width);--ifm-button-font-weight:var(--ifm-font-weight-bold);--ifm-button-padding-horizontal:1.5rem;--ifm-button-padding-vertical:0.375rem;--ifm-button-size-multiplier:1;--ifm-button-transition-duration:var(--ifm-transition-fast);--ifm-button-border-radius:calc(var(--ifm-global-radius)*var(--ifm-button-size-multiplier));--ifm-button-group-spacing:2px;--ifm-card-background-color:var(--ifm-background-surface-color);--ifm-card-border-radius:calc(var(--ifm-global-radius)*2);--ifm-card-horizontal-spacing:var(--ifm-global-spacing);--ifm-card-vertical-spacing:var(--ifm-global-spacing);--ifm-toc-border-color:var(--ifm-color-emphasis-300);--ifm-toc-link-color:var(--ifm-color-content-secondary);--ifm-toc-padding-vertical:0.5rem;--ifm-toc-padding-horizontal:0.5rem;--ifm-dropdown-background-color:var(--ifm-background-surface-color);--ifm-dropdown-font-weight:var(--ifm-font-weight-semibold);--ifm-dropdown-link-color:var(--ifm-font-color-base);--ifm-dropdown-hover-background-color:var(--ifm-hover-overlay);--ifm-footer-background-color:var(--ifm-color-emphasis-100);--ifm-footer-color:inherit;--ifm-footer-link-color:var(--ifm-color-emphasis-700);--ifm-footer-link-hover-color:var(--ifm-color-primary);--ifm-footer-link-horizontal-spacing:0.5rem;--ifm-footer-padding-horizontal:calc(var(--ifm-spacing-horizontal)*2);--ifm-footer-padding-vertical:calc(var(--ifm-spacing-vertical)*2);--ifm-footer-title-color:inherit;--ifm-footer-logo-max-width:min(30rem,90vw);--ifm-hero-background-color:var(--ifm-background-surface-color);--ifm-hero-text-color:var(--ifm-color-emphasis-800);--ifm-menu-color:var(--ifm-color-emphasis-700);--ifm-menu-color-active:var(--ifm-color-primary);--ifm-menu-color-background-active:var(--ifm-hover-overlay);--ifm-menu-color-background-hover:var(--ifm-hover-overlay);--ifm-menu-link-padding-horizontal:0.75rem;--ifm-menu-link-padding-vertical:0.375rem;--ifm-menu-link-sublist-icon:url('data:image/svg+xml;utf8,');--ifm-menu-link-sublist-icon-filter:none;--ifm-navbar-background-color:var(--ifm-background-surface-color);--ifm-navbar-height:3.75rem;--ifm-navbar-item-padding-horizontal:0.75rem;--ifm-navbar-item-padding-vertical:0.25rem;--ifm-navbar-link-color:var(--ifm-font-color-base);--ifm-navbar-link-active-color:var(--ifm-link-color);--ifm-navbar-padding-horizontal:var(--ifm-spacing-horizontal);--ifm-navbar-padding-vertical:calc(var(--ifm-spacing-vertical)*0.5);--ifm-navbar-shadow:var(--ifm-global-shadow-lw);--ifm-navbar-search-input-background-color:var(--ifm-color-emphasis-200);--ifm-navbar-search-input-color:var(--ifm-color-emphasis-800);--ifm-navbar-search-input-placeholder-color:var(--ifm-color-emphasis-500);--ifm-navbar-search-input-icon:url('data:image/svg+xml;utf8,');--ifm-navbar-sidebar-width:83vw;--ifm-pagination-border-radius:var(--ifm-global-radius);--ifm-pagination-color-active:var(--ifm-color-primary);--ifm-pagination-font-size:1rem;--ifm-pagination-item-active-background:var(--ifm-hover-overlay);--ifm-pagination-page-spacing:0.2em;--ifm-pagination-padding-horizontal:calc(var(--ifm-spacing-horizontal)*1);--ifm-pagination-padding-vertical:calc(var(--ifm-spacing-vertical)*0.25);--ifm-pagination-nav-border-radius:var(--ifm-global-radius);--ifm-pagination-nav-color-hover:var(--ifm-color-primary);--ifm-pills-color-active:var(--ifm-color-primary);--ifm-pills-color-background-active:var(--ifm-hover-overlay);--ifm-pills-spacing:0.125rem;--ifm-tabs-color:var(--ifm-font-color-secondary);--ifm-tabs-color-active:var(--ifm-color-primary);--ifm-tabs-color-active-border:var(--ifm-tabs-color-active);--ifm-tabs-padding-horizontal:1rem;--ifm-tabs-padding-vertical:1rem}.badge--danger,.badge--info,.badge--primary,.badge--secondary,.badge--success,.badge--warning{--ifm-badge-border-color:var(--ifm-badge-background-color)}.button--link,.button--outline{--ifm-button-background-color:#0000}*{box-sizing:border-box}html{background-color:var(--ifm-background-color);color:var(--ifm-font-color-base);color-scheme:var(--ifm-color-scheme);font:var(--ifm-font-size-base)/var(--ifm-line-height-base) var(--ifm-font-family-base);-webkit-font-smoothing:antialiased;-webkit-tap-highlight-color:transparent;text-rendering:optimizelegibility;-webkit-text-size-adjust:100%;text-size-adjust:100%}iframe{border:0;color-scheme:auto}.container{margin:0 auto;max-width:var(--ifm-container-width)}.container--fluid{max-width:inherit}.row{display:flex;flex-wrap:wrap;margin:0 calc(var(--ifm-spacing-horizontal)*-1)}.margin-bottom--none,.margin-vert--none,.markdown>:last-child{margin-bottom:0!important}.margin-top--none,.margin-vert--none{margin-top:0!important}.row--no-gutters{margin-left:0;margin-right:0}.margin-horiz--none,.margin-right--none{margin-right:0!important}.row--no-gutters>.col{padding-left:0;padding-right:0}.row--align-top{align-items:flex-start}.row--align-bottom{align-items:flex-end}.row--align-center{align-items:center}.row--align-stretch{align-items:stretch}.row--align-baseline{align-items:baseline}.col{--ifm-col-width:100%;flex:1 0;margin-left:0;max-width:var(--ifm-col-width)}.padding-bottom--none,.padding-vert--none{padding-bottom:0!important}.padding-top--none,.padding-vert--none{padding-top:0!important}.padding-horiz--none,.padding-left--none{padding-left:0!important}.padding-horiz--none,.padding-right--none{padding-right:0!important}.col[class*=col--]{flex:0 0 var(--ifm-col-width)}.col--1{--ifm-col-width:8.33333%}.col--offset-1{margin-left:8.33333%}.col--2{--ifm-col-width:16.66667%}.col--offset-2{margin-left:16.66667%}.col--3{--ifm-col-width:25%}.col--offset-3{margin-left:25%}.col--4{--ifm-col-width:33.33333%}.col--offset-4{margin-left:33.33333%}.col--5{--ifm-col-width:41.66667%}.col--offset-5{margin-left:41.66667%}.col--6{--ifm-col-width:50%}.col--offset-6{margin-left:50%}.col--7{--ifm-col-width:58.33333%}.col--offset-7{margin-left:58.33333%}.col--8{--ifm-col-width:66.66667%}.col--offset-8{margin-left:66.66667%}.col--9{--ifm-col-width:75%}.col--offset-9{margin-left:75%}.col--10{--ifm-col-width:83.33333%}.col--offset-10{margin-left:83.33333%}.col--11{--ifm-col-width:91.66667%}.col--offset-11{margin-left:91.66667%}.col--12{--ifm-col-width:100%}.col--offset-12{margin-left:100%}.margin-horiz--none,.margin-left--none{margin-left:0!important}.margin--none{margin:0!important}.margin-bottom--xs,.margin-vert--xs{margin-bottom:.25rem!important}.margin-top--xs,.margin-vert--xs{margin-top:.25rem!important}.margin-horiz--xs,.margin-left--xs{margin-left:.25rem!important}.margin-horiz--xs,.margin-right--xs{margin-right:.25rem!important}.margin--xs{margin:.25rem!important}.margin-bottom--sm,.margin-vert--sm{margin-bottom:.5rem!important}.margin-top--sm,.margin-vert--sm{margin-top:.5rem!important}.margin-horiz--sm,.margin-left--sm{margin-left:.5rem!important}.margin-horiz--sm,.margin-right--sm{margin-right:.5rem!important}.margin--sm{margin:.5rem!important}.margin-bottom--md,.margin-vert--md{margin-bottom:1rem!important}.margin-top--md,.margin-vert--md{margin-top:1rem!important}.margin-horiz--md,.margin-left--md{margin-left:1rem!important}.margin-horiz--md,.margin-right--md{margin-right:1rem!important}.margin--md{margin:1rem!important}.margin-bottom--lg,.margin-vert--lg{margin-bottom:2rem!important}.margin-top--lg,.margin-vert--lg{margin-top:2rem!important}.margin-horiz--lg,.margin-left--lg{margin-left:2rem!important}.margin-horiz--lg,.margin-right--lg{margin-right:2rem!important}.margin--lg{margin:2rem!important}.margin-bottom--xl,.margin-vert--xl{margin-bottom:5rem!important}.margin-top--xl,.margin-vert--xl{margin-top:5rem!important}.margin-horiz--xl,.margin-left--xl{margin-left:5rem!important}.margin-horiz--xl,.margin-right--xl{margin-right:5rem!important}.margin--xl{margin:5rem!important}.padding--none{padding:0!important}.padding-bottom--xs,.padding-vert--xs{padding-bottom:.25rem!important}.padding-top--xs,.padding-vert--xs{padding-top:.25rem!important}.padding-horiz--xs,.padding-left--xs{padding-left:.25rem!important}.padding-horiz--xs,.padding-right--xs{padding-right:.25rem!important}.padding--xs{padding:.25rem!important}.padding-bottom--sm,.padding-vert--sm{padding-bottom:.5rem!important}.padding-top--sm,.padding-vert--sm{padding-top:.5rem!important}.padding-horiz--sm,.padding-left--sm{padding-left:.5rem!important}.padding-horiz--sm,.padding-right--sm{padding-right:.5rem!important}.padding--sm{padding:.5rem!important}.padding-bottom--md,.padding-vert--md{padding-bottom:1rem!important}.padding-top--md,.padding-vert--md{padding-top:1rem!important}.padding-horiz--md,.padding-left--md{padding-left:1rem!important}.padding-horiz--md,.padding-right--md{padding-right:1rem!important}.padding--md{padding:1rem!important}.padding-bottom--lg,.padding-vert--lg{padding-bottom:2rem!important}.padding-top--lg,.padding-vert--lg{padding-top:2rem!important}.padding-horiz--lg,.padding-left--lg{padding-left:2rem!important}.padding-horiz--lg,.padding-right--lg{padding-right:2rem!important}.padding--lg{padding:2rem!important}.padding-bottom--xl,.padding-vert--xl{padding-bottom:5rem!important}.padding-top--xl,.padding-vert--xl{padding-top:5rem!important}.padding-horiz--xl,.padding-left--xl{padding-left:5rem!important}.padding-horiz--xl,.padding-right--xl{padding-right:5rem!important}.padding--xl{padding:5rem!important}code{background-color:var(--ifm-code-background);border:.1rem solid #0000001a;border-radius:var(--ifm-code-border-radius);font-family:var(--ifm-font-family-monospace);font-size:var(--ifm-code-font-size);padding:var(--ifm-code-padding-vertical) var(--ifm-code-padding-horizontal)}a code{color:inherit}pre{background-color:var(--ifm-pre-background);border-radius:var(--ifm-pre-border-radius);color:var(--ifm-pre-color);font:var(--ifm-code-font-size)/var(--ifm-pre-line-height) var(--ifm-font-family-monospace);padding:var(--ifm-pre-padding)}pre code{background-color:initial;border:none;font-size:100%;line-height:inherit;padding:0}kbd{background-color:var(--ifm-color-emphasis-0);border:1px solid var(--ifm-color-emphasis-400);border-radius:.2rem;box-shadow:inset 0 -1px 0 var(--ifm-color-emphasis-400);color:var(--ifm-color-emphasis-800);font:80% var(--ifm-font-family-monospace);padding:.15rem .3rem}h1,h2,h3,h4,h5,h6{color:var(--ifm-heading-color);font-family:var(--ifm-heading-font-family);font-weight:var(--ifm-heading-font-weight);line-height:var(--ifm-heading-line-height);margin:var(--ifm-heading-margin-top) 0 var(--ifm-heading-margin-bottom) 0}h1{font-size:var(--ifm-h1-font-size)}h2{font-size:var(--ifm-h2-font-size)}h3{font-size:var(--ifm-h3-font-size)}h4{font-size:var(--ifm-h4-font-size)}h5{font-size:var(--ifm-h5-font-size)}h6{font-size:var(--ifm-h6-font-size)}img{max-width:100%}img[align=right]{padding-left:var(--image-alignment-padding)}img[align=left]{padding-right:var(--image-alignment-padding)}.markdown{--ifm-h1-vertical-rhythm-top:3;--ifm-h2-vertical-rhythm-top:2;--ifm-h3-vertical-rhythm-top:1.5;--ifm-heading-vertical-rhythm-top:1.25;--ifm-h1-vertical-rhythm-bottom:1.25;--ifm-heading-vertical-rhythm-bottom:1}.markdown:after,.markdown:before{content:"";display:table}.markdown:after{clear:both}.markdown h1:first-child{--ifm-h1-font-size:3rem;margin-bottom:calc(var(--ifm-h1-vertical-rhythm-bottom)*var(--ifm-leading))}.markdown>h2{--ifm-h2-font-size:2rem;margin-top:calc(var(--ifm-h2-vertical-rhythm-top)*var(--ifm-leading))}.markdown>h3{--ifm-h3-font-size:1.5rem;margin-top:calc(var(--ifm-h3-vertical-rhythm-top)*var(--ifm-leading))}.markdown>h4,.markdown>h5,.markdown>h6{margin-top:calc(var(--ifm-heading-vertical-rhythm-top)*var(--ifm-leading))}.markdown>p,.markdown>pre,.markdown>ul{margin-bottom:var(--ifm-leading)}.markdown li>p{margin-top:var(--ifm-list-paragraph-margin)}.markdown li+li{margin-top:var(--ifm-list-item-margin)}ol,ul{margin:0 0 var(--ifm-list-margin);padding-left:var(--ifm-list-left-padding)}ol ol,ul ol{list-style-type:lower-roman}ol ol ol,ol ul ol,ul ol ol,ul ul ol{list-style-type:lower-alpha}table{border-collapse:collapse;display:block;margin-bottom:var(--ifm-spacing-vertical)}table thead tr{border-bottom:2px solid var(--ifm-table-border-color)}table thead,table tr:nth-child(2n){background-color:var(--ifm-table-stripe-background)}table tr{background-color:var(--ifm-table-background);border-top:var(--ifm-table-border-width) solid var(--ifm-table-border-color)}table td,table th{border:var(--ifm-table-border-width) solid var(--ifm-table-border-color);padding:var(--ifm-table-cell-padding)}table th{background-color:var(--ifm-table-head-background);color:var(--ifm-table-head-color);font-weight:var(--ifm-table-head-font-weight)}table td{color:var(--ifm-table-cell-color)}strong{font-weight:var(--ifm-font-weight-bold)}a{color:var(--ifm-link-color);text-decoration:var(--ifm-link-decoration)}a:hover{color:var(--ifm-link-hover-color);text-decoration:var(--ifm-link-hover-decoration)}.button:hover,.text--no-decoration,.text--no-decoration:hover,a:not([href]){-webkit-text-decoration:none;text-decoration:none}p{margin:0 0 var(--ifm-paragraph-margin-bottom)}blockquote{border-left:var(--ifm-blockquote-border-left-width) solid var(--ifm-blockquote-border-color);box-shadow:var(--ifm-blockquote-shadow);color:var(--ifm-blockquote-color);font-size:var(--ifm-blockquote-font-size);padding:var(--ifm-blockquote-padding-vertical) var(--ifm-blockquote-padding-horizontal)}blockquote>:first-child{margin-top:0}blockquote>:last-child{margin-bottom:0}hr{background-color:var(--ifm-hr-background-color);border:0;height:var(--ifm-hr-height);margin:var(--ifm-hr-margin-vertical) 0}.shadow--lw{box-shadow:var(--ifm-global-shadow-lw)!important}.shadow--md{box-shadow:var(--ifm-global-shadow-md)!important}.shadow--tl{box-shadow:var(--ifm-global-shadow-tl)!important}.text--primary{color:var(--ifm-color-primary)}.text--secondary{color:var(--ifm-color-secondary)}.text--success{color:var(--ifm-color-success)}.text--info{color:var(--ifm-color-info)}.text--warning{color:var(--ifm-color-warning)}.text--danger{color:var(--ifm-color-danger)}.text--center{text-align:center}.text--left{text-align:left}.text--justify{text-align:justify}.text--right{text-align:right}.text--capitalize{text-transform:capitalize}.text--lowercase{text-transform:lowercase}.alert__heading,.text--uppercase{text-transform:uppercase}.text--light{font-weight:var(--ifm-font-weight-light)}.text--normal{font-weight:var(--ifm-font-weight-normal)}.text--semibold{font-weight:var(--ifm-font-weight-semibold)}.text--bold{font-weight:var(--ifm-font-weight-bold)}.text--italic{font-style:italic}.text--truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text--break{word-wrap:break-word!important;word-break:break-word!important}.clean-btn{background:none;border:none;color:inherit;cursor:pointer;font-family:inherit;padding:0}.alert,.alert .close{color:var(--ifm-alert-foreground-color)}.clean-list{list-style:none;padding-left:0}.alert--primary{--ifm-alert-background-color:var(--ifm-color-primary-contrast-background);--ifm-alert-background-color-highlight:#3578e526;--ifm-alert-foreground-color:var(--ifm-color-primary-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-primary-dark)}.alert--secondary{--ifm-alert-background-color:var(--ifm-color-secondary-contrast-background);--ifm-alert-background-color-highlight:#ebedf026;--ifm-alert-foreground-color:var(--ifm-color-secondary-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-secondary-dark)}.alert--success{--ifm-alert-background-color:var(--ifm-color-success-contrast-background);--ifm-alert-background-color-highlight:#00a40026;--ifm-alert-foreground-color:var(--ifm-color-success-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-success-dark)}.alert--info{--ifm-alert-background-color:var(--ifm-color-info-contrast-background);--ifm-alert-background-color-highlight:#54c7ec26;--ifm-alert-foreground-color:var(--ifm-color-info-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-info-dark)}.alert--warning{--ifm-alert-background-color:var(--ifm-color-warning-contrast-background);--ifm-alert-background-color-highlight:#ffba0026;--ifm-alert-foreground-color:var(--ifm-color-warning-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-warning-dark)}.alert--danger{--ifm-alert-background-color:var(--ifm-color-danger-contrast-background);--ifm-alert-background-color-highlight:#fa383e26;--ifm-alert-foreground-color:var(--ifm-color-danger-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-danger-dark)}.alert{--ifm-code-background:var(--ifm-alert-background-color-highlight);--ifm-link-color:var(--ifm-alert-foreground-color);--ifm-link-hover-color:var(--ifm-alert-foreground-color);--ifm-link-decoration:underline;--ifm-tabs-color:var(--ifm-alert-foreground-color);--ifm-tabs-color-active:var(--ifm-alert-foreground-color);--ifm-tabs-color-active-border:var(--ifm-alert-border-color);background-color:var(--ifm-alert-background-color);border:var(--ifm-alert-border-width) solid var(--ifm-alert-border-color);border-left-width:var(--ifm-alert-border-left-width);border-radius:var(--ifm-alert-border-radius);box-shadow:var(--ifm-alert-shadow);padding:var(--ifm-alert-padding-vertical) var(--ifm-alert-padding-horizontal)}.alert__heading{align-items:center;display:flex;font:700 var(--ifm-h5-font-size)/var(--ifm-heading-line-height) var(--ifm-heading-font-family);margin-bottom:.5rem}.alert__icon{display:inline-flex;margin-right:.4em}.alert__icon svg{fill:var(--ifm-alert-foreground-color);stroke:var(--ifm-alert-foreground-color);stroke-width:0}.alert .close{margin:calc(var(--ifm-alert-padding-vertical)*-1) calc(var(--ifm-alert-padding-horizontal)*-1) 0 0;opacity:.75}.alert .close:focus,.alert .close:hover{opacity:1}.alert a{text-decoration-color:var(--ifm-alert-border-color)}.alert a:hover{text-decoration-thickness:2px}.avatar{column-gap:var(--ifm-avatar-intro-margin);display:flex}.avatar__photo{border-radius:50%;display:block;height:var(--ifm-avatar-photo-size);overflow:hidden;width:var(--ifm-avatar-photo-size)}.card--full-height,.navbar__logo img{height:100%}.avatar__photo--sm{--ifm-avatar-photo-size:2rem}.avatar__photo--lg{--ifm-avatar-photo-size:4rem}.avatar__photo--xl{--ifm-avatar-photo-size:6rem}.avatar__intro{display:flex;flex:1 1;flex-direction:column;justify-content:center;text-align:var(--ifm-avatar-intro-alignment)}.badge,.breadcrumbs__item,.breadcrumbs__link,.button,.dropdown>.navbar__link:after{display:inline-block}.avatar__name{font:700 var(--ifm-h4-font-size)/var(--ifm-heading-line-height) var(--ifm-font-family-base)}.avatar__subtitle{margin-top:.25rem}.avatar--vertical{--ifm-avatar-intro-alignment:center;--ifm-avatar-intro-margin:0.5rem;align-items:center;flex-direction:column}.badge{background-color:var(--ifm-badge-background-color);border:var(--ifm-badge-border-width) solid var(--ifm-badge-border-color);border-radius:var(--ifm-badge-border-radius);color:var(--ifm-badge-color);font-size:75%;font-weight:var(--ifm-font-weight-bold);line-height:1;padding:var(--ifm-badge-padding-vertical) var(--ifm-badge-padding-horizontal)}.badge--primary{--ifm-badge-background-color:var(--ifm-color-primary)}.badge--secondary{--ifm-badge-background-color:var(--ifm-color-secondary);color:var(--ifm-color-black)}.breadcrumbs__link,.button.button--secondary.button--outline:not(.button--active):not(:hover){color:var(--ifm-font-color-base)}.badge--success{--ifm-badge-background-color:var(--ifm-color-success)}.badge--info{--ifm-badge-background-color:var(--ifm-color-info)}.badge--warning{--ifm-badge-background-color:var(--ifm-color-warning)}.badge--danger{--ifm-badge-background-color:var(--ifm-color-danger)}.breadcrumbs{margin-bottom:0;padding-left:0}.breadcrumbs__item:not(:last-child):after{background:var(--ifm-breadcrumb-separator) center;content:" ";display:inline-block;filter:var(--ifm-breadcrumb-separator-filter);height:calc(var(--ifm-breadcrumb-separator-size)*var(--ifm-breadcrumb-size-multiplier)*var(--ifm-breadcrumb-separator-size-multiplier));margin:0 var(--ifm-breadcrumb-spacing);opacity:.5;width:calc(var(--ifm-breadcrumb-separator-size)*var(--ifm-breadcrumb-size-multiplier)*var(--ifm-breadcrumb-separator-size-multiplier))}.breadcrumbs__item--active .breadcrumbs__link{background:var(--ifm-breadcrumb-item-background-active);color:var(--ifm-breadcrumb-color-active)}.breadcrumbs__link{border-radius:var(--ifm-breadcrumb-border-radius);font-size:calc(1rem*var(--ifm-breadcrumb-size-multiplier));padding:calc(var(--ifm-breadcrumb-padding-vertical)*var(--ifm-breadcrumb-size-multiplier)) calc(var(--ifm-breadcrumb-padding-horizontal)*var(--ifm-breadcrumb-size-multiplier));transition-duration:var(--ifm-transition-fast);transition-property:background,color}.breadcrumbs__link:any-link:hover,.breadcrumbs__link:link:hover,.breadcrumbs__link:visited:hover,area[href].breadcrumbs__link:hover{background:var(--ifm-breadcrumb-item-background-active);-webkit-text-decoration:none;text-decoration:none}.breadcrumbs--sm{--ifm-breadcrumb-size-multiplier:0.8}.breadcrumbs--lg{--ifm-breadcrumb-size-multiplier:1.2}.button{background-color:var(--ifm-button-background-color);border:var(--ifm-button-border-width) solid var(--ifm-button-border-color);border-radius:var(--ifm-button-border-radius);cursor:pointer;font-size:calc(.875rem*var(--ifm-button-size-multiplier));font-weight:var(--ifm-button-font-weight);line-height:1.5;padding:calc(var(--ifm-button-padding-vertical)*var(--ifm-button-size-multiplier)) calc(var(--ifm-button-padding-horizontal)*var(--ifm-button-size-multiplier));text-align:center;transition-duration:var(--ifm-button-transition-duration);transition-property:color,background,border-color;-webkit-user-select:none;user-select:none;white-space:nowrap}.button,.button:hover{color:var(--ifm-button-color)}.button--outline{--ifm-button-color:var(--ifm-button-border-color)}.button--outline:hover{--ifm-button-background-color:var(--ifm-button-border-color)}.button--link{--ifm-button-border-color:#0000;color:var(--ifm-link-color);text-decoration:var(--ifm-link-decoration)}.button--link.button--active,.button--link:active,.button--link:hover{color:var(--ifm-link-hover-color);text-decoration:var(--ifm-link-hover-decoration)}.dropdown__link--active,.dropdown__link:hover,.menu__link:hover,.navbar__brand:hover,.navbar__link--active,.navbar__link:hover,.pagination-nav__link:hover,.pagination__link:hover{-webkit-text-decoration:none;text-decoration:none}.button.disabled,.button:disabled,.button[disabled]{opacity:.65;pointer-events:none}.button--sm{--ifm-button-size-multiplier:0.8}.button--lg{--ifm-button-size-multiplier:1.35}.button--block{display:block;width:100%}.button.button--secondary{color:var(--ifm-color-gray-900)}:where(.button--primary){--ifm-button-background-color:var(--ifm-color-primary);--ifm-button-border-color:var(--ifm-color-primary)}:where(.button--primary):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-primary-dark);--ifm-button-border-color:var(--ifm-color-primary-dark)}.button--primary.button--active,.button--primary:active{--ifm-button-background-color:var(--ifm-color-primary-darker);--ifm-button-border-color:var(--ifm-color-primary-darker)}:where(.button--secondary){--ifm-button-background-color:var(--ifm-color-secondary);--ifm-button-border-color:var(--ifm-color-secondary)}:where(.button--secondary):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-secondary-dark);--ifm-button-border-color:var(--ifm-color-secondary-dark)}.button--secondary.button--active,.button--secondary:active{--ifm-button-background-color:var(--ifm-color-secondary-darker);--ifm-button-border-color:var(--ifm-color-secondary-darker)}:where(.button--success){--ifm-button-background-color:var(--ifm-color-success);--ifm-button-border-color:var(--ifm-color-success)}:where(.button--success):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-success-dark);--ifm-button-border-color:var(--ifm-color-success-dark)}.button--success.button--active,.button--success:active{--ifm-button-background-color:var(--ifm-color-success-darker);--ifm-button-border-color:var(--ifm-color-success-darker)}:where(.button--info){--ifm-button-background-color:var(--ifm-color-info);--ifm-button-border-color:var(--ifm-color-info)}:where(.button--info):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-info-dark);--ifm-button-border-color:var(--ifm-color-info-dark)}.button--info.button--active,.button--info:active{--ifm-button-background-color:var(--ifm-color-info-darker);--ifm-button-border-color:var(--ifm-color-info-darker)}:where(.button--warning){--ifm-button-background-color:var(--ifm-color-warning);--ifm-button-border-color:var(--ifm-color-warning)}:where(.button--warning):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-warning-dark);--ifm-button-border-color:var(--ifm-color-warning-dark)}.button--warning.button--active,.button--warning:active{--ifm-button-background-color:var(--ifm-color-warning-darker);--ifm-button-border-color:var(--ifm-color-warning-darker)}:where(.button--danger){--ifm-button-background-color:var(--ifm-color-danger);--ifm-button-border-color:var(--ifm-color-danger)}:where(.button--danger):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-danger-dark);--ifm-button-border-color:var(--ifm-color-danger-dark)}.button--danger.button--active,.button--danger:active{--ifm-button-background-color:var(--ifm-color-danger-darker);--ifm-button-border-color:var(--ifm-color-danger-darker)}.button-group{display:inline-flex;gap:var(--ifm-button-group-spacing)}.button-group>.button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.button-group>.button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.button-group--block{display:flex;justify-content:stretch}.button-group--block>.button{flex-grow:1}.card{background-color:var(--ifm-card-background-color);border-radius:var(--ifm-card-border-radius);box-shadow:var(--ifm-global-shadow-lw);display:flex;flex-direction:column;overflow:hidden}.card__image{padding-top:var(--ifm-card-vertical-spacing)}.card__image:first-child{padding-top:0}.card__body,.card__footer,.card__header{padding:var(--ifm-card-vertical-spacing) var(--ifm-card-horizontal-spacing)}.card__body:not(:last-child),.card__footer:not(:last-child),.card__header:not(:last-child){padding-bottom:0}.card__body>:last-child,.card__footer>:last-child,.card__header>:last-child{margin-bottom:0}.card__footer{margin-top:auto}.table-of-contents{font-size:.8rem;margin-bottom:0;padding:var(--ifm-toc-padding-vertical) 0}.table-of-contents,.table-of-contents ul{list-style:none;padding-left:var(--ifm-toc-padding-horizontal)}.table-of-contents li{margin:var(--ifm-toc-padding-vertical) var(--ifm-toc-padding-horizontal)}.table-of-contents__left-border{border-left:1px solid var(--ifm-toc-border-color)}.table-of-contents__link{color:var(--ifm-toc-link-color);display:block}.table-of-contents__link--active,.table-of-contents__link--active code,.table-of-contents__link:hover,.table-of-contents__link:hover code{color:var(--ifm-color-primary);-webkit-text-decoration:none;text-decoration:none}.close{color:var(--ifm-color-black);float:right;font-size:1.5rem;font-weight:var(--ifm-font-weight-bold);line-height:1;opacity:.5;padding:1rem;transition:opacity var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.close:hover{opacity:.7}.close:focus{opacity:.8}.dropdown{display:inline-flex;font-weight:var(--ifm-dropdown-font-weight);position:relative;vertical-align:top}.dropdown--hoverable:hover .dropdown__menu,.dropdown--show .dropdown__menu{opacity:1;pointer-events:all;transform:translateY(-1px);visibility:visible}.dropdown__menu,.navbar__item.dropdown .navbar__link:not([href]){pointer-events:none}.dropdown--right .dropdown__menu{left:inherit;right:0}.dropdown--nocaret .navbar__link:after{content:none!important}.dropdown__menu{background-color:var(--ifm-dropdown-background-color);border-radius:var(--ifm-global-radius);box-shadow:var(--ifm-global-shadow-md);left:0;list-style:none;max-height:80vh;min-width:10rem;opacity:0;overflow-y:auto;padding:.5rem;position:absolute;top:calc(100% - var(--ifm-navbar-item-padding-vertical) + .3rem);transform:translateY(-.625rem);transition-duration:var(--ifm-transition-fast);transition-property:opacity,transform,visibility;transition-timing-function:var(--ifm-transition-timing-default);visibility:hidden;z-index:var(--ifm-z-index-dropdown)}.menu__caret,.menu__link,.menu__list-item-collapsible{border-radius:.25rem;transition:background var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.dropdown__link{border-radius:.25rem;color:var(--ifm-dropdown-link-color);display:block;font-size:.875rem;margin-top:.2rem;padding:.25rem .5rem;white-space:nowrap}.dropdown__link--active,.dropdown__link:hover{background-color:var(--ifm-dropdown-hover-background-color);color:var(--ifm-dropdown-link-color)}.dropdown__link--active,.dropdown__link--active:hover{--ifm-dropdown-link-color:var(--ifm-link-color)}.dropdown>.navbar__link:after{border-color:currentcolor #0000;border-style:solid;border-width:.4em .4em 0;content:"";margin-left:.3em;position:relative;top:2px;transform:translateY(-50%)}.footer{background-color:var(--ifm-footer-background-color);color:var(--ifm-footer-color);padding:var(--ifm-footer-padding-vertical) var(--ifm-footer-padding-horizontal)}.footer--dark{--ifm-footer-background-color:#303846;--ifm-footer-color:var(--ifm-footer-link-color);--ifm-footer-link-color:var(--ifm-color-secondary);--ifm-footer-title-color:var(--ifm-color-white)}.footer__links{margin-bottom:1rem}.footer__link-item{color:var(--ifm-footer-link-color);line-height:2}.footer__link-item:hover{color:var(--ifm-footer-link-hover-color)}.footer__link-separator{margin:0 var(--ifm-footer-link-horizontal-spacing)}.footer__logo{margin-top:1rem;max-width:var(--ifm-footer-logo-max-width)}.footer__title{color:var(--ifm-footer-title-color);font:700 var(--ifm-h4-font-size)/var(--ifm-heading-line-height) var(--ifm-font-family-base);margin-bottom:var(--ifm-heading-margin-bottom)}.menu,.navbar__link{font-weight:var(--ifm-font-weight-semibold)}.footer__item{margin-top:0}.footer__items{margin-bottom:0}[type=checkbox]{padding:0}.hero{align-items:center;background-color:var(--ifm-hero-background-color);color:var(--ifm-hero-text-color);display:flex;padding:4rem 2rem}.hero--primary{--ifm-hero-background-color:var(--ifm-color-primary);--ifm-hero-text-color:var(--ifm-font-color-base-inverse)}.hero--dark{--ifm-hero-background-color:#303846;--ifm-hero-text-color:var(--ifm-color-white)}.hero__title{font-size:3rem}.hero__subtitle{font-size:1.5rem}.menu__list{list-style:none;margin:0;padding-left:0}.menu__caret,.menu__link{padding:var(--ifm-menu-link-padding-vertical) var(--ifm-menu-link-padding-horizontal)}.menu__list .menu__list{flex:0 0 100%;margin-top:.25rem;padding-left:var(--ifm-menu-link-padding-horizontal)}.menu__list-item:not(:first-child){margin-top:.25rem}.menu__list-item--collapsed .menu__list{height:0;overflow:hidden}.menu__list-item--collapsed .menu__caret:before,.menu__list-item--collapsed .menu__link--sublist:after{transform:rotate(90deg)}.menu__list-item-collapsible{display:flex;flex-wrap:wrap;position:relative}.menu__caret:hover,.menu__link:hover,.menu__list-item-collapsible--active,.menu__list-item-collapsible:hover{background:var(--ifm-menu-color-background-hover)}.menu__list-item-collapsible .menu__link--active,.menu__list-item-collapsible .menu__link:hover{background:none!important}.menu__caret,.menu__link{align-items:center;display:flex}.menu__link{color:var(--ifm-menu-color);flex:1;line-height:1.25}.menu__link:hover{color:var(--ifm-menu-color)}.menu__caret:before,.menu__link--sublist-caret:after{content:"";filter:var(--ifm-menu-link-sublist-icon-filter);height:1.25rem;transform:rotate(180deg);transition:transform var(--ifm-transition-fast) linear;width:1.25rem}.menu__link--sublist-caret:after{background:var(--ifm-menu-link-sublist-icon) 50%/2rem 2rem;margin-left:auto;min-width:1.25rem}.menu__link--active,.menu__link--active:hover{color:var(--ifm-menu-color-active)}.navbar__brand,.navbar__link{color:var(--ifm-navbar-link-color)}.menu__link--active:not(.menu__link--sublist){background-color:var(--ifm-menu-color-background-active)}.menu__caret:before{background:var(--ifm-menu-link-sublist-icon) 50%/2rem 2rem}.navbar--dark,html[data-theme=dark]{--ifm-menu-link-sublist-icon-filter:invert(100%) sepia(94%) saturate(17%) hue-rotate(223deg) brightness(104%) contrast(98%)}.navbar{background-color:var(--ifm-navbar-background-color);box-shadow:var(--ifm-navbar-shadow);height:var(--ifm-navbar-height);padding:var(--ifm-navbar-padding-vertical) var(--ifm-navbar-padding-horizontal)}.navbar,.navbar>.container,.navbar>.container-fluid{display:flex}.navbar--fixed-top{position:sticky;top:0;z-index:var(--ifm-z-index-fixed)}.navbar-sidebar,.navbar-sidebar__backdrop{bottom:0;left:0;opacity:0;position:fixed;top:0;transition-duration:var(--ifm-transition-fast);transition-timing-function:ease-in-out;visibility:hidden}.navbar__inner{display:flex;flex-wrap:wrap;justify-content:space-between;width:100%}.navbar__brand{align-items:center;display:flex;margin-right:1rem;min-width:0}.navbar__brand:hover{color:var(--ifm-navbar-link-hover-color)}.navbar__title{flex:1 1 auto}.navbar__toggle{display:none;margin-right:.5rem}.navbar__logo{flex:0 0 auto;height:2rem;margin-right:.5rem}.navbar__items{align-items:center;display:flex;flex:1;min-width:0}.navbar__items--center{flex:0 0 auto}.navbar__items--center .navbar__brand{margin:0}.navbar__items--center+.navbar__items--right{flex:1}.navbar__items--right{flex:0 0 auto;justify-content:flex-end}.navbar__items--right>:last-child{padding-right:0}.navbar__item{display:inline-block;padding:var(--ifm-navbar-item-padding-vertical) var(--ifm-navbar-item-padding-horizontal)}.navbar__link--active,.navbar__link:hover{color:var(--ifm-navbar-link-hover-color)}.navbar--dark,.navbar--primary{--ifm-menu-color:var(--ifm-color-gray-300);--ifm-navbar-link-color:var(--ifm-color-gray-100);--ifm-navbar-search-input-background-color:#ffffff1a;--ifm-navbar-search-input-placeholder-color:#ffffff80;color:var(--ifm-color-white)}.navbar--dark{--ifm-navbar-background-color:#242526;--ifm-menu-color-background-active:#ffffff0d;--ifm-navbar-search-input-color:var(--ifm-color-white)}.navbar--primary{--ifm-navbar-background-color:var(--ifm-color-primary);--ifm-navbar-link-hover-color:var(--ifm-color-white);--ifm-menu-color-active:var(--ifm-color-white);--ifm-navbar-search-input-color:var(--ifm-color-emphasis-500)}.navbar__search-input{appearance:none;background:var(--ifm-navbar-search-input-background-color) var(--ifm-navbar-search-input-icon) no-repeat .75rem center/1rem 1rem;border:none;border-radius:2rem;color:var(--ifm-navbar-search-input-color);cursor:text;display:inline-block;font-size:1rem;height:2rem;padding:0 .5rem 0 2.25rem;width:12.5rem}.navbar__search-input::placeholder{color:var(--ifm-navbar-search-input-placeholder-color)}.navbar-sidebar{background-color:var(--ifm-navbar-background-color);box-shadow:var(--ifm-global-shadow-md);transform:translate3d(-100%,0,0);transition-property:opacity,visibility,transform;width:var(--ifm-navbar-sidebar-width)}.navbar-sidebar--show .navbar-sidebar,.navbar-sidebar__items{transform:translateZ(0)}.navbar-sidebar--show .navbar-sidebar,.navbar-sidebar--show .navbar-sidebar__backdrop{opacity:1;visibility:visible}.navbar-sidebar__backdrop{background-color:#0009;right:0;transition-property:opacity,visibility}.navbar-sidebar__brand{align-items:center;box-shadow:var(--ifm-navbar-shadow);display:flex;flex:1;height:var(--ifm-navbar-height);padding:var(--ifm-navbar-padding-vertical) var(--ifm-navbar-padding-horizontal)}.navbar-sidebar__items{display:flex;height:calc(100% - var(--ifm-navbar-height));transition:transform var(--ifm-transition-fast) ease-in-out}.navbar-sidebar__items--show-secondary{transform:translate3d(calc((var(--ifm-navbar-sidebar-width))*-1),0,0)}.navbar-sidebar__item{flex-shrink:0;padding:.5rem;width:calc(var(--ifm-navbar-sidebar-width))}.navbar-sidebar__back{background:var(--ifm-menu-color-background-active);font-size:15px;font-weight:var(--ifm-button-font-weight);margin:0 0 .2rem -.5rem;padding:.6rem 1.5rem;position:relative;text-align:left;top:-.5rem;width:calc(100% + 1rem)}.navbar-sidebar__close{display:flex;margin-left:auto}.pagination{column-gap:var(--ifm-pagination-page-spacing);display:flex;font-size:var(--ifm-pagination-font-size);padding-left:0}.pagination--sm{--ifm-pagination-font-size:0.8rem;--ifm-pagination-padding-horizontal:0.8rem;--ifm-pagination-padding-vertical:0.2rem}.pagination--lg{--ifm-pagination-font-size:1.2rem;--ifm-pagination-padding-horizontal:1.2rem;--ifm-pagination-padding-vertical:0.3rem}.pagination__item{display:inline-flex}.pagination__item>span{padding:var(--ifm-pagination-padding-vertical)}.pagination__item--active .pagination__link{color:var(--ifm-pagination-color-active)}.pagination__item--active .pagination__link,.pagination__item:not(.pagination__item--active):hover .pagination__link{background:var(--ifm-pagination-item-active-background)}.pagination__item--disabled,.pagination__item[disabled]{opacity:.25;pointer-events:none}.pagination__link{border-radius:var(--ifm-pagination-border-radius);color:var(--ifm-font-color-base);display:inline-block;padding:var(--ifm-pagination-padding-vertical) var(--ifm-pagination-padding-horizontal);transition:background var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.pagination-nav{display:grid;grid-gap:var(--ifm-spacing-horizontal);gap:var(--ifm-spacing-horizontal);grid-template-columns:repeat(2,1fr)}.pagination-nav__link{border:1px solid var(--ifm-color-emphasis-300);border-radius:var(--ifm-pagination-nav-border-radius);display:block;height:100%;line-height:var(--ifm-heading-line-height);padding:var(--ifm-global-spacing);transition:border-color var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.pagination-nav__link:hover{border-color:var(--ifm-pagination-nav-color-hover)}.pagination-nav__link--next{grid-column:2/3;text-align:right}.pagination-nav__label{font-size:var(--ifm-h4-font-size);font-weight:var(--ifm-heading-font-weight);word-break:break-word}.pagination-nav__link--prev .pagination-nav__label:before{content:"« "}.pagination-nav__link--next .pagination-nav__label:after{content:" »"}.pagination-nav__sublabel{color:var(--ifm-color-content-secondary);font-size:var(--ifm-h5-font-size);font-weight:var(--ifm-font-weight-semibold);margin-bottom:.25rem}.pills__item,.tabs{font-weight:var(--ifm-font-weight-bold)}.pills{display:flex;gap:var(--ifm-pills-spacing);padding-left:0}.pills__item{border-radius:.5rem;cursor:pointer;display:inline-block;padding:.25rem 1rem;transition:background var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.pills__item--active{color:var(--ifm-pills-color-active)}.pills__item--active,.pills__item:not(.pills__item--active):hover{background:var(--ifm-pills-color-background-active)}.pills--block{justify-content:stretch}.pills--block .pills__item{flex-grow:1;text-align:center}.tabs{color:var(--ifm-tabs-color);display:flex;margin-bottom:0;overflow-x:auto;padding-left:0}.tabs__item{border-bottom:3px solid #0000;border-radius:var(--ifm-global-radius);cursor:pointer;display:inline-flex;padding:var(--ifm-tabs-padding-vertical) var(--ifm-tabs-padding-horizontal);transition:background-color var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.tabs__item--active{border-bottom-color:var(--ifm-tabs-color-active-border);border-bottom-left-radius:0;border-bottom-right-radius:0;color:var(--ifm-tabs-color-active)}.tabs__item:hover{background-color:var(--ifm-hover-overlay)}.tabs--block{justify-content:stretch}.tabs--block .tabs__item{flex-grow:1;justify-content:center}html[data-theme=dark]{--ifm-color-scheme:dark;--ifm-color-emphasis-0:var(--ifm-color-gray-1000);--ifm-color-emphasis-100:var(--ifm-color-gray-900);--ifm-color-emphasis-200:var(--ifm-color-gray-800);--ifm-color-emphasis-300:var(--ifm-color-gray-700);--ifm-color-emphasis-400:var(--ifm-color-gray-600);--ifm-color-emphasis-600:var(--ifm-color-gray-400);--ifm-color-emphasis-700:var(--ifm-color-gray-300);--ifm-color-emphasis-800:var(--ifm-color-gray-200);--ifm-color-emphasis-900:var(--ifm-color-gray-100);--ifm-color-emphasis-1000:var(--ifm-color-gray-0);--ifm-background-color:#1b1b1d;--ifm-background-surface-color:#242526;--ifm-hover-overlay:#ffffff0d;--ifm-color-content:#e3e3e3;--ifm-color-content-secondary:#fff;--ifm-breadcrumb-separator-filter:invert(64%) sepia(11%) saturate(0%) hue-rotate(149deg) brightness(99%) contrast(95%);--ifm-code-background:#ffffff1a;--ifm-scrollbar-track-background-color:#444;--ifm-scrollbar-thumb-background-color:#686868;--ifm-scrollbar-thumb-hover-background-color:#7a7a7a;--ifm-table-stripe-background:#ffffff12;--ifm-toc-border-color:var(--ifm-color-emphasis-200);--ifm-color-primary-contrast-background:#102445;--ifm-color-primary-contrast-foreground:#ebf2fc;--ifm-color-secondary-contrast-background:#474748;--ifm-color-secondary-contrast-foreground:#fdfdfe;--ifm-color-success-contrast-background:#003100;--ifm-color-success-contrast-foreground:#e6f6e6;--ifm-color-info-contrast-background:#193c47;--ifm-color-info-contrast-foreground:#eef9fd;--ifm-color-warning-contrast-background:#4d3800;--ifm-color-warning-contrast-foreground:#fff8e6;--ifm-color-danger-contrast-background:#4b1113;--ifm-color-danger-contrast-foreground:#ffebec}}.bharatml-hero .bharatml-button:hover,.bharatml-hero .button--outline:hover,[data-theme=dark] .bharatml-hero .bharatml-button:hover,[data-theme=dark] .bharatml-hero .button--outline:hover{background-color:#fff!important;border-color:#fff!important;color:var(--bharatml-primary)!important}.bharatml-hero .bharatml-button,.bharatml-hero .button--outline{border:2px solid #fff!important;color:#fff!important;transition:.3s}:root{--ifm-color-primary:#450839;--ifm-color-primary-dark:#3d0732;--ifm-color-primary-darker:#39062f;--ifm-color-primary-darkest:#2f0527;--ifm-color-primary-light:#4d0940;--ifm-color-primary-lighter:#510a43;--ifm-color-primary-lightest:#5d0c4d;--ifm-code-font-size:95%;--docusaurus-highlighted-code-line-bg:#0000001a;--bharatml-primary:#450839;--bharatml-primary-hover:#6a0c59;--bharatml-secondary:#f9f9f9;--bharatml-text:#1c1e21;--bharatml-text-light:#606770}[data-theme=dark]{--ifm-color-primary:#8b4582;--ifm-color-primary-dark:#7d3f75;--ifm-color-primary-darker:#763c6e;--ifm-color-primary-darkest:#62315a;--ifm-color-primary-light:#994b8f;--ifm-color-primary-lighter:#a04e96;--ifm-color-primary-lightest:#b657a9;--docusaurus-highlighted-code-line-bg:#0000004d;--bharatml-primary:#8b4582;--bharatml-primary-hover:#a04e96;--bharatml-secondary:#1e1e1e;--bharatml-text:#e3e3e3;--bharatml-text-light:#b4b4b4}.bharatml-hero{background:linear-gradient(135deg,var(--bharatml-primary) 0,var(--bharatml-primary-hover) 100%);color:#fff}.bharatml-hero .bharatml-button{background-color:var(--bharatml-primary)}.bharatml-hero .button--outline{background-color:initial!important}[data-theme=dark] .bharatml-hero .bharatml-button{background-color:var(--bharatml-primary);border:2px solid #fff!important;color:#fff!important}[data-theme=dark] .bharatml-hero .button--outline{background-color:initial!important;border:2px solid #fff!important;color:#fff!important}.bharatml-button{background-color:var(--bharatml-primary);border-color:var(--bharatml-primary);transition:.3s}.bharatml-button:hover{background-color:var(--bharatml-primary-hover);border-color:var(--bharatml-primary-hover);color:#fff}.bharatml-card{background:#fff;border:1px solid #4508391a;border-radius:8px;padding:2rem;transition:.3s}.bharatml-card:hover{border-color:var(--bharatml-primary);box-shadow:0 4px 20px #4508391a;transform:translateY(-2px)}.bharatml-icon{align-items:center;background:linear-gradient(135deg,var(--bharatml-primary),var(--bharatml-primary-hover));border-radius:12px;color:#fff;display:flex;font-size:1.5rem;height:64px;justify-content:center;margin:0 auto 1rem;width:64px}@layer docusaurus.core{#__docusaurus-base-url-issue-banner-container{display:none}}.aboutSection_udvw,.features_t9lD{background-color:var(--ifm-background-surface-color)}.featuresHeader_qR2i,.features_t9lD h3{color:var(--bharatml-primary);margin-bottom:1rem}.features_t9lD{display:block;padding:4rem 0;text-align:center;width:100%}.featureSvg_GfXr{height:200px;width:200px}.featuresHeader_qR2i{font-size:2.5rem;font-weight:700;text-align:center}.featuresSubtitle_VdGe{color:var(--ifm-font-color-base);font-size:1.2rem;opacity:1;text-align:center}.features_t9lD .bharatml-card_xZ6l{height:100%;margin-top:1rem}.features_t9lD .bharatml-icon_XBoJ{margin-bottom:1.5rem}.features_t9lD h3{font-size:1.25rem;font-weight:600}.features_t9lD p{color:var(--ifm-font-color-base)!important;font-size:.95rem;font-weight:400;line-height:1.6;margin:0}.featureDescription_sP1D{color:#1c1e21!important;font-size:.95rem!important;font-weight:400!important;line-height:1.6!important;margin:0!important}[data-theme=dark] .bharatml-card_xZ6l{background:#2a2a2a!important;border-color:#8b45824d;color:#fff}[data-theme=dark] .bharatml-card_xZ6l:hover{background:#333!important;border-color:var(--bharatml-primary);box-shadow:0 4px 20px #8b45824d}[data-theme=dark] .featureDescription_sP1D,[data-theme=dark] .featuresHeader_qR2i,[data-theme=dark] .features_t9lD h3,[data-theme=dark] .features_t9lD p{color:#a04e96!important}[data-theme=dark] .featuresSubtitle_VdGe{color:#e0e0e0!important}.heroBanner_qdFl{overflow:hidden;padding:4rem 0;position:relative;text-align:center}.logoContainer_xdaK{align-items:center;display:flex;justify-content:center;margin-bottom:2rem}.heroLogo_U6bI{filter:drop-shadow(0 4px 8px rgba(0,0,0,.1));height:180px;transition:transform .3s;width:180px}.heroLogo_U6bI:hover{transform:scale(1.05)}.buttons_AeoN{align-items:center;gap:1rem;margin-bottom:2rem}.buttons_AeoN,.statsContainer_KpvY{display:flex;justify-content:center}.statsContainer_KpvY{gap:3rem;margin-top:2rem;opacity:.9}.statItem_bwiZ{align-items:center;color:#fff;display:flex;flex-direction:column;text-align:center}.statItem_bwiZ strong{display:block;font-size:1.5rem;font-weight:700;margin-bottom:.25rem}.statItem_bwiZ span{font-size:.875rem;letter-spacing:.5px;opacity:.8;text-transform:uppercase}.aboutSection_udvw{padding:4rem 0}.highlightBox_Uhe8{background:linear-gradient(135deg,#f8f9ff,#e8f0ff);border:1px solid #4508391a;border-radius:12px;height:100%;padding:2rem}.highlightBox_Uhe8 h3{color:var(--bharatml-primary);font-size:1.25rem;margin-bottom:1rem}.highlightBox_Uhe8 li,[data-theme=dark] .highlightBox_Uhe8 li{color:var(--bharatml-text)}.highlightBox_Uhe8 ul{list-style:none;margin:0;padding:0}.highlightBox_Uhe8 li{font-size:.95rem;padding:.5rem 0}.highlightBox_Uhe8 li:not(:last-child){border-bottom:1px solid #4508390d}[data-theme=dark] .highlightBox_Uhe8{background:linear-gradient(135deg,#1a1a2e,#16213e);border-color:#8b458233}@layer docusaurus.theme-common{.themedComponent_mlkZ{display:none}[data-theme=dark] .themedComponent--dark_xIcU,[data-theme=light] .themedComponent--light_NVdE,html:not([data-theme]) .themedComponent--light_NVdE{display:initial}.errorBoundaryError_a6uf{color:red;white-space:pre-wrap}.errorBoundaryFallback_VBag{color:red;padding:.55rem}body:not(.navigation-with-keyboard) :not(input):focus{outline:0}.details_lb9f{--docusaurus-details-summary-arrow-size:0.38rem;--docusaurus-details-transition:transform 200ms ease;--docusaurus-details-decoration-color:grey}.details_lb9f>summary{cursor:pointer;list-style:none;padding-left:1rem;position:relative}.details_lb9f>summary::-webkit-details-marker{display:none}.details_lb9f>summary:before{border-color:#0000 #0000 #0000 var(--docusaurus-details-decoration-color);border-style:solid;border-width:var(--docusaurus-details-summary-arrow-size);content:"";left:0;position:absolute;top:.45rem;transform:rotate(0);transform-origin:calc(var(--docusaurus-details-summary-arrow-size)/2) 50%;transition:var(--docusaurus-details-transition)}.details_lb9f[data-collapsed=false].isBrowser_bmU9>summary:before,.details_lb9f[open]:not(.isBrowser_bmU9)>summary:before{transform:rotate(90deg)}.collapsibleContent_i85q{border-top:1px solid var(--docusaurus-details-decoration-color);margin-top:1rem;padding-top:1rem}.collapsibleContent_i85q p:last-child,.details_lb9f>summary>p:last-child{margin-bottom:0}}@layer docusaurus.theme-classic{:root{--docusaurus-progress-bar-color:var(--ifm-color-primary);--docusaurus-announcement-bar-height:auto;--docusaurus-collapse-button-bg:#0000;--docusaurus-collapse-button-bg-hover:#0000001a;--doc-sidebar-width:300px;--doc-sidebar-hidden-width:30px;--docusaurus-blog-social-icon-size:1rem;--docusaurus-tag-list-border:var(--ifm-color-emphasis-300)}#nprogress{pointer-events:none}#nprogress .bar{background:var(--docusaurus-progress-bar-color);height:2px;left:0;position:fixed;top:0;width:100%;z-index:1031}#nprogress .peg{box-shadow:0 0 10px var(--docusaurus-progress-bar-color),0 0 5px var(--docusaurus-progress-bar-color);height:100%;opacity:1;position:absolute;right:0;transform:rotate(3deg) translateY(-4px);width:100px}.backToTopButton_sjWU{background-color:var(--ifm-color-emphasis-200);border-radius:50%;bottom:1.3rem;box-shadow:var(--ifm-global-shadow-lw);height:3rem;opacity:0;position:fixed;right:1.3rem;transform:scale(0);transition:all var(--ifm-transition-fast) var(--ifm-transition-timing-default);visibility:hidden;width:3rem;z-index:calc(var(--ifm-z-index-fixed) - 1)}.backToTopButton_sjWU:after{background-color:var(--ifm-color-emphasis-1000);content:" ";display:inline-block;height:100%;-webkit-mask:var(--ifm-menu-link-sublist-icon) 50%/2rem 2rem no-repeat;mask:var(--ifm-menu-link-sublist-icon) 50%/2rem 2rem no-repeat;width:100%}.backToTopButtonShow_xfvO{opacity:1;transform:scale(1);visibility:visible}.skipToContent_fXgn{background-color:var(--ifm-background-surface-color);color:var(--ifm-color-emphasis-900);left:100%;padding:calc(var(--ifm-global-spacing)/2) var(--ifm-global-spacing);position:fixed;top:1rem;z-index:calc(var(--ifm-z-index-fixed) + 1)}.skipToContent_fXgn:focus{box-shadow:var(--ifm-global-shadow-md);left:1rem}.closeButton_CVFx{line-height:0;padding:0}.content_knG7{font-size:85%;padding:5px 0;text-align:center}.content_knG7 a{color:inherit;-webkit-text-decoration:underline;text-decoration:underline}.announcementBar_mb4j{align-items:center;background-color:var(--ifm-color-white);border-bottom:1px solid var(--ifm-color-emphasis-100);color:var(--ifm-color-black);display:flex;height:var(--docusaurus-announcement-bar-height)}.docSidebarContainer_YfHR,.navbarSearchContainer_Bca1:empty,.sidebarLogo_isFc,.toggleIcon_g3eP,html[data-announcement-bar-initially-dismissed=true] .announcementBar_mb4j{display:none}.announcementBarPlaceholder_vyr4{flex:0 0 10px}.announcementBarClose_gvF7{align-self:stretch;flex:0 0 30px}.announcementBarContent_xLdY{flex:1 1 auto}.toggle_vylO{height:2rem;width:2rem}.toggleButton_gllP{-webkit-tap-highlight-color:transparent;align-items:center;border-radius:50%;display:flex;height:100%;justify-content:center;transition:background var(--ifm-transition-fast);width:100%}.toggleButton_gllP:hover{background:var(--ifm-color-emphasis-200)}[data-theme-choice=dark] .darkToggleIcon_wfgR,[data-theme-choice=light] .lightToggleIcon_pyhR,[data-theme-choice=system] .systemToggleIcon_QzmC{display:initial}.toggleButtonDisabled_aARS{cursor:not-allowed}.darkNavbarColorModeToggle_X3D1:hover{background:var(--ifm-color-gray-800)}[data-theme=dark]:root{--docusaurus-collapse-button-bg:#ffffff0d;--docusaurus-collapse-button-bg-hover:#ffffff1a}.collapseSidebarButton_PEFL{display:none;margin:0}.iconExternalLink_nPIU{margin-left:.3rem}.menuExternalLink_NmtK{align-items:center}.docMainContainer_TBSr,.docRoot_UBD9{display:flex;width:100%}.docsWrapper_hBAB{display:flex;flex:1 0 auto}.dropdownNavbarItemMobile_J0Sd{cursor:pointer}.iconLanguage_nlXk{margin-right:5px;vertical-align:text-bottom}.navbarHideable_m1mJ{transition:transform var(--ifm-transition-fast) ease}.navbarHidden_jGov{transform:translate3d(0,calc(-100% - 2px),0)}.navbar__items--right>:last-child{padding-right:0}.footerLogoLink_BH7S{opacity:.5;transition:opacity var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.footerLogoLink_BH7S:hover,.hash-link:focus,:hover>.hash-link{opacity:1}.authorSocialIcon_XYv3,.authorSocialLink_owbf{width:var(--docusaurus-blog-social-icon-size)}.anchorWithStickyNavbar_LWe7{scroll-margin-top:calc(var(--ifm-navbar-height) + .5rem)}.anchorWithHideOnScrollNavbar_WYt5{scroll-margin-top:.5rem}.hash-link{opacity:0;padding-left:.5rem;transition:opacity var(--ifm-transition-fast);-webkit-user-select:none;user-select:none}.hash-link:before{content:"#"}.docCardListItem_W1sv>*,body,html{height:100%}.mainWrapper_z2l0{display:flex;flex:1 0 auto;flex-direction:column}.docusaurus-mt-lg{margin-top:3rem}#__docusaurus{display:flex;flex-direction:column;min-height:100%}.sidebar_re4s{max-height:calc(100vh - var(--ifm-navbar-height) - 2rem);overflow-y:auto;position:sticky;top:calc(var(--ifm-navbar-height) + 2rem)}.authorSocials_rSDt,.authorTitle_nd0D{overflow:hidden;-webkit-box-orient:vertical}.sidebarItemTitle_pO2u{font-size:var(--ifm-h3-font-size);font-weight:var(--ifm-font-weight-bold)}.container_mt6G,.sidebarItemList_Yudw{font-size:.9rem}.sidebarItem__DBe{margin-top:.7rem}.sidebarItemLink_mo7H{color:var(--ifm-font-color-base);display:block}.sidebarItemLink_mo7H:hover{-webkit-text-decoration:none;text-decoration:none}.sidebarItemLinkActive_I1ZP{color:var(--ifm-color-primary)!important}.yearGroupHeading_rMGB{margin-bottom:.4rem;margin-top:1.6rem}.yearGroupHeading_QT03{margin:1rem .75rem .5rem}.cardContainer_fWXF{--ifm-link-color:var(--ifm-color-emphasis-800);--ifm-link-hover-color:var(--ifm-color-emphasis-700);--ifm-link-hover-decoration:none;border:1px solid var(--ifm-color-emphasis-200);box-shadow:0 1.5px 3px 0 #00000026;transition:all var(--ifm-transition-fast) ease;transition-property:border,box-shadow}.cardContainer_fWXF:hover{border-color:var(--ifm-color-primary);box-shadow:0 3px 6px 0 #0003}.admonitionContent_BuS1>:last-child,.cardContainer_fWXF :last-child{margin-bottom:0}.cardTitle_rnsV{font-size:1.2rem}.cardDescription_PWke{font-size:.8rem}.docCardListItem_W1sv{margin-bottom:2rem}.title_f1Hy{font-size:3rem}[data-theme=dark] .githubSvg_Uu4N,[data-theme=dark] .instagramSvg_YC40,[data-theme=dark] .threadsSvg_PTXY,[data-theme=dark] .xSvg_y3PF{fill:var(--light)}[data-theme=light] .githubSvg_Uu4N,[data-theme=light] .instagramSvg_YC40,[data-theme=light] .threadsSvg_PTXY,[data-theme=light] .xSvg_y3PF{fill:var(--dark)}.authorSocials_rSDt{align-items:center;display:flex;flex-wrap:wrap;line-clamp:1;-webkit-line-clamp:1}.authorSocialLink_owbf,.authorSocials_rSDt{height:var(--docusaurus-blog-social-icon-size);line-height:0}.authorSocialLink_owbf{margin-right:.4rem}.authorSocialIcon_XYv3{height:var(--docusaurus-blog-social-icon-size)}.authorImage_XqGP{--ifm-avatar-photo-size:3.6rem}.author-as-h1_n9oJ .authorImage_XqGP{--ifm-avatar-photo-size:7rem}.author-as-h2_gXvM .authorImage_XqGP{--ifm-avatar-photo-size:5.4rem}.authorDetails_lV9A{align-items:flex-start;display:flex;flex-direction:column;justify-content:space-around}.authorName_yefp{display:flex;flex-direction:row;font-size:1.1rem;line-height:1.1rem}.author-as-h1_n9oJ .authorName_yefp{display:inline;font-size:2.4rem;line-height:2.4rem}.author-as-h2_gXvM .authorName_yefp{display:inline;font-size:1.4rem;line-height:1.4rem}.authorTitle_nd0D{display:-webkit-box;font-size:.8rem;line-height:1rem;line-clamp:1;-webkit-line-clamp:1}.author-as-h1_n9oJ .authorTitle_nd0D{font-size:1.2rem;line-height:1.6rem}.author-as-h2_gXvM .authorTitle_nd0D{font-size:1rem;line-height:1.3rem}.authorBlogPostCount_iiJ5{background:var(--ifm-color-secondary);border-radius:var(--ifm-global-radius);color:var(--ifm-color-black);font-size:.8rem;line-height:1.2;margin-left:.3rem;padding:.1rem .4rem}.authorListItem_n3yI{list-style-type:none;margin-bottom:2rem}.authorCol_Hf19{max-width:inherit!important}.imageOnlyAuthorRow_pa_O{display:flex;flex-flow:row wrap}.imageOnlyAuthorCol_G86a{margin-left:.3rem;margin-right:.3rem}.codeBlockContainer_Ckt0{background:var(--prism-background-color);border-radius:var(--ifm-code-border-radius);box-shadow:var(--ifm-global-shadow-lw);color:var(--prism-color);margin-bottom:var(--ifm-leading)}.codeBlock_bY9V{--ifm-pre-background:var(--prism-background-color);margin:0;padding:0}.codeBlockStandalone_MEMb{padding:0}.codeBlockLines_e6Vv{float:left;font:inherit;min-width:100%;padding:var(--ifm-pre-padding)}.codeBlockLinesWithNumbering_o6Pm{display:table;padding:var(--ifm-pre-padding) 0}:where(:root){--docusaurus-highlighted-code-line-bg:#484d5b}:where([data-theme=dark]){--docusaurus-highlighted-code-line-bg:#646464}.theme-code-block-highlighted-line{background-color:var(--docusaurus-highlighted-code-line-bg);display:block;margin:0 calc(var(--ifm-pre-padding)*-1);padding:0 var(--ifm-pre-padding)}.codeLine_lJS_{counter-increment:a;display:table-row}.codeLineNumber_Tfdd{background:var(--ifm-pre-background);display:table-cell;left:0;overflow-wrap:normal;padding:0 var(--ifm-pre-padding);position:sticky;text-align:right;width:1%}.codeLineNumber_Tfdd:before{content:counter(a);opacity:.4}.theme-code-block-highlighted-line .codeLineNumber_Tfdd:before{opacity:.8}.codeLineContent_feaV{padding-right:var(--ifm-pre-padding)}.theme-code-block:hover .copyButtonCopied_Vdqa{opacity:1!important}.copyButtonIcons_IEyt{height:1.125rem;position:relative;width:1.125rem}.copyButtonIcon_TrPX,.copyButtonSuccessIcon_cVMy{left:0;position:absolute;top:0;fill:currentColor;height:inherit;opacity:inherit;transition:all var(--ifm-transition-fast) ease;width:inherit}.copyButtonSuccessIcon_cVMy{color:#00d600;left:50%;opacity:0;top:50%;transform:translate(-50%,-50%) scale(.33)}.copyButtonCopied_Vdqa .copyButtonIcon_TrPX{opacity:0;transform:scale(.33)}.copyButtonCopied_Vdqa .copyButtonSuccessIcon_cVMy{opacity:1;transform:translate(-50%,-50%) scale(1);transition-delay:75ms}.wordWrapButtonIcon_b1P5{height:1.2rem;width:1.2rem}.wordWrapButtonEnabled_uzNF .wordWrapButtonIcon_b1P5{color:var(--ifm-color-primary)}.buttonGroup_M5ko{column-gap:.2rem;display:flex;position:absolute;right:calc(var(--ifm-pre-padding)/2);top:calc(var(--ifm-pre-padding)/2)}.buttonGroup_M5ko button{align-items:center;background:var(--prism-background-color);border:1px solid var(--ifm-color-emphasis-300);border-radius:var(--ifm-global-radius);color:var(--prism-color);display:flex;line-height:0;opacity:0;padding:.4rem;transition:opacity var(--ifm-transition-fast) ease-in-out}.buttonGroup_M5ko button:focus-visible,.buttonGroup_M5ko button:hover{opacity:1!important}.theme-code-block:hover .buttonGroup_M5ko button{opacity:.4}.tag_zVej{border:1px solid var(--docusaurus-tag-list-border);transition:border var(--ifm-transition-fast)}.tag_zVej:hover{--docusaurus-tag-list-border:var(--ifm-link-color);-webkit-text-decoration:none;text-decoration:none}.tagRegular_sFm0{border-radius:var(--ifm-global-radius);font-size:90%;padding:.2rem .5rem .3rem}.tagWithCount_h2kH{align-items:center;border-left:0;display:flex;padding:0 .5rem 0 1rem;position:relative}.tagWithCount_h2kH:after,.tagWithCount_h2kH:before{border:1px solid var(--docusaurus-tag-list-border);content:"";position:absolute;top:50%;transition:inherit}.tagWithCount_h2kH:before{border-bottom:0;border-right:0;height:1.18rem;right:100%;transform:translate(50%,-50%) rotate(-45deg);width:1.18rem}.tagWithCount_h2kH:after{border-radius:50%;height:.5rem;left:0;transform:translateY(-50%);width:.5rem}.tagWithCount_h2kH span{background:var(--ifm-color-secondary);border-radius:var(--ifm-global-radius);color:var(--ifm-color-black);font-size:.7rem;line-height:1.2;margin-left:.3rem;padding:.1rem .4rem}.tag_Nnez{display:inline-block;margin:.5rem .5rem 0 1rem}.codeBlockContent_QJqH{border-radius:inherit;direction:ltr;position:relative}.codeBlockTitle_OeMC{border-bottom:1px solid var(--ifm-color-emphasis-300);border-top-left-radius:inherit;border-top-right-radius:inherit;font-size:var(--ifm-code-font-size);font-weight:500;padding:.75rem var(--ifm-pre-padding)}.codeBlockTitle_OeMC+.codeBlockContent_QJqH .codeBlock_a8dz{border-top-left-radius:0;border-top-right-radius:0}.tags_jXut{display:inline}.tag_QGVx{display:inline-block;margin:0 .4rem .5rem 0}.iconEdit_Z9Sw{margin-right:.3em;vertical-align:sub}.lastUpdated_JAkA{font-size:smaller;font-style:italic;margin-top:.2rem}.tocCollapsibleButton_TO0P{align-items:center;display:flex;font-size:inherit;justify-content:space-between;padding:.4rem .8rem;width:100%}.tocCollapsibleButton_TO0P:after{background:var(--ifm-menu-link-sublist-icon) 50% 50%/2rem 2rem no-repeat;content:"";filter:var(--ifm-menu-link-sublist-icon-filter);height:1.25rem;transform:rotate(180deg);transition:transform var(--ifm-transition-fast);width:1.25rem}.tocCollapsibleButtonExpanded_MG3E:after,.tocCollapsibleExpanded_sAul{transform:none}.tocCollapsible_ETCw{background-color:var(--ifm-menu-color-background-active);border-radius:var(--ifm-global-radius);margin:1rem 0}.tocCollapsibleContent_vkbj>ul{border-left:none;border-top:1px solid var(--ifm-color-emphasis-300);font-size:15px;padding:.2rem 0}.tocCollapsibleContent_vkbj ul li{margin:.4rem .8rem}.tocCollapsibleContent_vkbj a{display:block}.details_b_Ee{--docusaurus-details-decoration-color:var(--ifm-alert-border-color);--docusaurus-details-transition:transform var(--ifm-transition-fast) ease;border:1px solid var(--ifm-alert-border-color);margin:0 0 var(--ifm-spacing-vertical)}.containsTaskList_mC6p{list-style:none}:not(.containsTaskList_mC6p>li)>.containsTaskList_mC6p{padding-left:0}.img_ev3q{height:auto}.tableOfContents_bqdL{max-height:calc(100vh - var(--ifm-navbar-height) - 2rem);overflow-y:auto;position:sticky;top:calc(var(--ifm-navbar-height) + 1rem)}.admonition_xJq3{margin-bottom:1em}.admonitionHeading_Gvgb{font:var(--ifm-heading-font-weight) var(--ifm-h5-font-size)/var(--ifm-heading-line-height) var(--ifm-heading-font-family);text-transform:uppercase}.admonitionHeading_Gvgb:not(:last-child){margin-bottom:.3rem}.admonitionHeading_Gvgb code{text-transform:none}.admonitionIcon_Rf37{display:inline-block;margin-right:.4em;vertical-align:middle}.admonitionIcon_Rf37 svg{display:inline-block;height:1.6em;width:1.6em;fill:var(--ifm-alert-foreground-color)}.breadcrumbHomeIcon_YNFT{height:1.1rem;position:relative;top:1px;vertical-align:top;width:1.1rem}.breadcrumbsContainer_Z_bl{--ifm-breadcrumb-size-multiplier:0.8;margin-bottom:.8rem}.title_kItE{--ifm-h1-font-size:3rem;margin-bottom:calc(var(--ifm-leading)*1.25)}.docItemContainer_Djhp article>:first-child,.docItemContainer_Djhp header+*{margin-top:0}.mdxPageWrapper_j9I6{justify-content:center}}@media (min-width:997px){.collapseSidebarButton_PEFL,.expandButton_TmdG{background-color:var(--docusaurus-collapse-button-bg)}:root{--docusaurus-announcement-bar-height:30px}.announcementBarClose_gvF7,.announcementBarPlaceholder_vyr4{flex-basis:50px}.collapseSidebarButton_PEFL{border:1px solid var(--ifm-toc-border-color);border-radius:0;bottom:0;display:block!important;height:40px;position:sticky}.collapseSidebarButtonIcon_kv0_{margin-top:4px;transform:rotate(180deg)}.expandButtonIcon_i1dp,[dir=rtl] .collapseSidebarButtonIcon_kv0_{transform:rotate(0)}.collapseSidebarButton_PEFL:focus,.collapseSidebarButton_PEFL:hover,.expandButton_TmdG:focus,.expandButton_TmdG:hover{background-color:var(--docusaurus-collapse-button-bg-hover)}.menuHtmlItem_M9Kj{padding:var(--ifm-menu-link-padding-vertical) var(--ifm-menu-link-padding-horizontal)}.menu_SIkG{flex-grow:1;padding:.5rem}@supports (scrollbar-gutter:stable){.menu_SIkG{padding:.5rem 0 .5rem .5rem;scrollbar-gutter:stable}}.menuWithAnnouncementBar_GW3s{margin-bottom:var(--docusaurus-announcement-bar-height)}.sidebar_njMd{display:flex;flex-direction:column;height:100%;padding-top:var(--ifm-navbar-height);width:var(--doc-sidebar-width)}.sidebarWithHideableNavbar_wUlq{padding-top:0}.sidebarHidden_VK0M{opacity:0;visibility:hidden}.sidebarLogo_isFc{align-items:center;color:inherit!important;display:flex!important;margin:0 var(--ifm-navbar-padding-horizontal);max-height:var(--ifm-navbar-height);min-height:var(--ifm-navbar-height);-webkit-text-decoration:none!important;text-decoration:none!important}.sidebarLogo_isFc img{height:2rem;margin-right:.5rem}.expandButton_TmdG{align-items:center;display:flex;height:100%;justify-content:center;position:absolute;right:0;top:0;transition:background-color var(--ifm-transition-fast) ease;width:100%}[dir=rtl] .expandButtonIcon_i1dp{transform:rotate(180deg)}.docSidebarContainer_YfHR{border-right:1px solid var(--ifm-toc-border-color);clip-path:inset(0);display:block;margin-top:calc(var(--ifm-navbar-height)*-1);transition:width var(--ifm-transition-fast) ease;width:var(--doc-sidebar-width);will-change:width}.docSidebarContainerHidden_DPk8{cursor:pointer;width:var(--doc-sidebar-hidden-width)}.sidebarViewport_aRkj{height:100%;max-height:100vh;position:sticky;top:0}.docMainContainer_TBSr{flex-grow:1;max-width:calc(100% - var(--doc-sidebar-width))}.docMainContainerEnhanced_lQrH{max-width:calc(100% - var(--doc-sidebar-hidden-width))}.docItemWrapperEnhanced_JWYK{max-width:calc(var(--ifm-container-width) + var(--doc-sidebar-width))!important}.navbarSearchContainer_Bca1{padding:var(--ifm-navbar-item-padding-vertical) var(--ifm-navbar-item-padding-horizontal)}.lastUpdated_JAkA{text-align:right}.tocMobile_ITEo{display:none}.docItemCol_VOVn,.generatedIndexPage_vN6x{max-width:75%!important}}@media (min-width:1440px){.container{max-width:var(--ifm-container-width-xl)}}@media (max-width:996px){.col{--ifm-col-width:100%;flex-basis:var(--ifm-col-width);margin-left:0}.footer{--ifm-footer-padding-horizontal:0}.colorModeToggle_DEke,.footer__link-separator,.navbar__item,.sidebar_re4s,.tableOfContents_bqdL{display:none}.footer__col{margin-bottom:calc(var(--ifm-spacing-vertical)*3)}.footer__link-item{display:block;width:max-content}.hero{padding-left:0;padding-right:0}.navbar>.container,.navbar>.container-fluid{padding:0}.navbar__toggle{display:inherit}.navbar__search-input{width:9rem}.pills--block,.tabs--block{flex-direction:column}.navbarSearchContainer_Bca1{position:absolute;right:var(--ifm-navbar-padding-horizontal)}.docItemContainer_F8PC{padding:0 .3rem}}@media screen and (max-width:996px){.features_t9lD .bharatml-card_xZ6l{margin-bottom:2rem}.featuresHeader_qR2i{font-size:2rem}.featuresSubtitle_VdGe{font-size:1rem}.heroBanner_qdFl{padding:2rem}}@media screen and (max-width:768px){.heroLogo_U6bI{height:120px;width:120px}.logoContainer_xdaK{margin-bottom:1.5rem}.buttons_AeoN{flex-direction:column;gap:.5rem}.statsContainer_KpvY{align-items:center;flex-direction:column;gap:1rem}}@media (max-width:576px){.markdown h1:first-child{--ifm-h1-font-size:2rem}.markdown>h2{--ifm-h2-font-size:1.5rem}.markdown>h3{--ifm-h3-font-size:1.25rem}.title_f1Hy{font-size:2rem}}@media (hover:hover){.backToTopButton_sjWU:hover{background-color:var(--ifm-color-emphasis-300)}}@media (pointer:fine){.thin-scrollbar{scrollbar-width:thin}.thin-scrollbar::-webkit-scrollbar{height:var(--ifm-scrollbar-size);width:var(--ifm-scrollbar-size)}.thin-scrollbar::-webkit-scrollbar-track{background:var(--ifm-scrollbar-track-background-color);border-radius:10px}.thin-scrollbar::-webkit-scrollbar-thumb{background:var(--ifm-scrollbar-thumb-background-color);border-radius:10px}.thin-scrollbar::-webkit-scrollbar-thumb:hover{background:var(--ifm-scrollbar-thumb-hover-background-color)}}@media (prefers-reduced-motion:reduce){:root{--ifm-transition-fast:0ms;--ifm-transition-slow:0ms}}@media print{.announcementBar_mb4j,.footer,.menu,.navbar,.pagination-nav,.table-of-contents,.tocMobile_ITEo{display:none}.tabs{page-break-inside:avoid}.codeBlockLines_e6Vv{white-space:pre-wrap}} \ No newline at end of file diff --git a/docs/assets/css/styles.aaf16941.css b/docs/assets/css/styles.aaf16941.css new file mode 100644 index 00000000..b852d07c --- /dev/null +++ b/docs/assets/css/styles.aaf16941.css @@ -0,0 +1 @@ +@layer docusaurus.infima,docusaurus.theme-common,docusaurus.theme-classic,docusaurus.core,docusaurus.plugin-debug,docusaurus.theme-mermaid,docusaurus.theme-live-codeblock,docusaurus.theme-search-algolia.docsearch,docusaurus.theme-search-algolia;@layer docusaurus.infima{.col,.container{padding:0 var(--ifm-spacing-horizontal);width:100%}.markdown>h2,.markdown>h3,.markdown>h4,.markdown>h5,.markdown>h6{margin-bottom:calc(var(--ifm-heading-vertical-rhythm-bottom)*var(--ifm-leading))}.markdown li,body{word-wrap:break-word}body,ol ol,ol ul,ul ol,ul ul{margin:0}pre,table{overflow:auto}blockquote,pre{margin:0 0 var(--ifm-spacing-vertical)}.breadcrumbs__link,.button{transition-timing-function:var(--ifm-transition-timing-default)}.button,code{vertical-align:middle}.button--outline.button--active,.button--outline:active,.button--outline:hover,:root{--ifm-button-color:var(--ifm-font-color-base-inverse)}.menu__link:hover,a{transition:color var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.navbar--dark,:root{--ifm-navbar-link-hover-color:var(--ifm-color-primary)}.menu,.navbar-sidebar{overflow-x:hidden}:root,html[data-theme=dark]{--ifm-color-emphasis-500:var(--ifm-color-gray-500)}:root{--ifm-color-scheme:light;--ifm-dark-value:10%;--ifm-darker-value:15%;--ifm-darkest-value:30%;--ifm-light-value:15%;--ifm-lighter-value:30%;--ifm-lightest-value:50%;--ifm-contrast-background-value:90%;--ifm-contrast-foreground-value:70%;--ifm-contrast-background-dark-value:70%;--ifm-contrast-foreground-dark-value:90%;--ifm-color-primary:#3578e5;--ifm-color-secondary:#ebedf0;--ifm-color-success:#00a400;--ifm-color-info:#54c7ec;--ifm-color-warning:#ffba00;--ifm-color-danger:#fa383e;--ifm-color-primary-dark:#306cce;--ifm-color-primary-darker:#2d66c3;--ifm-color-primary-darkest:#2554a0;--ifm-color-primary-light:#538ce9;--ifm-color-primary-lighter:#72a1ed;--ifm-color-primary-lightest:#9abcf2;--ifm-color-primary-contrast-background:#ebf2fc;--ifm-color-primary-contrast-foreground:#102445;--ifm-color-secondary-dark:#d4d5d8;--ifm-color-secondary-darker:#c8c9cc;--ifm-color-secondary-darkest:#a4a6a8;--ifm-color-secondary-light:#eef0f2;--ifm-color-secondary-lighter:#f1f2f5;--ifm-color-secondary-lightest:#f5f6f8;--ifm-color-secondary-contrast-background:#fdfdfe;--ifm-color-secondary-contrast-foreground:#474748;--ifm-color-success-dark:#009400;--ifm-color-success-darker:#008b00;--ifm-color-success-darkest:#007300;--ifm-color-success-light:#26b226;--ifm-color-success-lighter:#4dbf4d;--ifm-color-success-lightest:#80d280;--ifm-color-success-contrast-background:#e6f6e6;--ifm-color-success-contrast-foreground:#003100;--ifm-color-info-dark:#4cb3d4;--ifm-color-info-darker:#47a9c9;--ifm-color-info-darkest:#3b8ba5;--ifm-color-info-light:#6ecfef;--ifm-color-info-lighter:#87d8f2;--ifm-color-info-lightest:#aae3f6;--ifm-color-info-contrast-background:#eef9fd;--ifm-color-info-contrast-foreground:#193c47;--ifm-color-warning-dark:#e6a700;--ifm-color-warning-darker:#d99e00;--ifm-color-warning-darkest:#b38200;--ifm-color-warning-light:#ffc426;--ifm-color-warning-lighter:#ffcf4d;--ifm-color-warning-lightest:#ffdd80;--ifm-color-warning-contrast-background:#fff8e6;--ifm-color-warning-contrast-foreground:#4d3800;--ifm-color-danger-dark:#e13238;--ifm-color-danger-darker:#d53035;--ifm-color-danger-darkest:#af272b;--ifm-color-danger-light:#fb565b;--ifm-color-danger-lighter:#fb7478;--ifm-color-danger-lightest:#fd9c9f;--ifm-color-danger-contrast-background:#ffebec;--ifm-color-danger-contrast-foreground:#4b1113;--ifm-color-white:#fff;--ifm-color-black:#000;--ifm-color-gray-0:var(--ifm-color-white);--ifm-color-gray-100:#f5f6f7;--ifm-color-gray-200:#ebedf0;--ifm-color-gray-300:#dadde1;--ifm-color-gray-400:#ccd0d5;--ifm-color-gray-500:#bec3c9;--ifm-color-gray-600:#8d949e;--ifm-color-gray-700:#606770;--ifm-color-gray-800:#444950;--ifm-color-gray-900:#1c1e21;--ifm-color-gray-1000:var(--ifm-color-black);--ifm-color-emphasis-0:var(--ifm-color-gray-0);--ifm-color-emphasis-100:var(--ifm-color-gray-100);--ifm-color-emphasis-200:var(--ifm-color-gray-200);--ifm-color-emphasis-300:var(--ifm-color-gray-300);--ifm-color-emphasis-400:var(--ifm-color-gray-400);--ifm-color-emphasis-600:var(--ifm-color-gray-600);--ifm-color-emphasis-700:var(--ifm-color-gray-700);--ifm-color-emphasis-800:var(--ifm-color-gray-800);--ifm-color-emphasis-900:var(--ifm-color-gray-900);--ifm-color-emphasis-1000:var(--ifm-color-gray-1000);--ifm-color-content:var(--ifm-color-emphasis-900);--ifm-color-content-inverse:var(--ifm-color-emphasis-0);--ifm-color-content-secondary:#525860;--ifm-background-color:#0000;--ifm-background-surface-color:var(--ifm-color-content-inverse);--ifm-global-border-width:1px;--ifm-global-radius:0.4rem;--ifm-hover-overlay:#0000000d;--ifm-font-color-base:var(--ifm-color-content);--ifm-font-color-base-inverse:var(--ifm-color-content-inverse);--ifm-font-color-secondary:var(--ifm-color-content-secondary);--ifm-font-family-base:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--ifm-font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--ifm-font-size-base:100%;--ifm-font-weight-light:300;--ifm-font-weight-normal:400;--ifm-font-weight-semibold:500;--ifm-font-weight-bold:700;--ifm-font-weight-base:var(--ifm-font-weight-normal);--ifm-line-height-base:1.65;--ifm-global-spacing:1rem;--ifm-spacing-vertical:var(--ifm-global-spacing);--ifm-spacing-horizontal:var(--ifm-global-spacing);--ifm-transition-fast:200ms;--ifm-transition-slow:400ms;--ifm-transition-timing-default:cubic-bezier(0.08,0.52,0.52,1);--ifm-global-shadow-lw:0 1px 2px 0 #0000001a;--ifm-global-shadow-md:0 5px 40px #0003;--ifm-global-shadow-tl:0 12px 28px 0 #0003,0 2px 4px 0 #0000001a;--ifm-z-index-dropdown:100;--ifm-z-index-fixed:200;--ifm-z-index-overlay:400;--ifm-container-width:1140px;--ifm-container-width-xl:1320px;--ifm-code-background:#f6f7f8;--ifm-code-border-radius:var(--ifm-global-radius);--ifm-code-font-size:90%;--ifm-code-padding-horizontal:0.1rem;--ifm-code-padding-vertical:0.1rem;--ifm-pre-background:var(--ifm-code-background);--ifm-pre-border-radius:var(--ifm-code-border-radius);--ifm-pre-color:inherit;--ifm-pre-line-height:1.45;--ifm-pre-padding:1rem;--ifm-heading-color:inherit;--ifm-heading-margin-top:0;--ifm-heading-margin-bottom:var(--ifm-spacing-vertical);--ifm-heading-font-family:var(--ifm-font-family-base);--ifm-heading-font-weight:var(--ifm-font-weight-bold);--ifm-heading-line-height:1.25;--ifm-h1-font-size:2rem;--ifm-h2-font-size:1.5rem;--ifm-h3-font-size:1.25rem;--ifm-h4-font-size:1rem;--ifm-h5-font-size:0.875rem;--ifm-h6-font-size:0.85rem;--ifm-image-alignment-padding:1.25rem;--ifm-leading-desktop:1.25;--ifm-leading:calc(var(--ifm-leading-desktop)*1rem);--ifm-list-left-padding:2rem;--ifm-list-margin:1rem;--ifm-list-item-margin:0.25rem;--ifm-list-paragraph-margin:1rem;--ifm-table-cell-padding:0.75rem;--ifm-table-background:#0000;--ifm-table-stripe-background:#00000008;--ifm-table-border-width:1px;--ifm-table-border-color:var(--ifm-color-emphasis-300);--ifm-table-head-background:inherit;--ifm-table-head-color:inherit;--ifm-table-head-font-weight:var(--ifm-font-weight-bold);--ifm-table-cell-color:inherit;--ifm-link-color:var(--ifm-color-primary);--ifm-link-decoration:none;--ifm-link-hover-color:var(--ifm-link-color);--ifm-link-hover-decoration:underline;--ifm-paragraph-margin-bottom:var(--ifm-leading);--ifm-blockquote-font-size:var(--ifm-font-size-base);--ifm-blockquote-border-left-width:2px;--ifm-blockquote-padding-horizontal:var(--ifm-spacing-horizontal);--ifm-blockquote-padding-vertical:0;--ifm-blockquote-shadow:none;--ifm-blockquote-color:var(--ifm-color-emphasis-800);--ifm-blockquote-border-color:var(--ifm-color-emphasis-300);--ifm-hr-background-color:var(--ifm-color-emphasis-500);--ifm-hr-height:1px;--ifm-hr-margin-vertical:1.5rem;--ifm-scrollbar-size:7px;--ifm-scrollbar-track-background-color:#f1f1f1;--ifm-scrollbar-thumb-background-color:silver;--ifm-scrollbar-thumb-hover-background-color:#a7a7a7;--ifm-alert-background-color:inherit;--ifm-alert-border-color:inherit;--ifm-alert-border-radius:var(--ifm-global-radius);--ifm-alert-border-width:0px;--ifm-alert-border-left-width:5px;--ifm-alert-color:var(--ifm-font-color-base);--ifm-alert-padding-horizontal:var(--ifm-spacing-horizontal);--ifm-alert-padding-vertical:var(--ifm-spacing-vertical);--ifm-alert-shadow:var(--ifm-global-shadow-lw);--ifm-avatar-intro-margin:1rem;--ifm-avatar-intro-alignment:inherit;--ifm-avatar-photo-size:3rem;--ifm-badge-background-color:inherit;--ifm-badge-border-color:inherit;--ifm-badge-border-radius:var(--ifm-global-radius);--ifm-badge-border-width:var(--ifm-global-border-width);--ifm-badge-color:var(--ifm-color-white);--ifm-badge-padding-horizontal:calc(var(--ifm-spacing-horizontal)*0.5);--ifm-badge-padding-vertical:calc(var(--ifm-spacing-vertical)*0.25);--ifm-breadcrumb-border-radius:1.5rem;--ifm-breadcrumb-spacing:0.5rem;--ifm-breadcrumb-color-active:var(--ifm-color-primary);--ifm-breadcrumb-item-background-active:var(--ifm-hover-overlay);--ifm-breadcrumb-padding-horizontal:0.8rem;--ifm-breadcrumb-padding-vertical:0.4rem;--ifm-breadcrumb-size-multiplier:1;--ifm-breadcrumb-separator:url('data:image/svg+xml;utf8,');--ifm-breadcrumb-separator-filter:none;--ifm-breadcrumb-separator-size:0.5rem;--ifm-breadcrumb-separator-size-multiplier:1.25;--ifm-button-background-color:inherit;--ifm-button-border-color:var(--ifm-button-background-color);--ifm-button-border-width:var(--ifm-global-border-width);--ifm-button-font-weight:var(--ifm-font-weight-bold);--ifm-button-padding-horizontal:1.5rem;--ifm-button-padding-vertical:0.375rem;--ifm-button-size-multiplier:1;--ifm-button-transition-duration:var(--ifm-transition-fast);--ifm-button-border-radius:calc(var(--ifm-global-radius)*var(--ifm-button-size-multiplier));--ifm-button-group-spacing:2px;--ifm-card-background-color:var(--ifm-background-surface-color);--ifm-card-border-radius:calc(var(--ifm-global-radius)*2);--ifm-card-horizontal-spacing:var(--ifm-global-spacing);--ifm-card-vertical-spacing:var(--ifm-global-spacing);--ifm-toc-border-color:var(--ifm-color-emphasis-300);--ifm-toc-link-color:var(--ifm-color-content-secondary);--ifm-toc-padding-vertical:0.5rem;--ifm-toc-padding-horizontal:0.5rem;--ifm-dropdown-background-color:var(--ifm-background-surface-color);--ifm-dropdown-font-weight:var(--ifm-font-weight-semibold);--ifm-dropdown-link-color:var(--ifm-font-color-base);--ifm-dropdown-hover-background-color:var(--ifm-hover-overlay);--ifm-footer-background-color:var(--ifm-color-emphasis-100);--ifm-footer-color:inherit;--ifm-footer-link-color:var(--ifm-color-emphasis-700);--ifm-footer-link-hover-color:var(--ifm-color-primary);--ifm-footer-link-horizontal-spacing:0.5rem;--ifm-footer-padding-horizontal:calc(var(--ifm-spacing-horizontal)*2);--ifm-footer-padding-vertical:calc(var(--ifm-spacing-vertical)*2);--ifm-footer-title-color:inherit;--ifm-footer-logo-max-width:min(30rem,90vw);--ifm-hero-background-color:var(--ifm-background-surface-color);--ifm-hero-text-color:var(--ifm-color-emphasis-800);--ifm-menu-color:var(--ifm-color-emphasis-700);--ifm-menu-color-active:var(--ifm-color-primary);--ifm-menu-color-background-active:var(--ifm-hover-overlay);--ifm-menu-color-background-hover:var(--ifm-hover-overlay);--ifm-menu-link-padding-horizontal:0.75rem;--ifm-menu-link-padding-vertical:0.375rem;--ifm-menu-link-sublist-icon:url('data:image/svg+xml;utf8,');--ifm-menu-link-sublist-icon-filter:none;--ifm-navbar-background-color:var(--ifm-background-surface-color);--ifm-navbar-height:3.75rem;--ifm-navbar-item-padding-horizontal:0.75rem;--ifm-navbar-item-padding-vertical:0.25rem;--ifm-navbar-link-color:var(--ifm-font-color-base);--ifm-navbar-link-active-color:var(--ifm-link-color);--ifm-navbar-padding-horizontal:var(--ifm-spacing-horizontal);--ifm-navbar-padding-vertical:calc(var(--ifm-spacing-vertical)*0.5);--ifm-navbar-shadow:var(--ifm-global-shadow-lw);--ifm-navbar-search-input-background-color:var(--ifm-color-emphasis-200);--ifm-navbar-search-input-color:var(--ifm-color-emphasis-800);--ifm-navbar-search-input-placeholder-color:var(--ifm-color-emphasis-500);--ifm-navbar-search-input-icon:url('data:image/svg+xml;utf8,');--ifm-navbar-sidebar-width:83vw;--ifm-pagination-border-radius:var(--ifm-global-radius);--ifm-pagination-color-active:var(--ifm-color-primary);--ifm-pagination-font-size:1rem;--ifm-pagination-item-active-background:var(--ifm-hover-overlay);--ifm-pagination-page-spacing:0.2em;--ifm-pagination-padding-horizontal:calc(var(--ifm-spacing-horizontal)*1);--ifm-pagination-padding-vertical:calc(var(--ifm-spacing-vertical)*0.25);--ifm-pagination-nav-border-radius:var(--ifm-global-radius);--ifm-pagination-nav-color-hover:var(--ifm-color-primary);--ifm-pills-color-active:var(--ifm-color-primary);--ifm-pills-color-background-active:var(--ifm-hover-overlay);--ifm-pills-spacing:0.125rem;--ifm-tabs-color:var(--ifm-font-color-secondary);--ifm-tabs-color-active:var(--ifm-color-primary);--ifm-tabs-color-active-border:var(--ifm-tabs-color-active);--ifm-tabs-padding-horizontal:1rem;--ifm-tabs-padding-vertical:1rem}.badge--danger,.badge--info,.badge--primary,.badge--secondary,.badge--success,.badge--warning{--ifm-badge-border-color:var(--ifm-badge-background-color)}.button--link,.button--outline{--ifm-button-background-color:#0000}*{box-sizing:border-box}html{background-color:var(--ifm-background-color);color:var(--ifm-font-color-base);color-scheme:var(--ifm-color-scheme);font:var(--ifm-font-size-base)/var(--ifm-line-height-base) var(--ifm-font-family-base);-webkit-font-smoothing:antialiased;-webkit-tap-highlight-color:transparent;text-rendering:optimizelegibility;-webkit-text-size-adjust:100%;text-size-adjust:100%}iframe{border:0;color-scheme:auto}.container{margin:0 auto;max-width:var(--ifm-container-width)}.container--fluid{max-width:inherit}.row{display:flex;flex-wrap:wrap;margin:0 calc(var(--ifm-spacing-horizontal)*-1)}.margin-bottom--none,.margin-vert--none,.markdown>:last-child{margin-bottom:0!important}.margin-top--none,.margin-vert--none{margin-top:0!important}.row--no-gutters{margin-left:0;margin-right:0}.margin-horiz--none,.margin-right--none{margin-right:0!important}.row--no-gutters>.col{padding-left:0;padding-right:0}.row--align-top{align-items:flex-start}.row--align-bottom{align-items:flex-end}.row--align-center{align-items:center}.row--align-stretch{align-items:stretch}.row--align-baseline{align-items:baseline}.col{--ifm-col-width:100%;flex:1 0;margin-left:0;max-width:var(--ifm-col-width)}.padding-bottom--none,.padding-vert--none{padding-bottom:0!important}.padding-top--none,.padding-vert--none{padding-top:0!important}.padding-horiz--none,.padding-left--none{padding-left:0!important}.padding-horiz--none,.padding-right--none{padding-right:0!important}.col[class*=col--]{flex:0 0 var(--ifm-col-width)}.col--1{--ifm-col-width:8.33333%}.col--offset-1{margin-left:8.33333%}.col--2{--ifm-col-width:16.66667%}.col--offset-2{margin-left:16.66667%}.col--3{--ifm-col-width:25%}.col--offset-3{margin-left:25%}.col--4{--ifm-col-width:33.33333%}.col--offset-4{margin-left:33.33333%}.col--5{--ifm-col-width:41.66667%}.col--offset-5{margin-left:41.66667%}.col--6{--ifm-col-width:50%}.col--offset-6{margin-left:50%}.col--7{--ifm-col-width:58.33333%}.col--offset-7{margin-left:58.33333%}.col--8{--ifm-col-width:66.66667%}.col--offset-8{margin-left:66.66667%}.col--9{--ifm-col-width:75%}.col--offset-9{margin-left:75%}.col--10{--ifm-col-width:83.33333%}.col--offset-10{margin-left:83.33333%}.col--11{--ifm-col-width:91.66667%}.col--offset-11{margin-left:91.66667%}.col--12{--ifm-col-width:100%}.col--offset-12{margin-left:100%}.margin-horiz--none,.margin-left--none{margin-left:0!important}.margin--none{margin:0!important}.margin-bottom--xs,.margin-vert--xs{margin-bottom:.25rem!important}.margin-top--xs,.margin-vert--xs{margin-top:.25rem!important}.margin-horiz--xs,.margin-left--xs{margin-left:.25rem!important}.margin-horiz--xs,.margin-right--xs{margin-right:.25rem!important}.margin--xs{margin:.25rem!important}.margin-bottom--sm,.margin-vert--sm{margin-bottom:.5rem!important}.margin-top--sm,.margin-vert--sm{margin-top:.5rem!important}.margin-horiz--sm,.margin-left--sm{margin-left:.5rem!important}.margin-horiz--sm,.margin-right--sm{margin-right:.5rem!important}.margin--sm{margin:.5rem!important}.margin-bottom--md,.margin-vert--md{margin-bottom:1rem!important}.margin-top--md,.margin-vert--md{margin-top:1rem!important}.margin-horiz--md,.margin-left--md{margin-left:1rem!important}.margin-horiz--md,.margin-right--md{margin-right:1rem!important}.margin--md{margin:1rem!important}.margin-bottom--lg,.margin-vert--lg{margin-bottom:2rem!important}.margin-top--lg,.margin-vert--lg{margin-top:2rem!important}.margin-horiz--lg,.margin-left--lg{margin-left:2rem!important}.margin-horiz--lg,.margin-right--lg{margin-right:2rem!important}.margin--lg{margin:2rem!important}.margin-bottom--xl,.margin-vert--xl{margin-bottom:5rem!important}.margin-top--xl,.margin-vert--xl{margin-top:5rem!important}.margin-horiz--xl,.margin-left--xl{margin-left:5rem!important}.margin-horiz--xl,.margin-right--xl{margin-right:5rem!important}.margin--xl{margin:5rem!important}.padding--none{padding:0!important}.padding-bottom--xs,.padding-vert--xs{padding-bottom:.25rem!important}.padding-top--xs,.padding-vert--xs{padding-top:.25rem!important}.padding-horiz--xs,.padding-left--xs{padding-left:.25rem!important}.padding-horiz--xs,.padding-right--xs{padding-right:.25rem!important}.padding--xs{padding:.25rem!important}.padding-bottom--sm,.padding-vert--sm{padding-bottom:.5rem!important}.padding-top--sm,.padding-vert--sm{padding-top:.5rem!important}.padding-horiz--sm,.padding-left--sm{padding-left:.5rem!important}.padding-horiz--sm,.padding-right--sm{padding-right:.5rem!important}.padding--sm{padding:.5rem!important}.padding-bottom--md,.padding-vert--md{padding-bottom:1rem!important}.padding-top--md,.padding-vert--md{padding-top:1rem!important}.padding-horiz--md,.padding-left--md{padding-left:1rem!important}.padding-horiz--md,.padding-right--md{padding-right:1rem!important}.padding--md{padding:1rem!important}.padding-bottom--lg,.padding-vert--lg{padding-bottom:2rem!important}.padding-top--lg,.padding-vert--lg{padding-top:2rem!important}.padding-horiz--lg,.padding-left--lg{padding-left:2rem!important}.padding-horiz--lg,.padding-right--lg{padding-right:2rem!important}.padding--lg{padding:2rem!important}.padding-bottom--xl,.padding-vert--xl{padding-bottom:5rem!important}.padding-top--xl,.padding-vert--xl{padding-top:5rem!important}.padding-horiz--xl,.padding-left--xl{padding-left:5rem!important}.padding-horiz--xl,.padding-right--xl{padding-right:5rem!important}.padding--xl{padding:5rem!important}code{background-color:var(--ifm-code-background);border:.1rem solid #0000001a;border-radius:var(--ifm-code-border-radius);font-family:var(--ifm-font-family-monospace);font-size:var(--ifm-code-font-size);padding:var(--ifm-code-padding-vertical) var(--ifm-code-padding-horizontal)}a code{color:inherit}pre{background-color:var(--ifm-pre-background);border-radius:var(--ifm-pre-border-radius);color:var(--ifm-pre-color);font:var(--ifm-code-font-size)/var(--ifm-pre-line-height) var(--ifm-font-family-monospace);padding:var(--ifm-pre-padding)}pre code{background-color:initial;border:none;font-size:100%;line-height:inherit;padding:0}kbd{background-color:var(--ifm-color-emphasis-0);border:1px solid var(--ifm-color-emphasis-400);border-radius:.2rem;box-shadow:inset 0 -1px 0 var(--ifm-color-emphasis-400);color:var(--ifm-color-emphasis-800);font:80% var(--ifm-font-family-monospace);padding:.15rem .3rem}h1,h2,h3,h4,h5,h6{color:var(--ifm-heading-color);font-family:var(--ifm-heading-font-family);font-weight:var(--ifm-heading-font-weight);line-height:var(--ifm-heading-line-height);margin:var(--ifm-heading-margin-top) 0 var(--ifm-heading-margin-bottom) 0}h1{font-size:var(--ifm-h1-font-size)}h2{font-size:var(--ifm-h2-font-size)}h3{font-size:var(--ifm-h3-font-size)}h4{font-size:var(--ifm-h4-font-size)}h5{font-size:var(--ifm-h5-font-size)}h6{font-size:var(--ifm-h6-font-size)}img{max-width:100%}img[align=right]{padding-left:var(--image-alignment-padding)}img[align=left]{padding-right:var(--image-alignment-padding)}.markdown{--ifm-h1-vertical-rhythm-top:3;--ifm-h2-vertical-rhythm-top:2;--ifm-h3-vertical-rhythm-top:1.5;--ifm-heading-vertical-rhythm-top:1.25;--ifm-h1-vertical-rhythm-bottom:1.25;--ifm-heading-vertical-rhythm-bottom:1}.markdown:after,.markdown:before{content:"";display:table}.markdown:after{clear:both}.markdown h1:first-child{--ifm-h1-font-size:3rem;margin-bottom:calc(var(--ifm-h1-vertical-rhythm-bottom)*var(--ifm-leading))}.markdown>h2{--ifm-h2-font-size:2rem;margin-top:calc(var(--ifm-h2-vertical-rhythm-top)*var(--ifm-leading))}.markdown>h3{--ifm-h3-font-size:1.5rem;margin-top:calc(var(--ifm-h3-vertical-rhythm-top)*var(--ifm-leading))}.markdown>h4,.markdown>h5,.markdown>h6{margin-top:calc(var(--ifm-heading-vertical-rhythm-top)*var(--ifm-leading))}.markdown>p,.markdown>pre,.markdown>ul{margin-bottom:var(--ifm-leading)}.markdown li>p{margin-top:var(--ifm-list-paragraph-margin)}.markdown li+li{margin-top:var(--ifm-list-item-margin)}ol,ul{margin:0 0 var(--ifm-list-margin);padding-left:var(--ifm-list-left-padding)}ol ol,ul ol{list-style-type:lower-roman}ol ol ol,ol ul ol,ul ol ol,ul ul ol{list-style-type:lower-alpha}table{border-collapse:collapse;display:block;margin-bottom:var(--ifm-spacing-vertical)}table thead tr{border-bottom:2px solid var(--ifm-table-border-color)}table thead,table tr:nth-child(2n){background-color:var(--ifm-table-stripe-background)}table tr{background-color:var(--ifm-table-background);border-top:var(--ifm-table-border-width) solid var(--ifm-table-border-color)}table td,table th{border:var(--ifm-table-border-width) solid var(--ifm-table-border-color);padding:var(--ifm-table-cell-padding)}table th{background-color:var(--ifm-table-head-background);color:var(--ifm-table-head-color);font-weight:var(--ifm-table-head-font-weight)}table td{color:var(--ifm-table-cell-color)}strong{font-weight:var(--ifm-font-weight-bold)}a{color:var(--ifm-link-color);text-decoration:var(--ifm-link-decoration)}a:hover{color:var(--ifm-link-hover-color);text-decoration:var(--ifm-link-hover-decoration)}.button:hover,.text--no-decoration,.text--no-decoration:hover,a:not([href]){-webkit-text-decoration:none;text-decoration:none}p{margin:0 0 var(--ifm-paragraph-margin-bottom)}blockquote{border-left:var(--ifm-blockquote-border-left-width) solid var(--ifm-blockquote-border-color);box-shadow:var(--ifm-blockquote-shadow);color:var(--ifm-blockquote-color);font-size:var(--ifm-blockquote-font-size);padding:var(--ifm-blockquote-padding-vertical) var(--ifm-blockquote-padding-horizontal)}blockquote>:first-child{margin-top:0}blockquote>:last-child{margin-bottom:0}hr{background-color:var(--ifm-hr-background-color);border:0;height:var(--ifm-hr-height);margin:var(--ifm-hr-margin-vertical) 0}.shadow--lw{box-shadow:var(--ifm-global-shadow-lw)!important}.shadow--md{box-shadow:var(--ifm-global-shadow-md)!important}.shadow--tl{box-shadow:var(--ifm-global-shadow-tl)!important}.text--primary{color:var(--ifm-color-primary)}.text--secondary{color:var(--ifm-color-secondary)}.text--success{color:var(--ifm-color-success)}.text--info{color:var(--ifm-color-info)}.text--warning{color:var(--ifm-color-warning)}.text--danger{color:var(--ifm-color-danger)}.text--center{text-align:center}.text--left{text-align:left}.text--justify{text-align:justify}.text--right{text-align:right}.text--capitalize{text-transform:capitalize}.text--lowercase{text-transform:lowercase}.alert__heading,.text--uppercase{text-transform:uppercase}.text--light{font-weight:var(--ifm-font-weight-light)}.text--normal{font-weight:var(--ifm-font-weight-normal)}.text--semibold{font-weight:var(--ifm-font-weight-semibold)}.text--bold{font-weight:var(--ifm-font-weight-bold)}.text--italic{font-style:italic}.text--truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text--break{word-wrap:break-word!important;word-break:break-word!important}.clean-btn{background:none;border:none;color:inherit;cursor:pointer;font-family:inherit;padding:0}.alert,.alert .close{color:var(--ifm-alert-foreground-color)}.clean-list{list-style:none;padding-left:0}.alert--primary{--ifm-alert-background-color:var(--ifm-color-primary-contrast-background);--ifm-alert-background-color-highlight:#3578e526;--ifm-alert-foreground-color:var(--ifm-color-primary-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-primary-dark)}.alert--secondary{--ifm-alert-background-color:var(--ifm-color-secondary-contrast-background);--ifm-alert-background-color-highlight:#ebedf026;--ifm-alert-foreground-color:var(--ifm-color-secondary-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-secondary-dark)}.alert--success{--ifm-alert-background-color:var(--ifm-color-success-contrast-background);--ifm-alert-background-color-highlight:#00a40026;--ifm-alert-foreground-color:var(--ifm-color-success-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-success-dark)}.alert--info{--ifm-alert-background-color:var(--ifm-color-info-contrast-background);--ifm-alert-background-color-highlight:#54c7ec26;--ifm-alert-foreground-color:var(--ifm-color-info-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-info-dark)}.alert--warning{--ifm-alert-background-color:var(--ifm-color-warning-contrast-background);--ifm-alert-background-color-highlight:#ffba0026;--ifm-alert-foreground-color:var(--ifm-color-warning-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-warning-dark)}.alert--danger{--ifm-alert-background-color:var(--ifm-color-danger-contrast-background);--ifm-alert-background-color-highlight:#fa383e26;--ifm-alert-foreground-color:var(--ifm-color-danger-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-danger-dark)}.alert{--ifm-code-background:var(--ifm-alert-background-color-highlight);--ifm-link-color:var(--ifm-alert-foreground-color);--ifm-link-hover-color:var(--ifm-alert-foreground-color);--ifm-link-decoration:underline;--ifm-tabs-color:var(--ifm-alert-foreground-color);--ifm-tabs-color-active:var(--ifm-alert-foreground-color);--ifm-tabs-color-active-border:var(--ifm-alert-border-color);background-color:var(--ifm-alert-background-color);border:var(--ifm-alert-border-width) solid var(--ifm-alert-border-color);border-left-width:var(--ifm-alert-border-left-width);border-radius:var(--ifm-alert-border-radius);box-shadow:var(--ifm-alert-shadow);padding:var(--ifm-alert-padding-vertical) var(--ifm-alert-padding-horizontal)}.alert__heading{align-items:center;display:flex;font:700 var(--ifm-h5-font-size)/var(--ifm-heading-line-height) var(--ifm-heading-font-family);margin-bottom:.5rem}.alert__icon{display:inline-flex;margin-right:.4em}.alert__icon svg{fill:var(--ifm-alert-foreground-color);stroke:var(--ifm-alert-foreground-color);stroke-width:0}.alert .close{margin:calc(var(--ifm-alert-padding-vertical)*-1) calc(var(--ifm-alert-padding-horizontal)*-1) 0 0;opacity:.75}.alert .close:focus,.alert .close:hover{opacity:1}.alert a{text-decoration-color:var(--ifm-alert-border-color)}.alert a:hover{text-decoration-thickness:2px}.avatar{column-gap:var(--ifm-avatar-intro-margin);display:flex}.avatar__photo{border-radius:50%;display:block;height:var(--ifm-avatar-photo-size);overflow:hidden;width:var(--ifm-avatar-photo-size)}.card--full-height,.navbar__logo img{height:100%}.avatar__photo--sm{--ifm-avatar-photo-size:2rem}.avatar__photo--lg{--ifm-avatar-photo-size:4rem}.avatar__photo--xl{--ifm-avatar-photo-size:6rem}.avatar__intro{display:flex;flex:1 1;flex-direction:column;justify-content:center;text-align:var(--ifm-avatar-intro-alignment)}.badge,.breadcrumbs__item,.breadcrumbs__link,.button,.dropdown>.navbar__link:after{display:inline-block}.avatar__name{font:700 var(--ifm-h4-font-size)/var(--ifm-heading-line-height) var(--ifm-font-family-base)}.avatar__subtitle{margin-top:.25rem}.avatar--vertical{--ifm-avatar-intro-alignment:center;--ifm-avatar-intro-margin:0.5rem;align-items:center;flex-direction:column}.badge{background-color:var(--ifm-badge-background-color);border:var(--ifm-badge-border-width) solid var(--ifm-badge-border-color);border-radius:var(--ifm-badge-border-radius);color:var(--ifm-badge-color);font-size:75%;font-weight:var(--ifm-font-weight-bold);line-height:1;padding:var(--ifm-badge-padding-vertical) var(--ifm-badge-padding-horizontal)}.badge--primary{--ifm-badge-background-color:var(--ifm-color-primary)}.badge--secondary{--ifm-badge-background-color:var(--ifm-color-secondary);color:var(--ifm-color-black)}.breadcrumbs__link,.button.button--secondary.button--outline:not(.button--active):not(:hover){color:var(--ifm-font-color-base)}.badge--success{--ifm-badge-background-color:var(--ifm-color-success)}.badge--info{--ifm-badge-background-color:var(--ifm-color-info)}.badge--warning{--ifm-badge-background-color:var(--ifm-color-warning)}.badge--danger{--ifm-badge-background-color:var(--ifm-color-danger)}.breadcrumbs{margin-bottom:0;padding-left:0}.breadcrumbs__item:not(:last-child):after{background:var(--ifm-breadcrumb-separator) center;content:" ";display:inline-block;filter:var(--ifm-breadcrumb-separator-filter);height:calc(var(--ifm-breadcrumb-separator-size)*var(--ifm-breadcrumb-size-multiplier)*var(--ifm-breadcrumb-separator-size-multiplier));margin:0 var(--ifm-breadcrumb-spacing);opacity:.5;width:calc(var(--ifm-breadcrumb-separator-size)*var(--ifm-breadcrumb-size-multiplier)*var(--ifm-breadcrumb-separator-size-multiplier))}.breadcrumbs__item--active .breadcrumbs__link{background:var(--ifm-breadcrumb-item-background-active);color:var(--ifm-breadcrumb-color-active)}.breadcrumbs__link{border-radius:var(--ifm-breadcrumb-border-radius);font-size:calc(1rem*var(--ifm-breadcrumb-size-multiplier));padding:calc(var(--ifm-breadcrumb-padding-vertical)*var(--ifm-breadcrumb-size-multiplier)) calc(var(--ifm-breadcrumb-padding-horizontal)*var(--ifm-breadcrumb-size-multiplier));transition-duration:var(--ifm-transition-fast);transition-property:background,color}.breadcrumbs__link:any-link:hover,.breadcrumbs__link:link:hover,.breadcrumbs__link:visited:hover,area[href].breadcrumbs__link:hover{background:var(--ifm-breadcrumb-item-background-active);-webkit-text-decoration:none;text-decoration:none}.breadcrumbs--sm{--ifm-breadcrumb-size-multiplier:0.8}.breadcrumbs--lg{--ifm-breadcrumb-size-multiplier:1.2}.button{background-color:var(--ifm-button-background-color);border:var(--ifm-button-border-width) solid var(--ifm-button-border-color);border-radius:var(--ifm-button-border-radius);cursor:pointer;font-size:calc(.875rem*var(--ifm-button-size-multiplier));font-weight:var(--ifm-button-font-weight);line-height:1.5;padding:calc(var(--ifm-button-padding-vertical)*var(--ifm-button-size-multiplier)) calc(var(--ifm-button-padding-horizontal)*var(--ifm-button-size-multiplier));text-align:center;transition-duration:var(--ifm-button-transition-duration);transition-property:color,background,border-color;-webkit-user-select:none;user-select:none;white-space:nowrap}.button,.button:hover{color:var(--ifm-button-color)}.button--outline{--ifm-button-color:var(--ifm-button-border-color)}.button--outline:hover{--ifm-button-background-color:var(--ifm-button-border-color)}.button--link{--ifm-button-border-color:#0000;color:var(--ifm-link-color);text-decoration:var(--ifm-link-decoration)}.button--link.button--active,.button--link:active,.button--link:hover{color:var(--ifm-link-hover-color);text-decoration:var(--ifm-link-hover-decoration)}.dropdown__link--active,.dropdown__link:hover,.menu__link:hover,.navbar__brand:hover,.navbar__link--active,.navbar__link:hover,.pagination-nav__link:hover,.pagination__link:hover{-webkit-text-decoration:none;text-decoration:none}.button.disabled,.button:disabled,.button[disabled]{opacity:.65;pointer-events:none}.button--sm{--ifm-button-size-multiplier:0.8}.button--lg{--ifm-button-size-multiplier:1.35}.button--block{display:block;width:100%}.button.button--secondary{color:var(--ifm-color-gray-900)}:where(.button--primary){--ifm-button-background-color:var(--ifm-color-primary);--ifm-button-border-color:var(--ifm-color-primary)}:where(.button--primary):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-primary-dark);--ifm-button-border-color:var(--ifm-color-primary-dark)}.button--primary.button--active,.button--primary:active{--ifm-button-background-color:var(--ifm-color-primary-darker);--ifm-button-border-color:var(--ifm-color-primary-darker)}:where(.button--secondary){--ifm-button-background-color:var(--ifm-color-secondary);--ifm-button-border-color:var(--ifm-color-secondary)}:where(.button--secondary):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-secondary-dark);--ifm-button-border-color:var(--ifm-color-secondary-dark)}.button--secondary.button--active,.button--secondary:active{--ifm-button-background-color:var(--ifm-color-secondary-darker);--ifm-button-border-color:var(--ifm-color-secondary-darker)}:where(.button--success){--ifm-button-background-color:var(--ifm-color-success);--ifm-button-border-color:var(--ifm-color-success)}:where(.button--success):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-success-dark);--ifm-button-border-color:var(--ifm-color-success-dark)}.button--success.button--active,.button--success:active{--ifm-button-background-color:var(--ifm-color-success-darker);--ifm-button-border-color:var(--ifm-color-success-darker)}:where(.button--info){--ifm-button-background-color:var(--ifm-color-info);--ifm-button-border-color:var(--ifm-color-info)}:where(.button--info):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-info-dark);--ifm-button-border-color:var(--ifm-color-info-dark)}.button--info.button--active,.button--info:active{--ifm-button-background-color:var(--ifm-color-info-darker);--ifm-button-border-color:var(--ifm-color-info-darker)}:where(.button--warning){--ifm-button-background-color:var(--ifm-color-warning);--ifm-button-border-color:var(--ifm-color-warning)}:where(.button--warning):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-warning-dark);--ifm-button-border-color:var(--ifm-color-warning-dark)}.button--warning.button--active,.button--warning:active{--ifm-button-background-color:var(--ifm-color-warning-darker);--ifm-button-border-color:var(--ifm-color-warning-darker)}:where(.button--danger){--ifm-button-background-color:var(--ifm-color-danger);--ifm-button-border-color:var(--ifm-color-danger)}:where(.button--danger):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-danger-dark);--ifm-button-border-color:var(--ifm-color-danger-dark)}.button--danger.button--active,.button--danger:active{--ifm-button-background-color:var(--ifm-color-danger-darker);--ifm-button-border-color:var(--ifm-color-danger-darker)}.button-group{display:inline-flex;gap:var(--ifm-button-group-spacing)}.button-group>.button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.button-group>.button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.button-group--block{display:flex;justify-content:stretch}.button-group--block>.button{flex-grow:1}.card{background-color:var(--ifm-card-background-color);border-radius:var(--ifm-card-border-radius);box-shadow:var(--ifm-global-shadow-lw);display:flex;flex-direction:column;overflow:hidden}.card__image{padding-top:var(--ifm-card-vertical-spacing)}.card__image:first-child{padding-top:0}.card__body,.card__footer,.card__header{padding:var(--ifm-card-vertical-spacing) var(--ifm-card-horizontal-spacing)}.card__body:not(:last-child),.card__footer:not(:last-child),.card__header:not(:last-child){padding-bottom:0}.card__body>:last-child,.card__footer>:last-child,.card__header>:last-child{margin-bottom:0}.card__footer{margin-top:auto}.table-of-contents{font-size:.8rem;margin-bottom:0;padding:var(--ifm-toc-padding-vertical) 0}.table-of-contents,.table-of-contents ul{list-style:none;padding-left:var(--ifm-toc-padding-horizontal)}.table-of-contents li{margin:var(--ifm-toc-padding-vertical) var(--ifm-toc-padding-horizontal)}.table-of-contents__left-border{border-left:1px solid var(--ifm-toc-border-color)}.table-of-contents__link{color:var(--ifm-toc-link-color);display:block}.table-of-contents__link--active,.table-of-contents__link--active code,.table-of-contents__link:hover,.table-of-contents__link:hover code{color:var(--ifm-color-primary);-webkit-text-decoration:none;text-decoration:none}.close{color:var(--ifm-color-black);float:right;font-size:1.5rem;font-weight:var(--ifm-font-weight-bold);line-height:1;opacity:.5;padding:1rem;transition:opacity var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.close:hover{opacity:.7}.close:focus{opacity:.8}.dropdown{display:inline-flex;font-weight:var(--ifm-dropdown-font-weight);position:relative;vertical-align:top}.dropdown--hoverable:hover .dropdown__menu,.dropdown--show .dropdown__menu{opacity:1;pointer-events:all;transform:translateY(-1px);visibility:visible}.dropdown__menu,.navbar__item.dropdown .navbar__link:not([href]){pointer-events:none}.dropdown--right .dropdown__menu{left:inherit;right:0}.dropdown--nocaret .navbar__link:after{content:none!important}.dropdown__menu{background-color:var(--ifm-dropdown-background-color);border-radius:var(--ifm-global-radius);box-shadow:var(--ifm-global-shadow-md);left:0;list-style:none;max-height:80vh;min-width:10rem;opacity:0;overflow-y:auto;padding:.5rem;position:absolute;top:calc(100% - var(--ifm-navbar-item-padding-vertical) + .3rem);transform:translateY(-.625rem);transition-duration:var(--ifm-transition-fast);transition-property:opacity,transform,visibility;transition-timing-function:var(--ifm-transition-timing-default);visibility:hidden;z-index:var(--ifm-z-index-dropdown)}.menu__caret,.menu__link,.menu__list-item-collapsible{border-radius:.25rem;transition:background var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.dropdown__link{border-radius:.25rem;color:var(--ifm-dropdown-link-color);display:block;font-size:.875rem;margin-top:.2rem;padding:.25rem .5rem;white-space:nowrap}.dropdown__link--active,.dropdown__link:hover{background-color:var(--ifm-dropdown-hover-background-color);color:var(--ifm-dropdown-link-color)}.dropdown__link--active,.dropdown__link--active:hover{--ifm-dropdown-link-color:var(--ifm-link-color)}.dropdown>.navbar__link:after{border-color:currentcolor #0000;border-style:solid;border-width:.4em .4em 0;content:"";margin-left:.3em;position:relative;top:2px;transform:translateY(-50%)}.footer{background-color:var(--ifm-footer-background-color);color:var(--ifm-footer-color);padding:var(--ifm-footer-padding-vertical) var(--ifm-footer-padding-horizontal)}.footer--dark{--ifm-footer-background-color:#303846;--ifm-footer-color:var(--ifm-footer-link-color);--ifm-footer-link-color:var(--ifm-color-secondary);--ifm-footer-title-color:var(--ifm-color-white)}.footer__links{margin-bottom:1rem}.footer__link-item{color:var(--ifm-footer-link-color);line-height:2}.footer__link-item:hover{color:var(--ifm-footer-link-hover-color)}.footer__link-separator{margin:0 var(--ifm-footer-link-horizontal-spacing)}.footer__logo{margin-top:1rem;max-width:var(--ifm-footer-logo-max-width)}.footer__title{color:var(--ifm-footer-title-color);font:700 var(--ifm-h4-font-size)/var(--ifm-heading-line-height) var(--ifm-font-family-base);margin-bottom:var(--ifm-heading-margin-bottom)}.menu,.navbar__link{font-weight:var(--ifm-font-weight-semibold)}.footer__item{margin-top:0}.footer__items{margin-bottom:0}[type=checkbox]{padding:0}.hero{align-items:center;background-color:var(--ifm-hero-background-color);color:var(--ifm-hero-text-color);display:flex;padding:4rem 2rem}.hero--primary{--ifm-hero-background-color:var(--ifm-color-primary);--ifm-hero-text-color:var(--ifm-font-color-base-inverse)}.hero--dark{--ifm-hero-background-color:#303846;--ifm-hero-text-color:var(--ifm-color-white)}.hero__title{font-size:3rem}.hero__subtitle{font-size:1.5rem}.menu__list{list-style:none;margin:0;padding-left:0}.menu__caret,.menu__link{padding:var(--ifm-menu-link-padding-vertical) var(--ifm-menu-link-padding-horizontal)}.menu__list .menu__list{flex:0 0 100%;margin-top:.25rem;padding-left:var(--ifm-menu-link-padding-horizontal)}.menu__list-item:not(:first-child){margin-top:.25rem}.menu__list-item--collapsed .menu__list{height:0;overflow:hidden}.menu__list-item--collapsed .menu__caret:before,.menu__list-item--collapsed .menu__link--sublist:after{transform:rotate(90deg)}.menu__list-item-collapsible{display:flex;flex-wrap:wrap;position:relative}.menu__caret:hover,.menu__link:hover,.menu__list-item-collapsible--active,.menu__list-item-collapsible:hover{background:var(--ifm-menu-color-background-hover)}.menu__list-item-collapsible .menu__link--active,.menu__list-item-collapsible .menu__link:hover{background:none!important}.menu__caret,.menu__link{align-items:center;display:flex}.menu__link{color:var(--ifm-menu-color);flex:1;line-height:1.25}.menu__link:hover{color:var(--ifm-menu-color)}.menu__caret:before,.menu__link--sublist-caret:after{content:"";filter:var(--ifm-menu-link-sublist-icon-filter);height:1.25rem;transform:rotate(180deg);transition:transform var(--ifm-transition-fast) linear;width:1.25rem}.menu__link--sublist-caret:after{background:var(--ifm-menu-link-sublist-icon) 50%/2rem 2rem;margin-left:auto;min-width:1.25rem}.menu__link--active,.menu__link--active:hover{color:var(--ifm-menu-color-active)}.navbar__brand,.navbar__link{color:var(--ifm-navbar-link-color)}.menu__link--active:not(.menu__link--sublist){background-color:var(--ifm-menu-color-background-active)}.menu__caret:before{background:var(--ifm-menu-link-sublist-icon) 50%/2rem 2rem}.navbar--dark,html[data-theme=dark]{--ifm-menu-link-sublist-icon-filter:invert(100%) sepia(94%) saturate(17%) hue-rotate(223deg) brightness(104%) contrast(98%)}.navbar{background-color:var(--ifm-navbar-background-color);box-shadow:var(--ifm-navbar-shadow);height:var(--ifm-navbar-height);padding:var(--ifm-navbar-padding-vertical) var(--ifm-navbar-padding-horizontal)}.navbar,.navbar>.container,.navbar>.container-fluid{display:flex}.navbar--fixed-top{position:sticky;top:0;z-index:var(--ifm-z-index-fixed)}.navbar-sidebar,.navbar-sidebar__backdrop{bottom:0;left:0;opacity:0;position:fixed;top:0;transition-duration:var(--ifm-transition-fast);transition-timing-function:ease-in-out;visibility:hidden}.navbar__inner{display:flex;flex-wrap:wrap;justify-content:space-between;width:100%}.navbar__brand{align-items:center;display:flex;margin-right:1rem;min-width:0}.navbar__brand:hover{color:var(--ifm-navbar-link-hover-color)}.navbar__title{flex:1 1 auto}.navbar__toggle{display:none;margin-right:.5rem}.navbar__logo{flex:0 0 auto;height:2rem;margin-right:.5rem}.navbar__items{align-items:center;display:flex;flex:1;min-width:0}.navbar__items--center{flex:0 0 auto}.navbar__items--center .navbar__brand{margin:0}.navbar__items--center+.navbar__items--right{flex:1}.navbar__items--right{flex:0 0 auto;justify-content:flex-end}.navbar__items--right>:last-child{padding-right:0}.navbar__item{display:inline-block;padding:var(--ifm-navbar-item-padding-vertical) var(--ifm-navbar-item-padding-horizontal)}.navbar__link--active,.navbar__link:hover{color:var(--ifm-navbar-link-hover-color)}.navbar--dark,.navbar--primary{--ifm-menu-color:var(--ifm-color-gray-300);--ifm-navbar-link-color:var(--ifm-color-gray-100);--ifm-navbar-search-input-background-color:#ffffff1a;--ifm-navbar-search-input-placeholder-color:#ffffff80;color:var(--ifm-color-white)}.navbar--dark{--ifm-navbar-background-color:#242526;--ifm-menu-color-background-active:#ffffff0d;--ifm-navbar-search-input-color:var(--ifm-color-white)}.navbar--primary{--ifm-navbar-background-color:var(--ifm-color-primary);--ifm-navbar-link-hover-color:var(--ifm-color-white);--ifm-menu-color-active:var(--ifm-color-white);--ifm-navbar-search-input-color:var(--ifm-color-emphasis-500)}.navbar__search-input{appearance:none;background:var(--ifm-navbar-search-input-background-color) var(--ifm-navbar-search-input-icon) no-repeat .75rem center/1rem 1rem;border:none;border-radius:2rem;color:var(--ifm-navbar-search-input-color);cursor:text;display:inline-block;font-size:1rem;height:2rem;padding:0 .5rem 0 2.25rem;width:12.5rem}.navbar__search-input::placeholder{color:var(--ifm-navbar-search-input-placeholder-color)}.navbar-sidebar{background-color:var(--ifm-navbar-background-color);box-shadow:var(--ifm-global-shadow-md);transform:translate3d(-100%,0,0);transition-property:opacity,visibility,transform;width:var(--ifm-navbar-sidebar-width)}.navbar-sidebar--show .navbar-sidebar,.navbar-sidebar__items{transform:translateZ(0)}.navbar-sidebar--show .navbar-sidebar,.navbar-sidebar--show .navbar-sidebar__backdrop{opacity:1;visibility:visible}.navbar-sidebar__backdrop{background-color:#0009;right:0;transition-property:opacity,visibility}.navbar-sidebar__brand{align-items:center;box-shadow:var(--ifm-navbar-shadow);display:flex;flex:1;height:var(--ifm-navbar-height);padding:var(--ifm-navbar-padding-vertical) var(--ifm-navbar-padding-horizontal)}.navbar-sidebar__items{display:flex;height:calc(100% - var(--ifm-navbar-height));transition:transform var(--ifm-transition-fast) ease-in-out}.navbar-sidebar__items--show-secondary{transform:translate3d(calc((var(--ifm-navbar-sidebar-width))*-1),0,0)}.navbar-sidebar__item{flex-shrink:0;padding:.5rem;width:calc(var(--ifm-navbar-sidebar-width))}.navbar-sidebar__back{background:var(--ifm-menu-color-background-active);font-size:15px;font-weight:var(--ifm-button-font-weight);margin:0 0 .2rem -.5rem;padding:.6rem 1.5rem;position:relative;text-align:left;top:-.5rem;width:calc(100% + 1rem)}.navbar-sidebar__close{display:flex;margin-left:auto}.pagination{column-gap:var(--ifm-pagination-page-spacing);display:flex;font-size:var(--ifm-pagination-font-size);padding-left:0}.pagination--sm{--ifm-pagination-font-size:0.8rem;--ifm-pagination-padding-horizontal:0.8rem;--ifm-pagination-padding-vertical:0.2rem}.pagination--lg{--ifm-pagination-font-size:1.2rem;--ifm-pagination-padding-horizontal:1.2rem;--ifm-pagination-padding-vertical:0.3rem}.pagination__item{display:inline-flex}.pagination__item>span{padding:var(--ifm-pagination-padding-vertical)}.pagination__item--active .pagination__link{color:var(--ifm-pagination-color-active)}.pagination__item--active .pagination__link,.pagination__item:not(.pagination__item--active):hover .pagination__link{background:var(--ifm-pagination-item-active-background)}.pagination__item--disabled,.pagination__item[disabled]{opacity:.25;pointer-events:none}.pagination__link{border-radius:var(--ifm-pagination-border-radius);color:var(--ifm-font-color-base);display:inline-block;padding:var(--ifm-pagination-padding-vertical) var(--ifm-pagination-padding-horizontal);transition:background var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.pagination-nav{display:grid;grid-gap:var(--ifm-spacing-horizontal);gap:var(--ifm-spacing-horizontal);grid-template-columns:repeat(2,1fr)}.pagination-nav__link{border:1px solid var(--ifm-color-emphasis-300);border-radius:var(--ifm-pagination-nav-border-radius);display:block;height:100%;line-height:var(--ifm-heading-line-height);padding:var(--ifm-global-spacing);transition:border-color var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.pagination-nav__link:hover{border-color:var(--ifm-pagination-nav-color-hover)}.pagination-nav__link--next{grid-column:2/3;text-align:right}.pagination-nav__label{font-size:var(--ifm-h4-font-size);font-weight:var(--ifm-heading-font-weight);word-break:break-word}.pagination-nav__link--prev .pagination-nav__label:before{content:"« "}.pagination-nav__link--next .pagination-nav__label:after{content:" »"}.pagination-nav__sublabel{color:var(--ifm-color-content-secondary);font-size:var(--ifm-h5-font-size);font-weight:var(--ifm-font-weight-semibold);margin-bottom:.25rem}.pills__item,.tabs{font-weight:var(--ifm-font-weight-bold)}.pills{display:flex;gap:var(--ifm-pills-spacing);padding-left:0}.pills__item{border-radius:.5rem;cursor:pointer;display:inline-block;padding:.25rem 1rem;transition:background var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.pills__item--active{color:var(--ifm-pills-color-active)}.pills__item--active,.pills__item:not(.pills__item--active):hover{background:var(--ifm-pills-color-background-active)}.pills--block{justify-content:stretch}.pills--block .pills__item{flex-grow:1;text-align:center}.tabs{color:var(--ifm-tabs-color);display:flex;margin-bottom:0;overflow-x:auto;padding-left:0}.tabs__item{border-bottom:3px solid #0000;border-radius:var(--ifm-global-radius);cursor:pointer;display:inline-flex;padding:var(--ifm-tabs-padding-vertical) var(--ifm-tabs-padding-horizontal);transition:background-color var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.tabs__item--active{border-bottom-color:var(--ifm-tabs-color-active-border);border-bottom-left-radius:0;border-bottom-right-radius:0;color:var(--ifm-tabs-color-active)}.tabs__item:hover{background-color:var(--ifm-hover-overlay)}.tabs--block{justify-content:stretch}.tabs--block .tabs__item{flex-grow:1;justify-content:center}html[data-theme=dark]{--ifm-color-scheme:dark;--ifm-color-emphasis-0:var(--ifm-color-gray-1000);--ifm-color-emphasis-100:var(--ifm-color-gray-900);--ifm-color-emphasis-200:var(--ifm-color-gray-800);--ifm-color-emphasis-300:var(--ifm-color-gray-700);--ifm-color-emphasis-400:var(--ifm-color-gray-600);--ifm-color-emphasis-600:var(--ifm-color-gray-400);--ifm-color-emphasis-700:var(--ifm-color-gray-300);--ifm-color-emphasis-800:var(--ifm-color-gray-200);--ifm-color-emphasis-900:var(--ifm-color-gray-100);--ifm-color-emphasis-1000:var(--ifm-color-gray-0);--ifm-background-color:#1b1b1d;--ifm-background-surface-color:#242526;--ifm-hover-overlay:#ffffff0d;--ifm-color-content:#e3e3e3;--ifm-color-content-secondary:#fff;--ifm-breadcrumb-separator-filter:invert(64%) sepia(11%) saturate(0%) hue-rotate(149deg) brightness(99%) contrast(95%);--ifm-code-background:#ffffff1a;--ifm-scrollbar-track-background-color:#444;--ifm-scrollbar-thumb-background-color:#686868;--ifm-scrollbar-thumb-hover-background-color:#7a7a7a;--ifm-table-stripe-background:#ffffff12;--ifm-toc-border-color:var(--ifm-color-emphasis-200);--ifm-color-primary-contrast-background:#102445;--ifm-color-primary-contrast-foreground:#ebf2fc;--ifm-color-secondary-contrast-background:#474748;--ifm-color-secondary-contrast-foreground:#fdfdfe;--ifm-color-success-contrast-background:#003100;--ifm-color-success-contrast-foreground:#e6f6e6;--ifm-color-info-contrast-background:#193c47;--ifm-color-info-contrast-foreground:#eef9fd;--ifm-color-warning-contrast-background:#4d3800;--ifm-color-warning-contrast-foreground:#fff8e6;--ifm-color-danger-contrast-background:#4b1113;--ifm-color-danger-contrast-foreground:#ffebec}}:root{--ifm-color-primary:#f59e0b;--ifm-color-primary-dark:#d97706;--ifm-color-primary-darker:#b45309;--ifm-color-primary-darkest:#92400e;--ifm-color-primary-light:#fbbf24;--ifm-color-primary-lighter:#fcd34d;--ifm-color-primary-lightest:#fde68a;--ifm-background-color:#f8fafc;--ifm-background-surface-color:#fff;--ifm-font-color-base:#1e293b;--ifm-font-color-secondary:#64748b;--ifm-heading-color:#0f172a;--ifm-link-color:#f59e0b;--ifm-link-hover-color:#d97706;--ifm-code-font-size:95%;--ifm-code-background:#f1f5f9;--ifm-code-border-radius:6px;--ifm-code-padding-horizontal:0.4rem;--ifm-code-padding-vertical:0.15rem;--docusaurus-highlighted-code-line-bg:#f59e0b14;--ifm-card-background-color:#fff;--ifm-global-shadow-lw:0 2px 8px #0000000f;--ifm-global-shadow-md:0 4px 16px #00000014;--ifm-global-shadow-tl:0 8px 32px #0000001a;--ifm-global-radius:8px;--ifm-toc-border-color:#00000014;--ifm-navbar-height:3.75rem;--hp-primary:#fbbf24;--hp-primary-dark:#f59e0b;--hp-secondary:#8b5cf6;--hp-accent:#06b6d4;--hp-success:#10b981;--hp-dark:#27001d;--hp-dark-light:#3d0029;--hp-text:#e2e8f0;--hp-text-muted:#94a3b8;--hp-bg-card:#ffffff08;--hp-bg-page:#27001d}[data-theme=dark]{--ifm-color-primary:#fbbf24;--ifm-color-primary-dark:#f59e0b;--ifm-color-primary-darker:#d97706;--ifm-color-primary-darkest:#b45309;--ifm-color-primary-light:#fcd34d;--ifm-color-primary-lighter:#fde68a;--ifm-color-primary-lightest:#fef3c7;--ifm-background-color:#27001d;--ifm-background-surface-color:#3d0029;--ifm-font-color-base:#e2e8f0;--ifm-font-color-secondary:#94a3b8;--ifm-heading-color:#f1f5f9;--ifm-link-color:#fbbf24;--ifm-link-hover-color:#fcd34d;--ifm-code-background:#ffffff0f;--docusaurus-highlighted-code-line-bg:#fbbf2426;--ifm-card-background-color:#ffffff08;--ifm-global-shadow-lw:0 2px 8px #0000004d;--ifm-global-shadow-md:0 4px 16px #0006;--ifm-global-shadow-tl:0 8px 32px #00000080;--ifm-toc-border-color:#ffffff0f}.gradient-bg-global{height:100%;left:0;pointer-events:none;position:fixed;top:0;width:100%;z-index:0}.gradient-orb-global{animation:25s ease-in-out infinite a;border-radius:50%;filter:blur(100px);opacity:.25;position:absolute}[data-theme=light] .gradient-orb-global{opacity:.1}.orb-global-1{background:radial-gradient(circle,#fbbf24,#0000);height:600px;left:-10%;top:-10%;width:600px}.orb-global-2{animation-delay:8s;background:radial-gradient(circle,#f59e0b,#0000);height:500px;right:-10%;top:50%;width:500px}.orb-global-3{animation-delay:15s;background:radial-gradient(circle,#06b6d4,#0000);bottom:-20%;height:700px;left:30%;width:700px}.logo_Ukns,.navbar__title{animation:3s infinite b;-webkit-text-fill-color:#0000}@keyframes a{0%,to{transform:translate(0) scale(1)}33%{transform:translate(60px,-60px) scale(1.1)}66%{transform:translate(-40px,40px) scale(.9)}}.navbar{backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);background:#27001dcc!important;border-bottom:1px solid #ffffff0d;box-shadow:none;position:sticky;z-index:100}[data-theme=light] .navbar{background:#ffffffd9!important;border-bottom:1px solid #00000014}.navbar__title{background:linear-gradient(135deg,#fbbf24,#f59e0b,#06b6d4);-webkit-background-clip:text;background-size:200% 200%;font-weight:800;background-clip:text}.navbar__link{font-weight:500}[data-theme=dark] .navbar__link,[data-theme=dark] .pagination-nav__label{color:#e2e8f0}[data-theme=dark] .navbar__link--active,[data-theme=dark] .navbar__link:hover{color:#fbbf24}.navbar__toggle{color:var(--ifm-font-color-base)}.navbar-sidebar{background:var(--ifm-background-color)}.footer{background:#3d0029!important;border-top:1px solid #ffffff0d}[data-theme=light] .footer{background:#f1f5f9!important;border-top:1px solid #00000014}.footer__title{color:#e2e8f0;font-weight:700}[data-theme=light] .footer__title{color:#1e293b}.footer__link-item{color:#94a3b8;transition:color .3s}.footer__link-item:hover{color:#fbbf24;-webkit-text-decoration:none;text-decoration:none}.footer__copyright,[data-theme=light] .footer__link-item{color:#64748b}[data-theme=light] .footer__link-item:hover{color:#f59e0b}[data-theme=dark] .theme-doc-sidebar-container{border-right:1px solid #ffffff0d!important}[data-theme=dark] .menu{background:#0000}[data-theme=dark] .menu__link{border-radius:8px;color:#cbd5e1;transition:.2s}[data-theme=dark] .menu__link:hover{background:#fbbf241a;color:#e2e8f0}[data-theme=dark] .menu__link--active:not(.menu__link--sublist){background:#fbbf2426;color:#fbbf24;font-weight:600}[data-theme=dark] .menu__list-item-collapsible:hover{background:#fbbf2414}[data-theme=dark] .theme-doc-sidebar-item-category>.menu__list-item-collapsible>.menu__link{color:#e2e8f0;font-weight:600}.main-wrapper,[class*=docMainContainer],[class*=mainWrapper]{position:relative;z-index:1}.markdown h1,.markdown h2,.markdown h3,.markdown h4,.markdown h5,.markdown h6{color:var(--ifm-heading-color)}[data-theme=dark] table{border-color:#ffffff14}[data-theme=dark] table thead tr{background:#ffffff0a;border-bottom:1px solid #ffffff14}[data-theme=dark] table tbody tr{border-bottom:1px solid #ffffff0a}[data-theme=dark] table tbody tr:nth-child(2n){background:#ffffff05}[data-theme=dark] hr,[data-theme=dark] td,[data-theme=dark] th{border-color:#ffffff0f}[data-theme=dark] blockquote{background:#fbbf240d;border-left-color:#fbbf24;color:#cbd5e1}[data-theme=dark] .prism-code{background:#ffffff0a!important;border:1px solid #ffffff0f}[data-theme=dark] code{background:#ffffff0f;border:1px solid #ffffff14;color:#e2e8f0}[data-theme=dark] a code{color:var(--ifm-link-color)}[data-theme=dark] .codeBlockTitle_node_modules-\@docusaurus-theme-classic-lib-theme-CodeBlock-Content-styles-module{background:#ffffff0f!important;border-bottom:1px solid #ffffff0f}[data-theme=dark] .alert{background:#ffffff08;border:1px solid #ffffff0f;color:#e2e8f0}[data-theme=dark] .alert--info{background:#06b6d40f;border-left:4px solid #06b6d4}[data-theme=dark] .alert--warning{background:#f59e0b0f;border-left:4px solid #f59e0b}[data-theme=dark] .alert--danger{background:#ef44440f;border-left:4px solid #ef4444}[data-theme=dark] .alert--success{background:#10b9810f;border-left:4px solid #10b981}[data-theme=dark] .alert--secondary{background:#fbbf240f;border-left:4px solid #fbbf24}[data-theme=dark] .admonitionHeading_node_modules-\@docusaurus-theme-classic-lib-theme-Admonition-Layout-styles-module{color:inherit}[data-theme=dark] .pagination-nav__sublabel,[data-theme=dark] .table-of-contents__link{color:#94a3b8}[data-theme=dark] .table-of-contents__link--active,[data-theme=dark] .table-of-contents__link:hover,[data-theme=dark] article .avatar__name a{color:#fbbf24}[data-theme=dark] .table-of-contents{border-left:1px solid #ffffff0f}[data-theme=dark] .pagination-nav__link{background:#ffffff08;border:1px solid #ffffff14;border-radius:12px;transition:.3s}[data-theme=dark] .pagination-nav__link:hover{background:#fbbf240f;border-color:#fbbf244d}[data-theme=dark] .blog-post-page article header h1{color:#f1f5f9}[data-theme=dark] .blog-tags a{background:#fbbf241a;border:1px solid #fbbf2433;color:#fbbf24}[data-theme=dark] .blog-tags a:hover{background:#fbbf2433;border-color:#fbbf2466;-webkit-text-decoration:none;text-decoration:none}[data-theme=dark] .navbar__search-input{background:#ffffff0d;border:1px solid #ffffff1a;color:#e2e8f0}[data-theme=dark] .navbar__search-input::placeholder{color:#64748b}[data-theme=dark] .breadcrumbs__link{background:#ffffff0a;border-radius:6px;color:#94a3b8}[data-theme=dark] .breadcrumbs__link:hover{background:#fbbf241a;color:#e2e8f0}[data-theme=dark] .breadcrumbs__item--active .breadcrumbs__link{background:#fbbf241f;color:#fbbf24}[data-theme=dark] .tabs__item{border-bottom-color:#0000;color:#94a3b8}[data-theme=dark] .tabs__item:hover{color:#e2e8f0}[data-theme=dark] .tabs__item--active{border-bottom-color:#fbbf24;color:#fbbf24}[data-theme=dark] ::-webkit-scrollbar{height:8px;width:8px}[data-theme=dark] ::-webkit-scrollbar-track{background:#0000}[data-theme=dark] ::-webkit-scrollbar-thumb{background:#ffffff1f;border-radius:4px}[data-theme=dark] ::-webkit-scrollbar-thumb:hover{background:#fff3}[data-theme=dark] .dropdown__menu{background:#3d0029;border:1px solid #ffffff14}[data-theme=dark] .dropdown__link{color:#cbd5e1}[data-theme=dark] .dropdown__link:hover{background:#fbbf241a;color:#e2e8f0}[data-theme=dark] .dropdown__link--active{background:#fbbf241f;color:#fbbf24}html.homepage-active .footer,html.homepage-active .navbar{display:none!important}html.homepage-active main{margin-top:0}html.homepage-active [class*=docMainContainer],html.homepage-active [class*=mainWrapper]{padding-top:0}[data-theme=light] .theme-doc-sidebar-container{border-right:1px solid #0000000f}[data-theme=light] .menu__link--active:not(.menu__link--sublist){background:#f59e0b14;color:#f59e0b;font-weight:600}[data-theme=light] .menu__link:hover{background:#f59e0b0d}[data-theme=light] .pagination-nav__link{border-radius:12px;transition:.3s}[data-theme=light] .pagination-nav__link:hover{border-color:#f59e0b4d;box-shadow:0 4px 16px #f59e0b14}[data-theme=light] blockquote{border-left-color:#f59e0b}@layer docusaurus.core{#__docusaurus-base-url-issue-banner-container{display:none}}.btn_bvfa,.btn_bvfa:hover,.componentLink_RzJT,.componentLink_RzJT:hover,.footerLinks_lH9U a,.footerLinks_lH9U a:hover,.footerList_2l2h a,.footerList_2l2h a:hover,.logo_Ukns,.navLink_aQaq,.navLink_aQaq:hover{-webkit-text-decoration:none;text-decoration:none}.hero_aEcG,.navContainer_E5Tz{margin:0 auto;max-width:1400px}[data-theme=light]{--hp-dark:#f8fafc;--hp-dark-light:#f1f5f9;--hp-text:#1e293b;--hp-text-muted:#64748b;--hp-bg-card:#00000005;--hp-bg-page:#f8fafc}.homepageWrapper_H_rv{background:var(--hp-bg-page);color:var(--hp-text);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;line-height:1.6;overflow-x:hidden}.customNav_xRNg{backdrop-filter:blur(20px);background:#27001dd9;border-bottom:1px solid #ffffff26;padding:1.2rem 0;position:fixed;top:0;transition:transform .3s;width:100%;z-index:1000}[data-theme=light] .customNav_xRNg{background:#ffffffd9;border-bottom:1px solid #00000014}.navContainer_E5Tz{align-items:center;display:flex;justify-content:space-between;padding:0 2rem}.logo_Ukns{background:linear-gradient(135deg,#fbbf24,#f59e0b,#06b6d4);-webkit-background-clip:text;background-size:200% 200%;font-size:1.6rem;font-weight:800;background-clip:text}@keyframes b{0%,to{background-position:0 50%}50%{background-position:100% 50%}}.navLinks_FO3Z{align-items:center;display:flex;gap:2.5rem}.navLink_aQaq{color:var(--hp-text);font-weight:500;transition:color .3s}.footerLinks_lH9U a:hover,.footerList_2l2h a:hover,.navLink_aQaq:hover{color:var(--hp-primary)}.btn_bvfa{border:none;border-radius:50px;cursor:pointer;display:inline-block;font-size:1rem;font-weight:600;padding:.75rem 2rem;transition:.4s cubic-bezier(.175,.885,.32,1.275)}.btnPrimary_hBjO{background:linear-gradient(135deg,#fbbf24,#f59e0b);box-shadow:0 10px 30px #fbbf244d;color:#fff}.btnPrimary_hBjO:hover{box-shadow:0 15px 40px #fbbf2480;color:#fff;transform:translateY(-3px)}.btnSecondary_mRVh{background:#ffffff0d;border:2px solid #fbbf2480;color:var(--hp-text)}[data-theme=light] .btnSecondary_mRVh{background:#fbbf240d;border-color:#fbbf2466}.btnSecondary_mRVh:hover{background:#fbbf2433;border-color:var(--hp-primary);color:var(--hp-text);transform:translateY(-3px)}.btnWhite_DoE5{background:#fff;color:var(--hp-primary)}.btnWhite_DoE5:hover{background:#f8fafc;color:var(--hp-primary);transform:translateY(-3px) scale(1.05)}.btnOutlineWhite_Kzbe{background:#0000;border:2px solid #fff;color:#fff}.btnOutlineWhite_Kzbe:hover{background:#ffffff26;color:#fff;transform:translateY(-3px)}.hero_aEcG{align-items:center;display:grid;gap:4rem;grid-template-columns:1fr 1fr;min-height:100vh;overflow:hidden;padding:10rem 2rem 5rem;position:relative;z-index:1}.networkCanvas_S8Th{height:100%;left:-2rem;pointer-events:none;position:absolute;top:0;width:calc(100% + 4rem);z-index:0}.ctaButtons_vsp7,.ctaDescription_HswS,.ctaTitle_arch,.customFooter_Ymmc,.heroContent_mKPX,.heroImage_xZN7,.section_Q9Zo{position:relative;z-index:1}.heroContent_mKPX{animation:1s ease-out c}@keyframes c{0%{opacity:0;transform:translateY(40px)}to{opacity:1;transform:translateY(0)}}.heroBadge_Z6oq{backdrop-filter:blur(10px);background:#fbbf241a;border:1px solid #fbbf244d;border-radius:50px;color:var(--hp-primary);display:inline-block;font-size:.9rem;font-weight:600;margin-bottom:2rem;padding:.5rem 1.5rem}.heroTitle_qg2I{background:linear-gradient(135deg,#fff,#a5b4fc);-webkit-background-clip:text;font-size:4.5rem;font-weight:900;line-height:1.1;margin-bottom:1.5rem;-webkit-text-fill-color:#0000;background-clip:text}.barrierAnswer_ZtxW,.barrierCard_tMSq p{line-height:1.8;color:var(--hp-text-muted)}[data-theme=light] .heroTitle_qg2I,[data-theme=light] .sectionTitle_Ut5p{background:linear-gradient(135deg,#1e293b,#fbbf24);-webkit-background-clip:text;-webkit-text-fill-color:#0000;background-clip:text}.heroSubtitle_jFu1{color:var(--hp-text-muted);font-size:1.25rem;line-height:1.8;margin-bottom:2.5rem}.heroButtons_r52D{display:flex;flex-wrap:wrap;gap:1.5rem}.heroImage_xZN7{animation:1s ease-out .3s both c}.heroImage_xZN7 img{border-radius:20px;box-shadow:0 40px 80px #00000080;width:100%}[data-theme=light] .heroImage_xZN7 img{box-shadow:0 40px 80px #00000026}.adoptionBadge_hbYR{animation:1s ease-out .6s both c;margin-top:3rem;text-align:center}.adoptionBadge_hbYR p{color:var(--hp-text-muted);font-size:.95rem}.section_Q9Zo{padding:8rem 2rem}.container_bfhl{margin:0 auto;max-width:1400px}.sectionHeader_Gahl{margin-bottom:5rem;text-align:center}.barrierCard_tMSq h3,.componentContent_xz2v h3,.sectionSubtitle_AZuW{font-weight:700;margin-bottom:1rem}.sectionSubtitle_AZuW{color:var(--hp-primary);font-size:.95rem;letter-spacing:2px;text-transform:uppercase}.sectionTitle_Ut5p{background:linear-gradient(135deg,#fff,#a5b4fc);-webkit-background-clip:text;font-size:3.5rem;font-weight:900;margin-bottom:1.5rem;-webkit-text-fill-color:#0000;background-clip:text}.barrierCard_tMSq,.componentCard_LlUg{backdrop-filter:blur(20px);background:var(--hp-bg-card);border:1px solid #ffffff14}.sectionDescription_cpL1{color:var(--hp-text-muted);font-size:1.2rem;margin:0 auto;max-width:800px}.barriersGrid_u0Jf,.videosGrid_FXHY{display:grid;gap:2.5rem;grid-template-columns:repeat(3,1fr);margin-top:4rem}.barrierCard_tMSq{border-radius:24px;padding:2.5rem;transition:.4s}[data-theme=light] .barrierCard_tMSq,[data-theme=light] .blogCard_hyds,[data-theme=light] .componentCard_LlUg,[data-theme=light] .statCard_w2S8,[data-theme=light] .videoCard_jGks{background:#fff;border-color:#00000014;box-shadow:0 4px 20px #0000000d}.barrierCard_tMSq:hover,.videoCard_jGks:hover{border-color:#fbbf244d;box-shadow:0 20px 50px #0006;transform:translateY(-8px)}.componentCardVisible_hAJc:hover,.componentCard_LlUg:hover{transform:translateY(-10px)}[data-theme=light] .barrierCard_tMSq:hover{border-color:#fbbf244d;box-shadow:0 20px 50px #fbbf241f}.barrierIcon_HTIA{font-size:2.5rem;margin-bottom:1.5rem}.barrierCard_tMSq h3{color:var(--hp-text);font-size:1.4rem}.barrierCard_tMSq p{font-size:.95rem}.barrierQuestions_jlWA{list-style:none;margin:1rem 0;padding:0}.barrierQuestions_jlWA li{color:var(--hp-text-muted);font-size:.92rem;line-height:1.6;padding:.4rem 0 .4rem 1.2rem;position:relative}.barrierQuestions_jlWA li:before{color:var(--hp-primary);content:"?";font-weight:700;left:0;position:absolute}.barrierAnswer_ZtxW{border-top:1px solid #ffffff0f;font-size:.92rem;margin-top:1rem;padding-top:1rem}.componentContent_xz2v,.statCard_w2S8{padding:2.5rem}[data-theme=light] .barrierAnswer_ZtxW{border-top-color:#0000000f}.componentsGrid_KtT5{display:grid;gap:3rem;grid-template-columns:repeat(3,1fr);margin-top:4rem}.componentCard_LlUg{border-radius:24px;opacity:0;overflow:hidden;transform:translateY(50px);transition:.5s cubic-bezier(.175,.885,.32,1.275)}.componentCardVisible_hAJc{opacity:1;transform:translateY(0)}.componentCard_LlUg:hover{border-color:#fbbf244d;box-shadow:0 30px 60px #00000080}[data-theme=light] .componentCard_LlUg:hover{box-shadow:0 30px 60px #fbbf241a}.blogCard_hyds:hover,.statCard_w2S8:hover{border-color:#fbbf244d;transform:translateY(-5px)}.componentContent_xz2v h3{color:var(--hp-text);font-size:1.6rem}.componentContent_xz2v p{color:var(--hp-text-muted);line-height:1.7;margin-bottom:1.5rem}.componentLink_RzJT{align-items:center;display:inline-flex;font-weight:600;gap:.5rem;transition:gap .3s}.blogCard_hyds,.statCard_w2S8,.videoCard_jGks{backdrop-filter:blur(20px);transition:.4s}.componentLink_RzJT,.componentLink_RzJT:hover{color:var(--hp-primary)}.componentLink_RzJT:hover{gap:1rem}.componentIcon_JDYs{align-items:center;background:linear-gradient(135deg,#fbbf241a,#f59e0b1a);display:flex;font-size:4rem;height:180px;justify-content:center;width:100%}[data-theme=light] .componentIcon_JDYs{background:linear-gradient(135deg,#fbbf240f,#f59e0b0f)}.statsSection_GUBq{background:#0003}[data-theme=light] .statsSection_GUBq{background:#fbbf2408}.statsGrid_wBRk{display:grid;gap:2.5rem;grid-template-columns:repeat(4,1fr);margin-top:4rem}.statCard_w2S8{background:var(--hp-bg-card);border:1px solid #ffffff14;border-radius:20px;text-align:center}.statLabel_I99V{color:var(--hp-text-muted);font-size:.9rem;letter-spacing:1.5px;margin-bottom:.5rem;text-transform:uppercase}.statValue_tB6D{background:linear-gradient(135deg,#fbbf24,#f59e0b);-webkit-background-clip:text;font-size:2.5rem;font-weight:900;-webkit-text-fill-color:#0000;background-clip:text}.statDescription_WIU_{color:var(--hp-text-muted);font-size:.95rem;margin-top:.5rem}.blogCard_hyds,.blogCard_hyds:hover{color:inherit;-webkit-text-decoration:none;text-decoration:none}.videoCard_jGks{background:var(--hp-bg-card);border:1px solid #ffffff14;border-radius:24px;overflow:hidden}[data-theme=light] .videoCard_jGks:hover{box-shadow:0 20px 50px #fbbf241f}.videoWrapper_XWWU{aspect-ratio:16/9;background:#000;overflow:hidden;position:relative;width:100%}.videoPlayer_Nt7m{display:block;height:100%;object-fit:cover;width:100%}.videoContent_pd0B{padding:1.5rem 2rem 2rem}.videoContent_pd0B h3{color:var(--hp-text);font-size:1.3rem;font-weight:700;margin-bottom:.5rem}.videoContent_pd0B p{color:var(--hp-text-muted);font-size:.92rem;line-height:1.6;margin:0}.blogGrid_Qec3{display:grid;gap:2.5rem;grid-template-columns:repeat(auto-fill,minmax(350px,1fr));margin-top:4rem}.blogCard_hyds{background:var(--hp-bg-card);border:1px solid #ffffff14;border-radius:20px;display:block;overflow:hidden}.blogCardIcon_JPeR{align-items:center;background:linear-gradient(135deg,#fbbf2426,#06b6d426);display:flex;font-size:3rem;height:160px;justify-content:center;width:100%}[data-theme=light] .blogCardIcon_JPeR{background:linear-gradient(135deg,#fbbf2414,#06b6d414)}.blogContent_dJxs{padding:2rem}.blogCategory_UY54{background:#fbbf2433;border-radius:12px;color:var(--hp-primary);display:inline-block;font-size:.75rem;font-weight:700;margin-bottom:1rem;padding:.25rem .75rem;text-transform:uppercase}.blogCard_hyds h3{color:var(--hp-text);font-size:1.3rem;font-weight:700;margin-bottom:.75rem}.blogMeta_skDH{align-items:center;color:var(--hp-text-muted);display:flex;font-size:.85rem;gap:.5rem}.ctaSection_bmsv{background:linear-gradient(135deg,#8b004d4d,#63003666);border:2px solid #8b004d80;border-radius:40px;margin:2rem 0;overflow:hidden;padding:6rem 4rem;position:relative;text-align:center}.ctaSection_bmsv:before{animation:20s linear infinite d;background:radial-gradient(circle,#ffffff1a 0,#0000 70%);content:"";height:200%;left:-50%;position:absolute;top:-50%;width:200%}@keyframes d{0%{transform:rotate(0)}to{transform:rotate(1turn)}}.ctaTitle_arch{background:none;color:#fff;font-size:3.5rem;font-weight:900;margin-bottom:1.5rem;-webkit-text-fill-color:#fff}.ctaDescription_HswS{color:#ffffffe6;font-size:1.3rem;margin-bottom:3rem}.ctaButtons_vsp7{display:flex;flex-wrap:wrap;gap:1.5rem;justify-content:center}.customFooter_Ymmc{background:var(--hp-dark-light);border-top:1px solid #ffffff0d;padding:5rem 2rem 2rem}[data-theme=light] .customFooter_Ymmc{background:#f1f5f9;border-top-color:#00000014}.footerContent_obNo{display:grid;gap:4rem;grid-template-columns:2fr 1fr 1fr 1fr;margin:0 auto 3rem;max-width:1400px}.footerSection__c07 h4{color:var(--hp-text);font-size:1.2rem;font-weight:700;margin-bottom:1.5rem}.footerBottom_nS2f,.footerLinks_lH9U a,.footerList_2l2h a,.footerSection__c07 p{color:var(--hp-text-muted)}.footerSection__c07 p{line-height:1.8}.footerList_2l2h{list-style:none;margin:0;padding:0}.footerList_2l2h li{margin-bottom:.75rem}.footerList_2l2h a{transition:.3s}.footerBottom_nS2f{align-items:center;border-top:1px solid #ffffff0d;display:flex;flex-wrap:wrap;gap:1rem;justify-content:space-between;margin:0 auto;max-width:1400px;padding-top:2rem}[data-theme=light] .footerBottom_nS2f{border-top-color:#00000014}.footerLinks_lH9U{display:flex;gap:2rem}.footerLinks_lH9U a{transition:color .3s}@layer docusaurus.theme-common{body:not(.navigation-with-keyboard) :not(input):focus{outline:0}.themedComponent_mlkZ{display:none}[data-theme=dark] .themedComponent--dark_xIcU,[data-theme=light] .themedComponent--light_NVdE,html:not([data-theme]) .themedComponent--light_NVdE{display:initial}.errorBoundaryError_a6uf{color:red;white-space:pre-wrap}.errorBoundaryFallback_VBag{color:red;padding:.55rem}.details_lb9f{--docusaurus-details-summary-arrow-size:0.38rem;--docusaurus-details-transition:transform 200ms ease;--docusaurus-details-decoration-color:grey}.details_lb9f>summary{cursor:pointer;list-style:none;padding-left:1rem;position:relative}.details_lb9f>summary::-webkit-details-marker{display:none}.details_lb9f>summary:before{border-color:#0000 #0000 #0000 var(--docusaurus-details-decoration-color);border-style:solid;border-width:var(--docusaurus-details-summary-arrow-size);content:"";left:0;position:absolute;top:.45rem;transform:rotate(0);transform-origin:calc(var(--docusaurus-details-summary-arrow-size)/2) 50%;transition:var(--docusaurus-details-transition)}.details_lb9f[data-collapsed=false].isBrowser_bmU9>summary:before,.details_lb9f[open]:not(.isBrowser_bmU9)>summary:before{transform:rotate(90deg)}.collapsibleContent_i85q{border-top:1px solid var(--docusaurus-details-decoration-color);margin-top:1rem;padding-top:1rem}.collapsibleContent_i85q p:last-child,.details_lb9f>summary>p:last-child{margin-bottom:0}}@layer docusaurus.theme-classic{:root{--docusaurus-progress-bar-color:var(--ifm-color-primary);--docusaurus-announcement-bar-height:auto;--docusaurus-collapse-button-bg:#0000;--docusaurus-collapse-button-bg-hover:#0000001a;--doc-sidebar-width:300px;--doc-sidebar-hidden-width:30px;--docusaurus-blog-social-icon-size:1rem;--docusaurus-tag-list-border:var(--ifm-color-emphasis-300)}#nprogress{pointer-events:none}#nprogress .bar{background:var(--docusaurus-progress-bar-color);height:2px;left:0;position:fixed;top:0;width:100%;z-index:1031}#nprogress .peg{box-shadow:0 0 10px var(--docusaurus-progress-bar-color),0 0 5px var(--docusaurus-progress-bar-color);height:100%;opacity:1;position:absolute;right:0;transform:rotate(3deg) translateY(-4px);width:100px}.skipToContent_fXgn{background-color:var(--ifm-background-surface-color);color:var(--ifm-color-emphasis-900);left:100%;padding:calc(var(--ifm-global-spacing)/2) var(--ifm-global-spacing);position:fixed;top:1rem;z-index:calc(var(--ifm-z-index-fixed) + 1)}.skipToContent_fXgn:focus{box-shadow:var(--ifm-global-shadow-md);left:1rem}.closeButton_CVFx{line-height:0;padding:0}.content_knG7{font-size:85%;padding:5px 0;text-align:center}.content_knG7 a{color:inherit;-webkit-text-decoration:underline;text-decoration:underline}.announcementBar_mb4j{align-items:center;background-color:var(--ifm-color-white);border-bottom:1px solid var(--ifm-color-emphasis-100);color:var(--ifm-color-black);display:flex;height:var(--docusaurus-announcement-bar-height)}.docSidebarContainer_YfHR,.navbarSearchContainer_Bca1:empty,.sidebarLogo_isFc,.toggleIcon_g3eP,html[data-announcement-bar-initially-dismissed=true] .announcementBar_mb4j{display:none}.announcementBarPlaceholder_vyr4{flex:0 0 10px}.announcementBarClose_gvF7{align-self:stretch;flex:0 0 30px}.announcementBarContent_xLdY{flex:1 1 auto}.toggle_vylO{height:2rem;width:2rem}.toggleButton_gllP{-webkit-tap-highlight-color:transparent;align-items:center;border-radius:50%;display:flex;height:100%;justify-content:center;transition:background var(--ifm-transition-fast);width:100%}.toggleButton_gllP:hover{background:var(--ifm-color-emphasis-200)}[data-theme-choice=dark] .darkToggleIcon_wfgR,[data-theme-choice=light] .lightToggleIcon_pyhR,[data-theme-choice=system] .systemToggleIcon_QzmC{display:initial}.toggleButtonDisabled_aARS{cursor:not-allowed}.darkNavbarColorModeToggle_X3D1:hover{background:var(--ifm-color-gray-800)}.backToTopButton_sjWU{background-color:var(--ifm-color-emphasis-200);border-radius:50%;bottom:1.3rem;box-shadow:var(--ifm-global-shadow-lw);height:3rem;opacity:0;position:fixed;right:1.3rem;transform:scale(0);transition:all var(--ifm-transition-fast) var(--ifm-transition-timing-default);visibility:hidden;width:3rem;z-index:calc(var(--ifm-z-index-fixed) - 1)}.backToTopButton_sjWU:after{background-color:var(--ifm-color-emphasis-1000);content:" ";display:inline-block;height:100%;-webkit-mask:var(--ifm-menu-link-sublist-icon) 50%/2rem 2rem no-repeat;mask:var(--ifm-menu-link-sublist-icon) 50%/2rem 2rem no-repeat;width:100%}.backToTopButtonShow_xfvO{opacity:1;transform:scale(1);visibility:visible}[data-theme=dark]:root{--docusaurus-collapse-button-bg:#ffffff0d;--docusaurus-collapse-button-bg-hover:#ffffff1a}.collapseSidebarButton_PEFL{display:none;margin:0}.iconExternalLink_nPIU{margin-left:.3rem}.dropdownNavbarItemMobile_J0Sd{cursor:pointer}.iconLanguage_nlXk{margin-right:5px;vertical-align:text-bottom}.navbarHideable_m1mJ{transition:transform var(--ifm-transition-fast) ease}.navbarHidden_jGov{transform:translate3d(0,calc(-100% - 2px),0)}.navbar__items--right>:last-child{padding-right:0}.footerLogoLink_BH7S{opacity:.5;transition:opacity var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.footerLogoLink_BH7S:hover,.hash-link:focus,:hover>.hash-link{opacity:1}.menuExternalLink_NmtK{align-items:center}.docMainContainer_TBSr,.docRoot_UBD9{display:flex;width:100%}.authorSocialIcon_XYv3,.authorSocialLink_owbf{width:var(--docusaurus-blog-social-icon-size)}.docsWrapper_hBAB{display:flex;flex:1 0 auto}.anchorWithStickyNavbar_LWe7{scroll-margin-top:calc(var(--ifm-navbar-height) + .5rem)}.anchorWithHideOnScrollNavbar_WYt5{scroll-margin-top:.5rem}.hash-link{opacity:0;padding-left:.5rem;transition:opacity var(--ifm-transition-fast);-webkit-user-select:none;user-select:none}.hash-link:before{content:"#"}.docCardListItem_W1sv>*,body,html{height:100%}.mainWrapper_z2l0{display:flex;flex:1 0 auto;flex-direction:column}.docusaurus-mt-lg{margin-top:3rem}#__docusaurus{display:flex;flex-direction:column;min-height:100%}.sidebar_re4s{max-height:calc(100vh - var(--ifm-navbar-height) - 2rem);overflow-y:auto;position:sticky;top:calc(var(--ifm-navbar-height) + 2rem)}.authorSocials_rSDt,.authorTitle_nd0D{overflow:hidden;-webkit-box-orient:vertical}.sidebarItemTitle_pO2u{font-size:var(--ifm-h3-font-size);font-weight:var(--ifm-font-weight-bold)}.container_mt6G,.sidebarItemList_Yudw{font-size:.9rem}.sidebarItem__DBe{margin-top:.7rem}.sidebarItemLink_mo7H{color:var(--ifm-font-color-base);display:block}.sidebarItemLink_mo7H:hover{-webkit-text-decoration:none;text-decoration:none}.sidebarItemLinkActive_I1ZP{color:var(--ifm-color-primary)!important}.yearGroupHeading_rMGB{margin-bottom:.4rem;margin-top:1.6rem}.yearGroupHeading_QT03{margin:1rem .75rem .5rem}.cardContainer_fWXF{--ifm-link-color:var(--ifm-color-emphasis-800);--ifm-link-hover-color:var(--ifm-color-emphasis-700);--ifm-link-hover-decoration:none;border:1px solid var(--ifm-color-emphasis-200);box-shadow:0 1.5px 3px 0 #00000026;transition:all var(--ifm-transition-fast) ease;transition-property:border,box-shadow}.cardContainer_fWXF:hover{border-color:var(--ifm-color-primary);box-shadow:0 3px 6px 0 #0003}.admonitionContent_BuS1>:last-child,.cardContainer_fWXF :last-child{margin-bottom:0}.cardTitle_rnsV{font-size:1.2rem}.cardDescription_PWke{font-size:.8rem}.docCardListItem_W1sv{margin-bottom:2rem}.title_f1Hy{font-size:3rem}[data-theme=dark] .githubSvg_Uu4N,[data-theme=dark] .instagramSvg_YC40,[data-theme=dark] .threadsSvg_PTXY,[data-theme=dark] .xSvg_y3PF{fill:var(--light)}[data-theme=light] .githubSvg_Uu4N,[data-theme=light] .instagramSvg_YC40,[data-theme=light] .threadsSvg_PTXY,[data-theme=light] .xSvg_y3PF{fill:var(--dark)}.authorSocials_rSDt{align-items:center;display:flex;flex-wrap:wrap;line-clamp:1;-webkit-line-clamp:1}.authorSocialLink_owbf,.authorSocials_rSDt{height:var(--docusaurus-blog-social-icon-size);line-height:0}.authorSocialLink_owbf{margin-right:.4rem}.authorSocialIcon_XYv3{height:var(--docusaurus-blog-social-icon-size)}.authorImage_XqGP{--ifm-avatar-photo-size:3.6rem}.author-as-h1_n9oJ .authorImage_XqGP{--ifm-avatar-photo-size:7rem}.author-as-h2_gXvM .authorImage_XqGP{--ifm-avatar-photo-size:5.4rem}.authorDetails_lV9A{align-items:flex-start;display:flex;flex-direction:column;justify-content:space-around}.authorName_yefp{display:flex;flex-direction:row;font-size:1.1rem;line-height:1.1rem}.author-as-h1_n9oJ .authorName_yefp{display:inline;font-size:2.4rem;line-height:2.4rem}.author-as-h2_gXvM .authorName_yefp{display:inline;font-size:1.4rem;line-height:1.4rem}.authorTitle_nd0D{display:-webkit-box;font-size:.8rem;line-height:1rem;line-clamp:1;-webkit-line-clamp:1}.author-as-h1_n9oJ .authorTitle_nd0D{font-size:1.2rem;line-height:1.6rem}.author-as-h2_gXvM .authorTitle_nd0D{font-size:1rem;line-height:1.3rem}.authorBlogPostCount_iiJ5{background:var(--ifm-color-secondary);border-radius:var(--ifm-global-radius);color:var(--ifm-color-black);font-size:.8rem;line-height:1.2;margin-left:.3rem;padding:.1rem .4rem}.authorListItem_n3yI{list-style-type:none;margin-bottom:2rem}.authorCol_Hf19{max-width:inherit!important}.imageOnlyAuthorRow_pa_O{display:flex;flex-flow:row wrap}.imageOnlyAuthorCol_G86a{margin-left:.3rem;margin-right:.3rem}.codeBlockContainer_Ckt0{background:var(--prism-background-color);border-radius:var(--ifm-code-border-radius);box-shadow:var(--ifm-global-shadow-lw);color:var(--prism-color);margin-bottom:var(--ifm-leading)}.codeBlock_bY9V{--ifm-pre-background:var(--prism-background-color);margin:0;padding:0}.codeBlockStandalone_MEMb{padding:0}.codeBlockLines_e6Vv{float:left;font:inherit;min-width:100%;padding:var(--ifm-pre-padding)}.codeBlockLinesWithNumbering_o6Pm{display:table;padding:var(--ifm-pre-padding) 0}:where(:root){--docusaurus-highlighted-code-line-bg:#484d5b}:where([data-theme=dark]){--docusaurus-highlighted-code-line-bg:#646464}.theme-code-block-highlighted-line{background-color:var(--docusaurus-highlighted-code-line-bg);display:block;margin:0 calc(var(--ifm-pre-padding)*-1);padding:0 var(--ifm-pre-padding)}.codeLine_lJS_{counter-increment:a;display:table-row}.codeLineNumber_Tfdd{background:var(--ifm-pre-background);display:table-cell;left:0;overflow-wrap:normal;padding:0 var(--ifm-pre-padding);position:sticky;text-align:right;width:1%}.codeLineNumber_Tfdd:before{content:counter(a);opacity:.4}.theme-code-block-highlighted-line .codeLineNumber_Tfdd:before{opacity:.8}.codeLineContent_feaV{padding-right:var(--ifm-pre-padding)}.theme-code-block:hover .copyButtonCopied_Vdqa{opacity:1!important}.copyButtonIcons_IEyt{height:1.125rem;position:relative;width:1.125rem}.copyButtonIcon_TrPX,.copyButtonSuccessIcon_cVMy{left:0;position:absolute;top:0;fill:currentColor;height:inherit;opacity:inherit;transition:all var(--ifm-transition-fast) ease;width:inherit}.copyButtonSuccessIcon_cVMy{color:#00d600;left:50%;opacity:0;top:50%;transform:translate(-50%,-50%) scale(.33)}.copyButtonCopied_Vdqa .copyButtonIcon_TrPX{opacity:0;transform:scale(.33)}.copyButtonCopied_Vdqa .copyButtonSuccessIcon_cVMy{opacity:1;transform:translate(-50%,-50%) scale(1);transition-delay:75ms}.wordWrapButtonIcon_b1P5{height:1.2rem;width:1.2rem}.wordWrapButtonEnabled_uzNF .wordWrapButtonIcon_b1P5{color:var(--ifm-color-primary)}.buttonGroup_M5ko{column-gap:.2rem;display:flex;position:absolute;right:calc(var(--ifm-pre-padding)/2);top:calc(var(--ifm-pre-padding)/2)}.buttonGroup_M5ko button{align-items:center;background:var(--prism-background-color);border:1px solid var(--ifm-color-emphasis-300);border-radius:var(--ifm-global-radius);color:var(--prism-color);display:flex;line-height:0;opacity:0;padding:.4rem;transition:opacity var(--ifm-transition-fast) ease-in-out}.buttonGroup_M5ko button:focus-visible,.buttonGroup_M5ko button:hover{opacity:1!important}.theme-code-block:hover .buttonGroup_M5ko button{opacity:.4}.tag_zVej{border:1px solid var(--docusaurus-tag-list-border);transition:border var(--ifm-transition-fast)}.tag_zVej:hover{--docusaurus-tag-list-border:var(--ifm-link-color);-webkit-text-decoration:none;text-decoration:none}.tagRegular_sFm0{border-radius:var(--ifm-global-radius);font-size:90%;padding:.2rem .5rem .3rem}.tagWithCount_h2kH{align-items:center;border-left:0;display:flex;padding:0 .5rem 0 1rem;position:relative}.tagWithCount_h2kH:after,.tagWithCount_h2kH:before{border:1px solid var(--docusaurus-tag-list-border);content:"";position:absolute;top:50%;transition:inherit}.tagWithCount_h2kH:before{border-bottom:0;border-right:0;height:1.18rem;right:100%;transform:translate(50%,-50%) rotate(-45deg);width:1.18rem}.tagWithCount_h2kH:after{border-radius:50%;height:.5rem;left:0;transform:translateY(-50%);width:.5rem}.tagWithCount_h2kH span{background:var(--ifm-color-secondary);border-radius:var(--ifm-global-radius);color:var(--ifm-color-black);font-size:.7rem;line-height:1.2;margin-left:.3rem;padding:.1rem .4rem}.tag_Nnez{display:inline-block;margin:.5rem .5rem 0 1rem}.codeBlockContent_QJqH{border-radius:inherit;direction:ltr;position:relative}.codeBlockTitle_OeMC{border-bottom:1px solid var(--ifm-color-emphasis-300);border-top-left-radius:inherit;border-top-right-radius:inherit;font-size:var(--ifm-code-font-size);font-weight:500;padding:.75rem var(--ifm-pre-padding)}.codeBlockTitle_OeMC+.codeBlockContent_QJqH .codeBlock_a8dz{border-top-left-radius:0;border-top-right-radius:0}.tags_jXut{display:inline}.tag_QGVx{display:inline-block;margin:0 .4rem .5rem 0}.iconEdit_Z9Sw{margin-right:.3em;vertical-align:sub}.lastUpdated_JAkA{font-size:smaller;font-style:italic;margin-top:.2rem}.tocCollapsibleButton_TO0P{align-items:center;display:flex;font-size:inherit;justify-content:space-between;padding:.4rem .8rem;width:100%}.tocCollapsibleButton_TO0P:after{background:var(--ifm-menu-link-sublist-icon) 50% 50%/2rem 2rem no-repeat;content:"";filter:var(--ifm-menu-link-sublist-icon-filter);height:1.25rem;transform:rotate(180deg);transition:transform var(--ifm-transition-fast);width:1.25rem}.tocCollapsibleButtonExpanded_MG3E:after,.tocCollapsibleExpanded_sAul{transform:none}.tocCollapsible_ETCw{background-color:var(--ifm-menu-color-background-active);border-radius:var(--ifm-global-radius);margin:1rem 0}.tocCollapsibleContent_vkbj>ul{border-left:none;border-top:1px solid var(--ifm-color-emphasis-300);font-size:15px;padding:.2rem 0}.tocCollapsibleContent_vkbj ul li{margin:.4rem .8rem}.tocCollapsibleContent_vkbj a{display:block}.details_b_Ee{--docusaurus-details-decoration-color:var(--ifm-alert-border-color);--docusaurus-details-transition:transform var(--ifm-transition-fast) ease;border:1px solid var(--ifm-alert-border-color);margin:0 0 var(--ifm-spacing-vertical)}.containsTaskList_mC6p{list-style:none}:not(.containsTaskList_mC6p>li)>.containsTaskList_mC6p{padding-left:0}.img_ev3q{height:auto}.tableOfContents_bqdL{max-height:calc(100vh - var(--ifm-navbar-height) - 2rem);overflow-y:auto;position:sticky;top:calc(var(--ifm-navbar-height) + 1rem)}.admonition_xJq3{margin-bottom:1em}.admonitionHeading_Gvgb{font:var(--ifm-heading-font-weight) var(--ifm-h5-font-size)/var(--ifm-heading-line-height) var(--ifm-heading-font-family);text-transform:uppercase}.admonitionHeading_Gvgb:not(:last-child){margin-bottom:.3rem}.admonitionHeading_Gvgb code{text-transform:none}.admonitionIcon_Rf37{display:inline-block;margin-right:.4em;vertical-align:middle}.admonitionIcon_Rf37 svg{display:inline-block;height:1.6em;width:1.6em;fill:var(--ifm-alert-foreground-color)}.breadcrumbHomeIcon_YNFT{height:1.1rem;position:relative;top:1px;vertical-align:top;width:1.1rem}.breadcrumbsContainer_Z_bl{--ifm-breadcrumb-size-multiplier:0.8;margin-bottom:.8rem}.title_kItE{--ifm-h1-font-size:3rem;margin-bottom:calc(var(--ifm-leading)*1.25)}.docItemContainer_Djhp article>:first-child,.docItemContainer_Djhp header+*{margin-top:0}.mdxPageWrapper_j9I6{justify-content:center}}@media (min-width:997px){.collapseSidebarButton_PEFL,.expandButton_TmdG{background-color:var(--docusaurus-collapse-button-bg)}:root{--docusaurus-announcement-bar-height:30px}.announcementBarClose_gvF7,.announcementBarPlaceholder_vyr4{flex-basis:50px}.collapseSidebarButton_PEFL{border:1px solid var(--ifm-toc-border-color);border-radius:0;bottom:0;display:block!important;height:40px;position:sticky}.collapseSidebarButtonIcon_kv0_{margin-top:4px;transform:rotate(180deg)}.expandButtonIcon_i1dp,[dir=rtl] .collapseSidebarButtonIcon_kv0_{transform:rotate(0)}.collapseSidebarButton_PEFL:focus,.collapseSidebarButton_PEFL:hover,.expandButton_TmdG:focus,.expandButton_TmdG:hover{background-color:var(--docusaurus-collapse-button-bg-hover)}.navbarSearchContainer_Bca1{padding:var(--ifm-navbar-item-padding-vertical) var(--ifm-navbar-item-padding-horizontal)}.menuHtmlItem_M9Kj{padding:var(--ifm-menu-link-padding-vertical) var(--ifm-menu-link-padding-horizontal)}.menu_SIkG{flex-grow:1;padding:.5rem}@supports (scrollbar-gutter:stable){.menu_SIkG{padding:.5rem 0 .5rem .5rem;scrollbar-gutter:stable}}.menuWithAnnouncementBar_GW3s{margin-bottom:var(--docusaurus-announcement-bar-height)}.sidebar_njMd{display:flex;flex-direction:column;height:100%;padding-top:var(--ifm-navbar-height);width:var(--doc-sidebar-width)}.sidebarWithHideableNavbar_wUlq{padding-top:0}.sidebarHidden_VK0M{opacity:0;visibility:hidden}.sidebarLogo_isFc{align-items:center;color:inherit!important;display:flex!important;margin:0 var(--ifm-navbar-padding-horizontal);max-height:var(--ifm-navbar-height);min-height:var(--ifm-navbar-height);-webkit-text-decoration:none!important;text-decoration:none!important}.sidebarLogo_isFc img{height:2rem;margin-right:.5rem}.expandButton_TmdG{align-items:center;display:flex;height:100%;justify-content:center;position:absolute;right:0;top:0;transition:background-color var(--ifm-transition-fast) ease;width:100%}[dir=rtl] .expandButtonIcon_i1dp{transform:rotate(180deg)}.docSidebarContainer_YfHR{border-right:1px solid var(--ifm-toc-border-color);clip-path:inset(0);display:block;margin-top:calc(var(--ifm-navbar-height)*-1);transition:width var(--ifm-transition-fast) ease;width:var(--doc-sidebar-width);will-change:width}.docSidebarContainerHidden_DPk8{cursor:pointer;width:var(--doc-sidebar-hidden-width)}.sidebarViewport_aRkj{height:100%;max-height:100vh;position:sticky;top:0}.docMainContainer_TBSr{flex-grow:1;max-width:calc(100% - var(--doc-sidebar-width))}.docMainContainerEnhanced_lQrH{max-width:calc(100% - var(--doc-sidebar-hidden-width))}.docItemWrapperEnhanced_JWYK{max-width:calc(var(--ifm-container-width) + var(--doc-sidebar-width))!important}.lastUpdated_JAkA{text-align:right}.tocMobile_ITEo{display:none}.docItemCol_VOVn,.generatedIndexPage_vN6x{max-width:75%!important}}@media (min-width:1440px){.container{max-width:var(--ifm-container-width-xl)}}@media (max-width:1024px){.hero_aEcG{grid-template-columns:1fr;padding-top:8rem;text-align:center}.heroImage_xZN7{margin:2rem 0 0;order:-1}.heroContent_mKPX{order:1}.heroButtons_r52D{justify-content:center}.componentsGrid_KtT5,.footerContent_obNo,.statsGrid_wBRk,.videosGrid_FXHY{grid-template-columns:1fr 1fr}.barriersGrid_u0Jf{grid-template-columns:1fr}}@media (max-width:996px){.col{--ifm-col-width:100%;flex-basis:var(--ifm-col-width);margin-left:0}.footer{--ifm-footer-padding-horizontal:0}.colorModeToggle_DEke,.footer__link-separator,.navbar__item,.sidebar_re4s,.tableOfContents_bqdL{display:none}.footer__col{margin-bottom:calc(var(--ifm-spacing-vertical)*3)}.footer__link-item{display:block;width:max-content}.hero{padding-left:0;padding-right:0}.navbar>.container,.navbar>.container-fluid{padding:0}.navbar__toggle{display:inherit}.navbar__search-input{width:9rem}.pills--block,.tabs--block{flex-direction:column}.navbarSearchContainer_Bca1{position:absolute;right:var(--ifm-navbar-padding-horizontal)}.docItemContainer_F8PC{padding:0 .3rem}}@media (max-width:768px){.heroTitle_qg2I{font-size:3rem}.ctaTitle_arch,.sectionTitle_Ut5p{font-size:2.5rem}.navLinks_FO3Z a:not(.btn_bvfa):not(.btnPrimary_hBjO){display:none}.blogGrid_Qec3,.componentsGrid_KtT5,.footerContent_obNo,.statsGrid_wBRk,.videosGrid_FXHY{grid-template-columns:1fr}.ctaSection_bmsv{border-radius:20px;padding:4rem 2rem}.section_Q9Zo{padding:4rem 1.5rem}.hero_aEcG{padding:7rem 1.5rem 3rem}}@media (max-width:576px){.markdown h1:first-child{--ifm-h1-font-size:2rem}.markdown>h2{--ifm-h2-font-size:1.5rem}.markdown>h3{--ifm-h3-font-size:1.25rem}.title_f1Hy{font-size:2rem}}@media (max-width:480px){.heroTitle_qg2I{font-size:2.2rem}.sectionTitle_Ut5p{font-size:2rem}.heroButtons_r52D{align-items:center;flex-direction:column}}@media (hover:hover){.backToTopButton_sjWU:hover{background-color:var(--ifm-color-emphasis-300)}}@media (pointer:fine){.thin-scrollbar{scrollbar-width:thin}.thin-scrollbar::-webkit-scrollbar{height:var(--ifm-scrollbar-size);width:var(--ifm-scrollbar-size)}.thin-scrollbar::-webkit-scrollbar-track{background:var(--ifm-scrollbar-track-background-color);border-radius:10px}.thin-scrollbar::-webkit-scrollbar-thumb{background:var(--ifm-scrollbar-thumb-background-color);border-radius:10px}.thin-scrollbar::-webkit-scrollbar-thumb:hover{background:var(--ifm-scrollbar-thumb-hover-background-color)}}@media (prefers-reduced-motion:reduce){:root{--ifm-transition-fast:0ms;--ifm-transition-slow:0ms}}@media print{.announcementBar_mb4j,.footer,.menu,.navbar,.pagination-nav,.table-of-contents,.tocMobile_ITEo{display:none}.tabs{page-break-inside:avoid}.codeBlockLines_e6Vv{white-space:pre-wrap}} \ No newline at end of file diff --git a/docs/assets/images/bharatmlstack-72e1796337bfa224dee2a0f59ec4e2da.png b/docs/assets/images/bharatmlstack-72e1796337bfa224dee2a0f59ec4e2da.png deleted file mode 100644 index 3559779f..00000000 Binary files a/docs/assets/images/bharatmlstack-72e1796337bfa224dee2a0f59ec4e2da.png and /dev/null differ diff --git a/docs/assets/images/bms-7399e8796d2cd24617c432518ce3f312.png b/docs/assets/images/bms-7399e8796d2cd24617c432518ce3f312.png new file mode 100644 index 00000000..b397fc88 Binary files /dev/null and b/docs/assets/images/bms-7399e8796d2cd24617c432518ce3f312.png differ diff --git a/docs/assets/images/interaction-str-d9e7aefea121aefb4e94c6c9f060d016.png b/docs/assets/images/interaction-str-d9e7aefea121aefb4e94c6c9f060d016.png new file mode 100644 index 00000000..99ddd4e9 Binary files /dev/null and b/docs/assets/images/interaction-str-d9e7aefea121aefb4e94c6c9f060d016.png differ diff --git a/docs/assets/images/llm-plat-9ac69c0ffd8c387d177e582611b8c775.png b/docs/assets/images/llm-plat-9ac69c0ffd8c387d177e582611b8c775.png new file mode 100644 index 00000000..1f0fa9f7 Binary files /dev/null and b/docs/assets/images/llm-plat-9ac69c0ffd8c387d177e582611b8c775.png differ diff --git a/docs/assets/images/mp-dag-976ff51caf25f09d977ccc10e70918f3.png b/docs/assets/images/mp-dag-976ff51caf25f09d977ccc10e70918f3.png new file mode 100644 index 00000000..4a13af56 Binary files /dev/null and b/docs/assets/images/mp-dag-976ff51caf25f09d977ccc10e70918f3.png differ diff --git a/docs/assets/images/mp-matrix-43994f433f78905ccbd10cfe284f3c9f.png b/docs/assets/images/mp-matrix-43994f433f78905ccbd10cfe284f3c9f.png new file mode 100644 index 00000000..44b21256 Binary files /dev/null and b/docs/assets/images/mp-matrix-43994f433f78905ccbd10cfe284f3c9f.png differ diff --git a/docs/assets/images/skye-rt-consumer-flow-7f064a31c41151ff4516900b3170dbc8.png b/docs/assets/images/skye-rt-consumer-flow-7f064a31c41151ff4516900b3170dbc8.png new file mode 100644 index 00000000..11e40769 Binary files /dev/null and b/docs/assets/images/skye-rt-consumer-flow-7f064a31c41151ff4516900b3170dbc8.png differ diff --git a/docs/assets/images/skye-system-overview-24940f4c319f41fb3b7583a525b0a534.png b/docs/assets/images/skye-system-overview-24940f4c319f41fb3b7583a525b0a534.png new file mode 100644 index 00000000..2f992dbf Binary files /dev/null and b/docs/assets/images/skye-system-overview-24940f4c319f41fb3b7583a525b0a534.png differ diff --git a/docs/assets/images/v1.0.0-inferflow-arch-bce54b3b4f7d3be68fa22dc204529f53.png b/docs/assets/images/v1.0.0-inferflow-arch-bce54b3b4f7d3be68fa22dc204529f53.png new file mode 100644 index 00000000..acad4888 Binary files /dev/null and b/docs/assets/images/v1.0.0-inferflow-arch-bce54b3b4f7d3be68fa22dc204529f53.png differ diff --git a/docs/assets/images/v1.0.0-inferflow-dag-matrix-0f13b51422587e6099cf4ee783844db1.png b/docs/assets/images/v1.0.0-inferflow-dag-matrix-0f13b51422587e6099cf4ee783844db1.png new file mode 100644 index 00000000..2345d69f Binary files /dev/null and b/docs/assets/images/v1.0.0-inferflow-dag-matrix-0f13b51422587e6099cf4ee783844db1.png differ diff --git a/docs/assets/images/v1.0.0-predator-hld-949215d6604ae103e724c3978e803443.png b/docs/assets/images/v1.0.0-predator-hld-949215d6604ae103e724c3978e803443.png new file mode 100644 index 00000000..3e8a21ad Binary files /dev/null and b/docs/assets/images/v1.0.0-predator-hld-949215d6604ae103e724c3978e803443.png differ diff --git a/docs/assets/images/vss-c482f6eac4c68b3219e4c562a6b717ec.png b/docs/assets/images/vss-c482f6eac4c68b3219e4c562a6b717ec.png new file mode 100644 index 00000000..c6b18475 Binary files /dev/null and b/docs/assets/images/vss-c482f6eac4c68b3219e4c562a6b717ec.png differ diff --git a/docs/assets/js/01a85c17.5487421a.js b/docs/assets/js/01a85c17.5487421a.js deleted file mode 100644 index a84c5428..00000000 --- a/docs/assets/js/01a85c17.5487421a.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[8209],{4096:(e,t,a)=>{a.d(t,{in:()=>c,OU:()=>P,Ki:()=>y,kJ:()=>b,x:()=>l,e7:()=>m,J_:()=>f,Gx:()=>A});var s=a(6540),n=a(9532),i=a(6803),r=a(4848);function l(){const e=(0,i.A)(),t=e?.data?.blogMetadata;if(!t)throw new Error("useBlogMetadata() can't be called on the current route because the blog metadata could not be found in route context");return t}const o=s.createContext(null);function c({children:e,content:t,isBlogPostPage:a=!1}){const n=function({content:e,isBlogPostPage:t}){return(0,s.useMemo)((()=>({metadata:e.metadata,frontMatter:e.frontMatter,assets:e.assets,toc:e.toc,isBlogPostPage:t})),[e,t])}({content:t,isBlogPostPage:a});return(0,r.jsx)(o.Provider,{value:n,children:e})}function m(){const e=(0,s.useContext)(o);if(null===e)throw new n.dV("BlogPostProvider");return e}var d=a(6025),u=a(4586);const g=e=>new Date(e).toISOString();function h(e){const t=e.map(x);return{author:1===t.length?t[0]:t}}function p(e,t,a){return e?{image:j({imageUrl:t(e,{absolute:!0}),caption:`title image for the blog post: ${a}`})}:{}}function b(e){const{siteConfig:t}=(0,u.A)(),{withBaseUrl:a}=(0,d.hH)(),{metadata:{blogDescription:s,blogTitle:n,permalink:i}}=e,r=`${t.url}${i}`;return{"@context":"https://schema.org","@type":"Blog","@id":r,mainEntityOfPage:r,headline:n,description:s,blogPost:e.items.map((e=>function(e,t,a){const{assets:s,frontMatter:n,metadata:i}=e,{date:r,title:l,description:o,lastUpdatedAt:c}=i,m=s.image??n.image,d=n.keywords??[],u=`${t.url}${i.permalink}`,b=c?g(c):void 0;return{"@type":"BlogPosting","@id":u,mainEntityOfPage:u,url:u,headline:l,name:l,description:o,datePublished:r,...b?{dateModified:b}:{},...h(i.authors),...p(m,a,l),...d?{keywords:d}:{}}}(e.content,t,a)))}}function f(){const e=l(),{assets:t,metadata:a}=m(),{siteConfig:s}=(0,u.A)(),{withBaseUrl:n}=(0,d.hH)(),{date:i,title:r,description:o,frontMatter:c,lastUpdatedAt:b}=a,f=t.image??c.image,x=c.keywords??[],j=b?g(b):void 0,N=`${s.url}${a.permalink}`;return{"@context":"https://schema.org","@type":"BlogPosting","@id":N,mainEntityOfPage:N,url:N,headline:r,name:r,description:o,datePublished:i,...j?{dateModified:j}:{},...h(a.authors),...p(f,n,r),...x?{keywords:x}:{},isPartOf:{"@type":"Blog","@id":`${s.url}${e.blogBasePath}`,name:e.blogTitle}}}function x(e){return{"@type":"Person",...e.name?{name:e.name}:{},...e.title?{description:e.title}:{},...e.url?{url:e.url}:{},...e.email?{email:e.email}:{},...e.imageURL?{image:e.imageURL}:{}}}function j({imageUrl:e,caption:t}){return{"@type":"ImageObject","@id":e,url:e,contentUrl:e,caption:t}}var N=a(6347),v=a(8774),C=a(1682),k=a(9169);function A(e){const{pathname:t}=(0,N.zy)();return(0,s.useMemo)((()=>e.filter((e=>function(e,t){return!(e.unlisted&&!(0,k.ys)(e.permalink,t))}(e,t)))),[e,t])}function y(e){const t=(0,C.$z)(e,(e=>`${new Date(e.date).getFullYear()}`)),a=Object.entries(t);return a.reverse(),a}function P({items:e,ulClassName:t,liClassName:a,linkClassName:s,linkActiveClassName:n}){return(0,r.jsx)("ul",{className:t,children:e.map((e=>(0,r.jsx)("li",{className:a,children:(0,r.jsx)(v.A,{isNavLink:!0,to:e.permalink,className:s,activeClassName:n,children:e.title})},e.permalink)))})}},6133:(e,t,a)=>{a.d(t,{A:()=>l});a(6540);var s=a(4164),n=a(8774);const i={tag:"tag_zVej",tagRegular:"tagRegular_sFm0",tagWithCount:"tagWithCount_h2kH"};var r=a(4848);function l({permalink:e,label:t,count:a,description:l}){return(0,r.jsxs)(n.A,{rel:"tag",href:e,title:l,className:(0,s.A)(i.tag,a?i.tagWithCount:i.tagRegular),children:[t,a&&(0,r.jsx)("span",{children:a})]})}},8027:(e,t,a)=>{a.d(t,{A:()=>U});var s=a(6540),n=a(4164),i=a(1656),r=a(4581),l=a(1312),o=a(4096),c=a(6342),m=a(1107),d=a(4848);function u({year:e,yearGroupHeadingClassName:t,children:a}){return(0,d.jsxs)("div",{role:"group",children:[(0,d.jsx)(m.A,{as:"h3",className:t,children:e}),a]})}function g({items:e,yearGroupHeadingClassName:t,ListComponent:a}){if((0,c.p)().blog.sidebar.groupByYear){const s=(0,o.Ki)(e);return(0,d.jsx)(d.Fragment,{children:s.map((([e,s])=>(0,d.jsx)(u,{year:e,yearGroupHeadingClassName:t,children:(0,d.jsx)(a,{items:s})},e)))})}return(0,d.jsx)(a,{items:e})}const h=(0,s.memo)(g),p="sidebar_re4s",b="sidebarItemTitle_pO2u",f="sidebarItemList_Yudw",x="sidebarItem__DBe",j="sidebarItemLink_mo7H",N="sidebarItemLinkActive_I1ZP",v="yearGroupHeading_rMGB",C=({items:e})=>(0,d.jsx)(o.OU,{items:e,ulClassName:(0,n.A)(f,"clean-list"),liClassName:x,linkClassName:j,linkActiveClassName:N});function k({sidebar:e}){const t=(0,o.Gx)(e.items);return(0,d.jsx)("aside",{className:"col col--3",children:(0,d.jsxs)("nav",{className:(0,n.A)(p,"thin-scrollbar"),"aria-label":(0,l.T)({id:"theme.blog.sidebar.navAriaLabel",message:"Blog recent posts navigation",description:"The ARIA label for recent posts in the blog sidebar"}),children:[(0,d.jsx)("div",{className:(0,n.A)(b,"margin-bottom--md"),children:e.title}),(0,d.jsx)(h,{items:t,ListComponent:C,yearGroupHeadingClassName:v})]})})}const A=(0,s.memo)(k);var y=a(5600);const P="yearGroupHeading_QT03",_=({items:e})=>(0,d.jsx)(o.OU,{items:e,ulClassName:"menu__list",liClassName:"menu__list-item",linkClassName:"menu__link",linkActiveClassName:"menu__link--active"});function w({sidebar:e}){const t=(0,o.Gx)(e.items);return(0,d.jsx)(h,{items:t,ListComponent:_,yearGroupHeadingClassName:P})}function B(e){return(0,d.jsx)(y.GX,{component:w,props:e})}const G=(0,s.memo)(B);function O({sidebar:e}){const t=(0,r.l)();return e?.items.length?"mobile"===t?(0,d.jsx)(G,{sidebar:e}):(0,d.jsx)(A,{sidebar:e}):null}function U(e){const{sidebar:t,toc:a,children:s,...r}=e,l=t&&t.items.length>0;return(0,d.jsx)(i.A,{...r,children:(0,d.jsx)("div",{className:"container margin-vert--lg",children:(0,d.jsxs)("div",{className:"row",children:[(0,d.jsx)(O,{sidebar:t}),(0,d.jsx)("main",{className:(0,n.A)("col",{"col--7":l,"col--9 col--offset-1":!l}),children:s}),a&&(0,d.jsx)("div",{className:"col col--2",children:a})]})})})}},9158:(e,t,a)=>{a.r(t),a.d(t,{default:()=>b});a(6540);var s=a(4164),n=a(1312);const i=()=>(0,n.T)({id:"theme.tags.tagsPageTitle",message:"Tags",description:"The title of the tag list page"});var r=a(5500),l=a(7559),o=a(8027),c=a(6133),m=a(1107);const d={tag:"tag_Nnez"};var u=a(4848);function g({letterEntry:e}){return(0,u.jsxs)("article",{children:[(0,u.jsx)(m.A,{as:"h2",id:e.letter,children:e.letter}),(0,u.jsx)("ul",{className:"padding--none",children:e.tags.map((e=>(0,u.jsx)("li",{className:d.tag,children:(0,u.jsx)(c.A,{...e})},e.permalink)))}),(0,u.jsx)("hr",{})]})}function h({tags:e}){const t=function(e){const t={};return Object.values(e).forEach((e=>{const a=function(e){return e[0].toUpperCase()}(e.label);t[a]??=[],t[a].push(e)})),Object.entries(t).sort((([e],[t])=>e.localeCompare(t))).map((([e,t])=>({letter:e,tags:t.sort(((e,t)=>e.label.localeCompare(t.label)))})))}(e);return(0,u.jsx)("section",{className:"margin-vert--lg",children:t.map((e=>(0,u.jsx)(g,{letterEntry:e},e.letter)))})}var p=a(1463);function b({tags:e,sidebar:t}){const a=i();return(0,u.jsxs)(r.e3,{className:(0,s.A)(l.G.wrapper.blogPages,l.G.page.blogTagsListPage),children:[(0,u.jsx)(r.be,{title:a}),(0,u.jsx)(p.A,{tag:"blog_tags_list"}),(0,u.jsxs)(o.A,{sidebar:t,children:[(0,u.jsx)(m.A,{as:"h1",children:a}),(0,u.jsx)(h,{tags:e})]})]})}}}]); \ No newline at end of file diff --git a/docs/assets/js/01a85c17.9618aedf.js b/docs/assets/js/01a85c17.9618aedf.js new file mode 100644 index 00000000..d1f89fa3 --- /dev/null +++ b/docs/assets/js/01a85c17.9618aedf.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[8209],{4096:(e,t,a)=>{a.d(t,{in:()=>c,OU:()=>P,Ki:()=>y,kJ:()=>b,x:()=>l,e7:()=>m,J_:()=>f,Gx:()=>A});var s=a(6540),n=a(9532),i=a(6803),r=a(4848);function l(){const e=(0,i.A)(),t=e?.data?.blogMetadata;if(!t)throw new Error("useBlogMetadata() can't be called on the current route because the blog metadata could not be found in route context");return t}const o=s.createContext(null);function c({children:e,content:t,isBlogPostPage:a=!1}){const n=function({content:e,isBlogPostPage:t}){return(0,s.useMemo)(()=>({metadata:e.metadata,frontMatter:e.frontMatter,assets:e.assets,toc:e.toc,isBlogPostPage:t}),[e,t])}({content:t,isBlogPostPage:a});return(0,r.jsx)(o.Provider,{value:n,children:e})}function m(){const e=(0,s.useContext)(o);if(null===e)throw new n.dV("BlogPostProvider");return e}var d=a(6025),u=a(4586);const g=e=>new Date(e).toISOString();function h(e){const t=e.map(x);return{author:1===t.length?t[0]:t}}function p(e,t,a){return e?{image:j({imageUrl:t(e,{absolute:!0}),caption:`title image for the blog post: ${a}`})}:{}}function b(e){const{siteConfig:t}=(0,u.A)(),{withBaseUrl:a}=(0,d.hH)(),{metadata:{blogDescription:s,blogTitle:n,permalink:i}}=e,r=`${t.url}${i}`;return{"@context":"https://schema.org","@type":"Blog","@id":r,mainEntityOfPage:r,headline:n,description:s,blogPost:e.items.map(e=>function(e,t,a){const{assets:s,frontMatter:n,metadata:i}=e,{date:r,title:l,description:o,lastUpdatedAt:c}=i,m=s.image??n.image,d=n.keywords??[],u=`${t.url}${i.permalink}`,b=c?g(c):void 0;return{"@type":"BlogPosting","@id":u,mainEntityOfPage:u,url:u,headline:l,name:l,description:o,datePublished:r,...b?{dateModified:b}:{},...h(i.authors),...p(m,a,l),...d?{keywords:d}:{}}}(e.content,t,a))}}function f(){const e=l(),{assets:t,metadata:a}=m(),{siteConfig:s}=(0,u.A)(),{withBaseUrl:n}=(0,d.hH)(),{date:i,title:r,description:o,frontMatter:c,lastUpdatedAt:b}=a,f=t.image??c.image,x=c.keywords??[],j=b?g(b):void 0,N=`${s.url}${a.permalink}`;return{"@context":"https://schema.org","@type":"BlogPosting","@id":N,mainEntityOfPage:N,url:N,headline:r,name:r,description:o,datePublished:i,...j?{dateModified:j}:{},...h(a.authors),...p(f,n,r),...x?{keywords:x}:{},isPartOf:{"@type":"Blog","@id":`${s.url}${e.blogBasePath}`,name:e.blogTitle}}}function x(e){return{"@type":"Person",...e.name?{name:e.name}:{},...e.title?{description:e.title}:{},...e.url?{url:e.url}:{},...e.email?{email:e.email}:{},...e.imageURL?{image:e.imageURL}:{}}}function j({imageUrl:e,caption:t}){return{"@type":"ImageObject","@id":e,url:e,contentUrl:e,caption:t}}var N=a(6347),v=a(8774),C=a(1682),k=a(9169);function A(e){const{pathname:t}=(0,N.zy)();return(0,s.useMemo)(()=>e.filter(e=>function(e,t){return!(e.unlisted&&!(0,k.ys)(e.permalink,t))}(e,t)),[e,t])}function y(e){const t=(0,C.$z)(e,e=>`${new Date(e.date).getFullYear()}`),a=Object.entries(t);return a.reverse(),a}function P({items:e,ulClassName:t,liClassName:a,linkClassName:s,linkActiveClassName:n}){return(0,r.jsx)("ul",{className:t,children:e.map(e=>(0,r.jsx)("li",{className:a,children:(0,r.jsx)(v.A,{isNavLink:!0,to:e.permalink,className:s,activeClassName:n,children:e.title})},e.permalink))})}},6133:(e,t,a)=>{a.d(t,{A:()=>l});a(6540);var s=a(4164),n=a(8774);const i={tag:"tag_zVej",tagRegular:"tagRegular_sFm0",tagWithCount:"tagWithCount_h2kH"};var r=a(4848);function l({permalink:e,label:t,count:a,description:l}){return(0,r.jsxs)(n.A,{rel:"tag",href:e,title:l,className:(0,s.A)(i.tag,a?i.tagWithCount:i.tagRegular),children:[t,a&&(0,r.jsx)("span",{children:a})]})}},8027:(e,t,a)=>{a.d(t,{A:()=>U});var s=a(6540),n=a(4164),i=a(1656),r=a(4581),l=a(1312),o=a(4096),c=a(6342),m=a(1107),d=a(4848);function u({year:e,yearGroupHeadingClassName:t,children:a}){return(0,d.jsxs)("div",{role:"group",children:[(0,d.jsx)(m.A,{as:"h3",className:t,children:e}),a]})}function g({items:e,yearGroupHeadingClassName:t,ListComponent:a}){if((0,c.p)().blog.sidebar.groupByYear){const s=(0,o.Ki)(e);return(0,d.jsx)(d.Fragment,{children:s.map(([e,s])=>(0,d.jsx)(u,{year:e,yearGroupHeadingClassName:t,children:(0,d.jsx)(a,{items:s})},e))})}return(0,d.jsx)(a,{items:e})}const h=(0,s.memo)(g),p="sidebar_re4s",b="sidebarItemTitle_pO2u",f="sidebarItemList_Yudw",x="sidebarItem__DBe",j="sidebarItemLink_mo7H",N="sidebarItemLinkActive_I1ZP",v="yearGroupHeading_rMGB",C=({items:e})=>(0,d.jsx)(o.OU,{items:e,ulClassName:(0,n.A)(f,"clean-list"),liClassName:x,linkClassName:j,linkActiveClassName:N});function k({sidebar:e}){const t=(0,o.Gx)(e.items);return(0,d.jsx)("aside",{className:"col col--3",children:(0,d.jsxs)("nav",{className:(0,n.A)(p,"thin-scrollbar"),"aria-label":(0,l.T)({id:"theme.blog.sidebar.navAriaLabel",message:"Blog recent posts navigation",description:"The ARIA label for recent posts in the blog sidebar"}),children:[(0,d.jsx)("div",{className:(0,n.A)(b,"margin-bottom--md"),children:e.title}),(0,d.jsx)(h,{items:t,ListComponent:C,yearGroupHeadingClassName:v})]})})}const A=(0,s.memo)(k);var y=a(5600);const P="yearGroupHeading_QT03",_=({items:e})=>(0,d.jsx)(o.OU,{items:e,ulClassName:"menu__list",liClassName:"menu__list-item",linkClassName:"menu__link",linkActiveClassName:"menu__link--active"});function w({sidebar:e}){const t=(0,o.Gx)(e.items);return(0,d.jsx)(h,{items:t,ListComponent:_,yearGroupHeadingClassName:P})}function B(e){return(0,d.jsx)(y.GX,{component:w,props:e})}const G=(0,s.memo)(B);function O({sidebar:e}){const t=(0,r.l)();return e?.items.length?"mobile"===t?(0,d.jsx)(G,{sidebar:e}):(0,d.jsx)(A,{sidebar:e}):null}function U(e){const{sidebar:t,toc:a,children:s,...r}=e,l=t&&t.items.length>0;return(0,d.jsx)(i.A,{...r,children:(0,d.jsx)("div",{className:"container margin-vert--lg",children:(0,d.jsxs)("div",{className:"row",children:[(0,d.jsx)(O,{sidebar:t}),(0,d.jsx)("main",{className:(0,n.A)("col",{"col--7":l,"col--9 col--offset-1":!l}),children:s}),a&&(0,d.jsx)("div",{className:"col col--2",children:a})]})})})}},9158:(e,t,a)=>{a.r(t),a.d(t,{default:()=>b});a(6540);var s=a(4164),n=a(1312);const i=()=>(0,n.T)({id:"theme.tags.tagsPageTitle",message:"Tags",description:"The title of the tag list page"});var r=a(5500),l=a(7559),o=a(8027),c=a(6133),m=a(1107);const d={tag:"tag_Nnez"};var u=a(4848);function g({letterEntry:e}){return(0,u.jsxs)("article",{children:[(0,u.jsx)(m.A,{as:"h2",id:e.letter,children:e.letter}),(0,u.jsx)("ul",{className:"padding--none",children:e.tags.map(e=>(0,u.jsx)("li",{className:d.tag,children:(0,u.jsx)(c.A,{...e})},e.permalink))}),(0,u.jsx)("hr",{})]})}function h({tags:e}){const t=function(e){const t={};return Object.values(e).forEach(e=>{const a=function(e){return e[0].toUpperCase()}(e.label);t[a]??=[],t[a].push(e)}),Object.entries(t).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>({letter:e,tags:t.sort((e,t)=>e.label.localeCompare(t.label))}))}(e);return(0,u.jsx)("section",{className:"margin-vert--lg",children:t.map(e=>(0,u.jsx)(g,{letterEntry:e},e.letter))})}var p=a(1463);function b({tags:e,sidebar:t}){const a=i();return(0,u.jsxs)(r.e3,{className:(0,s.A)(l.G.wrapper.blogPages,l.G.page.blogTagsListPage),children:[(0,u.jsx)(r.be,{title:a}),(0,u.jsx)(p.A,{tag:"blog_tags_list"}),(0,u.jsxs)(o.A,{sidebar:t,children:[(0,u.jsx)(m.A,{as:"h1",children:a}),(0,u.jsx)(h,{tags:e})]})]})}}}]); \ No newline at end of file diff --git a/docs/assets/js/0413d9af.cb9ba41f.js b/docs/assets/js/0413d9af.cb9ba41f.js deleted file mode 100644 index 6070c9ad..00000000 --- a/docs/assets/js/0413d9af.cb9ba41f.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[9919],{7114:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>l,default:()=>h,frontMatter:()=>a,metadata:()=>s,toc:()=>o});const s=JSON.parse('{"id":"sdks/python/v1.0.0/grpc_feature_client","title":"GRPC Feature client","description":"PyPI version","source":"@site/docs/sdks/python/v1.0.0/grpc_feature_client.md","sourceDirName":"sdks/python/v1.0.0","slug":"/sdks/python/v1.0.0/grpc_feature_client","permalink":"/BharatMLStack/sdks/python/v1.0.0/grpc_feature_client","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/sdks/python/v1.0.0/grpc_feature_client.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"GRPC Feature client","sidebar_position":1},"sidebar":"tutorialSidebar","previous":{"title":"v1.0.0","permalink":"/BharatMLStack/category/v100"},"next":{"title":"Spark client","permalink":"/BharatMLStack/sdks/python/v1.0.0/spark_feature_push_client"}}');var i=t(4848),r=t(8453);const a={title:"GRPC Feature client",sidebar_position:1},l="GRPC Feature Client",c={},o=[{value:"Installation",id:"installation",level:2},{value:"Dependencies",id:"dependencies",level:2},{value:"Features",id:"features",level:2},{value:"Quick Start",id:"quick-start",level:2},{value:"API Reference",id:"api-reference",level:2},{value:"GRPCFeatureClient",id:"grpcfeatureclient",level:3},{value:"GRPCClientConfig",id:"grpcclientconfig",level:3},{value:"Usage Examples",id:"usage-examples",level:2},{value:"Persisting Features",id:"persisting-features",level:3},{value:"Retrieving Features",id:"retrieving-features",level:3},{value:"With Context Management",id:"with-context-management",level:3},{value:"When to Use",id:"when-to-use",level:2},{value:"Related Packages",id:"related-packages",level:2},{value:"Contributing",id:"contributing",level:2},{value:"Community & Support",id:"community--support",level:2},{value:"License",id:"license",level:2}];function d(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",img:"img",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,r.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.header,{children:(0,i.jsx)(n.h1,{id:"grpc-feature-client",children:"GRPC Feature Client"})}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.a,{href:"https://badge.fury.io/py/grpc_feature_client",children:(0,i.jsx)(n.img,{src:"https://img.shields.io/pypi/v/grpc_feature_client?label=pypi-package&color=light%20green",alt:"PyPI version"})}),"\n",(0,i.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/actions/workflows/py-sdk.yml",children:(0,i.jsx)(n.img,{src:"https://github.com/Meesho/BharatMLStack/actions/workflows/py-sdk.yml/badge.svg",alt:"Build Status"})}),"\n",(0,i.jsx)(n.a,{href:"https://www.python.org/downloads/",children:(0,i.jsx)(n.img,{src:"https://img.shields.io/badge/python-3.7+-blue.svg",alt:"Python 3.7+"})}),"\n",(0,i.jsx)(n.a,{href:"https://discord.gg/XkT7XsV2AU",children:(0,i.jsx)(n.img,{src:"https://img.shields.io/badge/Discord-Join%20Chat-7289da?style=flat&logo=discord&logoColor=white",alt:"Discord"})}),"\n",(0,i.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md",children:(0,i.jsx)(n.img,{src:"https://img.shields.io/badge/License-BharatMLStack%20BSL%201.1-blue.svg",alt:"License"})})]}),"\n",(0,i.jsx)(n.p,{children:"High-performance gRPC client for BharatML Stack real-time feature operations with direct API access."}),"\n",(0,i.jsx)(n.h2,{id:"installation",children:"Installation"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"pip install grpc_feature_client\n"})}),"\n",(0,i.jsx)(n.h2,{id:"dependencies",children:"Dependencies"}),"\n",(0,i.jsx)(n.p,{children:"This package depends on:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:(0,i.jsx)(n.a,{href:"https://pypi.org/project/bharatml_commons/",children:"bharatml_commons"})}),": Common utilities and protobuf definitions"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"grpcio>=1.50.0"}),": gRPC framework"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"grpcio-tools>=1.50.0"}),": gRPC tools for protobuf"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"features",children:"Features"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Direct gRPC API"}),": persist, retrieve, retrieveDecoded operations"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Go SDK Compatible"}),": Same authentication and API semantics"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Batch Processing"}),": Automatic batching with parallel execution"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Real-time Focus"}),": Low-latency feature persistence and retrieval"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Context Management"}),": Timeout and metadata handling"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Connection Pooling"}),": Efficient connection management"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"quick-start",children:"Quick Start"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-python",children:'from grpc_feature_client import GRPCFeatureClient, GRPCClientConfig\n\n# Configure for real-time operations\nconfig = GRPCClientConfig(\n server_address="localhost:50051",\n job_id="realtime-service",\n job_token="api-token"\n)\n\nclient = GRPCFeatureClient(config)\n\n# Direct API operations\nresult = client.persist_features(entity_label, keys_schema, feature_groups, data)\nfeatures = client.retrieve_decoded_features(entity_label, feature_groups, keys, entity_keys)\n'})}),"\n",(0,i.jsx)(n.h2,{id:"api-reference",children:"API Reference"}),"\n",(0,i.jsx)(n.h3,{id:"grpcfeatureclient",children:"GRPCFeatureClient"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-python",children:"class GRPCFeatureClient:\n def __init__(self, config: GRPCClientConfig)\n \n def persist_features(\n self,\n entity_label: str,\n keys_schema: List[str],\n feature_group_schemas: List[Dict[str, Any]],\n data_rows: List[Dict[str, Any]],\n timeout: Optional[float] = None\n ) -> Dict[str, Any]\n \n def retrieve_features(\n self,\n entity_label: str,\n feature_groups: List[Dict[str, Any]],\n keys_schema: List[str],\n entity_keys: List[List[str]],\n timeout: Optional[float] = None\n ) -> Dict[str, Any]\n \n def retrieve_decoded_features(\n self,\n entity_label: str,\n feature_groups: List[Dict[str, Any]],\n keys_schema: List[str],\n entity_keys: List[List[str]],\n timeout: Optional[float] = None\n ) -> Dict[str, Any]\n"})}),"\n",(0,i.jsx)(n.h3,{id:"grpcclientconfig",children:"GRPCClientConfig"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-python",children:"class GRPCClientConfig:\n def __init__(\n self,\n server_address: str,\n job_id: str,\n job_token: str,\n use_tls: bool = False,\n timeout_seconds: float = 30.0,\n metadata: Dict[str, str] = None,\n max_receive_message_length: int = 4 * 1024 * 1024,\n max_send_message_length: int = 4 * 1024 * 1024\n )\n"})}),"\n",(0,i.jsx)(n.h2,{id:"usage-examples",children:"Usage Examples"}),"\n",(0,i.jsx)(n.h3,{id:"persisting-features",children:"Persisting Features"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-python",children:'from grpc_feature_client import GRPCFeatureClient, GRPCClientConfig\n\nconfig = GRPCClientConfig(\n server_address="feature-store.example.com:50051",\n job_id="predator",\n job_token="api-token"\n)\n\nclient = GRPCFeatureClient(config)\n\n# Persist real-time features\nresult = client.persist_features(\n entity_label="user_interaction",\n keys_schema=["user_id", "session_id"],\n feature_group_schemas=[{\n "label": "realtime_features",\n "feature_labels": ["click_count", "page_views"]\n }],\n data_rows=[{\n "user_id": "u123",\n "session_id": "s456",\n "click_count": 5,\n "page_views": 3\n }]\n)\n\nprint(f"Persist result: {result}")\n'})}),"\n",(0,i.jsx)(n.h3,{id:"retrieving-features",children:"Retrieving Features"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-python",children:'# Retrieve features for ML model inference\nfeatures = client.retrieve_decoded_features(\n entity_label="user_interaction",\n feature_groups=[{\n "label": "user_features",\n "feature_labels": ["age", "location"]\n }],\n keys_schema=["user_id"],\n entity_keys=[["u123"], ["u456"]]\n)\n\nprint(f"Retrieved features: {features}")\n'})}),"\n",(0,i.jsx)(n.h3,{id:"with-context-management",children:"With Context Management"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-python",children:"# Use client with automatic cleanup\nwith GRPCFeatureClient(config) as client:\n result = client.persist_features(...)\n features = client.retrieve_decoded_features(...)\n# Connection automatically closed\n"})}),"\n",(0,i.jsx)(n.h2,{id:"when-to-use",children:"When to Use"}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Use grpc_feature_client for:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["\ud83d\ude80 ",(0,i.jsx)(n.strong,{children:"Real-time Operations"}),": Direct persist/retrieve operations"]}),"\n",(0,i.jsxs)(n.li,{children:["\ud83d\udd0d ",(0,i.jsx)(n.strong,{children:"Interactive Queries"}),": Low-latency feature lookups"]}),"\n",(0,i.jsxs)(n.li,{children:["\ud83c\udfaf ",(0,i.jsx)(n.strong,{children:"API Integration"}),": Service-to-service communication"]}),"\n",(0,i.jsxs)(n.li,{children:["\ud83d\udca8 ",(0,i.jsx)(n.strong,{children:"Single Records"}),": Persisting individual feature records"]}),"\n",(0,i.jsxs)(n.li,{children:["\ud83d\udd04 ",(0,i.jsx)(n.strong,{children:"Model Serving"}),": Feature retrieval for online inference"]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Use spark_feature_push_client for:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["\ud83d\udd04 ",(0,i.jsx)(n.strong,{children:"Batch ETL Pipelines"}),": Scheduled feature computation and publishing"]}),"\n",(0,i.jsxs)(n.li,{children:["\ud83d\udcca ",(0,i.jsx)(n.strong,{children:"Historical Data Backfill"}),": Loading historical features into online store"]}),"\n",(0,i.jsxs)(n.li,{children:["\ud83c\udfd7\ufe0f ",(0,i.jsx)(n.strong,{children:"Data Engineering"}),": Spark-based feature transformations"]}),"\n",(0,i.jsxs)(n.li,{children:["\ud83d\udcc8 ",(0,i.jsx)(n.strong,{children:"Large Scale Processing"}),": Processing millions of records efficiently"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"related-packages",children:"Related Packages"}),"\n",(0,i.jsx)(n.p,{children:"This package is part of the BharatML Stack ecosystem:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:(0,i.jsx)(n.a,{href:"https://pypi.org/project/bharatml_commons/",children:"bharatml_commons"})}),": Common utilities and protobuf definitions (required dependency)"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:(0,i.jsx)(n.a,{href:"https://pypi.org/project/spark_feature_push_client/",children:"spark_feature_push_client"})}),": Spark-based data pipeline client"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"contributing",children:"Contributing"}),"\n",(0,i.jsxs)(n.p,{children:["We welcome contributions from the community! Please see our ",(0,i.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTING.md",children:"Contributing Guide"})," for details on how to get started."]}),"\n",(0,i.jsx)(n.h2,{id:"community--support",children:"Community & Support"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["\ud83d\udcac ",(0,i.jsx)(n.strong,{children:"Discord"}),": Join our ",(0,i.jsx)(n.a,{href:"https://discord.gg/XkT7XsV2AU",children:"community chat"})]}),"\n",(0,i.jsxs)(n.li,{children:["\ud83d\udc1b ",(0,i.jsx)(n.strong,{children:"Issues"}),": Report bugs and request features on ",(0,i.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/issues",children:"GitHub Issues"})]}),"\n",(0,i.jsxs)(n.li,{children:["\ud83d\udce7 ",(0,i.jsx)(n.strong,{children:"Email"}),": Contact us at ",(0,i.jsx)(n.a,{href:"mailto:ml-oss@meesho.com",children:"ml-oss@meesho.com"})]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"license",children:"License"}),"\n",(0,i.jsxs)(n.p,{children:["BharatMLStack is open-source software licensed under the ",(0,i.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md",children:"BharatMLStack Business Source License 1.1"}),"."]}),"\n",(0,i.jsx)(n.hr,{}),"\n",(0,i.jsx)("div",{align:"center",children:(0,i.jsx)("strong",{children:"Built with \u2764\ufe0f for the ML community from Meesho"})}),"\n",(0,i.jsx)("div",{align:"center",children:(0,i.jsx)("strong",{children:"If you find this useful, \u2b50\ufe0f the repo \u2014 your support means the world to us!"})})]})}function h(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(d,{...e})}):d(e)}},8453:(e,n,t)=>{t.d(n,{R:()=>a,x:()=>l});var s=t(6540);const i={},r=s.createContext(i);function a(e){const n=s.useContext(r);return s.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:a(e.components),s.createElement(r.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/0413d9af.fc3050c7.js b/docs/assets/js/0413d9af.fc3050c7.js new file mode 100644 index 00000000..ca32179a --- /dev/null +++ b/docs/assets/js/0413d9af.fc3050c7.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[9919],{7114:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>l,default:()=>h,frontMatter:()=>a,metadata:()=>s,toc:()=>o});const s=JSON.parse('{"id":"sdks/python/v1.0.0/grpc_feature_client","title":"GRPC Feature client","description":"PyPI version","source":"@site/docs/sdks/python/v1.0.0/grpc_feature_client.md","sourceDirName":"sdks/python/v1.0.0","slug":"/sdks/python/v1.0.0/grpc_feature_client","permalink":"/BharatMLStack/sdks/python/v1.0.0/grpc_feature_client","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/sdks/python/v1.0.0/grpc_feature_client.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"GRPC Feature client","sidebar_position":1},"sidebar":"tutorialSidebar","previous":{"title":"v1.0.0","permalink":"/BharatMLStack/sdks/python/v1.0.0"},"next":{"title":"Spark client","permalink":"/BharatMLStack/sdks/python/v1.0.0/spark_feature_push_client"}}');var i=t(4848),r=t(8453);const a={title:"GRPC Feature client",sidebar_position:1},l="GRPC Feature Client",c={},o=[{value:"Installation",id:"installation",level:2},{value:"Dependencies",id:"dependencies",level:2},{value:"Features",id:"features",level:2},{value:"Quick Start",id:"quick-start",level:2},{value:"API Reference",id:"api-reference",level:2},{value:"GRPCFeatureClient",id:"grpcfeatureclient",level:3},{value:"GRPCClientConfig",id:"grpcclientconfig",level:3},{value:"Usage Examples",id:"usage-examples",level:2},{value:"Persisting Features",id:"persisting-features",level:3},{value:"Retrieving Features",id:"retrieving-features",level:3},{value:"With Context Management",id:"with-context-management",level:3},{value:"When to Use",id:"when-to-use",level:2},{value:"Related Packages",id:"related-packages",level:2},{value:"Contributing",id:"contributing",level:2},{value:"Community & Support",id:"community--support",level:2},{value:"License",id:"license",level:2}];function d(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",img:"img",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,r.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.header,{children:(0,i.jsx)(n.h1,{id:"grpc-feature-client",children:"GRPC Feature Client"})}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.a,{href:"https://badge.fury.io/py/grpc_feature_client",children:(0,i.jsx)(n.img,{src:"https://img.shields.io/pypi/v/grpc_feature_client?label=pypi-package&color=light%20green",alt:"PyPI version"})}),"\n",(0,i.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/actions/workflows/py-sdk.yml",children:(0,i.jsx)(n.img,{src:"https://github.com/Meesho/BharatMLStack/actions/workflows/py-sdk.yml/badge.svg",alt:"Build Status"})}),"\n",(0,i.jsx)(n.a,{href:"https://www.python.org/downloads/",children:(0,i.jsx)(n.img,{src:"https://img.shields.io/badge/python-3.7+-blue.svg",alt:"Python 3.7+"})}),"\n",(0,i.jsx)(n.a,{href:"https://discord.gg/XkT7XsV2AU",children:(0,i.jsx)(n.img,{src:"https://img.shields.io/badge/Discord-Join%20Chat-7289da?style=flat&logo=discord&logoColor=white",alt:"Discord"})}),"\n",(0,i.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md",children:(0,i.jsx)(n.img,{src:"https://img.shields.io/badge/License-BharatMLStack%20BSL%201.1-blue.svg",alt:"License"})})]}),"\n",(0,i.jsx)(n.p,{children:"High-performance gRPC client for BharatML Stack real-time feature operations with direct API access."}),"\n",(0,i.jsx)(n.h2,{id:"installation",children:"Installation"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"pip install grpc_feature_client\n"})}),"\n",(0,i.jsx)(n.h2,{id:"dependencies",children:"Dependencies"}),"\n",(0,i.jsx)(n.p,{children:"This package depends on:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:(0,i.jsx)(n.a,{href:"https://pypi.org/project/bharatml_commons/",children:"bharatml_commons"})}),": Common utilities and protobuf definitions"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"grpcio>=1.50.0"}),": gRPC framework"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"grpcio-tools>=1.50.0"}),": gRPC tools for protobuf"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"features",children:"Features"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Direct gRPC API"}),": persist, retrieve, retrieveDecoded operations"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Go SDK Compatible"}),": Same authentication and API semantics"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Batch Processing"}),": Automatic batching with parallel execution"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Real-time Focus"}),": Low-latency feature persistence and retrieval"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Context Management"}),": Timeout and metadata handling"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Connection Pooling"}),": Efficient connection management"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"quick-start",children:"Quick Start"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-python",children:'from grpc_feature_client import GRPCFeatureClient, GRPCClientConfig\n\n# Configure for real-time operations\nconfig = GRPCClientConfig(\n server_address="localhost:50051",\n job_id="realtime-service",\n job_token="api-token"\n)\n\nclient = GRPCFeatureClient(config)\n\n# Direct API operations\nresult = client.persist_features(entity_label, keys_schema, feature_groups, data)\nfeatures = client.retrieve_decoded_features(entity_label, feature_groups, keys, entity_keys)\n'})}),"\n",(0,i.jsx)(n.h2,{id:"api-reference",children:"API Reference"}),"\n",(0,i.jsx)(n.h3,{id:"grpcfeatureclient",children:"GRPCFeatureClient"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-python",children:"class GRPCFeatureClient:\n def __init__(self, config: GRPCClientConfig)\n \n def persist_features(\n self,\n entity_label: str,\n keys_schema: List[str],\n feature_group_schemas: List[Dict[str, Any]],\n data_rows: List[Dict[str, Any]],\n timeout: Optional[float] = None\n ) -> Dict[str, Any]\n \n def retrieve_features(\n self,\n entity_label: str,\n feature_groups: List[Dict[str, Any]],\n keys_schema: List[str],\n entity_keys: List[List[str]],\n timeout: Optional[float] = None\n ) -> Dict[str, Any]\n \n def retrieve_decoded_features(\n self,\n entity_label: str,\n feature_groups: List[Dict[str, Any]],\n keys_schema: List[str],\n entity_keys: List[List[str]],\n timeout: Optional[float] = None\n ) -> Dict[str, Any]\n"})}),"\n",(0,i.jsx)(n.h3,{id:"grpcclientconfig",children:"GRPCClientConfig"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-python",children:"class GRPCClientConfig:\n def __init__(\n self,\n server_address: str,\n job_id: str,\n job_token: str,\n use_tls: bool = False,\n timeout_seconds: float = 30.0,\n metadata: Dict[str, str] = None,\n max_receive_message_length: int = 4 * 1024 * 1024,\n max_send_message_length: int = 4 * 1024 * 1024\n )\n"})}),"\n",(0,i.jsx)(n.h2,{id:"usage-examples",children:"Usage Examples"}),"\n",(0,i.jsx)(n.h3,{id:"persisting-features",children:"Persisting Features"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-python",children:'from grpc_feature_client import GRPCFeatureClient, GRPCClientConfig\n\nconfig = GRPCClientConfig(\n server_address="feature-store.example.com:50051",\n job_id="predator-service",\n job_token="api-token"\n)\n\nclient = GRPCFeatureClient(config)\n\n# Persist real-time features\nresult = client.persist_features(\n entity_label="user_interaction",\n keys_schema=["user_id", "session_id"],\n feature_group_schemas=[{\n "label": "realtime_features",\n "feature_labels": ["click_count", "page_views"]\n }],\n data_rows=[{\n "user_id": "u123",\n "session_id": "s456",\n "click_count": 5,\n "page_views": 3\n }]\n)\n\nprint(f"Persist result: {result}")\n'})}),"\n",(0,i.jsx)(n.h3,{id:"retrieving-features",children:"Retrieving Features"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-python",children:'# Retrieve features for ML model inference\nfeatures = client.retrieve_decoded_features(\n entity_label="user_interaction",\n feature_groups=[{\n "label": "user_features",\n "feature_labels": ["age", "location"]\n }],\n keys_schema=["user_id"],\n entity_keys=[["u123"], ["u456"]]\n)\n\nprint(f"Retrieved features: {features}")\n'})}),"\n",(0,i.jsx)(n.h3,{id:"with-context-management",children:"With Context Management"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-python",children:"# Use client with automatic cleanup\nwith GRPCFeatureClient(config) as client:\n result = client.persist_features(...)\n features = client.retrieve_decoded_features(...)\n# Connection automatically closed\n"})}),"\n",(0,i.jsx)(n.h2,{id:"when-to-use",children:"When to Use"}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Use grpc_feature_client for:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["\ud83d\ude80 ",(0,i.jsx)(n.strong,{children:"Real-time Operations"}),": Direct persist/retrieve operations"]}),"\n",(0,i.jsxs)(n.li,{children:["\ud83d\udd0d ",(0,i.jsx)(n.strong,{children:"Interactive Queries"}),": Low-latency feature lookups"]}),"\n",(0,i.jsxs)(n.li,{children:["\ud83c\udfaf ",(0,i.jsx)(n.strong,{children:"API Integration"}),": Service-to-service communication"]}),"\n",(0,i.jsxs)(n.li,{children:["\ud83d\udca8 ",(0,i.jsx)(n.strong,{children:"Single Records"}),": Persisting individual feature records"]}),"\n",(0,i.jsxs)(n.li,{children:["\ud83d\udd04 ",(0,i.jsx)(n.strong,{children:"Model Serving"}),": Feature retrieval for online inference"]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Use spark_feature_push_client for:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["\ud83d\udd04 ",(0,i.jsx)(n.strong,{children:"Batch ETL Pipelines"}),": Scheduled feature computation and publishing"]}),"\n",(0,i.jsxs)(n.li,{children:["\ud83d\udcca ",(0,i.jsx)(n.strong,{children:"Historical Data Backfill"}),": Loading historical features into online store"]}),"\n",(0,i.jsxs)(n.li,{children:["\ud83c\udfd7\ufe0f ",(0,i.jsx)(n.strong,{children:"Data Engineering"}),": Spark-based feature transformations"]}),"\n",(0,i.jsxs)(n.li,{children:["\ud83d\udcc8 ",(0,i.jsx)(n.strong,{children:"Large Scale Processing"}),": Processing millions of records efficiently"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"related-packages",children:"Related Packages"}),"\n",(0,i.jsx)(n.p,{children:"This package is part of the BharatML Stack ecosystem:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:(0,i.jsx)(n.a,{href:"https://pypi.org/project/bharatml_commons/",children:"bharatml_commons"})}),": Common utilities and protobuf definitions (required dependency)"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:(0,i.jsx)(n.a,{href:"https://pypi.org/project/spark_feature_push_client/",children:"spark_feature_push_client"})}),": Spark-based data pipeline client"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"contributing",children:"Contributing"}),"\n",(0,i.jsxs)(n.p,{children:["We welcome contributions from the community! Please see our ",(0,i.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTING.md",children:"Contributing Guide"})," for details on how to get started."]}),"\n",(0,i.jsx)(n.h2,{id:"community--support",children:"Community & Support"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["\ud83d\udcac ",(0,i.jsx)(n.strong,{children:"Discord"}),": Join our ",(0,i.jsx)(n.a,{href:"https://discord.gg/XkT7XsV2AU",children:"community chat"})]}),"\n",(0,i.jsxs)(n.li,{children:["\ud83d\udc1b ",(0,i.jsx)(n.strong,{children:"Issues"}),": Report bugs and request features on ",(0,i.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/issues",children:"GitHub Issues"})]}),"\n",(0,i.jsxs)(n.li,{children:["\ud83d\udce7 ",(0,i.jsx)(n.strong,{children:"Email"}),": Contact us at ",(0,i.jsx)(n.a,{href:"mailto:ml-oss@meesho.com",children:"ml-oss@meesho.com"})]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"license",children:"License"}),"\n",(0,i.jsxs)(n.p,{children:["BharatMLStack is open-source software licensed under the ",(0,i.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md",children:"BharatMLStack Business Source License 1.1"}),"."]}),"\n",(0,i.jsx)(n.hr,{}),"\n",(0,i.jsx)("div",{align:"center",children:(0,i.jsx)("strong",{children:"Built with \u2764\ufe0f for the ML community from Meesho"})}),"\n",(0,i.jsx)("div",{align:"center",children:(0,i.jsx)("strong",{children:"If you find this useful, \u2b50\ufe0f the repo \u2014 your support means the world to us!"})})]})}function h(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(d,{...e})}):d(e)}},8453:(e,n,t)=>{t.d(n,{R:()=>a,x:()=>l});var s=t(6540);const i={},r=s.createContext(i);function a(e){const n=s.useContext(r);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:a(e.components),s.createElement(r.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/08daf6b6.852abb6b.js b/docs/assets/js/08daf6b6.852abb6b.js new file mode 100644 index 00000000..d12c0df2 --- /dev/null +++ b/docs/assets/js/08daf6b6.852abb6b.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[1686],{848:a=>{a.exports=JSON.parse('{"tag":{"label":"model-inference","permalink":"/BharatMLStack/blog/tags/model-inference","allTagsPath":"/BharatMLStack/blog/tags","count":1,"unlisted":false},"listMetadata":{"permalink":"/BharatMLStack/blog/tags/model-inference","page":1,"postsPerPage":10,"totalPages":1,"totalCount":1,"blogDescription":"Blog","blogTitle":"Blog"}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/09dd5be9.9cd209bb.js b/docs/assets/js/09dd5be9.9cd209bb.js deleted file mode 100644 index d07ca928..00000000 --- a/docs/assets/js/09dd5be9.9cd209bb.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[6273],{1012:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/interaction-store-v0-68167b64c6e462ef2f177f0f86d55bda.png"},3190:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/schema-d699efc400ed0f83bba421c1f55ab211.png"},3518:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/old-batch-arch-bc2cedbc1fed0fc6f08479ba8fe52996.png"},3983:e=>{e.exports=JSON.parse('{"permalink":"/BharatMLStack/blog/post-one","editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/blog/bharatmlstack-history/post-one/index.md","source":"@site/blog/bharatmlstack-history/post-one/index.md","title":"Building Meesho\u2019s ML Platform: From Chaos to Cutting-Edge (Part 1)","description":"BharatMLStack","date":"2022-11-15T00:00:00.000Z","tags":[{"inline":true,"label":"online-feature-store","permalink":"/BharatMLStack/blog/tags/online-feature-store"},{"inline":true,"label":"interaction-store","permalink":"/BharatMLStack/blog/tags/interaction-store"},{"inline":true,"label":"mlplatform","permalink":"/BharatMLStack/blog/tags/mlplatform"},{"inline":true,"label":"meesho","permalink":"/BharatMLStack/blog/tags/meesho"}],"readingTime":10.25,"hasTruncateMarker":false,"authors":[{"name":"Adarsha Das","title":"Senior Architect @ Meesho","url":"https://github.com/a0d00kc","imageURL":"https://github.com/a0d00kc.png","key":"adarsha","page":null},{"name":"Aditya Kumar","title":"SDE-III @ Meesho","url":"https://github.com/Adit2607","imageURL":"https://github.com/Adit2607.png","key":"aditya","page":null},{"name":"Bhawani Singh","title":"SDE-IV @ Meesho","url":"https://github.com/singh-bhawani","imageURL":"https://github.com/singh-bhawani.png","key":"bhawani","page":null},{"name":"Jigar Dave","title":"SDE-IV @ Meesho","url":"https://github.com/jigarpatel26","imageURL":"https://github.com/jigarpatel26.png","key":"jigar","page":null}],"frontMatter":{"slug":"post-one","title":"Building Meesho\u2019s ML Platform: From Chaos to Cutting-Edge (Part 1)","authors":["adarsha","aditya","bhawani","jigar"],"date":"2022-11-15T00:00:00.000Z","tags":["online-feature-store","interaction-store","mlplatform","meesho"]},"unlisted":false}')},5728:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/online-feature-store-v0-86ec0010947ae24621f39ebd0d1729ca.png"},7131:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/first-gen-arch-7c0b286810aecb7eff42b48f51caee1f.png"},8453:(e,n,i)=>{i.d(n,{R:()=>a,x:()=>o});var t=i(6540);const s={},r=t.createContext(s);function a(e){const n=t.useContext(r);return t.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:a(e.components),t.createElement(r.Provider,{value:n},e.children)}},8831:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>l,contentTitle:()=>o,default:()=>c,frontMatter:()=>a,metadata:()=>t,toc:()=>d});var t=i(3983),s=i(4848),r=i(8453);const a={slug:"post-one",title:"Building Meesho\u2019s ML Platform: From Chaos to Cutting-Edge (Part 1)",authors:["adarsha","aditya","bhawani","jigar"],date:new Date("2022-11-15T00:00:00.000Z"),tags:["online-feature-store","interaction-store","mlplatform","meesho"]},o=void 0,l={authorsImageUrls:[void 0,void 0,void 0,void 0]},d=[{value:"The Genesis: How a Friday Night Roast Sparked Meesho\u2019s ML Platform",id:"the-genesis-how-a-friday-night-roast-sparked-meeshos-ml-platform",level:2},{value:"The Turning Point: From Batch to Real-Time",id:"the-turning-point-from-batch-to-real-time",level:2},{value:"First Generation Design",id:"first-generation-design",level:2},{value:"1. IOP Framework: A Real-Time DAG Executor",id:"1-iop-framework-a-real-time-dag-executor",level:3},{value:"2. Online Feature Store - 0th Version",id:"2-online-feature-store---0th-version",level:3},{value:"3. Interaction Store - 0th Version",id:"3-interaction-store---0th-version",level:3},{value:"Building the Online Feature Store - 0th Version",id:"building-the-online-feature-store---0th-version",level:2},{value:"Choosing the Right Tech Stack",id:"choosing-the-right-tech-stack",level:3},{value:"Streamlining the Data Flow",id:"streamlining-the-data-flow",level:3},{value:"The Challenges: Data Format and Storage",id:"the-challenges-data-format-and-storage",level:2},{value:"Feature Consistency",id:"feature-consistency",level:3},{value:"TTL Granularity",id:"ttl-granularity",level:3},{value:"Extensibility Across Databases",id:"extensibility-across-databases",level:3},{value:"Overcoming Technical Constraints",id:"overcoming-technical-constraints",level:2},{value:"The Solution: Schema Separation",id:"the-solution-schema-separation",level:2},{value:"Tracking Changes in Feature Groups",id:"tracking-changes-in-feature-groups",level:2},{value:"Common Real-World Scenarios:",id:"common-real-world-scenarios",level:3},{value:"The Solution: Schema Versioning",id:"the-solution-schema-versioning",level:2},{value:"Backward Compatibility",id:"backward-compatibility",level:3},{value:"Partial Availability Handling",id:"partial-availability-handling",level:3},{value:"Safe Writes Without Pipeline Pauses",id:"safe-writes-without-pipeline-pauses",level:3},{value:"Interaction Store - 0th Version",id:"interaction-store---0th-version",level:2},{value:"Event Ingestion",id:"event-ingestion",level:2},{value:"Storage Design",id:"storage-design",level:2},{value:"Why Redis?",id:"why-redis",level:3},{value:"Storage Structure",id:"storage-structure",level:3},{value:"Built-in Guardrails",id:"built-in-guardrails",level:3},{value:"Conclusion: Laying the Foundation for Real-Time ML",id:"conclusion-laying-the-foundation-for-real-time-ml",level:2}];function h(e){const n={a:"a",br:"br",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",hr:"hr",img:"img",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,r.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"BharatMLStack",src:i(9930).A+"",width:"1472",height:"892"})}),"\n",(0,s.jsx)(n.h2,{id:"the-genesis-how-a-friday-night-roast-sparked-meeshos-ml-platform",children:"The Genesis: How a Friday Night Roast Sparked Meesho\u2019s ML Platform"}),"\n",(0,s.jsx)(n.p,{children:"It all started in early 2022, over a casual Friday evening catch-up. Like many great origin stories, this one began with friendly banter between a group of backend engineers and data scientists. As the conversations unfolded, so did the roasting\u2014until one remark hit a little too close to home:"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.em,{children:'"Why are we still crunching data for Monthly Active Users (MAU) when the next day it\u2019s all about Daily Active Users (DAU)?"'})}),"\n",(0,s.jsx)(n.p,{children:"The laughter died down, and the question lingered. When we regrouped on Monday\u2014clear-headed and slightly reflective\u2014we decided to dig into the numbers. What they discovered was quite revealing: a large portion of compute resources wasn\u2019t being put to good use.\nMuch of the system\u2019s effort was spent supporting users who weren\u2019t actively engaging, and even for new users, the experience wasn\u2019t optimized to make a meaningful impact."}),"\n",(0,s.jsxs)(n.p,{children:["At the same time, Meesho had just launched a company-wide initiative to reduce costs\u2014and every team had to contribute. This realization sparked the journey that would eventually lead to the ",(0,s.jsx)(n.strong,{children:"Meesho ML Platform"}),", known today as ",(0,s.jsx)(n.strong,{children:"BharatMLStack"}),"."]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Alt Text",src:i(3518).A+"",width:"1600",height:"1078"})}),"\n",(0,s.jsx)(n.p,{children:"Before the ML Platform, our recommendation and ranking pipelines followed a batch processing approach:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Data Ingestion"}),": The Data Platform team executed ETL jobs to ingest raw user data\u2014including user profiles, interaction logs, and product impressions\u2014into designated S3 buckets."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Layer 1"}),": Embedding Generation: On the Data Science side, Spark jobs pulled data from multiple S3 sources, cleaned and preprocessed it, and applied matrix factorization to generate user and item embeddings. The processed data and embeddings were then stored back in S3 in a structured format."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Layer 2"}),": Candidate Generation (CG): In this stage, Spark jobs leveraged embeddings and historical interaction data to generate candidate recommendations for users. These candidate lists were subsequently written to S3."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Layer 3"}),": Ranking and Merging \u2013 A final round of processing ranked the generated candidates using ML models, combined different candidate lists, and stored the final ranked recommendations in a caching system."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Serving"}),': A microservice retrieved ranked recommendations from an in-memory data store via exposed APIs, delivering personalized listings across key surfaces such as "For You" and Category Landing Pages (CLP).']}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"This approach held up well\u2014until Meesho started seeing a significant surge in traffic."}),"\n",(0,s.jsx)(n.h2,{id:"the-turning-point-from-batch-to-real-time",children:"The Turning Point: From Batch to Real-Time"}),"\n",(0,s.jsxs)(n.p,{children:["At this time, the team was iterating on new ",(0,s.jsx)(n.strong,{children:"Ranker models"}),", and real-time inference seemed like the next logical step. But Rankers needed ",(0,s.jsx)(n.strong,{children:"real-time feature retrieval"}),", which meant an ",(0,s.jsx)(n.strong,{children:"online feature store"})," had to be built first."]}),"\n",(0,s.jsxs)(n.p,{children:["Exploring open-source options led to ",(0,s.jsx)(n.strong,{children:"cost vs. performance trade-offs"}),", but Meesho\u2019s surging traffic meant that ",(0,s.jsx)(n.strong,{children:"latency and stability were non-negotiable"}),". After multiple debates and stakeholder discussions, a bold decision was made:"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.em,{children:"We would build our own feature store."})}),"\n",(0,s.jsxs)(n.p,{children:["Meanwhile, efforts began to bring ",(0,s.jsx)(n.strong,{children:"Candidate Generators (CGs)"})," to real-time. The challenge? ",(0,s.jsx)(n.strong,{children:"Storing and retrieving user interactions quickly enough"})," to power real-time recommendations."]}),"\n",(0,s.jsxs)(n.p,{children:["As the team dove deeper, a new roadblock emerged:",(0,s.jsx)(n.br,{}),"\n","Our ML jobs were orchestrated using ",(0,s.jsx)(n.strong,{children:"Airflow DAGs"}),", giving data scientists flexibility in experimentation. But transitioning to real-time execution threatened this agility. Every change would now require backend engineering support, ",(0,s.jsx)(n.strong,{children:"slowing down iteration cycles"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["That\u2019s when the idea struck:",(0,s.jsx)(n.br,{}),"\n","We needed a ",(0,s.jsx)(n.strong,{children:"framework for real-time DAG execution"}),"\u2014one that preserved the same flexibility as Airflow but worked for ",(0,s.jsx)(n.strong,{children:"streaming data"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["This moment shaped the ",(0,s.jsx)(n.strong,{children:"next phase of our journey"}),"."]}),"\n",(0,s.jsx)(n.h2,{id:"first-generation-design",children:"First Generation Design"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Alt Text",src:i(7131).A+"",width:"1600",height:"1006"})}),"\n",(0,s.jsx)(n.h1,{id:"laying-the-groundwork-the-first-gen-ml-platform",children:"Laying the Groundwork: The First-Gen ML Platform"}),"\n",(0,s.jsx)(n.p,{children:"To solve these challenges, the team built three foundational components:"}),"\n",(0,s.jsx)(n.h3,{id:"1-iop-framework-a-real-time-dag-executor",children:"1. IOP Framework: A Real-Time DAG Executor"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Reusable Nodes"}),": Each DAG node (e.g., an invocation to a CG service, a ranker, or a filter) had to be implemented only once. After that, it could be reused across any workflow by referencing it in config."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Config-driven Dynamic Graphs"}),": Execution graphs were defined as adjacency lists stored in ",(0,s.jsx)(n.strong,{children:"ZooKeeper"}),", allowing teams to modify the sequence or structure of operations without touching application code."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Plug-and-play CGs"}),": The Candidate Generator interface was preserved, so a single CG node could call any CG service by passing ",(0,s.jsx)(n.code,{children:"cg_name"})," in the request. This drastically reduced the code surface area and improved maintainability."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Production-Grade DAGs"}),": DAGs were designed to execute in ",(0,s.jsx)(n.strong,{children:"low-latency real-time environments"}),", with support for ",(0,s.jsx)(n.strong,{children:"parallel execution, retries, and branching"}),"."]}),"\n"]}),"\n",(0,s.jsx)("u",{children:(0,s.jsx)(n.a,{href:"https://www.meesho.io/blog/rebuilding-meeshos-ranking-platform",children:"More about IOP DAG"})}),"\n",(0,s.jsx)(n.h3,{id:"2-online-feature-store---0th-version",children:"2. Online Feature Store - 0th Version"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Used ",(0,s.jsx)(n.strong,{children:"Cassandra"})," and ",(0,s.jsx)(n.strong,{children:"Redis"})," for low-latency feature serving."]}),"\n",(0,s.jsxs)(n.li,{children:["Maintained feature consistency using ",(0,s.jsx)(n.strong,{children:"Feature Groups"})," with TTL-based expiry."]}),"\n",(0,s.jsxs)(n.li,{children:["A hybrid schema was used: feature keys stored in ",(0,s.jsx)(n.strong,{children:"ZooKeeper"}),", data stored in ",(0,s.jsx)(n.strong,{children:"compact arrays"}),"."]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"3-interaction-store---0th-version",children:"3. Interaction Store - 0th Version"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Captured real-time user interactions like clicks, orders, and add-to-cart events."}),"\n",(0,s.jsxs)(n.li,{children:["Stored event data in ",(0,s.jsx)(n.strong,{children:"Redis ZSETs (sorted sets)"})," to enable fast lookups for recommendation engines."]}),"\n",(0,s.jsxs)(n.li,{children:["Provided an API to fetch a user's ",(0,s.jsxs)(n.strong,{children:["last ",(0,s.jsx)(n.em,{children:"k"})," interactions"]})," or ",(0,s.jsx)(n.strong,{children:"interactions within a time window"}),"."]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["With these components in place, ",(0,s.jsx)(n.strong,{children:"real-time ML at Meesho became a reality"}),"."]}),"\n",(0,s.jsx)(n.p,{children:"This was just the beginning."}),"\n",(0,s.jsx)(n.h2,{id:"building-the-online-feature-store---0th-version",children:"Building the Online Feature Store - 0th Version"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Alt text",src:i(5728).A+"",width:"1574",height:"562"})}),"\n",(0,s.jsx)(n.h3,{id:"choosing-the-right-tech-stack",children:"Choosing the Right Tech Stack"}),"\n",(0,s.jsxs)(n.p,{children:["We spent considerable time evaluating various databases, caches, and communication protocols for our ",(0,s.jsx)(n.strong,{children:"online feature store"}),". After carefully weighing ",(0,s.jsx)(n.strong,{children:"cost, latency, throughput"}),", and ",(0,s.jsx)(n.strong,{children:"operational stability"}),", we settled on a combination of:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Cassandra"})," and ",(0,s.jsx)(n.strong,{children:"Redis"})," for storage"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"gRPC + Proto3"})," as our communication layer"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"streamlining-the-data-flow",children:"Streamlining the Data Flow"}),"\n",(0,s.jsx)(n.p,{children:"To keep things simple in the initial version:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Feature engineering jobs"})," wrote raw outputs to an ",(0,s.jsx)(n.strong,{children:"S3 bucket"})]}),"\n",(0,s.jsxs)(n.li,{children:["A ",(0,s.jsx)(n.strong,{children:"daily feature push job"}),":","\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Read from S3"}),"\n",(0,s.jsxs)(n.li,{children:["Grouped related features into ",(0,s.jsx)(n.strong,{children:"Feature Groups"})," (ensuring consistency)"]}),"\n",(0,s.jsxs)(n.li,{children:["Pushed them to ",(0,s.jsx)(n.strong,{children:"Kafka"})]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"For features requiring frequent updates:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Ad-hoc jobs"})," computed features in higher frequency"]}),"\n",(0,s.jsxs)(n.li,{children:["These jobs pushed to both ",(0,s.jsx)(n.strong,{children:"Kafka"})," and ",(0,s.jsx)(n.strong,{children:"S3"})," (S3 preserved historical data for future model training)"]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"the-challenges-data-format-and-storage",children:"The Challenges: Data Format and Storage"}),"\n",(0,s.jsxs)(n.p,{children:["One of the most critical design challenges was how to store feature data ",(0,s.jsx)(n.strong,{children:"efficiently and consistently"}),", especially in databases like ",(0,s.jsx)(n.strong,{children:"Cassandra"})," and ",(0,s.jsx)(n.strong,{children:"Redis"}),", which come with unique storage constraints."]}),"\n",(0,s.jsx)(n.p,{children:"We had to solve for three key requirements:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.h3,{id:"feature-consistency",children:"Feature Consistency"}),"\n",(0,s.jsxs)(n.p,{children:["When a feature group contains features like ",(0,s.jsx)(n.code,{children:"order_count_1h"})," and ",(0,s.jsx)(n.code,{children:"click_count_1h"}),", both must reflect the ",(0,s.jsx)(n.strong,{children:"same time window"}),". Inconsistent updates would lead to ",(0,s.jsx)(n.strong,{children:"unreliable model predictions"}),"."]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.h3,{id:"ttl-granularity",children:"TTL Granularity"}),"\n",(0,s.jsxs)(n.p,{children:["Each feature group required an ",(0,s.jsx)(n.strong,{children:"expiry timestamp"}),", so that ",(0,s.jsx)(n.strong,{children:"all features within it expired together"}),"\u2014preserving consistency during reads."]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.h3,{id:"extensibility-across-databases",children:"Extensibility Across Databases"}),"\n",(0,s.jsxs)(n.p,{children:["We anticipated that infra needs would evolve. To future-proof our system, the data format was designed to be ",(0,s.jsx)(n.strong,{children:"decoupled from DB-specific layouts"}),", enabling portability to systems like ",(0,s.jsx)(n.strong,{children:"ScyllaDB"}),", ",(0,s.jsx)(n.strong,{children:"DynamoDB"}),", ",(0,s.jsx)(n.strong,{children:"HBase"}),", or ",(0,s.jsx)(n.strong,{children:"BigTable"}),"."]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"overcoming-technical-constraints",children:"Overcoming Technical Constraints"}),"\n",(0,s.jsx)(n.p,{children:'At the time, we were using Cassandra, which not only imposed a soft limit of 75 columns per row, but also exhibited significant performance degradation as the number of columns increased further, particularly in memory constrained machines. Wide rows caused high memory usage during reads, unpredictable latencies due to heavy deserialization overhead, and inefficiencies during compactions and repairs. This ruled out the naive "one column per feature" approach. We needed a format that was compact, minimized the number of columns, and remained efficient and portable across different storage systems.'}),"\n",(0,s.jsx)(n.h2,{id:"the-solution-schema-separation",children:"The Solution: Schema Separation"}),"\n",(0,s.jsx)(n.p,{children:"We introduced the concept of Feature Groups\u2014logical groupings of features that must remain consistent with one another.\nTo represent these groups efficiently, we adopted a layered storage approach:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Feature Labels (Keys)"})," were stored in ZooKeeper, serving as the schema."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Feature Values"})," were stored as a comma-separated string array in Cassandra or Redis."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Expiry Timestamp and Schema Version"})," were appended using a semi-colon delimiter at the end of the string."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Example:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"feature_1_value,feature_2_value,feature_3_value;expiry_ts\n"})}),"\n",(0,s.jsx)(n.p,{children:"This format allowed:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Consistent writes and reads at the group level"}),"\n",(0,s.jsx)(n.li,{children:"Easy parsing of feature values using the schema lookup from ZooKeeper"}),"\n",(0,s.jsx)(n.li,{children:"Efficient storage with minimal DB column usage"}),"\n",(0,s.jsx)(n.li,{children:"Support for per-group TTLs and schema evolution"}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"tracking-changes-in-feature-groups",children:"Tracking Changes in Feature Groups"}),"\n",(0,s.jsx)(n.p,{children:"Feature groups don\u2019t stay static. As models evolve, features get added, renamed, or removed. But schema changes often go live before the data is ready\u2014and stopping ingestion just to wait for everything to align isn't feasible."}),"\n",(0,s.jsx)(n.h3,{id:"common-real-world-scenarios",children:"Common Real-World Scenarios:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"A new feature is added to the schema, but ingestion jobs still use the older schema version."}),"\n",(0,s.jsx)(n.li,{children:"Ongoing writes don\u2019t include the newly added feature, and stopping ingestion would break freshness for existing features."}),"\n",(0,s.jsx)(n.li,{children:"During serving, models request a mix of old and new features, depending on rollout stages."}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"the-solution-schema-versioning",children:"The Solution: Schema Versioning"}),"\n",(0,s.jsx)(n.p,{children:"We solved this with versioned feature group schemas, which unlocked several capabilities:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.h3,{id:"backward-compatibility",children:"Backward Compatibility"}),"\n","Older ingestion jobs can continue writing using older schema versions. During reads, the system uses the schema version embedded in the value to interpret the data correctly."]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.h3,{id:"partial-availability-handling",children:"Partial Availability Handling"}),"\n","During inference, if some features in the request aren\u2019t available (due to rollout delays or missing data), the system serves default values, ensuring the inference call doesn\u2019t fail."]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.h3,{id:"safe-writes-without-pipeline-pauses",children:"Safe Writes Without Pipeline Pauses"}),"\n","With schema versioning, we no longer had to stop ingestion pipelines for schema updates. Writes using previous versions can continue safely, and downstream consumers evolve independently.\nThis design gave us the flexibility to move fast without breaking things\u2014preserving data quality, enabling experimentation, and ensuring reliability at scale."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Alt Text",src:i(3190).A+"",width:"1600",height:"599"})}),"\n",(0,s.jsx)(n.h2,{id:"interaction-store---0th-version",children:"Interaction Store - 0th Version"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Alt Text",src:i(1012).A+"",width:"1600",height:"518"})}),"\n",(0,s.jsxs)(n.p,{children:["To power real-time Candidate Generators (CGs), we needed fast access to user behavior signals\u2014like what a user recently clicked, ordered, or added to their cart. These interactions form the basis for many real-time recommendations, such as ",(0,s.jsx)(n.strong,{children:"Similar Products"}),", ",(0,s.jsx)(n.strong,{children:"People Also Viewed"}),", or ",(0,s.jsx)(n.strong,{children:"Recently Ordered Again"}),".\nFor the ",(0,s.jsx)(n.strong,{children:"0th version"})," of the Interaction Store, we focused on a design that was ",(0,s.jsx)(n.strong,{children:"simple, fast, and reliable"})," \u2014 optimized for high-throughput ingestion and low-latency lookups."]}),"\n",(0,s.jsx)(n.h2,{id:"event-ingestion",children:"Event Ingestion"}),"\n",(0,s.jsx)(n.p,{children:"We instrumented our backend services to emit key user interaction events to Kafka in real time. These included:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Click"}),"\n",(0,s.jsx)(n.li,{children:"Order"}),"\n",(0,s.jsx)(n.li,{children:"Add to Cart"}),"\n",(0,s.jsx)(n.li,{children:"Wishlist"}),"\n",(0,s.jsx)(n.li,{children:"Share"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Each event carried essential metadata:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"userId \u2014 uniquely identifies the user"}),"\n",(0,s.jsx)(n.li,{children:"productId \u2014 the item being interacted with"}),"\n",(0,s.jsx)(n.li,{children:"timestamp \u2014 the moment the interaction occurred"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"This decoupled the interaction logging from storage, allowing ingestion and consumption to scale independently."}),"\n",(0,s.jsx)(n.h2,{id:"storage-design",children:"Storage Design"}),"\n",(0,s.jsx)(n.p,{children:"To store these events, we built Kafka consumers that processed the incoming streams and wrote the data into Redis, using sorted sets (ZSETs) as the primary data structure."}),"\n",(0,s.jsx)(n.h3,{id:"why-redis",children:"Why Redis?"}),"\n",(0,s.jsx)(n.p,{children:"Redis gave us:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Low-latency"})," reads and writes"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Time-ordered data"})," using ZSETs (via score = timestamp)"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Native TTL support"}),", if needed in later versions"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"In-memory performance"})," \u2014ideal for real-time CGs"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"storage-structure",children:"Storage Structure"}),"\n",(0,s.jsx)(n.p,{children:"Each user\u2019s interactions were stored using a composite key format, uniquely identifying the user and interaction type. This structure allowed efficient organization and quick retrieval of recent activity for recommendation generation:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"userId_eventType \u2192 ZSET[...(pid, ts)...]\n"})}),"\n",(0,s.jsx)(n.p,{children:"Within each ZSET:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["The ",(0,s.jsx)(n.strong,{children:"timestamp"})," served as the score, maintaining temporal order"]}),"\n",(0,s.jsxs)(n.li,{children:["The ",(0,s.jsx)(n.strong,{children:"productId"})," (optionally with metadata) was the ",(0,s.jsx)(n.strong,{children:"value"})]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"This allowed us to efficiently retrieve the interactions with HTTP-based API server with two query modes:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Fetch the ",(0,s.jsx)(n.strong,{children:"last k interactions"})," of a specific type for a given user with ",(0,s.jsx)(n.code,{children:"ZREVRANGE(userId_eventType, count)"})]}),"\n",(0,s.jsxs)(n.li,{children:["Retrieve ",(0,s.jsx)(n.strong,{children:"all interactions within a time range"})," (e.g., last 24 hours) with ",(0,s.jsx)(n.code,{children:"ZREVRANGEBYSCORE(userId_eventType, timeRange)"})]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"built-in-guardrails",children:"Built-in Guardrails"}),"\n",(0,s.jsx)(n.p,{children:"Since Redis was the sole store, we implemented High Availability (HA) to prevent data loss. To optimize memory usage, we also enforced size limits per event type\u2014only storing the last k interactions per user, with older entries getting truncated."}),"\n",(0,s.jsx)(n.h2,{id:"conclusion-laying-the-foundation-for-real-time-ml",children:"Conclusion: Laying the Foundation for Real-Time ML"}),"\n",(0,s.jsxs)(n.p,{children:["In this first phase, we tackled the ",(0,s.jsx)(n.strong,{children:"fundamentals"}),"\u2014shifting from batch-based recommendations to a ",(0,s.jsx)(n.strong,{children:"real-time Recommendation"})," using ML platform that could keep up with Meesho\u2019s growth."]}),"\n",(0,s.jsxs)(n.p,{children:["With the ",(0,s.jsx)(n.strong,{children:"IOP Framework"}),", ",(0,s.jsx)(n.strong,{children:"Online Feature Store"}),", and ",(0,s.jsx)(n.strong,{children:"Interaction Store"}),", we built the core infrastructure to support real-time personalization at scale. These wins have already unlocked:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"\u2705 Faster, more dynamic recommendations for millions of users."}),"\n",(0,s.jsx)(n.li,{children:"\u2705 Better infrastructure efficiency, reducing wasted compute power."}),"\n",(0,s.jsx)(n.li,{children:"\u2705 A flexible, modular system that allows for further experimentation."}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["But this is just the beginning. While we've solved key challenges, ",(0,s.jsx)(n.strong,{children:"certain roadblocks remain"})," \u2014from optimizing ",(0,s.jsx)(n.strong,{children:"cost-performance trade-offs"})," to ",(0,s.jsx)(n.strong,{children:"seamlessly evolving schemas"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["This foundational work laid the path for a reliable and scalable ",(0,s.jsx)(n.strong,{children:"real-time feature serving layer"}),"."]})]})}function c(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(h,{...e})}):h(e)}},9930:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/bharatmlstack-72e1796337bfa224dee2a0f59ec4e2da.png"}}]); \ No newline at end of file diff --git a/docs/assets/js/0a89f5c9.190be82b.js b/docs/assets/js/0a89f5c9.190be82b.js new file mode 100644 index 00000000..cf662ca4 --- /dev/null +++ b/docs/assets/js/0a89f5c9.190be82b.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[7508],{5641:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>c,contentTitle:()=>o,default:()=>h,frontMatter:()=>l,metadata:()=>r,toc:()=>d});const r=JSON.parse('{"id":"inferflow/v1.0.0/functionalities","title":"Key Functionalities","description":"Overview","source":"@site/docs/inferflow/v1.0.0/functionalities.md","sourceDirName":"inferflow/v1.0.0","slug":"/inferflow/v1.0.0/functionalities","permalink":"/BharatMLStack/inferflow/v1.0.0/functionalities","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/inferflow/v1.0.0/functionalities.md","tags":[],"version":"current","sidebarPosition":2,"frontMatter":{"title":"Key Functionalities","sidebar_position":2},"sidebar":"tutorialSidebar","previous":{"title":"Architecture","permalink":"/BharatMLStack/inferflow/v1.0.0/architecture"},"next":{"title":"Configuration Guide","permalink":"/BharatMLStack/inferflow/v1.0.0/configuration"}}');var s=i(4848),t=i(8453);const l={title:"Key Functionalities",sidebar_position:2},o="Inferflow - Key Functionalities",c={},d=[{value:"Overview",id:"overview",level:2},{value:"Core Capabilities",id:"core-capabilities",level:2},{value:"Graph-Driven Feature Retrieval",id:"graph-driven-feature-retrieval",level:3},{value:"DAG Topology Executor",id:"dag-topology-executor",level:3},{value:"Multi-Pattern Inference APIs",id:"multi-pattern-inference-apis",level:2},{value:"PointWise Inference",id:"pointwise-inference",level:3},{value:"PairWise Inference",id:"pairwise-inference",level:3},{value:"SlateWise Inference",id:"slatewise-inference",level:3},{value:"Entity & Legacy API",id:"entity--legacy-api",level:2},{value:"RetrieveModelScore",id:"retrievemodelscore",level:3},{value:"Component Types",id:"component-types",level:2},{value:"FeatureInitComponent",id:"featureinitcomponent",level:3},{value:"FeatureComponent",id:"featurecomponent",level:3},{value:"PredatorComponent",id:"predatorcomponent",level:3},{value:"NumerixComponent",id:"numerixcomponent",level:3},{value:"Feature Retrieval Pipeline",id:"feature-retrieval-pipeline",level:2},{value:"Key Resolution",id:"key-resolution",level:3},{value:"Batched Retrieval",id:"batched-retrieval",level:3},{value:"In-Memory Caching",id:"in-memory-caching",level:3},{value:"Data Types",id:"data-types",level:2},{value:"Inference Logging",id:"inference-logging",level:2},{value:"Serialization Formats",id:"serialization-formats",level:3},{value:"Sampling Controls",id:"sampling-controls",level:3},{value:"Log Content",id:"log-content",level:3},{value:"Configuration Hot-Reload",id:"configuration-hot-reload",level:2},{value:"Performance Characteristics",id:"performance-characteristics",level:2},{value:"Concurrency Model",id:"concurrency-model",level:3},{value:"Memory Efficiency",id:"memory-efficiency",level:3},{value:"Serialization",id:"serialization",level:3},{value:"Contributing",id:"contributing",level:2},{value:"Community & Support",id:"community--support",level:2},{value:"License",id:"license",level:2}];function a(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,t.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.header,{children:(0,s.jsx)(n.h1,{id:"inferflow---key-functionalities",children:"Inferflow - Key Functionalities"})}),"\n",(0,s.jsx)(n.h2,{id:"overview",children:"Overview"}),"\n",(0,s.jsxs)(n.p,{children:["Inferflow is a high-performance, config-driven ML inference orchestration engine built in ",(0,s.jsx)(n.strong,{children:"Go"}),". It provides ",(0,s.jsx)(n.strong,{children:"no-code feature retrieval"}),", ",(0,s.jsx)(n.strong,{children:"DAG-based execution"}),", and ",(0,s.jsx)(n.strong,{children:"multi-pattern model inference"})," \u2014 enabling ML teams to onboard new models through configuration changes alone."]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"core-capabilities",children:"Core Capabilities"}),"\n",(0,s.jsx)(n.h3,{id:"graph-driven-feature-retrieval",children:"Graph-Driven Feature Retrieval"}),"\n",(0,s.jsx)(n.p,{children:"Inferflow's defining feature is its ability to resolve entity relationships and retrieve features through configurable DAG topologies \u2014 no custom code required."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"How it works:"})}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["A ",(0,s.jsx)(n.code,{children:"model_config_id"})," maps to a pre-defined DAG of components"]}),"\n",(0,s.jsxs)(n.li,{children:["Context entity IDs (e.g., ",(0,s.jsx)(n.code,{children:"userId"}),", ",(0,s.jsx)(n.code,{children:"productIds"}),") are provided at request time"]}),"\n",(0,s.jsxs)(n.li,{children:["The DAG resolves intermediate entity relationships (e.g., extracting ",(0,s.jsx)(n.code,{children:"category"})," from ",(0,s.jsx)(n.code,{children:"product"})," to fetch ",(0,s.jsx)(n.code,{children:"user x category"})," features)"]}),"\n",(0,s.jsx)(n.li,{children:"Features are fetched in parallel from the Online Feature Store"}),"\n",(0,s.jsx)(n.li,{children:"A 2D feature matrix is assembled and passed to model scoring"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"Impact:"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"New models require only a config update \u2014 no code changes"}),"\n",(0,s.jsx)(n.li,{children:"Feature consistency is guaranteed across experiments"}),"\n",(0,s.jsx)(n.li,{children:"Iteration cycles drop from days to minutes"}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"dag-topology-executor",children:"DAG Topology Executor"}),"\n",(0,s.jsxs)(n.p,{children:["The execution engine uses ",(0,s.jsx)(n.strong,{children:"Kahn's algorithm"})," for topological ordering with ",(0,s.jsx)(n.strong,{children:"concurrent goroutine execution"})," at each level:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:'component_dependency: {\n "feature_initializer": ["fs_user", "fs_product"],\n "fs_user": ["ranker"],\n "fs_product": ["ranker"],\n "ranker": ["reranker"],\n "reranker": []\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:"This config defines:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"feature_initializer"})," runs first (zero in-degree)"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"fs_user"})," and ",(0,s.jsx)(n.code,{children:"fs_product"})," run ",(0,s.jsx)(n.strong,{children:"in parallel"})," after init"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"ranker"})," runs after both feature components complete"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"reranker"})," runs after the ranker"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"Key properties:"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Cycle detection via in-degree analysis"}),"\n",(0,s.jsx)(n.li,{children:"DAG topologies cached using Murmur3 hashing (Ristretto cache)"}),"\n",(0,s.jsxs)(n.li,{children:["Components are registered and resolved via a ",(0,s.jsx)(n.code,{children:"ComponentProvider"})]}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"multi-pattern-inference-apis",children:"Multi-Pattern Inference APIs"}),"\n",(0,s.jsxs)(n.p,{children:["Inferflow supports three inference patterns via the ",(0,s.jsx)(n.strong,{children:"Predict API"}),", each designed for different ML use cases:"]}),"\n",(0,s.jsx)(n.h3,{id:"pointwise-inference",children:"PointWise Inference"}),"\n",(0,s.jsx)(n.p,{children:"Score each target independently against context features."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-protobuf",children:"rpc InferPointWise(PredictRequest) returns (PredictResponse);\n"})}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Use cases:"})," Click-through rate prediction, fraud scoring, relevance ranking"]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Input:"})," Context features + list of targets (e.g., products)\n",(0,s.jsx)(n.strong,{children:"Output:"})," Per-target scores"]}),"\n",(0,s.jsx)(n.h3,{id:"pairwise-inference",children:"PairWise Inference"}),"\n",(0,s.jsx)(n.p,{children:"Score pairs of targets relative to each other."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-protobuf",children:"rpc InferPairWise(PredictRequest) returns (PredictResponse);\n"})}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Use cases:"})," Preference learning, comparison-based ranking"]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Input:"})," Context features + targets + pair indices (first/second)\n",(0,s.jsx)(n.strong,{children:"Output:"})," Per-pair scores + optional per-target scores"]}),"\n",(0,s.jsx)(n.h3,{id:"slatewise-inference",children:"SlateWise Inference"}),"\n",(0,s.jsx)(n.p,{children:"Score groups (slates) of targets together, capturing inter-item effects."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-protobuf",children:"rpc InferSlateWise(PredictRequest) returns (PredictResponse);\n"})}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Use cases:"})," Whole-page optimization, slate-level reranking, diversity-aware scoring"]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Input:"})," Context features + targets + slate definitions (target indices per slate)\n",(0,s.jsx)(n.strong,{children:"Output:"})," Per-slate scores + optional per-target scores"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"entity--legacy-api",children:"Entity & Legacy API"}),"\n",(0,s.jsx)(n.h3,{id:"retrievemodelscore",children:"RetrieveModelScore"}),"\n",(0,s.jsx)(n.p,{children:"The original Inferflow API for entity-based feature retrieval and scoring:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-protobuf",children:"service Inferflow {\n rpc RetrieveModelScore(InferflowRequestProto) returns (InferflowResponseProto);\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"Request structure:"})}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Field"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"entities"})}),(0,s.jsx)(n.td,{children:"List of entity types with their IDs and optional inline features"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"model_config_id"})}),(0,s.jsx)(n.td,{children:"Identifies the model configuration (DAG, components, response format)"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"tracking_id"})}),(0,s.jsx)(n.td,{children:"Request-level tracing identifier"})]})]})]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"Entity structure:"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"entity"}),": Entity type label (e.g., ",(0,s.jsx)(n.code,{children:'"user"'}),", ",(0,s.jsx)(n.code,{children:'"product"'}),")"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"ids"}),": List of entity IDs"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"features"}),": Optional inline features (name + per-ID values)"]}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"component-types",children:"Component Types"}),"\n",(0,s.jsx)(n.h3,{id:"featureinitcomponent",children:"FeatureInitComponent"}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Role:"})," Root DAG node \u2014 initializes the shared ",(0,s.jsx)(n.code,{children:"ComponentMatrix"}),"."]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Sets up rows from entity IDs"}),"\n",(0,s.jsx)(n.li,{children:"Populates schema columns (string + byte) for all downstream components"}),"\n",(0,s.jsxs)(n.li,{children:["For slate APIs: initializes ",(0,s.jsx)(n.code,{children:"SlateData"})," with ",(0,s.jsx)(n.code,{children:"slate_target_indices"})]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"featurecomponent",children:"FeatureComponent"}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Role:"})," Fetches features from the Online Feature Store (OnFS) for a specific entity type."]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Reads ",(0,s.jsx)(n.code,{children:"FSKeys"})," from config to extract lookup keys from the matrix"]}),"\n",(0,s.jsx)(n.li,{children:"Batches unique entities and calls OnFS via gRPC"}),"\n",(0,s.jsxs)(n.li,{children:["Optional ",(0,s.jsx)(n.strong,{children:"in-memory caching"})," keyed by ",(0,s.jsx)(n.code,{children:"model_id:version:component:entity"})]}),"\n",(0,s.jsx)(n.li,{children:"Writes binary feature values into matrix byte columns"}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Column naming convention:"})," ",(0,s.jsx)(n.code,{children:"entity_label:feature_group:feature_name"})]}),"\n",(0,s.jsx)(n.h3,{id:"predatorcomponent",children:"PredatorComponent"}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Role:"})," Calls model serving endpoints for inference."]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Builds feature payloads from matrix columns with type conversion"}),"\n",(0,s.jsxs)(n.li,{children:["Supports ",(0,s.jsx)(n.strong,{children:"percentage-based traffic routing"})," across multiple model endpoints"]}),"\n",(0,s.jsxs)(n.li,{children:["Handles ",(0,s.jsx)(n.strong,{children:"slate-level inference"}),": per-slate matrix \u2192 separate inference \u2192 scores to ",(0,s.jsx)(n.code,{children:"SlateData"})]}),"\n",(0,s.jsxs)(n.li,{children:["Configurable ",(0,s.jsx)(n.strong,{children:"calibration"})," and ",(0,s.jsx)(n.strong,{children:"batch sizing"})]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"numerixcomponent",children:"NumerixComponent"}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Role:"})," Calls the Numerix compute engine for operations like reranking."]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Uses ",(0,s.jsx)(n.code,{children:"ScoreMapping"})," config to map matrix columns to compute inputs"]}),"\n",(0,s.jsx)(n.li,{children:"Writes a single score column back to the matrix"}),"\n",(0,s.jsx)(n.li,{children:"Supports slate mode for per-slate compute operations"}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"feature-retrieval-pipeline",children:"Feature Retrieval Pipeline"}),"\n",(0,s.jsx)(n.h3,{id:"key-resolution",children:"Key Resolution"}),"\n",(0,s.jsxs)(n.p,{children:["Feature components use ",(0,s.jsx)(n.code,{children:"FSKeys"})," configuration to dynamically resolve entity keys:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n "FSKeys": {\n "schema": ["user_id"],\n "col": "user:profile:user_id"\n }\n}\n'})}),"\n",(0,s.jsxs)(n.p,{children:["The component reads key values from the existing matrix columns, enabling ",(0,s.jsx)(n.strong,{children:"chained entity resolution"})," \u2014 e.g., fetch product entity first, extract category, then fetch user x category features."]}),"\n",(0,s.jsx)(n.h3,{id:"batched-retrieval",children:"Batched Retrieval"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Features are fetched via ",(0,s.jsx)(n.code,{children:"FeatureService.RetrieveFeatures"})," gRPC call"]}),"\n",(0,s.jsx)(n.li,{children:"Requests are batched by unique entity keys"}),"\n",(0,s.jsx)(n.li,{children:"Configurable batch size and deadline per component"}),"\n",(0,s.jsxs)(n.li,{children:["Auth via ",(0,s.jsx)(n.code,{children:"CALLER_ID"})," and ",(0,s.jsx)(n.code,{children:"CALLER_TOKEN"})," metadata"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"in-memory-caching",children:"In-Memory Caching"}),"\n",(0,s.jsx)(n.p,{children:"Optional per-component caching reduces OnFS load:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Cache key: ",(0,s.jsx)(n.code,{children:"model_id:cache_version:component_name:entity_key"})]}),"\n",(0,s.jsx)(n.li,{children:"Configurable TTL per component"}),"\n",(0,s.jsx)(n.li,{children:"Zero-GC-overhead cache implementation available"}),"\n",(0,s.jsx)(n.li,{children:"Cache hit/miss metrics tracked via StatsD"}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"data-types",children:"Data Types"}),"\n",(0,s.jsx)(n.p,{children:"Inferflow supports comprehensive ML data types for feature encoding and model input/output:"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Data Type"}),(0,s.jsx)(n.th,{children:"Variants"}),(0,s.jsx)(n.th,{children:"Usage"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"Integers"})}),(0,s.jsx)(n.td,{children:"int8, int16, int32, int64"}),(0,s.jsx)(n.td,{children:"Categorical encodings, counts, IDs"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"Floats"})}),(0,s.jsx)(n.td,{children:"float8 (e4m3, e5m2), float16, float32, float64"}),(0,s.jsx)(n.td,{children:"Continuous features, embeddings, scores"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"Strings"})}),(0,s.jsx)(n.td,{children:"Variable length"}),(0,s.jsx)(n.td,{children:"Categories, metadata"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"Booleans"})}),(0,s.jsx)(n.td,{children:"Bit-packed"}),(0,s.jsx)(n.td,{children:"Binary indicators"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"Vectors"})}),(0,s.jsx)(n.td,{children:"All scalar types"}),(0,s.jsx)(n.td,{children:"Embeddings, feature arrays"})]})]})]}),"\n",(0,s.jsxs)(n.p,{children:["Type conversion is handled by the ",(0,s.jsx)(n.code,{children:"datatypeconverter"})," package with optimized float8 implementations."]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"inference-logging",children:"Inference Logging"}),"\n",(0,s.jsx)(n.p,{children:"Inferflow supports async inference logging to Kafka for model monitoring and debugging:"}),"\n",(0,s.jsx)(n.h3,{id:"serialization-formats",children:"Serialization Formats"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Format"}),(0,s.jsx)(n.th,{children:"Use Case"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"Proto"})}),(0,s.jsx)(n.td,{children:"Default, compact"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"Arrow"})}),(0,s.jsx)(n.td,{children:"Columnar analytics"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"Parquet"})}),(0,s.jsx)(n.td,{children:"Long-term storage, query-friendly"})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"sampling-controls",children:"Sampling Controls"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Config"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"LoggingPerc"})}),(0,s.jsx)(n.td,{children:"Percentage of requests to log (0-100)"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"LogBatchSize"})}),(0,s.jsx)(n.td,{children:"Batch size for log message grouping"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"LogFeatures"})}),(0,s.jsx)(n.td,{children:"Specific features to include in logs"})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"log-content",children:"Log Content"}),"\n",(0,s.jsxs)(n.p,{children:["Each ",(0,s.jsx)(n.code,{children:"InferflowLog"})," message includes:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"user_id"}),", ",(0,s.jsx)(n.code,{children:"tracking_id"}),", ",(0,s.jsx)(n.code,{children:"model_config_id"})]}),"\n",(0,s.jsx)(n.li,{children:"Entity IDs and feature values"}),"\n",(0,s.jsx)(n.li,{children:"Model scores and metadata"}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"configuration-hot-reload",children:"Configuration Hot-Reload"}),"\n",(0,s.jsxs)(n.p,{children:["Model configurations are stored in ",(0,s.jsx)(n.strong,{children:"etcd"})," and support ",(0,s.jsx)(n.strong,{children:"live updates without redeployment"}),":"]}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsx)(n.li,{children:"Inferflow registers watchers on etcd config paths"}),"\n",(0,s.jsxs)(n.li,{children:["On config change, watchers trigger ",(0,s.jsx)(n.code,{children:"ReloadModelConfigMapAndRegisterComponents"})]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"ConfigMap"})," is updated in memory"]}),"\n",(0,s.jsx)(n.li,{children:"Feature schemas are re-initialized"}),"\n",(0,s.jsx)(n.li,{children:"DAG components are re-registered"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"This enables:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Adding new models in production without restarts"}),"\n",(0,s.jsx)(n.li,{children:"A/B testing with different model configurations"}),"\n",(0,s.jsx)(n.li,{children:"Instant rollback by reverting etcd config"}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"performance-characteristics",children:"Performance Characteristics"}),"\n",(0,s.jsx)(n.h3,{id:"concurrency-model",children:"Concurrency Model"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"DAG components at the same level execute concurrently in goroutines"}),"\n",(0,s.jsx)(n.li,{children:"Feature retrieval is parallelized across entity types"}),"\n",(0,s.jsx)(n.li,{children:"External gRPC calls use connection pooling"}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"memory-efficiency",children:"Memory Efficiency"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Built in Go \u2014 significantly lower memory footprint than Java equivalents (~80% reduction)"}),"\n",(0,s.jsxs)(n.li,{children:["Object pooling for ",(0,s.jsx)(n.code,{children:"ComponentMatrix"})," and serialization buffers"]}),"\n",(0,s.jsx)(n.li,{children:"In-memory cache with zero-GC-overhead option (freecache)"}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"serialization",children:"Serialization"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"gRPC with Proto3 for all external communication"}),"\n",(0,s.jsxs)(n.li,{children:["Binary feature encoding in the ",(0,s.jsx)(n.code,{children:"ComponentMatrix"})," for minimal overhead"]}),"\n",(0,s.jsx)(n.li,{children:"Configurable compression for Kafka logging"}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"contributing",children:"Contributing"}),"\n",(0,s.jsxs)(n.p,{children:["We welcome contributions from the community! Please see our ",(0,s.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTING.md",children:"Contributing Guide"})," for details on how to get started."]}),"\n",(0,s.jsx)(n.h2,{id:"community--support",children:"Community & Support"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Discord"}),": Join our ",(0,s.jsx)(n.a,{href:"https://discord.gg/XkT7XsV2AU",children:"community chat"})]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Issues"}),": Report bugs and request features on ",(0,s.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/issues",children:"GitHub Issues"})]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Email"}),": Contact us at ",(0,s.jsx)(n.a,{href:"mailto:ml-oss@meesho.com",children:"ml-oss@meesho.com"})]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"license",children:"License"}),"\n",(0,s.jsxs)(n.p,{children:["BharatMLStack is open-source software licensed under the ",(0,s.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md",children:"BharatMLStack Business Source License 1.1"}),"."]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)("div",{align:"center",children:(0,s.jsx)("strong",{children:"Built with \u2764\ufe0f for the ML community from Meesho"})}),"\n",(0,s.jsx)("div",{align:"center",children:(0,s.jsx)("strong",{children:"If you find this useful, \u2b50\ufe0f the repo \u2014 your support means the world to us!"})})]})}function h(e={}){const{wrapper:n}={...(0,t.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(a,{...e})}):a(e)}},8453:(e,n,i)=>{i.d(n,{R:()=>l,x:()=>o});var r=i(6540);const s={},t=r.createContext(s);function l(e){const n=r.useContext(t);return r.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:l(e.components),r.createElement(t.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/0dae2a8b.a19d1bf1.js b/docs/assets/js/0dae2a8b.a19d1bf1.js new file mode 100644 index 00000000..fc977032 --- /dev/null +++ b/docs/assets/js/0dae2a8b.a19d1bf1.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[2092],{1335:a=>{a.exports=JSON.parse('{"tag":{"label":"memory","permalink":"/BharatMLStack/blog/tags/memory","allTagsPath":"/BharatMLStack/blog/tags","count":1,"unlisted":false},"listMetadata":{"permalink":"/BharatMLStack/blog/tags/memory","page":1,"postsPerPage":10,"totalPages":1,"totalCount":1,"blogDescription":"Blog","blogTitle":"Blog"}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/0e384e19.cb894d32.js b/docs/assets/js/0e384e19.cb894d32.js new file mode 100644 index 00000000..ae6953f5 --- /dev/null +++ b/docs/assets/js/0e384e19.cb894d32.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[3976],{2053:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>h,frontMatter:()=>i,metadata:()=>r,toc:()=>l});const r=JSON.parse('{"id":"intro","title":"BharatMLStack Documentation","description":"Welcome to the BharatMLStack documentation. BharatMLStack is an open-source, end-to-end ML infrastructure stack built for scale, speed, and simplicity. Explore the components below to get started.","source":"@site/docs/intro.md","sourceDirName":".","slug":"/intro","permalink":"/BharatMLStack/intro","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/intro.md","tags":[],"version":"current","sidebarPosition":0,"frontMatter":{"sidebar_position":0,"title":"BharatMLStack Documentation","slug":"intro"},"sidebar":"tutorialSidebar","next":{"title":"Online Feature Store","permalink":"/BharatMLStack/category/online-feature-store"}}');var o=n(4848),a=n(8453);const i={sidebar_position:0,title:"BharatMLStack Documentation",slug:"intro"},s="BharatMLStack Documentation",c={},l=[{value:"Quick Start",id:"quick-start",level:2},{value:"Online Feature Store",id:"online-feature-store",level:2},{value:"Inferflow",id:"inferflow",level:2},{value:"Trufflebox UI",id:"trufflebox-ui",level:2},{value:"SDKs",id:"sdks",level:2},{value:"Numerix",id:"numerix",level:2}];function d(e){const t={a:"a",h1:"h1",h2:"h2",header:"header",hr:"hr",p:"p",strong:"strong",...(0,a.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(t.header,{children:(0,o.jsx)(t.h1,{id:"bharatmlstack-documentation",children:"BharatMLStack Documentation"})}),"\n",(0,o.jsx)(t.p,{children:"Welcome to the BharatMLStack documentation. BharatMLStack is an open-source, end-to-end ML infrastructure stack built for scale, speed, and simplicity. Explore the components below to get started."}),"\n",(0,o.jsx)(t.hr,{}),"\n",(0,o.jsx)(t.h2,{id:"quick-start",children:"Quick Start"}),"\n",(0,o.jsx)(t.p,{children:"Get up and running with BharatMLStack in minutes. Step-by-step instructions, sample data, and Docker Compose setup for local development and testing."}),"\n",(0,o.jsx)(t.p,{children:(0,o.jsx)(t.strong,{children:(0,o.jsx)(t.a,{href:"/category/quick-start",children:"Go to Quick Start \u2192"})})}),"\n",(0,o.jsx)(t.hr,{}),"\n",(0,o.jsx)(t.h2,{id:"online-feature-store",children:"Online Feature Store"}),"\n",(0,o.jsx)(t.p,{children:"Sub-10ms, high-throughput access to machine learning features for real-time inference. Supports batch and streaming ingestion, schema validation, and compact versioned feature groups."}),"\n",(0,o.jsx)(t.p,{children:(0,o.jsx)(t.strong,{children:(0,o.jsx)(t.a,{href:"/category/online-feature-store",children:"Go to Online Feature Store \u2192"})})}),"\n",(0,o.jsx)(t.hr,{}),"\n",(0,o.jsx)(t.h2,{id:"inferflow",children:"Inferflow"}),"\n",(0,o.jsx)(t.p,{children:"Graph-driven feature retrieval and model inference orchestration engine. Dynamically resolves entity relationships, retrieves features, and orchestrates model scoring \u2014 all without custom code."}),"\n",(0,o.jsx)(t.p,{children:(0,o.jsx)(t.strong,{children:(0,o.jsx)(t.a,{href:"/category/inferflow",children:"Go to Inferflow \u2192"})})}),"\n",(0,o.jsx)(t.hr,{}),"\n",(0,o.jsx)(t.h2,{id:"trufflebox-ui",children:"Trufflebox UI"}),"\n",(0,o.jsx)(t.p,{children:"Modern, feature-rich UI framework for MLOps management. Supports feature catalog, user management, and admin operations with approval flows."}),"\n",(0,o.jsx)(t.p,{children:(0,o.jsx)(t.strong,{children:(0,o.jsx)(t.a,{href:"/category/trufflebox-ui",children:"Go to Trufflebox UI \u2192"})})}),"\n",(0,o.jsx)(t.hr,{}),"\n",(0,o.jsx)(t.h2,{id:"sdks",children:"SDKs"}),"\n",(0,o.jsx)(t.p,{children:"Client libraries for Go and Python to interact with the Online Feature Store and other platform components. Includes gRPC clients, REST APIs, and Apache Spark integration."}),"\n",(0,o.jsx)(t.p,{children:(0,o.jsx)(t.strong,{children:(0,o.jsx)(t.a,{href:"/category/sdks",children:"Go to SDKs \u2192"})})}),"\n",(0,o.jsx)(t.hr,{}),"\n",(0,o.jsx)(t.h2,{id:"numerix",children:"Numerix"}),"\n",(0,o.jsx)(t.p,{children:"High-performance compute engine for ultra-fast element-wise matrix operations. Built in Rust with SIMD acceleration for sub-5ms p99 latency."}),"\n",(0,o.jsx)(t.p,{children:(0,o.jsx)(t.strong,{children:(0,o.jsx)(t.a,{href:"/category/numerix",children:"Go to Numerix \u2192"})})})]})}function h(e={}){const{wrapper:t}={...(0,a.R)(),...e.components};return t?(0,o.jsx)(t,{...e,children:(0,o.jsx)(d,{...e})}):d(e)}},8453:(e,t,n)=>{n.d(t,{R:()=>i,x:()=>s});var r=n(6540);const o={},a=r.createContext(o);function i(e){const t=r.useContext(a);return r.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function s(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:i(e.components),r.createElement(a.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/0fff8dc8.70193857.js b/docs/assets/js/0fff8dc8.70193857.js new file mode 100644 index 00000000..39ec1e2e --- /dev/null +++ b/docs/assets/js/0fff8dc8.70193857.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[9596],{5958:(e,n,s)=>{s.r(n),s.d(n,{assets:()=>a,contentTitle:()=>c,default:()=>h,frontMatter:()=>t,metadata:()=>r,toc:()=>o});const r=JSON.parse('{"id":"quick-start/v1.0.0/quick-start","title":"Quick Start","description":"Discord","source":"@site/docs/quick-start/v1.0.0/quick-start.md","sourceDirName":"quick-start/v1.0.0","slug":"/quick-start/v1.0.0/quick-start","permalink":"/BharatMLStack/quick-start/v1.0.0/quick-start","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/quick-start/v1.0.0/quick-start.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"Quick Start","sidebar_position":1},"sidebar":"tutorialSidebar","previous":{"title":"v1.0.0","permalink":"/BharatMLStack/quick-start/v1.0.0"},"next":{"title":"Trufflebox UI","permalink":"/BharatMLStack/category/trufflebox-ui"}}');var i=s(4848),l=s(8453);const t={title:"Quick Start",sidebar_position:1},c="BharatML Stack Quick Start Guide",a={},o=[{value:"Prerequisites",id:"prerequisites",level:2},{value:"System Components",id:"system-components",level:2},{value:"Quick Start",id:"quick-start",level:2},{value:"Starting the System",id:"starting-the-system",level:3},{value:"Testing Different Versions",id:"testing-different-versions",level:3},{value:"Stopping the System",id:"stopping-the-system",level:3},{value:"Accessing Services",id:"accessing-services",level:2},{value:"Frontend UI",id:"frontend-ui",level:3},{value:"API Endpoints",id:"api-endpoints",level:3},{value:"Database Access",id:"database-access",level:3},{value:"Feature Store API Examples",id:"feature-store-api-examples",level:2},{value:"gRPC API Commands",id:"grpc-api-commands",level:3},{value:"Sample Request Bodies",id:"sample-request-bodies",level:3},{value:"Key Points",id:"key-points",level:3},{value:"Response Format Differences",id:"response-format-differences",level:3},{value:"Managing Services",id:"managing-services",level:2},{value:"Viewing Logs",id:"viewing-logs",level:3},{value:"Service Management",id:"service-management",level:3},{value:"Troubleshooting",id:"troubleshooting",level:2},{value:"Common Issues",id:"common-issues",level:3},{value:"Service Dependencies",id:"service-dependencies",level:3},{value:"Development",id:"development",level:2},{value:"Contributing",id:"contributing",level:2},{value:"Community & Support",id:"community--support",level:2},{value:"License",id:"license",level:2}];function d(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,l.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.header,{children:(0,i.jsx)(n.h1,{id:"bharatml-stack-quick-start-guide",children:"BharatML Stack Quick Start Guide"})}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.a,{href:"https://discord.gg/XkT7XsV2AU",children:(0,i.jsx)(n.img,{src:"https://img.shields.io/badge/Discord-Join%20Chat-7289da?style=flat&logo=discord&logoColor=white",alt:"Discord"})})}),"\n",(0,i.jsx)(n.p,{children:"A quick way to get the BharatML Stack Online Feature Store platform up and running locally for development and testing."}),"\n",(0,i.jsx)(n.h2,{id:"prerequisites",children:"Prerequisites"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Docker and Docker Compose"}),"\n",(0,i.jsx)(n.li,{children:"Go 1.22 or later"}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"nc"})," (netcat) command for connectivity checks"]}),"\n",(0,i.jsx)(n.li,{children:"Bash shell"}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"grpcurl"})," for testing gRPC API endpoints (install from ",(0,i.jsx)(n.a,{href:"https://github.com/fullstorydev/grpcurl",children:"https://github.com/fullstorydev/grpcurl"}),")"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"system-components",children:"System Components"}),"\n",(0,i.jsx)(n.p,{children:"BharatMLStack's Online Feature Store consists of several interconnected services:"}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Infrastructure Services:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"ScyllaDB"}),": NoSQL database for high-performance feature storage"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"MySQL"}),": Relational database for metadata and configuration"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Redis"}),": In-memory data store for caching"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"etcd"}),": Distributed key-value store for service coordination"]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Application Services:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Horizon"}),": Backend API service (runs on port 8082)"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Trufflebox UI"}),": Frontend web interface (runs on port 3000)"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Online Feature Store gRPC API Server"}),": High-performance gRPC interface (runs on port 8089)"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"etcd Workbench"}),": etcd management interface (runs on port 8081)"]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"All services are orchestrated using Docker Compose with pre-built images from GitHub Container Registry (GHCR)."}),"\n",(0,i.jsx)(n.h2,{id:"quick-start",children:"Quick Start"}),"\n",(0,i.jsx)(n.h3,{id:"starting-the-system",children:"Starting the System"}),"\n",(0,i.jsx)(n.p,{children:"Run the start script to set up your workspace and launch all services:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"./start.sh\n"})}),"\n",(0,i.jsx)(n.h3,{id:"testing-different-versions",children:"Testing Different Versions"}),"\n",(0,i.jsx)(n.p,{children:"You can easily test different versions of the application services by setting environment variables:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"# Test specific versions [Replace with actual versions]\nONFS_VERSION=v1.2.3 HORIZON_VERSION=v2.1.0 TRUFFLEBOX_VERSION=v1.0.5 ./start.sh\n\n# Or set them in your workspace and run docker-compose directly\ncd workspace\nONFS_VERSION=main docker-compose up -d onfs-api-server\n"})}),"\n",(0,i.jsx)(n.p,{children:"Available version formats:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"latest"})," (default) - Latest stable release"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"main"})," - Latest development build"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"v1.2.3"})," - Specific version tag"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"sha-abcd1234"})," - Specific commit SHA"]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"This will:"}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsx)(n.li,{children:"Check for Go installation (1.22+ required)"}),"\n",(0,i.jsx)(n.li,{children:"Create a workspace directory with configuration files"}),"\n",(0,i.jsxs)(n.li,{children:["Pull and start all services using ",(0,i.jsx)(n.code,{children:"docker-compose up -d"})]}),"\n",(0,i.jsx)(n.li,{children:"Wait for services to become healthy"}),"\n",(0,i.jsx)(n.li,{children:"Initialize databases with required schemas"}),"\n",(0,i.jsx)(n.li,{children:"Display access information and helpful commands"}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"Once complete, you can access:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Trufflebox UI"}),": ",(0,i.jsx)(n.a,{href:"http://localhost:3000",children:"http://localhost:3000"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Horizon API"}),": ",(0,i.jsx)(n.a,{href:"http://localhost:8082",children:"http://localhost:8082"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Online Feature Store gRPC API"}),": ",(0,i.jsx)(n.a,{href:"http://localhost:8089",children:"http://localhost:8089"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"etcd Workbench"}),": ",(0,i.jsx)(n.a,{href:"http://localhost:8081",children:"http://localhost:8081"})]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"stopping-the-system",children:"Stopping the System"}),"\n",(0,i.jsx)(n.p,{children:"To stop all services:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"./stop.sh\n"})}),"\n",(0,i.jsx)(n.p,{children:"To stop and completely purge all containers, volumes, and workspace:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"./stop.sh --purge\n"})}),"\n",(0,i.jsx)(n.h2,{id:"accessing-services",children:"Accessing Services"}),"\n",(0,i.jsx)(n.h3,{id:"frontend-ui",children:"Frontend UI"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"URL"}),": ",(0,i.jsx)(n.a,{href:"http://localhost:3000",children:"http://localhost:3000"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Default admin credentials"}),":","\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["Email: ",(0,i.jsx)(n.code,{children:"admin@admin.com"})]}),"\n",(0,i.jsxs)(n.li,{children:["Password: ",(0,i.jsx)(n.code,{children:"admin"})]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"api-endpoints",children:"API Endpoints"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Horizon API"}),": ",(0,i.jsx)(n.a,{href:"http://localhost:8082",children:"http://localhost:8082"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["Health check: ",(0,i.jsx)(n.a,{href:"http://localhost:8082/health",children:"http://localhost:8082/health"})]}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"ONFS gRPC API"}),": ",(0,i.jsx)(n.a,{href:"http://localhost:8089",children:"http://localhost:8089"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["Health check: ",(0,i.jsx)(n.a,{href:"http://localhost:8089/health/self",children:"http://localhost:8089/health/self"})]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"database-access",children:"Database Access"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"MySQL"}),":"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Host: localhost"}),"\n",(0,i.jsx)(n.li,{children:"Port: 3306"}),"\n",(0,i.jsx)(n.li,{children:"Username: root"}),"\n",(0,i.jsx)(n.li,{children:"Password: root"}),"\n",(0,i.jsx)(n.li,{children:"Database: testdb"}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"ScyllaDB"}),":"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Host: localhost"}),"\n",(0,i.jsx)(n.li,{children:"Port: 9042"}),"\n",(0,i.jsx)(n.li,{children:"Keyspace: onfs"}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Redis"}),":"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Host: localhost"}),"\n",(0,i.jsx)(n.li,{children:"Port: 6379"}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"etcd"}),":"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["Endpoint: ",(0,i.jsx)(n.a,{href:"http://localhost:2379",children:"http://localhost:2379"})]}),"\n",(0,i.jsxs)(n.li,{children:["Workbench: ",(0,i.jsx)(n.a,{href:"http://localhost:8081",children:"http://localhost:8081"})]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"feature-store-api-examples",children:"Feature Store API Examples"}),"\n",(0,i.jsx)(n.h3,{id:"grpc-api-commands",children:"gRPC API Commands"}),"\n",(0,i.jsxs)(n.p,{children:["Use the following ",(0,i.jsx)(n.code,{children:"grpcurl"})," commands to interact with the Online Feature Store gRPC API:"]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Persist Features:"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:'grpcurl -plaintext -H "online-feature-store-caller-id: " -H "online-feature-store-auth-token: " -d \'\' localhost:8089 persist.FeatureService/PersistFeatures\n'})}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Retrieve Features (Decoded):"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:'grpcurl -plaintext -H "online-feature-store-caller-id: " -H "online-feature-store-auth-token: " -d \'\' localhost:8089 retrieve.FeatureService/RetrieveDecodedResult\n'})}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Retrieve Features (Binary):"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:'grpcurl -plaintext -H "online-feature-store-caller-id: " -H "online-feature-store-auth-token: " -d \'\' localhost:8089 retrieve.FeatureService/RetrieveFeatures\n'})}),"\n",(0,i.jsx)(n.h3,{id:"sample-request-bodies",children:"Sample Request Bodies"}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Single Feature Group Persist:"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-json",children:'{\n "data": [{\n "key_values": ["10"],\n "feature_values": [{\n "values": {"fp32_values": [123.45]}\n }]\n }],\n "entity_label": "catalog",\n "feature_group_schema": [{\n "label": "int_fg",\n "feature_labels": ["id"]\n }],\n "keys_schema": ["catalog_id"]\n}\n'})}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Single Feature Group Retrieve:"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-json",children:'{\n "entity_label": "catalog",\n "feature_groups": [{\n "label": "int_fg",\n "feature_labels": ["id"]\n }],\n "keys_schema": ["catalog_id"],\n "keys": [{"cols": ["10"]}]\n}\n'})}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Multiple Feature Groups Persist:"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-json",children:'{\n "data": [\n {\n "key_values": ["1"],\n "feature_values": [\n {"values": {"fp32_values": [28.5]}},\n {"values": {"string_values": ["Bharat"]}}\n ]\n },\n {\n "key_values": ["2"],\n "feature_values": [\n {"values": {"fp32_values": [32.0]}},\n {"values": {"string_values": ["India"]}}\n ]\n }\n ],\n "entity_label": "catalog",\n "feature_group_schema": [\n {"label": "int_fg", "feature_labels": ["id"]},\n {"label": "string_fg", "feature_labels": ["name"]}\n ],\n "keys_schema": ["catalog_id"]\n}\n'})}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Multiple Feature Groups Retrieve:"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-json",children:'{\n "entity_label": "catalog",\n "feature_groups": [\n {"label": "int_fg", "feature_labels": ["id"]},\n {"label": "string_fg", "feature_labels": ["name"]}\n ],\n "keys_schema": ["catalog_id"],\n "keys": [\n {"cols": ["1"]},\n {"cols": ["2"]}\n ]\n}\n'})}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Vector Feature Group Persist:"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-json",children:'{\n "data": [{\n "key_values": ["123"],\n "feature_values": [{\n "values": {\n "vector": [{\n "values": {"fp32_values": [1.0, 2.0, 3.0, 4.0]}\n }]\n }\n }]\n }],\n "entity_label": "catalog",\n "feature_group_schema": [{\n "label": "vector_fg",\n "feature_labels": ["embedding"]\n }],\n "keys_schema": ["catalog_id"]\n}\n'})}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Vector Feature Group Retrieve:"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-json",children:'{\n "entity_label": "catalog",\n "feature_groups": [{\n "label": "vector_fg",\n "feature_labels": ["embedding"]\n }],\n "keys_schema": ["catalog_id"],\n "keys": [{"cols": ["123"]}]\n}\n'})}),"\n",(0,i.jsx)(n.h3,{id:"key-points",children:"Key Points"}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Only one type per feature value block:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"feature_values"})," is a list, and each item in the list has only one value type populated"]}),"\n",(0,i.jsxs)(n.li,{children:["For example: one item has only ",(0,i.jsx)(n.code,{children:"fp32_values"}),", another has only ",(0,i.jsx)(n.code,{children:"int64_values"})]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Field Types:"}),"\nThe following value types are supported:"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"fp32_values"}),": ",(0,i.jsx)(n.code,{children:"float32[]"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"fp64_values"}),": ",(0,i.jsx)(n.code,{children:"float64[]"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"int32_values"}),": ",(0,i.jsx)(n.code,{children:"int32[]"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"int64_values"}),": ",(0,i.jsx)(n.code,{children:"string[]"})," (because JSON doesn't support 64-bit ints directly)"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"uint32_values"}),": ",(0,i.jsx)(n.code,{children:"uint32[]"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"uint64_values"}),": ",(0,i.jsx)(n.code,{children:"string[]"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"string_values"}),": ",(0,i.jsx)(n.code,{children:"string[]"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"bool_values"}),": ",(0,i.jsx)(n.code,{children:"bool[]"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"vector"}),": list of objects with nested values (used for embedded features)"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"response-format-differences",children:"Response Format Differences"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Retrieve Features (Binary)"}),": Returns data in binary format for optimal performance and reduced network overhead"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Retrieve Features (Decoded)"}),": Returns data in human-readable string format for easier debugging and development purposes"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"managing-services",children:"Managing Services"}),"\n",(0,i.jsx)(n.h3,{id:"viewing-logs",children:"Viewing Logs"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"# View logs for all services\ncd workspace && docker-compose logs -f\n\n# View logs for specific services\ncd workspace && docker-compose logs -f horizon\ncd workspace && docker-compose logs -f trufflebox-ui\ncd workspace && docker-compose logs -f onfs-api-server\n"})}),"\n",(0,i.jsx)(n.h3,{id:"service-management",children:"Service Management"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"# Restart a specific service\ncd workspace && docker-compose restart horizon\n\n# Stop all services\ncd workspace && docker-compose down\n\n# Start services again\ncd workspace && docker-compose up -d\n\n# Check service status\ncd workspace && docker-compose ps\n"})}),"\n",(0,i.jsx)(n.h2,{id:"troubleshooting",children:"Troubleshooting"}),"\n",(0,i.jsx)(n.h3,{id:"common-issues",children:"Common Issues"}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Port conflicts"}),": Ensure ports 3000, 8081, 8082, 8089, 9042, 3306, 6379, and 2379 are not in use by other applications."]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Docker network issues"}),": If containers can't communicate, try recreating:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"docker network rm onfs-network\ndocker network create onfs-network\n"})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Service health checks failing"}),": Check if all infrastructure services (databases) are running:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"cd workspace && docker-compose ps\n"})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Image pull issues"}),": Ensure you have access to GitHub Container Registry:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"docker login ghcr.io\n"})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.a,{href:"https://github.com/tzfun/etcd-workbench/blob/master/README.md",children:"How to use Etcd Workbench ?"})}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"service-dependencies",children:"Service Dependencies"}),"\n",(0,i.jsx)(n.p,{children:"Services start in the following order:"}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsx)(n.li,{children:"Infrastructure services (ScyllaDB, MySQL, Redis, etcd)"}),"\n",(0,i.jsx)(n.li,{children:"Online Feature Store gRPC API Server"}),"\n",(0,i.jsx)(n.li,{children:"Horizon (depends on databases + ONFS API)"}),"\n",(0,i.jsx)(n.li,{children:"Trufflebox UI (depends on Horizon)"}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"If a service fails to start, check its dependencies are healthy first."}),"\n",(0,i.jsx)(n.h2,{id:"development",children:"Development"}),"\n",(0,i.jsx)(n.p,{children:"The workspace directory contains all runtime configuration:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"workspace/docker-compose.yml"})," - Complete service orchestration"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"workspace/check_db_and_init.sh"})," - Database initialization script"]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"You can modify environment variables in the docker-compose.yml file and restart services."}),"\n",(0,i.jsx)(n.h2,{id:"contributing",children:"Contributing"}),"\n",(0,i.jsxs)(n.p,{children:["We welcome contributions from the community! Please see our ",(0,i.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTING.md",children:"Contributing Guide"})," for details on how to get started."]}),"\n",(0,i.jsx)(n.h2,{id:"community--support",children:"Community & Support"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["\ud83d\udcac ",(0,i.jsx)(n.strong,{children:"Discord"}),": Join our ",(0,i.jsx)(n.a,{href:"https://discord.gg/XkT7XsV2AU",children:"community chat"})]}),"\n",(0,i.jsxs)(n.li,{children:["\ud83d\udc1b ",(0,i.jsx)(n.strong,{children:"Issues"}),": Report bugs and request features on ",(0,i.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/issues",children:"GitHub Issues"})]}),"\n",(0,i.jsxs)(n.li,{children:["\ud83d\udce7 ",(0,i.jsx)(n.strong,{children:"Email"}),": Contact us at ",(0,i.jsx)(n.a,{href:"mailto:ml-oss@meesho.com",children:"ml-oss@meesho.com"})]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"license",children:"License"}),"\n",(0,i.jsxs)(n.p,{children:["BharatMLStack is open-source software licensed under the ",(0,i.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md",children:"BharatMLStack Business Source License 1.1"}),"."]}),"\n",(0,i.jsx)(n.hr,{}),"\n",(0,i.jsx)("div",{align:"center",children:(0,i.jsx)("strong",{children:"Built with \u2764\ufe0f for the ML community from Meesho"})}),"\n",(0,i.jsx)("div",{align:"center",children:(0,i.jsx)("strong",{children:"If you find this useful, \u2b50\ufe0f the repo \u2014 your support means the world to us!"})})]})}function h(e={}){const{wrapper:n}={...(0,l.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(d,{...e})}):d(e)}},8453:(e,n,s)=>{s.d(n,{R:()=>t,x:()=>c});var r=s(6540);const i={},l=r.createContext(i);function t(e){const n=r.useContext(l);return r.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function c(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:t(e.components),r.createElement(l.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/0fff8dc8.7a470540.js b/docs/assets/js/0fff8dc8.7a470540.js deleted file mode 100644 index 87386b52..00000000 --- a/docs/assets/js/0fff8dc8.7a470540.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[9596],{5958:(e,n,s)=>{s.r(n),s.d(n,{assets:()=>a,contentTitle:()=>c,default:()=>h,frontMatter:()=>t,metadata:()=>r,toc:()=>o});const r=JSON.parse('{"id":"quick-start/v1.0.0/quick-start","title":"Quick Start","description":"Discord","source":"@site/docs/quick-start/v1.0.0/quick-start.md","sourceDirName":"quick-start/v1.0.0","slug":"/quick-start/v1.0.0/quick-start","permalink":"/BharatMLStack/quick-start/v1.0.0/quick-start","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/quick-start/v1.0.0/quick-start.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"Quick Start","sidebar_position":1},"sidebar":"tutorialSidebar","previous":{"title":"Quick Start","permalink":"/BharatMLStack/category/quick-start"},"next":{"title":"Trufflebox UI","permalink":"/BharatMLStack/category/trufflebox-ui"}}');var i=s(4848),l=s(8453);const t={title:"Quick Start",sidebar_position:1},c="BharatML Stack Quick Start Guide",a={},o=[{value:"Prerequisites",id:"prerequisites",level:2},{value:"System Components",id:"system-components",level:2},{value:"Quick Start",id:"quick-start",level:2},{value:"Starting the System",id:"starting-the-system",level:3},{value:"Testing Different Versions",id:"testing-different-versions",level:3},{value:"Stopping the System",id:"stopping-the-system",level:3},{value:"Accessing Services",id:"accessing-services",level:2},{value:"Frontend UI",id:"frontend-ui",level:3},{value:"API Endpoints",id:"api-endpoints",level:3},{value:"Database Access",id:"database-access",level:3},{value:"Feature Store API Examples",id:"feature-store-api-examples",level:2},{value:"gRPC API Commands",id:"grpc-api-commands",level:3},{value:"Sample Request Bodies",id:"sample-request-bodies",level:3},{value:"Key Points",id:"key-points",level:3},{value:"Response Format Differences",id:"response-format-differences",level:3},{value:"Managing Services",id:"managing-services",level:2},{value:"Viewing Logs",id:"viewing-logs",level:3},{value:"Service Management",id:"service-management",level:3},{value:"Troubleshooting",id:"troubleshooting",level:2},{value:"Common Issues",id:"common-issues",level:3},{value:"Service Dependencies",id:"service-dependencies",level:3},{value:"Development",id:"development",level:2},{value:"Contributing",id:"contributing",level:2},{value:"Community & Support",id:"community--support",level:2},{value:"License",id:"license",level:2}];function d(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,l.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.header,{children:(0,i.jsx)(n.h1,{id:"bharatml-stack-quick-start-guide",children:"BharatML Stack Quick Start Guide"})}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.a,{href:"https://discord.gg/XkT7XsV2AU",children:(0,i.jsx)(n.img,{src:"https://img.shields.io/badge/Discord-Join%20Chat-7289da?style=flat&logo=discord&logoColor=white",alt:"Discord"})})}),"\n",(0,i.jsx)(n.p,{children:"A quick way to get the BharatML Stack Online Feature Store platform up and running locally for development and testing."}),"\n",(0,i.jsx)(n.h2,{id:"prerequisites",children:"Prerequisites"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Docker and Docker Compose"}),"\n",(0,i.jsx)(n.li,{children:"Go 1.22 or later"}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"nc"})," (netcat) command for connectivity checks"]}),"\n",(0,i.jsx)(n.li,{children:"Bash shell"}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"grpcurl"})," for testing gRPC API endpoints (install from ",(0,i.jsx)(n.a,{href:"https://github.com/fullstorydev/grpcurl",children:"https://github.com/fullstorydev/grpcurl"}),")"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"system-components",children:"System Components"}),"\n",(0,i.jsx)(n.p,{children:"BharatMLStack's Online Feature Store consists of several interconnected services:"}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Infrastructure Services:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"ScyllaDB"}),": NoSQL database for high-performance feature storage"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"MySQL"}),": Relational database for metadata and configuration"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Redis"}),": In-memory data store for caching"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"etcd"}),": Distributed key-value store for service coordination"]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Application Services:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Horizon"}),": Backend API service (runs on port 8082)"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Trufflebox UI"}),": Frontend web interface (runs on port 3000)"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Online Feature Store gRPC API Server"}),": High-performance gRPC interface (runs on port 8089)"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"etcd Workbench"}),": etcd management interface (runs on port 8081)"]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"All services are orchestrated using Docker Compose with pre-built images from GitHub Container Registry (GHCR)."}),"\n",(0,i.jsx)(n.h2,{id:"quick-start",children:"Quick Start"}),"\n",(0,i.jsx)(n.h3,{id:"starting-the-system",children:"Starting the System"}),"\n",(0,i.jsx)(n.p,{children:"Run the start script to set up your workspace and launch all services:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"./start.sh\n"})}),"\n",(0,i.jsx)(n.h3,{id:"testing-different-versions",children:"Testing Different Versions"}),"\n",(0,i.jsx)(n.p,{children:"You can easily test different versions of the application services by setting environment variables:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"# Test specific versions [Replace with actual versions]\nONFS_VERSION=v1.2.3 HORIZON_VERSION=v2.1.0 TRUFFLEBOX_VERSION=v1.0.5 ./start.sh\n\n# Or set them in your workspace and run docker-compose directly\ncd workspace\nONFS_VERSION=main docker-compose up -d onfs-api-server\n"})}),"\n",(0,i.jsx)(n.p,{children:"Available version formats:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"latest"})," (default) - Latest stable release"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"main"})," - Latest development build"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"v1.2.3"})," - Specific version tag"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"sha-abcd1234"})," - Specific commit SHA"]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"This will:"}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsx)(n.li,{children:"Check for Go installation (1.22+ required)"}),"\n",(0,i.jsx)(n.li,{children:"Create a workspace directory with configuration files"}),"\n",(0,i.jsxs)(n.li,{children:["Pull and start all services using ",(0,i.jsx)(n.code,{children:"docker-compose up -d"})]}),"\n",(0,i.jsx)(n.li,{children:"Wait for services to become healthy"}),"\n",(0,i.jsx)(n.li,{children:"Initialize databases with required schemas"}),"\n",(0,i.jsx)(n.li,{children:"Display access information and helpful commands"}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"Once complete, you can access:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Trufflebox UI"}),": ",(0,i.jsx)(n.a,{href:"http://localhost:3000",children:"http://localhost:3000"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Horizon API"}),": ",(0,i.jsx)(n.a,{href:"http://localhost:8082",children:"http://localhost:8082"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Online Feature Store gRPC API"}),": ",(0,i.jsx)(n.a,{href:"http://localhost:8089",children:"http://localhost:8089"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"etcd Workbench"}),": ",(0,i.jsx)(n.a,{href:"http://localhost:8081",children:"http://localhost:8081"})]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"stopping-the-system",children:"Stopping the System"}),"\n",(0,i.jsx)(n.p,{children:"To stop all services:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"./stop.sh\n"})}),"\n",(0,i.jsx)(n.p,{children:"To stop and completely purge all containers, volumes, and workspace:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"./stop.sh --purge\n"})}),"\n",(0,i.jsx)(n.h2,{id:"accessing-services",children:"Accessing Services"}),"\n",(0,i.jsx)(n.h3,{id:"frontend-ui",children:"Frontend UI"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"URL"}),": ",(0,i.jsx)(n.a,{href:"http://localhost:3000",children:"http://localhost:3000"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Default admin credentials"}),":","\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["Email: ",(0,i.jsx)(n.code,{children:"admin@admin.com"})]}),"\n",(0,i.jsxs)(n.li,{children:["Password: ",(0,i.jsx)(n.code,{children:"admin"})]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"api-endpoints",children:"API Endpoints"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Horizon API"}),": ",(0,i.jsx)(n.a,{href:"http://localhost:8082",children:"http://localhost:8082"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["Health check: ",(0,i.jsx)(n.a,{href:"http://localhost:8082/health",children:"http://localhost:8082/health"})]}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"ONFS gRPC API"}),": ",(0,i.jsx)(n.a,{href:"http://localhost:8089",children:"http://localhost:8089"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["Health check: ",(0,i.jsx)(n.a,{href:"http://localhost:8089/health/self",children:"http://localhost:8089/health/self"})]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"database-access",children:"Database Access"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"MySQL"}),":"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Host: localhost"}),"\n",(0,i.jsx)(n.li,{children:"Port: 3306"}),"\n",(0,i.jsx)(n.li,{children:"Username: root"}),"\n",(0,i.jsx)(n.li,{children:"Password: root"}),"\n",(0,i.jsx)(n.li,{children:"Database: testdb"}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"ScyllaDB"}),":"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Host: localhost"}),"\n",(0,i.jsx)(n.li,{children:"Port: 9042"}),"\n",(0,i.jsx)(n.li,{children:"Keyspace: onfs"}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Redis"}),":"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Host: localhost"}),"\n",(0,i.jsx)(n.li,{children:"Port: 6379"}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"etcd"}),":"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["Endpoint: ",(0,i.jsx)(n.a,{href:"http://localhost:2379",children:"http://localhost:2379"})]}),"\n",(0,i.jsxs)(n.li,{children:["Workbench: ",(0,i.jsx)(n.a,{href:"http://localhost:8081",children:"http://localhost:8081"})]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"feature-store-api-examples",children:"Feature Store API Examples"}),"\n",(0,i.jsx)(n.h3,{id:"grpc-api-commands",children:"gRPC API Commands"}),"\n",(0,i.jsxs)(n.p,{children:["Use the following ",(0,i.jsx)(n.code,{children:"grpcurl"})," commands to interact with the Online Feature Store gRPC API:"]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Persist Features:"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:'grpcurl -plaintext -H "online-feature-store-caller-id: " -H "online-feature-store-auth-token: " -d \'\' localhost:8089 persist.FeatureService/PersistFeatures\n'})}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Retrieve Features (Decoded):"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:'grpcurl -plaintext -H "online-feature-store-caller-id: " -H "online-feature-store-auth-token: " -d \'\' localhost:8089 retrieve.FeatureService/RetrieveDecodedResult\n'})}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Retrieve Features (Binary):"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:'grpcurl -plaintext -H "online-feature-store-caller-id: " -H "online-feature-store-auth-token: " -d \'\' localhost:8089 retrieve.FeatureService/RetrieveFeatures\n'})}),"\n",(0,i.jsx)(n.h3,{id:"sample-request-bodies",children:"Sample Request Bodies"}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Single Feature Group Persist:"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-json",children:'{\n "data": [{\n "key_values": ["10"],\n "feature_values": [{\n "values": {"fp32_values": [123.45]}\n }]\n }],\n "entity_label": "catalog",\n "feature_group_schema": [{\n "label": "int_fg",\n "feature_labels": ["id"]\n }],\n "keys_schema": ["catalog_id"]\n}\n'})}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Single Feature Group Retrieve:"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-json",children:'{\n "entity_label": "catalog",\n "feature_groups": [{\n "label": "int_fg",\n "feature_labels": ["id"]\n }],\n "keys_schema": ["catalog_id"],\n "keys": [{"cols": ["10"]}]\n}\n'})}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Multiple Feature Groups Persist:"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-json",children:'{\n "data": [\n {\n "key_values": ["1"],\n "feature_values": [\n {"values": {"fp32_values": [28.5]}},\n {"values": {"string_values": ["Bharat"]}}\n ]\n },\n {\n "key_values": ["2"],\n "feature_values": [\n {"values": {"fp32_values": [32.0]}},\n {"values": {"string_values": ["India"]}}\n ]\n }\n ],\n "entity_label": "catalog",\n "feature_group_schema": [\n {"label": "int_fg", "feature_labels": ["id"]},\n {"label": "string_fg", "feature_labels": ["name"]}\n ],\n "keys_schema": ["catalog_id"]\n}\n'})}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Multiple Feature Groups Retrieve:"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-json",children:'{\n "entity_label": "catalog",\n "feature_groups": [\n {"label": "int_fg", "feature_labels": ["id"]},\n {"label": "string_fg", "feature_labels": ["name"]}\n ],\n "keys_schema": ["catalog_id"],\n "keys": [\n {"cols": ["1"]},\n {"cols": ["2"]}\n ]\n}\n'})}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Vector Feature Group Persist:"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-json",children:'{\n "data": [{\n "key_values": ["123"],\n "feature_values": [{\n "values": {\n "vector": [{\n "values": {"fp32_values": [1.0, 2.0, 3.0, 4.0]}\n }]\n }\n }]\n }],\n "entity_label": "catalog",\n "feature_group_schema": [{\n "label": "vector_fg",\n "feature_labels": ["embedding"]\n }],\n "keys_schema": ["catalog_id"]\n}\n'})}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Vector Feature Group Retrieve:"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-json",children:'{\n "entity_label": "catalog",\n "feature_groups": [{\n "label": "vector_fg",\n "feature_labels": ["embedding"]\n }],\n "keys_schema": ["catalog_id"],\n "keys": [{"cols": ["123"]}]\n}\n'})}),"\n",(0,i.jsx)(n.h3,{id:"key-points",children:"Key Points"}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Only one type per feature value block:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"feature_values"})," is a list, and each item in the list has only one value type populated"]}),"\n",(0,i.jsxs)(n.li,{children:["For example: one item has only ",(0,i.jsx)(n.code,{children:"fp32_values"}),", another has only ",(0,i.jsx)(n.code,{children:"int64_values"})]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Field Types:"}),"\nThe following value types are supported:"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"fp32_values"}),": ",(0,i.jsx)(n.code,{children:"float32[]"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"fp64_values"}),": ",(0,i.jsx)(n.code,{children:"float64[]"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"int32_values"}),": ",(0,i.jsx)(n.code,{children:"int32[]"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"int64_values"}),": ",(0,i.jsx)(n.code,{children:"string[]"})," (because JSON doesn't support 64-bit ints directly)"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"uint32_values"}),": ",(0,i.jsx)(n.code,{children:"uint32[]"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"uint64_values"}),": ",(0,i.jsx)(n.code,{children:"string[]"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"string_values"}),": ",(0,i.jsx)(n.code,{children:"string[]"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"bool_values"}),": ",(0,i.jsx)(n.code,{children:"bool[]"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"vector"}),": list of objects with nested values (used for embedded features)"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"response-format-differences",children:"Response Format Differences"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Retrieve Features (Binary)"}),": Returns data in binary format for optimal performance and reduced network overhead"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Retrieve Features (Decoded)"}),": Returns data in human-readable string format for easier debugging and development purposes"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"managing-services",children:"Managing Services"}),"\n",(0,i.jsx)(n.h3,{id:"viewing-logs",children:"Viewing Logs"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"# View logs for all services\ncd workspace && docker-compose logs -f\n\n# View logs for specific services\ncd workspace && docker-compose logs -f horizon\ncd workspace && docker-compose logs -f trufflebox-ui\ncd workspace && docker-compose logs -f onfs-api-server\n"})}),"\n",(0,i.jsx)(n.h3,{id:"service-management",children:"Service Management"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"# Restart a specific service\ncd workspace && docker-compose restart horizon\n\n# Stop all services\ncd workspace && docker-compose down\n\n# Start services again\ncd workspace && docker-compose up -d\n\n# Check service status\ncd workspace && docker-compose ps\n"})}),"\n",(0,i.jsx)(n.h2,{id:"troubleshooting",children:"Troubleshooting"}),"\n",(0,i.jsx)(n.h3,{id:"common-issues",children:"Common Issues"}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Port conflicts"}),": Ensure ports 3000, 8081, 8082, 8089, 9042, 3306, 6379, and 2379 are not in use by other applications."]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Docker network issues"}),": If containers can't communicate, try recreating:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"docker network rm onfs-network\ndocker network create onfs-network\n"})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Service health checks failing"}),": Check if all infrastructure services (databases) are running:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"cd workspace && docker-compose ps\n"})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Image pull issues"}),": Ensure you have access to GitHub Container Registry:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"docker login ghcr.io\n"})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.a,{href:"https://github.com/tzfun/etcd-workbench/blob/master/README.md",children:"How to use Etcd Workbench ?"})}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"service-dependencies",children:"Service Dependencies"}),"\n",(0,i.jsx)(n.p,{children:"Services start in the following order:"}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsx)(n.li,{children:"Infrastructure services (ScyllaDB, MySQL, Redis, etcd)"}),"\n",(0,i.jsx)(n.li,{children:"Online Feature Store gRPC API Server"}),"\n",(0,i.jsx)(n.li,{children:"Horizon (depends on databases + ONFS API)"}),"\n",(0,i.jsx)(n.li,{children:"Trufflebox UI (depends on Horizon)"}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"If a service fails to start, check its dependencies are healthy first."}),"\n",(0,i.jsx)(n.h2,{id:"development",children:"Development"}),"\n",(0,i.jsx)(n.p,{children:"The workspace directory contains all runtime configuration:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"workspace/docker-compose.yml"})," - Complete service orchestration"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"workspace/check_db_and_init.sh"})," - Database initialization script"]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"You can modify environment variables in the docker-compose.yml file and restart services."}),"\n",(0,i.jsx)(n.h2,{id:"contributing",children:"Contributing"}),"\n",(0,i.jsxs)(n.p,{children:["We welcome contributions from the community! Please see our ",(0,i.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTING.md",children:"Contributing Guide"})," for details on how to get started."]}),"\n",(0,i.jsx)(n.h2,{id:"community--support",children:"Community & Support"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["\ud83d\udcac ",(0,i.jsx)(n.strong,{children:"Discord"}),": Join our ",(0,i.jsx)(n.a,{href:"https://discord.gg/XkT7XsV2AU",children:"community chat"})]}),"\n",(0,i.jsxs)(n.li,{children:["\ud83d\udc1b ",(0,i.jsx)(n.strong,{children:"Issues"}),": Report bugs and request features on ",(0,i.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/issues",children:"GitHub Issues"})]}),"\n",(0,i.jsxs)(n.li,{children:["\ud83d\udce7 ",(0,i.jsx)(n.strong,{children:"Email"}),": Contact us at ",(0,i.jsx)(n.a,{href:"mailto:ml-oss@meesho.com",children:"ml-oss@meesho.com"})]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"license",children:"License"}),"\n",(0,i.jsxs)(n.p,{children:["BharatMLStack is open-source software licensed under the ",(0,i.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md",children:"BharatMLStack Business Source License 1.1"}),"."]}),"\n",(0,i.jsx)(n.hr,{}),"\n",(0,i.jsx)("div",{align:"center",children:(0,i.jsx)("strong",{children:"Built with \u2764\ufe0f for the ML community from Meesho"})}),"\n",(0,i.jsx)("div",{align:"center",children:(0,i.jsx)("strong",{children:"If you find this useful, \u2b50\ufe0f the repo \u2014 your support means the world to us!"})})]})}function h(e={}){const{wrapper:n}={...(0,l.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(d,{...e})}):d(e)}},8453:(e,n,s)=>{s.d(n,{R:()=>t,x:()=>c});var r=s(6540);const i={},l=r.createContext(i);function t(e){const n=r.useContext(l);return r.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function c(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:t(e.components),r.createElement(l.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/14064408.9ca9709f.js b/docs/assets/js/14064408.9ca9709f.js new file mode 100644 index 00000000..fa99219e --- /dev/null +++ b/docs/assets/js/14064408.9ca9709f.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[4582],{9416:t=>{t.exports=JSON.parse('{"categoryGeneratedIndex":{"title":"Quick Start","description":"Quick Start guide for BharatML Stack. Get up and running quickly with step-by-step instructions, sample data, and Docker Compose setup for local development and testing.","slug":"/category/quick-start","permalink":"/BharatMLStack/category/quick-start","sidebar":"tutorialSidebar","navigation":{"previous":{"title":"Release Notes","permalink":"/BharatMLStack/inferflow/v1.0.0/release-notes"},"next":{"title":"v1.0.0","permalink":"/BharatMLStack/quick-start/v1.0.0"}}}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/14064408.be0f96be.js b/docs/assets/js/14064408.be0f96be.js deleted file mode 100644 index 1b02768a..00000000 --- a/docs/assets/js/14064408.be0f96be.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[4582],{9416:t=>{t.exports=JSON.parse('{"categoryGeneratedIndex":{"title":"Quick Start","description":"Quick Start guide for BharatML Stack. Get up and running quickly with step-by-step instructions, sample data, and Docker Compose setup for local development and testing.","slug":"/category/quick-start","permalink":"/BharatMLStack/category/quick-start","sidebar":"tutorialSidebar","navigation":{"previous":{"title":"Release Notes","permalink":"/BharatMLStack/online-feature-store/v1.0.0/release-notes"},"next":{"title":"Quick Start","permalink":"/BharatMLStack/quick-start/v1.0.0/quick-start"}}}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/14eb3368.60af715e.js b/docs/assets/js/14eb3368.60af715e.js new file mode 100644 index 00000000..8d277bdd --- /dev/null +++ b/docs/assets/js/14eb3368.60af715e.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[6969],{594:(e,s,n)=>{n.d(s,{A:()=>j});n(6540);var t=n(4164),r=n(7559),a=n(6972),i=n(9169),c=n(8774),l=n(1312),o=n(6025),d=n(4848);function u(e){return(0,d.jsx)("svg",{viewBox:"0 0 24 24",...e,children:(0,d.jsx)("path",{d:"M10 19v-5h4v5c0 .55.45 1 1 1h3c.55 0 1-.45 1-1v-7h1.7c.46 0 .68-.57.33-.87L12.67 3.6c-.38-.34-.96-.34-1.34 0l-8.36 7.53c-.34.3-.13.87.33.87H5v7c0 .55.45 1 1 1h3c.55 0 1-.45 1-1z",fill:"currentColor"})})}const m={breadcrumbHomeIcon:"breadcrumbHomeIcon_YNFT"};function h(){const e=(0,o.Ay)("/");return(0,d.jsx)("li",{className:"breadcrumbs__item",children:(0,d.jsx)(c.A,{"aria-label":(0,l.T)({id:"theme.docs.breadcrumbs.home",message:"Home page",description:"The ARIA label for the home page in the breadcrumbs"}),className:"breadcrumbs__link",href:e,children:(0,d.jsx)(u,{className:m.breadcrumbHomeIcon})})})}var b=n(5260),x=n(4586);function p(e){const s=function({breadcrumbs:e}){const{siteConfig:s}=(0,x.A)();return{"@context":"https://schema.org","@type":"BreadcrumbList",itemListElement:e.filter(e=>e.href).map((e,n)=>({"@type":"ListItem",position:n+1,name:e.label,item:`${s.url}${e.href}`}))}}({breadcrumbs:e.breadcrumbs});return(0,d.jsx)(b.A,{children:(0,d.jsx)("script",{type:"application/ld+json",children:JSON.stringify(s)})})}const v={breadcrumbsContainer:"breadcrumbsContainer_Z_bl"};function g({children:e,href:s,isLast:n}){const t="breadcrumbs__link";return n?(0,d.jsx)("span",{className:t,children:e}):s?(0,d.jsx)(c.A,{className:t,href:s,children:(0,d.jsx)("span",{children:e})}):(0,d.jsx)("span",{className:t,children:e})}function f({children:e,active:s}){return(0,d.jsx)("li",{className:(0,t.A)("breadcrumbs__item",{"breadcrumbs__item--active":s}),children:e})}function j(){const e=(0,a.OF)(),s=(0,i.Dt)();return e?(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)(p,{breadcrumbs:e}),(0,d.jsx)("nav",{className:(0,t.A)(r.G.docs.docBreadcrumbs,v.breadcrumbsContainer),"aria-label":(0,l.T)({id:"theme.docs.breadcrumbs.navAriaLabel",message:"Breadcrumbs",description:"The ARIA label for the breadcrumbs"}),children:(0,d.jsxs)("ul",{className:"breadcrumbs",children:[s&&(0,d.jsx)(h,{}),e.map((s,n)=>{const t=n===e.length-1,r="category"===s.type&&s.linkUnlisted?void 0:s.href;return(0,d.jsx)(f,{active:t,children:(0,d.jsx)(g,{href:r,isLast:t,children:s.label})},n)})]})})]}):null}},1878:(e,s,n)=>{n.d(s,{A:()=>p});n(6540);var t=n(4164),r=n(4586),a=n(8774),i=n(1312),c=n(4070),l=n(7559),o=n(3886),d=n(3025),u=n(4848);const m={unreleased:function({siteTitle:e,versionMetadata:s}){return(0,u.jsx)(i.A,{id:"theme.docs.versions.unreleasedVersionLabel",description:"The label used to tell the user that he's browsing an unreleased doc version",values:{siteTitle:e,versionLabel:(0,u.jsx)("b",{children:s.label})},children:"This is unreleased documentation for {siteTitle} {versionLabel} version."})},unmaintained:function({siteTitle:e,versionMetadata:s}){return(0,u.jsx)(i.A,{id:"theme.docs.versions.unmaintainedVersionLabel",description:"The label used to tell the user that he's browsing an unmaintained doc version",values:{siteTitle:e,versionLabel:(0,u.jsx)("b",{children:s.label})},children:"This is documentation for {siteTitle} {versionLabel}, which is no longer actively maintained."})}};function h(e){const s=m[e.versionMetadata.banner];return(0,u.jsx)(s,{...e})}function b({versionLabel:e,to:s,onClick:n}){return(0,u.jsx)(i.A,{id:"theme.docs.versions.latestVersionSuggestionLabel",description:"The label used to tell the user to check the latest version",values:{versionLabel:e,latestVersionLink:(0,u.jsx)("b",{children:(0,u.jsx)(a.A,{to:s,onClick:n,children:(0,u.jsx)(i.A,{id:"theme.docs.versions.latestVersionLinkLabel",description:"The label used for the latest version suggestion link label",children:"latest version"})})})},children:"For up-to-date documentation, see the {latestVersionLink} ({versionLabel})."})}function x({className:e,versionMetadata:s}){const{siteConfig:{title:n}}=(0,r.A)(),{pluginId:a}=(0,c.vT)({failfast:!0}),{savePreferredVersionName:i}=(0,o.g1)(a),{latestDocSuggestion:d,latestVersionSuggestion:m}=(0,c.HW)(a),x=d??(p=m).docs.find(e=>e.id===p.mainDocId);var p;return(0,u.jsxs)("div",{className:(0,t.A)(e,l.G.docs.docVersionBanner,"alert alert--warning margin-bottom--md"),role:"alert",children:[(0,u.jsx)("div",{children:(0,u.jsx)(h,{siteTitle:n,versionMetadata:s})}),(0,u.jsx)("div",{className:"margin-top--md",children:(0,u.jsx)(b,{versionLabel:m.label,to:x.path,onClick:()=>i(m.name)})})]})}function p({className:e}){const s=(0,d.r)();return s.banner?(0,u.jsx)(x,{className:e,versionMetadata:s}):null}},4267:(e,s,n)=>{n.d(s,{A:()=>l});n(6540);var t=n(4164),r=n(1312),a=n(7559),i=n(3025),c=n(4848);function l({className:e}){const s=(0,i.r)();return s.badge?(0,c.jsx)("span",{className:(0,t.A)(e,a.G.docs.docVersionBadge,"badge badge--secondary"),children:(0,c.jsx)(r.A,{id:"theme.docs.versionBadge.label",values:{versionLabel:s.label},children:"Version: {versionLabel}"})}):null}},4795:(e,s,n)=>{n.d(s,{A:()=>j});n(6540);var t=n(4164),r=n(6972),a=n(8774),i=n(5846),c=n(6654),l=n(1312),o=n(1107);const d={cardContainer:"cardContainer_fWXF",cardTitle:"cardTitle_rnsV",cardDescription:"cardDescription_PWke"};var u=n(4848);function m({className:e,href:s,children:n}){return(0,u.jsx)(a.A,{href:s,className:(0,t.A)("card padding--lg",d.cardContainer,e),children:n})}function h({className:e,href:s,icon:n,title:r,description:a}){return(0,u.jsxs)(m,{href:s,className:e,children:[(0,u.jsxs)(o.A,{as:"h2",className:(0,t.A)("text--truncate",d.cardTitle),title:r,children:[n," ",r]}),a&&(0,u.jsx)("p",{className:(0,t.A)("text--truncate",d.cardDescription),title:a,children:a})]})}function b({item:e}){const s=(0,r.Nr)(e),n=function(){const{selectMessage:e}=(0,i.W)();return s=>e(s,(0,l.T)({message:"1 item|{count} items",id:"theme.docs.DocCard.categoryDescription.plurals",description:"The default description for a category card in the generated index about how many items this category includes"},{count:s}))}();return s?(0,u.jsx)(h,{className:e.className,href:s,icon:"\ud83d\uddc3\ufe0f",title:e.label,description:e.description??n(e.items.length)}):null}function x({item:e}){const s=(0,c.A)(e.href)?"\ud83d\udcc4\ufe0f":"\ud83d\udd17",n=(0,r.cC)(e.docId??void 0);return(0,u.jsx)(h,{className:e.className,href:e.href,icon:s,title:e.label,description:e.description??n?.description})}function p({item:e}){switch(e.type){case"link":return(0,u.jsx)(x,{item:e});case"category":return(0,u.jsx)(b,{item:e});default:throw new Error(`unknown item type ${JSON.stringify(e)}`)}}const v={docCardListItem:"docCardListItem_W1sv"};function g({className:e}){const s=(0,r.a4)();return(0,u.jsx)(j,{items:s,className:e})}function f({item:e}){return(0,u.jsx)("article",{className:(0,t.A)(v.docCardListItem,"col col--6"),children:(0,u.jsx)(p,{item:e})})}function j(e){const{items:s,className:n}=e;if(!s)return(0,u.jsx)(g,{...e});const a=(0,r.d1)(s);return(0,u.jsx)("section",{className:(0,t.A)("row",n),children:a.map((e,s)=>(0,u.jsx)(f,{item:e},s))})}},5846:(e,s,n)=>{n.d(s,{W:()=>o});var t=n(6540),r=n(4586);const a=["zero","one","two","few","many","other"];function i(e){return a.filter(s=>e.includes(s))}const c={locale:"en",pluralForms:i(["one","other"]),select:e=>1===e?"one":"other"};function l(){const{i18n:{currentLocale:e}}=(0,r.A)();return(0,t.useMemo)(()=>{try{return function(e){const s=new Intl.PluralRules(e);return{locale:e,pluralForms:i(s.resolvedOptions().pluralCategories),select:e=>s.select(e)}}(e)}catch(s){return console.error(`Failed to use Intl.PluralRules for locale "${e}".\nDocusaurus will fallback to the default (English) implementation.\nError: ${s.message}\n`),c}},[e])}function o(){const e=l();return{selectMessage:(s,n)=>function(e,s,n){const t=e.split("|");if(1===t.length)return t[0];t.length>n.pluralForms.length&&console.error(`For locale=${n.locale}, a maximum of ${n.pluralForms.length} plural forms are expected (${n.pluralForms.join(",")}), but the message contains ${t.length}: ${e}`);const r=n.select(s),a=n.pluralForms.indexOf(r);return t[Math.min(a,t.length-1)]}(n,s,e)}}},5847:(e,s,n)=>{n.r(s),n.d(s,{default:()=>p});n(6540);var t=n(5500),r=n(6972),a=n(6025),i=n(4795),c=n(7719),l=n(1878),o=n(4267),d=n(594),u=n(1107);const m={generatedIndexPage:"generatedIndexPage_vN6x",title:"title_kItE"};var h=n(4848);function b({categoryGeneratedIndex:e}){return(0,h.jsx)(t.be,{title:e.title,description:e.description,keywords:e.keywords,image:(0,a.Ay)(e.image)})}function x({categoryGeneratedIndex:e}){const s=(0,r.$S)();return(0,h.jsxs)("div",{className:m.generatedIndexPage,children:[(0,h.jsx)(l.A,{}),(0,h.jsx)(d.A,{}),(0,h.jsx)(o.A,{}),(0,h.jsxs)("header",{children:[(0,h.jsx)(u.A,{as:"h1",className:m.title,children:e.title}),e.description&&(0,h.jsx)("p",{children:e.description})]}),(0,h.jsx)("article",{className:"margin-top--lg",children:(0,h.jsx)(i.A,{items:s.items,className:m.list})}),(0,h.jsx)("footer",{className:"margin-top--md",children:(0,h.jsx)(c.A,{previous:e.navigation.previous,next:e.navigation.next})})]})}function p(e){return(0,h.jsxs)(h.Fragment,{children:[(0,h.jsx)(b,{...e}),(0,h.jsx)(x,{...e})]})}},7719:(e,s,n)=>{n.d(s,{A:()=>c});n(6540);var t=n(4164),r=n(1312),a=n(9022),i=n(4848);function c(e){const{className:s,previous:n,next:c}=e;return(0,i.jsxs)("nav",{className:(0,t.A)(s,"pagination-nav"),"aria-label":(0,r.T)({id:"theme.docs.paginator.navAriaLabel",message:"Docs pages",description:"The ARIA label for the docs pagination"}),children:[n&&(0,i.jsx)(a.A,{...n,subLabel:(0,i.jsx)(r.A,{id:"theme.docs.paginator.previous",description:"The label used to navigate to the previous doc",children:"Previous"})}),c&&(0,i.jsx)(a.A,{...c,subLabel:(0,i.jsx)(r.A,{id:"theme.docs.paginator.next",description:"The label used to navigate to the next doc",children:"Next"}),isNext:!0})]})}},9022:(e,s,n)=>{n.d(s,{A:()=>i});n(6540);var t=n(4164),r=n(8774),a=n(4848);function i(e){const{permalink:s,title:n,subLabel:i,isNext:c}=e;return(0,a.jsxs)(r.A,{className:(0,t.A)("pagination-nav__link",c?"pagination-nav__link--next":"pagination-nav__link--prev"),to:s,children:[i&&(0,a.jsx)("div",{className:"pagination-nav__sublabel",children:i}),(0,a.jsx)("div",{className:"pagination-nav__label",children:n})]})}}}]); \ No newline at end of file diff --git a/docs/assets/js/14eb3368.e9006523.js b/docs/assets/js/14eb3368.e9006523.js deleted file mode 100644 index c72af022..00000000 --- a/docs/assets/js/14eb3368.e9006523.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[6969],{477:(e,s,n)=>{n.r(s),n.d(s,{default:()=>w});n(6540);var t=n(5500),r=n(6972),a=n(6025),i=n(4164),c=n(8774),l=n(5846),o=n(6654),d=n(1312),u=n(1107);const m={cardContainer:"cardContainer_fWXF",cardTitle:"cardTitle_rnsV",cardDescription:"cardDescription_PWke"};var h=n(4848);function b({className:e,href:s,children:n}){return(0,h.jsx)(c.A,{href:s,className:(0,i.A)("card padding--lg",m.cardContainer,e),children:n})}function x({className:e,href:s,icon:n,title:t,description:r}){return(0,h.jsxs)(b,{href:s,className:e,children:[(0,h.jsxs)(u.A,{as:"h2",className:(0,i.A)("text--truncate",m.cardTitle),title:t,children:[n," ",t]}),r&&(0,h.jsx)("p",{className:(0,i.A)("text--truncate",m.cardDescription),title:r,children:r})]})}function p({item:e}){const s=(0,r.Nr)(e),n=function(){const{selectMessage:e}=(0,l.W)();return s=>e(s,(0,d.T)({message:"1 item|{count} items",id:"theme.docs.DocCard.categoryDescription.plurals",description:"The default description for a category card in the generated index about how many items this category includes"},{count:s}))}();return s?(0,h.jsx)(x,{className:e.className,href:s,icon:"\ud83d\uddc3\ufe0f",title:e.label,description:e.description??n(e.items.length)}):null}function v({item:e}){const s=(0,o.A)(e.href)?"\ud83d\udcc4\ufe0f":"\ud83d\udd17",n=(0,r.cC)(e.docId??void 0);return(0,h.jsx)(x,{className:e.className,href:e.href,icon:s,title:e.label,description:e.description??n?.description})}function g({item:e}){switch(e.type){case"link":return(0,h.jsx)(v,{item:e});case"category":return(0,h.jsx)(p,{item:e});default:throw new Error(`unknown item type ${JSON.stringify(e)}`)}}const f={docCardListItem:"docCardListItem_W1sv"};function j({className:e}){const s=(0,r.a4)();return(0,h.jsx)(N,{items:s,className:e})}function A({item:e}){return(0,h.jsx)("article",{className:(0,i.A)(f.docCardListItem,"col col--6"),children:(0,h.jsx)(g,{item:e})})}function N(e){const{items:s,className:n}=e;if(!s)return(0,h.jsx)(j,{...e});const t=(0,r.d1)(s);return(0,h.jsx)("section",{className:(0,i.A)("row",n),children:t.map(((e,s)=>(0,h.jsx)(A,{item:e},s)))})}var L=n(7719),_=n(1878),T=n(4267),k=n(594);const y={generatedIndexPage:"generatedIndexPage_vN6x",title:"title_kItE"};function I({categoryGeneratedIndex:e}){return(0,h.jsx)(t.be,{title:e.title,description:e.description,keywords:e.keywords,image:(0,a.Ay)(e.image)})}function C({categoryGeneratedIndex:e}){const s=(0,r.$S)();return(0,h.jsxs)("div",{className:y.generatedIndexPage,children:[(0,h.jsx)(_.A,{}),(0,h.jsx)(k.A,{}),(0,h.jsx)(T.A,{}),(0,h.jsxs)("header",{children:[(0,h.jsx)(u.A,{as:"h1",className:y.title,children:e.title}),e.description&&(0,h.jsx)("p",{children:e.description})]}),(0,h.jsx)("article",{className:"margin-top--lg",children:(0,h.jsx)(N,{items:s.items,className:y.list})}),(0,h.jsx)("footer",{className:"margin-top--md",children:(0,h.jsx)(L.A,{previous:e.navigation.previous,next:e.navigation.next})})]})}function w(e){return(0,h.jsxs)(h.Fragment,{children:[(0,h.jsx)(I,{...e}),(0,h.jsx)(C,{...e})]})}},594:(e,s,n)=>{n.d(s,{A:()=>j});n(6540);var t=n(4164),r=n(7559),a=n(6972),i=n(9169),c=n(8774),l=n(1312),o=n(6025),d=n(4848);function u(e){return(0,d.jsx)("svg",{viewBox:"0 0 24 24",...e,children:(0,d.jsx)("path",{d:"M10 19v-5h4v5c0 .55.45 1 1 1h3c.55 0 1-.45 1-1v-7h1.7c.46 0 .68-.57.33-.87L12.67 3.6c-.38-.34-.96-.34-1.34 0l-8.36 7.53c-.34.3-.13.87.33.87H5v7c0 .55.45 1 1 1h3c.55 0 1-.45 1-1z",fill:"currentColor"})})}const m={breadcrumbHomeIcon:"breadcrumbHomeIcon_YNFT"};function h(){const e=(0,o.Ay)("/");return(0,d.jsx)("li",{className:"breadcrumbs__item",children:(0,d.jsx)(c.A,{"aria-label":(0,l.T)({id:"theme.docs.breadcrumbs.home",message:"Home page",description:"The ARIA label for the home page in the breadcrumbs"}),className:"breadcrumbs__link",href:e,children:(0,d.jsx)(u,{className:m.breadcrumbHomeIcon})})})}var b=n(5260),x=n(4586);function p(e){const s=function({breadcrumbs:e}){const{siteConfig:s}=(0,x.A)();return{"@context":"https://schema.org","@type":"BreadcrumbList",itemListElement:e.filter((e=>e.href)).map(((e,n)=>({"@type":"ListItem",position:n+1,name:e.label,item:`${s.url}${e.href}`})))}}({breadcrumbs:e.breadcrumbs});return(0,d.jsx)(b.A,{children:(0,d.jsx)("script",{type:"application/ld+json",children:JSON.stringify(s)})})}const v={breadcrumbsContainer:"breadcrumbsContainer_Z_bl"};function g({children:e,href:s,isLast:n}){const t="breadcrumbs__link";return n?(0,d.jsx)("span",{className:t,children:e}):s?(0,d.jsx)(c.A,{className:t,href:s,children:(0,d.jsx)("span",{children:e})}):(0,d.jsx)("span",{className:t,children:e})}function f({children:e,active:s}){return(0,d.jsx)("li",{className:(0,t.A)("breadcrumbs__item",{"breadcrumbs__item--active":s}),children:e})}function j(){const e=(0,a.OF)(),s=(0,i.Dt)();return e?(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)(p,{breadcrumbs:e}),(0,d.jsx)("nav",{className:(0,t.A)(r.G.docs.docBreadcrumbs,v.breadcrumbsContainer),"aria-label":(0,l.T)({id:"theme.docs.breadcrumbs.navAriaLabel",message:"Breadcrumbs",description:"The ARIA label for the breadcrumbs"}),children:(0,d.jsxs)("ul",{className:"breadcrumbs",children:[s&&(0,d.jsx)(h,{}),e.map(((s,n)=>{const t=n===e.length-1,r="category"===s.type&&s.linkUnlisted?void 0:s.href;return(0,d.jsx)(f,{active:t,children:(0,d.jsx)(g,{href:r,isLast:t,children:s.label})},n)}))]})})]}):null}},1878:(e,s,n)=>{n.d(s,{A:()=>p});n(6540);var t=n(4164),r=n(4586),a=n(8774),i=n(1312),c=n(4070),l=n(7559),o=n(3886),d=n(3025),u=n(4848);const m={unreleased:function({siteTitle:e,versionMetadata:s}){return(0,u.jsx)(i.A,{id:"theme.docs.versions.unreleasedVersionLabel",description:"The label used to tell the user that he's browsing an unreleased doc version",values:{siteTitle:e,versionLabel:(0,u.jsx)("b",{children:s.label})},children:"This is unreleased documentation for {siteTitle} {versionLabel} version."})},unmaintained:function({siteTitle:e,versionMetadata:s}){return(0,u.jsx)(i.A,{id:"theme.docs.versions.unmaintainedVersionLabel",description:"The label used to tell the user that he's browsing an unmaintained doc version",values:{siteTitle:e,versionLabel:(0,u.jsx)("b",{children:s.label})},children:"This is documentation for {siteTitle} {versionLabel}, which is no longer actively maintained."})}};function h(e){const s=m[e.versionMetadata.banner];return(0,u.jsx)(s,{...e})}function b({versionLabel:e,to:s,onClick:n}){return(0,u.jsx)(i.A,{id:"theme.docs.versions.latestVersionSuggestionLabel",description:"The label used to tell the user to check the latest version",values:{versionLabel:e,latestVersionLink:(0,u.jsx)("b",{children:(0,u.jsx)(a.A,{to:s,onClick:n,children:(0,u.jsx)(i.A,{id:"theme.docs.versions.latestVersionLinkLabel",description:"The label used for the latest version suggestion link label",children:"latest version"})})})},children:"For up-to-date documentation, see the {latestVersionLink} ({versionLabel})."})}function x({className:e,versionMetadata:s}){const{siteConfig:{title:n}}=(0,r.A)(),{pluginId:a}=(0,c.vT)({failfast:!0}),{savePreferredVersionName:i}=(0,o.g1)(a),{latestDocSuggestion:d,latestVersionSuggestion:m}=(0,c.HW)(a),x=d??(p=m).docs.find((e=>e.id===p.mainDocId));var p;return(0,u.jsxs)("div",{className:(0,t.A)(e,l.G.docs.docVersionBanner,"alert alert--warning margin-bottom--md"),role:"alert",children:[(0,u.jsx)("div",{children:(0,u.jsx)(h,{siteTitle:n,versionMetadata:s})}),(0,u.jsx)("div",{className:"margin-top--md",children:(0,u.jsx)(b,{versionLabel:m.label,to:x.path,onClick:()=>i(m.name)})})]})}function p({className:e}){const s=(0,d.r)();return s.banner?(0,u.jsx)(x,{className:e,versionMetadata:s}):null}},4267:(e,s,n)=>{n.d(s,{A:()=>l});n(6540);var t=n(4164),r=n(1312),a=n(7559),i=n(3025),c=n(4848);function l({className:e}){const s=(0,i.r)();return s.badge?(0,c.jsx)("span",{className:(0,t.A)(e,a.G.docs.docVersionBadge,"badge badge--secondary"),children:(0,c.jsx)(r.A,{id:"theme.docs.versionBadge.label",values:{versionLabel:s.label},children:"Version: {versionLabel}"})}):null}},5846:(e,s,n)=>{n.d(s,{W:()=>o});var t=n(6540),r=n(4586);const a=["zero","one","two","few","many","other"];function i(e){return a.filter((s=>e.includes(s)))}const c={locale:"en",pluralForms:i(["one","other"]),select:e=>1===e?"one":"other"};function l(){const{i18n:{currentLocale:e}}=(0,r.A)();return(0,t.useMemo)((()=>{try{return function(e){const s=new Intl.PluralRules(e);return{locale:e,pluralForms:i(s.resolvedOptions().pluralCategories),select:e=>s.select(e)}}(e)}catch(s){return console.error(`Failed to use Intl.PluralRules for locale "${e}".\nDocusaurus will fallback to the default (English) implementation.\nError: ${s.message}\n`),c}}),[e])}function o(){const e=l();return{selectMessage:(s,n)=>function(e,s,n){const t=e.split("|");if(1===t.length)return t[0];t.length>n.pluralForms.length&&console.error(`For locale=${n.locale}, a maximum of ${n.pluralForms.length} plural forms are expected (${n.pluralForms.join(",")}), but the message contains ${t.length}: ${e}`);const r=n.select(s),a=n.pluralForms.indexOf(r);return t[Math.min(a,t.length-1)]}(n,s,e)}}},7719:(e,s,n)=>{n.d(s,{A:()=>c});n(6540);var t=n(4164),r=n(1312),a=n(9022),i=n(4848);function c(e){const{className:s,previous:n,next:c}=e;return(0,i.jsxs)("nav",{className:(0,t.A)(s,"pagination-nav"),"aria-label":(0,r.T)({id:"theme.docs.paginator.navAriaLabel",message:"Docs pages",description:"The ARIA label for the docs pagination"}),children:[n&&(0,i.jsx)(a.A,{...n,subLabel:(0,i.jsx)(r.A,{id:"theme.docs.paginator.previous",description:"The label used to navigate to the previous doc",children:"Previous"})}),c&&(0,i.jsx)(a.A,{...c,subLabel:(0,i.jsx)(r.A,{id:"theme.docs.paginator.next",description:"The label used to navigate to the next doc",children:"Next"}),isNext:!0})]})}},9022:(e,s,n)=>{n.d(s,{A:()=>i});n(6540);var t=n(4164),r=n(8774),a=n(4848);function i(e){const{permalink:s,title:n,subLabel:i,isNext:c}=e;return(0,a.jsxs)(r.A,{className:(0,t.A)("pagination-nav__link",c?"pagination-nav__link--next":"pagination-nav__link--prev"),to:s,children:[i&&(0,a.jsx)("div",{className:"pagination-nav__sublabel",children:i}),(0,a.jsx)("div",{className:"pagination-nav__label",children:n})]})}}}]); \ No newline at end of file diff --git a/docs/assets/js/176d210f.47d21595.js b/docs/assets/js/176d210f.47d21595.js deleted file mode 100644 index 239e1e8f..00000000 --- a/docs/assets/js/176d210f.47d21595.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[6100],{239:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-register-job-e45c350f42a09adaeea50ef00d53df55.png"},598:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-feature-discovery-fg-details-a2dda4f72568878138e3b2d50fa20e8f.png"},753:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>l,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>r,toc:()=>c});const r=JSON.parse('{"id":"trufflebox-ui/v1.0.0/userguide","title":"User Manual","description":"This guide covers the complete setup and usage of the Online Feature Store system, including the core services (Online Feature Store and Horizon) and the TruffleBox UI for feature management.","source":"@site/docs/trufflebox-ui/v1.0.0/userguide.md","sourceDirName":"trufflebox-ui/v1.0.0","slug":"/trufflebox-ui/v1.0.0/userguide","permalink":"/BharatMLStack/trufflebox-ui/v1.0.0/userguide","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/trufflebox-ui/v1.0.0/userguide.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"User Manual","sidebar_position":1},"sidebar":"tutorialSidebar","previous":{"title":"Trufflebox UI","permalink":"/BharatMLStack/category/trufflebox-ui"},"next":{"title":"SDKs","permalink":"/BharatMLStack/category/sdks"}}');var s=i(4848),t=i(8453);const a={title:"User Manual",sidebar_position:1},o="Usage Guide",l={},c=[{value:"Table of Contents",id:"table-of-contents",level:2},{value:"System Overview",id:"system-overview",level:2},{value:"Environment Setup",id:"environment-setup",level:2},{value:"Online Feature Store Configuration",id:"online-feature-store-configuration",level:3},{value:"Core Application Settings",id:"core-application-settings",level:4},{value:"Storage Configuration",id:"storage-configuration",level:4},{value:"Caching Configuration",id:"caching-configuration",level:4},{value:"Service Discovery and Configuration",id:"service-discovery-and-configuration",level:4},{value:"Horizon Configuration",id:"horizon-configuration",level:3},{value:"Core Application Settings",id:"core-application-settings-1",level:4},{value:"Database Configuration",id:"database-configuration",level:4},{value:"ScyllaDB Configuration",id:"scylladb-configuration",level:4},{value:"Service Integration",id:"service-integration",level:4},{value:"Key Constructs",id:"key-constructs",level:2},{value:"Store ID",id:"store-id",level:3},{value:"Entity",id:"entity",level:3},{value:"Feature Group",id:"feature-group",level:3},{value:"Feature",id:"feature",level:3},{value:"Job",id:"job",level:3},{value:"Configuration Hierarchy",id:"configuration-hierarchy",level:3},{value:"Table of Contents",id:"table-of-contents-1",level:2},{value:"User Flow",id:"user-flow",level:2},{value:"Getting Started with TruffleBox",id:"getting-started-with-trufflebox",level:3},{value:"Authentication",id:"authentication",level:4},{value:"User Management",id:"user-management",level:4},{value:"Navigation",id:"navigation",level:4},{value:"Feature Discovery",id:"feature-discovery",level:3},{value:"Entity Management",id:"entity-management",level:4},{value:"Feature Group Management",id:"feature-group-management",level:4},{value:"Feature Management",id:"feature-management",level:4},{value:"Store Discovery",id:"store-discovery",level:4},{value:"Job Discovery",id:"job-discovery",level:4},{value:"Feature Registry",id:"feature-registry",level:3},{value:"Request Status Tracking",id:"request-status-tracking",level:4},{value:"Step-by-Step Registration Guide",id:"step-by-step-registration-guide",level:4},{value:"Store Registry",id:"store-registry",level:4},{value:"Job Registry",id:"job-registry",level:4},{value:"Entity Registry",id:"entity-registry",level:4},{value:"Feature Group Registry",id:"feature-group-registry",level:4},{value:"Feature Addition",id:"feature-addition",level:4},{value:"Need Help?",id:"need-help",level:4},{value:"Admin Approval Flow",id:"admin-approval-flow",level:2},{value:"Request Management",id:"request-management",level:3},{value:"Viewing All Requests",id:"viewing-all-requests",level:4},{value:"Request Approval Process",id:"request-approval-process",level:4},{value:"Admin Support",id:"admin-support",level:4},{value:"Contributing",id:"contributing",level:2},{value:"Community & Support",id:"community--support",level:2},{value:"License",id:"license",level:2}];function d(e){const n={a:"a",br:"br",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",hr:"hr",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,t.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.header,{children:(0,s.jsx)(n.h1,{id:"usage-guide",children:"Usage Guide"})}),"\n",(0,s.jsx)(n.p,{children:"This guide covers the complete setup and usage of the Online Feature Store system, including the core services (Online Feature Store and Horizon) and the TruffleBox UI for feature management."}),"\n",(0,s.jsx)(n.h2,{id:"table-of-contents",children:"Table of Contents"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"#system-overview",children:"System Overview"})}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"#environment-setup",children:"Environment Setup"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"#online-feature-store-configuration",children:"Online Feature Store Configuration"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"#horizon-configuration",children:"Horizon Configuration"})}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"#key-constructs",children:"Key Constructs"})}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"#trufflebox-ui-guide",children:"TruffleBox UI Guide"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"#user-flow",children:"User Flow"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"#admin-approval-flow",children:"Admin Approval Flow"})}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"system-overview",children:"System Overview"}),"\n",(0,s.jsx)(n.p,{children:"The Online Feature Store is a comprehensive feature management system consisting of two main components:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Online Feature Store"}),": The core feature serving service that provides real-time feature retrieval with multiple storage backends and caching layers"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Horizon"}),": The configuration and metadata management service that handles feature definitions, stores, and job configurations"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"These services work together to provide a scalable, high-performance feature store for machine learning applications."}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"environment-setup",children:"Environment Setup"}),"\n",(0,s.jsx)(n.h3,{id:"online-feature-store-configuration",children:"Online Feature Store Configuration"}),"\n",(0,s.jsx)(n.p,{children:"The Online Feature Store requires several environment variables to configure storage backends, caching, and service settings."}),"\n",(0,s.jsx)(n.h4,{id:"core-application-settings",children:"Core Application Settings"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"APP_ENV=prod\nAPP_LOG_LEVEL=DEBUG\nAPP_METRIC_SAMPLING_RATE=1\nAPP_NAME=online-feature-store\nAPP_PORT=8005\nAUTH_TOKEN=ofs-token\n"})}),"\n",(0,s.jsx)(n.h4,{id:"storage-configuration",children:"Storage Configuration"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"ScyllaDB Storage (Primary Storage)"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"# Primary ScyllaDB cluster\nSTORAGE_SCYLLA_1_CONTACT_POINTS=localhost\nSTORAGE_SCYLLA_1_KEYSPACE=ofs\nSTORAGE_SCYLLA_1_NUM_CONNS=1\nSTORAGE_SCYLLA_1_PORT=9042\nSTORAGE_SCYLLA_1_TIMEOUT_IN_MS=300000\nSTORAGE_SCYLLA_1_PASSWORD=\nSTORAGE_SCYLLA_1_USERNAME=ofs\n\n# Secondary ScyllaDB cluster\nSTORAGE_SCYLLA_5_CONTACT_POINTS=localhost\nSTORAGE_SCYLLA_5_KEYSPACE=onfs\nSTORAGE_SCYLLA_5_NUM_CONNS=1\nSTORAGE_SCYLLA_5_PASSWORD=\nSTORAGE_SCYLLA_5_PORT=9042\nSTORAGE_SCYLLA_5_TIMEOUT_IN_MS=300000\nSTORAGE_SCYLLA_5_USERNAME=\n\n# Active ScyllaDB configurations\nSTORAGE_SCYLLA_ACTIVE_CONFIG_IDS=1,5\n"})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"Redis Storage Configuration"})}),"\n",(0,s.jsx)(n.p,{children:"Redis serves dual purposes in the Online Feature Store:"}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Primary Storage Backend"}),": For fast feature retrieval and storage"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Distributed Cache Layer"}),": For improved performance and reduced latency"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Redis configurations can be referenced by their IDs in Store configurations, similar to ScyllaDB. Each Redis configuration can be independently used as either a storage backend or cache layer."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"# Redis Failover Configuration 1 (ID: 2)\nSTORAGE_REDIS_FAILOVER_2_SENTINEL_ADDRESSES=localhost:26379\nSTORAGE_REDIS_FAILOVER_2_DB=0\nSTORAGE_REDIS_FAILOVER_2_DISABLE_IDENTITY=true\nSTORAGE_REDIS_FAILOVER_2_MASTER_NAME=mymaster\nSTORAGE_REDIS_FAILOVER_2_MAX_IDLE_CONN=32\nSTORAGE_REDIS_FAILOVER_2_MIN_IDLE_CONN=20\nSTORAGE_REDIS_FAILOVER_2_MAX_ACTIVE_CONN=32\nSTORAGE_REDIS_FAILOVER_2_MAX_RETRY=-1\nSTORAGE_REDIS_FAILOVER_2_POOL_FIFO=false\nSTORAGE_REDIS_FAILOVER_2_READ_TIMEOUT_IN_MS=3000\nSTORAGE_REDIS_FAILOVER_2_WRITE_TIMEOUT_IN_MS=3000\nSTORAGE_REDIS_FAILOVER_2_POOL_TIMEOUT_IN_MS=3000\nSTORAGE_REDIS_FAILOVER_2_POOL_SIZE=32\nSTORAGE_REDIS_FAILOVER_2_CONN_MAX_IDLE_TIMEOUT_IN_MINUTES=15\nSTORAGE_REDIS_FAILOVER_2_CONN_MAX_AGE_IN_MINUTES=30\n\n# Redis Failover Configuration 2 (ID: 4)\nSTORAGE_REDIS_FAILOVER_4_SENTINEL_ADDRESSES=localhost:26379\nSTORAGE_REDIS_FAILOVER_4_DB=0\nSTORAGE_REDIS_FAILOVER_4_DISABLE_IDENTITY=true\nSTORAGE_REDIS_FAILOVER_4_MASTER_NAME=mymaster\nSTORAGE_REDIS_FAILOVER_4_MAX_IDLE_CONN=32\nSTORAGE_REDIS_FAILOVER_4_MIN_IDLE_CONN=20\nSTORAGE_REDIS_FAILOVER_4_MAX_ACTIVE_CONN=32\nSTORAGE_REDIS_FAILOVER_4_MAX_RETRY=-1\nSTORAGE_REDIS_FAILOVER_4_POOL_FIFO=false\nSTORAGE_REDIS_FAILOVER_4_READ_TIMEOUT_IN_MS=3000\nSTORAGE_REDIS_FAILOVER_4_WRITE_TIMEOUT_IN_MS=3000\nSTORAGE_REDIS_FAILOVER_4_POOL_TIMEOUT_IN_MS=3000\nSTORAGE_REDIS_FAILOVER_4_POOL_SIZE=32\nSTORAGE_REDIS_FAILOVER_4_CONN_MAX_IDLE_TIMEOUT_IN_MINUTES=15\nSTORAGE_REDIS_FAILOVER_4_CONN_MAX_AGE_IN_MINUTES=30\n\n# High-Performance Redis Configuration (ID: 6)\nSTORAGE_REDIS_FAILOVER_6_CONN_MAX_AGE_IN_MINUTES=-1\nSTORAGE_REDIS_FAILOVER_6_CONN_MAX_IDLE_TIMEOUT_IN_MINUTES=30\nSTORAGE_REDIS_FAILOVER_6_DB=0\nSTORAGE_REDIS_FAILOVER_6_DISABLE_IDENTITY=true\nSTORAGE_REDIS_FAILOVER_6_MASTER_NAME=mymaster\nSTORAGE_REDIS_FAILOVER_6_MAX_ACTIVE_CONN=202\nSTORAGE_REDIS_FAILOVER_6_MAX_IDLE_CONN=157\nSTORAGE_REDIS_FAILOVER_6_MAX_RETRY=-1\nSTORAGE_REDIS_FAILOVER_6_MIN_IDLE_CONN=52\nSTORAGE_REDIS_FAILOVER_6_PASSWORD=\nSTORAGE_REDIS_FAILOVER_6_POOL_FIFO=false\nSTORAGE_REDIS_FAILOVER_6_POOL_SIZE=202\nSTORAGE_REDIS_FAILOVER_6_POOL_TIMEOUT_IN_MS=2\nSTORAGE_REDIS_FAILOVER_6_READ_TIMEOUT_IN_MS=75\nSTORAGE_REDIS_FAILOVER_6_ROUTE_RANDOM=true\nSTORAGE_REDIS_FAILOVER_6_SENTINEL_ADDRESSES=localhost:26379\nSTORAGE_REDIS_FAILOVER_6_WRITE_TIMEOUT_IN_MS=300\n\n# Active Redis configurations\nSTORAGE_REDIS_FAILOVER_ACTIVE_CONFIG_IDS=2,4,6\n"})}),"\n",(0,s.jsx)(n.h4,{id:"caching-configuration",children:"Caching Configuration"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"# In-Memory Cache\nIN_MEM_CACHE_3_ENABLED=true\nIN_MEM_CACHE_3_NAME=onfs\nIN_MEM_CACHE_3_SIZE_IN_BYTES=10000000\nIN_MEM_CACHE_ACTIVE_CONFIG_IDS=3\n\n# Distributed Cache (uses Redis configurations)\n# Redis configurations (IDs: 2,4,6) can be used for distributed caching\nDISTRIBUTED_CACHE_CONF_IDS=2\n"})}),"\n",(0,s.jsx)(n.h4,{id:"service-discovery-and-configuration",children:"Service Discovery and Configuration"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"# ETCD Configuration for service discovery\nETCD_SERVER=0.0.0.0:2379\nETCD_WATCHER_ENABLED=true\n"})}),"\n",(0,s.jsx)(n.h3,{id:"horizon-configuration",children:"Horizon Configuration"}),"\n",(0,s.jsx)(n.p,{children:"Horizon manages the metadata and configuration for the Online Feature Store system."}),"\n",(0,s.jsx)(n.h4,{id:"core-application-settings-1",children:"Core Application Settings"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"APP_NAME=horizon\nAPP_ENVIRONMENT=PROD\nAPP_ENV=production\nAPP_PORT=8082\nAPP_LOG_LEVEL=DEBUG\nAPP_METRIC_SAMPLING_RATE=1\nAPP_GC_PERCENTAGE=1\n"})}),"\n",(0,s.jsx)(n.h4,{id:"database-configuration",children:"Database Configuration"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"# MySQL Master Configuration\nMYSQL_MASTER_MAX_POOL_SIZE=5\nMYSQL_MASTER_MIN_POOL_SIZE=2\nMYSQL_MASTER_PASSWORD=\nMYSQL_MASTER_HOST=127.0.0.1\nMYSQL_MASTER_PORT=3306\nMYSQL_DB_NAME=ml_config\nMYSQL_MASTER_USERNAME=root\n\n# MySQL Slave Configuration\nMYSQL_SLAVE_MAX_POOL_SIZE=5\nMYSQL_SLAVE_MIN_POOL_SIZE=2\nMYSQL_SLAVE_PASSWORD=\nMYSQL_SLAVE_HOST=127.0.0.1\nMYSQL_SLAVE_USERNAME=root\nMYSQL_SLAVE_PORT=3306\n"})}),"\n",(0,s.jsx)(n.h4,{id:"scylladb-configuration",children:"ScyllaDB Configuration"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"# ScyllaDB for Horizon\nSCYLLA_1_CONTACT_POINTS=localhost\nSCYLLA_1_KEYSPACE=onfs\nSCYLLA_1_NUM_CONNS=1\nSCYLLA_1_PORT=9042\nSCYLLA_1_TIMEOUT_IN_MS=300000\nSCYLLA_1_PASSWORD=\nSCYLLA_1_USERNAME=\nSCYLLA_ACTIVE_CONFIG_IDS=1\n"})}),"\n",(0,s.jsx)(n.h4,{id:"service-integration",children:"Service Integration"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"# ETCD Configuration\nETCD_WATCHER_ENABLED=true\nETCD_SERVER=localhost:2379\n\n# Integration with Online Feature Store\nONLINE_FEATURE_STORE_APP_NAME=online-feature-store\n"})}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"key-constructs",children:"Key Constructs"}),"\n",(0,s.jsx)(n.p,{children:"Understanding these key constructs is essential for effectively using the Online Feature Store:"}),"\n",(0,s.jsx)(n.h3,{id:"store-id",children:"Store ID"}),"\n",(0,s.jsxs)(n.p,{children:["A ",(0,s.jsx)(n.strong,{children:"Store ID"})," is a unique identifier that represents a data storage configuration within the system. It defines:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Storage Backend"}),": Which underlying storage system (ScyllaDB, Redis, etc.) to use"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Configuration Parameters"}),": Connection settings, timeouts, pool sizes"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Access Patterns"}),": How data is read from and written to the store"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Store IDs are referenced throughout the system to:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Route feature requests to the appropriate storage backend"}),"\n",(0,s.jsx)(n.li,{children:"Apply specific caching strategies"}),"\n",(0,s.jsx)(n.li,{children:"Manage data lifecycle and retention policies"}),"\n",(0,s.jsx)(n.li,{children:"Configure stores in TruffleBox UI for feature groups and entities"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"Storage Backend Configuration:"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"ScyllaDB Store IDs"}),": ",(0,s.jsx)(n.code,{children:"STORAGE_SCYLLA_ACTIVE_CONFIG_IDS=1,5"})," indicates ScyllaDB configurations with IDs 1 and 5 are active"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Redis Store IDs"}),": ",(0,s.jsx)(n.code,{children:"STORAGE_REDIS_FAILOVER_ACTIVE_CONFIG_IDS=2,4,6"})," indicates Redis configurations with IDs 2, 4, and 6 are active"]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Dual Usage of Redis:"}),"\nRedis configurations can serve dual purposes:"]}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"As Storage Backend"}),": Redis IDs (2,4,6) can be configured as primary storage in Store configurations"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"As Distributed Cache"}),": Same Redis IDs can be used for caching via ",(0,s.jsx)(n.code,{children:"DISTRIBUTED_CACHE_CONF_IDS=2"})]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"When creating stores in TruffleBox, you can reference these storage configuration IDs to determine which backend (ScyllaDB ID 1/5 or Redis ID 2/4/6) will be used for your feature data."}),"\n",(0,s.jsx)(n.h3,{id:"entity",children:"Entity"}),"\n",(0,s.jsxs)(n.p,{children:["An ",(0,s.jsx)(n.strong,{children:"Entity"})," represents a logical grouping of related features, typically corresponding to a business object (e.g., User, Product, Transaction). Entities provide:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Namespace"}),": Logical separation of feature groups"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Identity"}),": Primary key definition for feature lookup"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Configuration"}),": Cache settings and storage preferences"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"feature-group",children:"Feature Group"}),"\n",(0,s.jsxs)(n.p,{children:["A ",(0,s.jsx)(n.strong,{children:"Feature Group"})," is a collection of related features that share:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Common Entity"}),": All features belong to the same entity"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Storage Configuration"}),": Same underlying storage and caching strategy"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Data Lifecycle"}),": Shared TTL and retention policies"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Access Patterns"}),": Similar read/write characteristics"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"feature",children:"Feature"}),"\n",(0,s.jsxs)(n.p,{children:["A ",(0,s.jsx)(n.strong,{children:"Feature"})," is an individual data point that can be retrieved for machine learning models. Each feature has:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Name"}),": Unique identifier within its feature group"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Data Type"}),": The type of data stored (string, integer, float, etc.)"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Default Value"}),": Value returned when feature data is not available"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Source Mapping"}),": How the feature maps to underlying storage columns"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"job",children:"Job"}),"\n",(0,s.jsxs)(n.p,{children:["A ",(0,s.jsx)(n.strong,{children:"Job"})," represents a data processing pipeline that:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Ingests Data"}),": Processes raw data from various sources"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Transforms Features"}),": Applies business logic and computations"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Updates Storage"}),": Writes processed features to the feature store"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Scheduling"}),": Defines when and how often the job runs"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"configuration-hierarchy",children:"Configuration Hierarchy"}),"\n",(0,s.jsx)(n.p,{children:"The system uses a hierarchical configuration approach:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"Store \u2192 Entity \u2192 Feature Group \u2192 Feature\n \u2193 \u2193 \u2193 \u2193\nConfig Identity Collection Individual\nLevel Level Level Level\n"})}),"\n",(0,s.jsx)(n.p,{children:"This hierarchy allows for:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Inheritance"}),": Lower levels inherit settings from higher levels"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Override"}),": Specific configurations can be overridden at each level"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Flexibility"}),": Different storage strategies for different use cases"]}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h1,{id:"trufflebox-ui-guide",children:"TruffleBox UI Guide"}),"\n",(0,s.jsx)(n.p,{children:"TruffleBox is a comprehensive and intuitive UI to help users onboard new features, models and related entities easily. We will build iteratively and add support overtime for entire feature lifecycle management."}),"\n",(0,s.jsx)(n.h2,{id:"table-of-contents-1",children:"Table of Contents"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"#user-flow",children:"User Flow"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"#getting-started-with-trufflebox",children:"Getting Started with TruffleBox"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"#feature-discovery",children:"Feature Discovery"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"#feature-registry",children:"Feature Registry"})}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"#admin-approval-flow",children:"Admin Approval Flow"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"#request-management",children:"Request Management"})}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"user-flow",children:"User Flow"}),"\n",(0,s.jsx)(n.h3,{id:"getting-started-with-trufflebox",children:"Getting Started with TruffleBox"}),"\n",(0,s.jsx)(n.h4,{id:"authentication",children:"Authentication"}),"\n",(0,s.jsx)(n.p,{children:"Users can access TruffleBox through registration or login:"}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Registration"}),":"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"New users should fill in all details and click Register."}),"\n",(0,s.jsx)(n.li,{children:"Once Registered, Please wait for an admin to activate your User"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Registration Screen",src:i(4125).A+"",width:"3438",height:"1690"})}),"\n",(0,s.jsx)(n.h4,{id:"user-management",children:"User Management"}),"\n",(0,s.jsx)(n.p,{children:"Admin users can manage other users through the User Management interface:"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"User Management",src:i(6387).A+"",width:"3398",height:"1676"})}),"\n",(0,s.jsx)(n.p,{children:"In the User Management page, admins can:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"View all registered users"}),"\n",(0,s.jsx)(n.li,{children:"Activate/deactivate user accounts"}),"\n",(0,s.jsx)(n.li,{children:"Modify user roles"}),"\n",(0,s.jsx)(n.li,{children:"Manage user permissions"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"This is a crucial step in the user onboarding process as new users must be activated by an admin before they can log in to the system."}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Login"}),": Existing users can login with their registered email and password."]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Login Screen",src:i(3239).A+"",width:"3438",height:"1690"})}),"\n",(0,s.jsx)(n.h4,{id:"navigation",children:"Navigation"}),"\n",(0,s.jsx)(n.p,{children:"After logging in, you'll be redirected to the feature-discovery page. Access the Control Center by clicking the hamburger icon in the top left corner."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Control Center Navigation",src:i(5352).A+"",width:"3450",height:"1700"})}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h3,{id:"feature-discovery",children:"Feature Discovery"}),"\n",(0,s.jsx)(n.p,{children:"The Feature Discovery page displays approved entities, feature groups, and features."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Feature Discovery Landing Page",src:i(6063).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsx)(n.p,{children:"You can:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"View details by clicking the info icon"}),"\n",(0,s.jsx)(n.li,{children:"Edit entities, feature groups, and features as needed"}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"entity-management",children:"Entity Management"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Entity Details",src:i(8162).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsx)(n.p,{children:'View entity details and edit them (limited to In Memory Cache and Distributed Cache details excluding config ID). Submit changes via "Save Changes" to raise an edit request.'}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Edit Entity",src:i(9992).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsx)(n.h4,{id:"feature-group-management",children:"Feature Group Management"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Feature Group Details",src:i(598).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsx)(n.p,{children:"Edit feature groups (TTL, In-Memory Cache Enabled, Distributed Cache Enabled, Layout Version) and submit changes to raise an edit request."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Edit Feature Group",src:i(4664).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsx)(n.h4,{id:"feature-management",children:"Feature Management"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Feature Details",src:i(1983).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsx)(n.p,{children:"Edit features (Default Value, Source Base Path, Source Data Column, Storage Provider) and submit changes to raise an edit request."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Edit Features",src:i(2564).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsx)(n.h4,{id:"store-discovery",children:"Store Discovery"}),"\n",(0,s.jsx)(n.p,{children:"Access Store Discovery from the Control Center to view all stores in the database."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Store Discovery",src:i(5350).A+"",width:"3456",height:"1680"})}),"\n",(0,s.jsx)(n.p,{children:"You can search for specific stores but have view-only access."}),"\n",(0,s.jsx)(n.h4,{id:"job-discovery",children:"Job Discovery"}),"\n",(0,s.jsx)(n.p,{children:"Access Job Discovery from the Control Center to view all jobs in the database."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Job Discovery",src:i(4440).A+"",width:"3456",height:"1680"})}),"\n",(0,s.jsx)(n.p,{children:"You can search for specific jobs but have view-only access."}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h3,{id:"feature-registry",children:"Feature Registry"}),"\n",(0,s.jsx)(n.p,{children:"In the Control Center, find the 'Feature Registry' accordion to access various registry options for component registration."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Feature Registry Accordion",src:i(5352).A+"",width:"3450",height:"1700"})}),"\n",(0,s.jsx)(n.h4,{id:"request-status-tracking",children:"Request Status Tracking"}),"\n",(0,s.jsx)(n.p,{children:"After raising a request, track its status in the respective registry page. For rejected requests, view the rejection reason by clicking the info icon in the Actions column."}),"\n",(0,s.jsx)(n.h4,{id:"step-by-step-registration-guide",children:"Step-by-Step Registration Guide"}),"\n",(0,s.jsx)(n.p,{children:"For proper feature lifecycle management, register components in this order:"}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsx)(n.li,{children:"Store"}),"\n",(0,s.jsx)(n.li,{children:"Job"}),"\n",(0,s.jsx)(n.li,{children:"Entity"}),"\n",(0,s.jsx)(n.li,{children:"Feature Group"}),"\n",(0,s.jsx)(n.li,{children:"Features (if not added during Feature Group registration)"}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"store-registry",children:"Store Registry"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Register Store",src:i(3849).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsx)(n.p,{children:"Access Store Registry from the Control Center to view raised requests and register new stores. Fill required data and submit to raise a request."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Store Details",src:i(9352).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"Important Considerations:"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Always add primary keys for proper data identification"}),"\n",(0,s.jsx)(n.li,{children:"Accurate store configuration is crucial as changes later can be complex"}),"\n",(0,s.jsx)(n.li,{children:"Admin approval creates a database table with your configuration"}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"job-registry",children:"Job Registry"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Create Job",src:i(239).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsx)(n.p,{children:"Access Job Registry from the Control Center to view raised requests and create new jobs. Fill required data and submit your request."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Job Details",src:i(8610).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsx)(n.p,{children:"Ensure job details are accurate before proceeding to Entity Registry."}),"\n",(0,s.jsx)(n.h4,{id:"entity-registry",children:"Entity Registry"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Create Entity",src:i(9013).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsx)(n.p,{children:"Access Entity Registry from the Control Center to view raised requests and create new entities. Fill required data and submit your request."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Entity Detail View",src:i(6172).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"Important Considerations:"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Ensure entity details align with your data model"}),"\n",(0,s.jsx)(n.li,{children:"The entity serves as a logical container for feature groups"}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"feature-group-registry",children:"Feature Group Registry"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Create Feature Group",src:i(2805).A+"",width:"3456",height:"1680"})}),"\n",(0,s.jsx)(n.p,{children:"Access Feature Group Registry from the Control Center to view raised requests and create new feature groups. Fill required data and submit your request."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Feature Group Detail View",src:i(1420).A+"",width:"3456",height:"1680"})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"Important Considerations:"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Primary keys must match the store primary keys"}),"\n",(0,s.jsx)(n.li,{children:"TTL settings determine how long feature data is stored"}),"\n",(0,s.jsx)(n.li,{children:"Configure cache settings based on access patterns"}),"\n",(0,s.jsx)(n.li,{children:"Approved feature groups automatically add necessary columns to the database table"}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"feature-addition",children:"Feature Addition"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Add Features",src:i(3581).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsx)(n.p,{children:"Access Feature Addition from the Control Center to view raised requests and add new features. Fill required data and submit your request."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Feature Detail View",src:i(7540).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"Important Considerations:"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Ensure feature data types are compatible with source data"}),"\n",(0,s.jsx)(n.li,{children:"Set appropriate default values and correct source data column mapping"}),"\n",(0,s.jsx)(n.li,{children:"Approved features automatically add columns to the database table"}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"need-help",children:"Need Help?"}),"\n",(0,s.jsx)(n.p,{children:"Please reach out to the BharatMLStack core team for any questions about using TruffleBox."}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"admin-approval-flow",children:"Admin Approval Flow"}),"\n",(0,s.jsx)(n.p,{children:"As an admin, you're responsible for reviewing and managing user requests."}),"\n",(0,s.jsx)(n.h3,{id:"request-management",children:"Request Management"}),"\n",(0,s.jsx)(n.h4,{id:"viewing-all-requests",children:"Viewing All Requests"}),"\n",(0,s.jsx)(n.p,{children:"After logging in as an admin, you can see all pending requests across different components (Stores, Jobs, Entities, Feature Groups, Features)."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Admin Dashboard",src:i(3247).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsx)(n.h4,{id:"request-approval-process",children:"Request Approval Process"}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Review Details"}),": Click the info icon to view complete request details"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Request Details",src:i(3247).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsxs)(n.ol,{start:"2",children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Approval Option"}),": After review, use the approve/reject buttons"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Approval Buttons",src:i(3247).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsxs)(n.ol,{start:"3",children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Approval Process"}),":",(0,s.jsx)(n.br,{}),"\n",'Click "Approve" to process the request. The system will create database tables or add columns as needed. A success message confirms completion.']}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Approval Success",src:i(3247).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsxs)(n.ol,{start:"4",children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Rejection Process"}),":",(0,s.jsx)(n.br,{}),"\n",'Click "Reject" to deny a request. Provide a rejection reason to help users understand why their request wasn\'t approved.']}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Rejection Reason",src:i(2538).A+"",width:"3456",height:"1680"})}),"\n",(0,s.jsx)(n.p,{children:"Users can view the rejection reason in their respective registry page."}),"\n",(0,s.jsx)(n.h4,{id:"admin-support",children:"Admin Support"}),"\n",(0,s.jsx)(n.p,{children:"If you need assistance with admin functions, please contact the BharatMLStack core team."}),"\n",(0,s.jsx)(n.h2,{id:"contributing",children:"Contributing"}),"\n",(0,s.jsxs)(n.p,{children:["We welcome contributions from the community! Please see our ",(0,s.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTING.md",children:"Contributing Guide"})," for details on how to get started."]}),"\n",(0,s.jsx)(n.h2,{id:"community--support",children:"Community & Support"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["\ud83d\udcac ",(0,s.jsx)(n.strong,{children:"Discord"}),": Join our ",(0,s.jsx)(n.a,{href:"https://discord.gg/XkT7XsV2AU",children:"community chat"})]}),"\n",(0,s.jsxs)(n.li,{children:["\ud83d\udc1b ",(0,s.jsx)(n.strong,{children:"Issues"}),": Report bugs and request features on ",(0,s.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/issues",children:"GitHub Issues"})]}),"\n",(0,s.jsxs)(n.li,{children:["\ud83d\udce7 ",(0,s.jsx)(n.strong,{children:"Email"}),": Contact us at ",(0,s.jsx)(n.a,{href:"mailto:ml-oss@meesho.com",children:"ml-oss@meesho.com"})]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"license",children:"License"}),"\n",(0,s.jsxs)(n.p,{children:["BharatMLStack is open-source software licensed under the ",(0,s.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md",children:"BharatMLStack Business Source License 1.1"}),"."]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)("div",{align:"center",children:(0,s.jsx)("strong",{children:"Built with \u2764\ufe0f for the ML community from Meesho"})}),"\n",(0,s.jsx)("div",{align:"center",children:(0,s.jsx)("strong",{children:"If you find this useful, \u2b50\ufe0f the repo \u2014 your support means the world to us!"})})]})}function h(e={}){const{wrapper:n}={...(0,t.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(d,{...e})}):d(e)}},1420:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-register-fg-details-1b1100bbb5d23fac31414b15f2a59366.png"},1983:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-feature-discovery-feature-details-b780eb1ede246eb257862a46f0fdb53e.png"},2538:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-reject-popup-9941183f1128e19034f41970d218d72f.png"},2564:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-edit-features-41cb78c09d70203c166fce91976d2ba0.png"},2805:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-register-fg-9c3b22e62b389f2c1baf968a6e201964.png"},3239:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-login-de1cbf15b2daa5c532875a94a4ad1a47.png"},3247:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-approve-store-1057c0853f92becfa9b1f87d165a72f9.png"},3581:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-add-features-6cb39960d91af3ee1c896492188cfcb5.png"},3849:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-register-store-d6f80ceb9a6570b225bba4653ac22dd8.png"},4125:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-registration-aed7738afc652b6418bdc00966850ec0.png"},4440:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-job-discovery-3fac78c4b09b6c76a7bc1dd0738cc93d.png"},4664:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-edit-fg-edc1a8999700e5c1e9ff023fe9f6413f.png"},5350:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-store-discovery-8c9042352255fff36b35b4aa193583f7.png"},5352:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-navigation-0e472fd13ccdae9448011eb9aebb990e.png"},6063:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-feature-discovery-c3a8456bb04479842666120a0ec082e6.png"},6172:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-register-entity-details-016ab5c5b2fef9f58bde75e6a07c9823.png"},6387:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-user-management-2c50fa8488f21ff07b9925c48a10f7cd.png"},7540:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-add-features-details-278a519cdfe25bead880d7a18e0b858e.png"},8162:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-feature-discovery-entity-details-839bb44b2cd99129eeb0ee785d19152c.png"},8453:(e,n,i)=>{i.d(n,{R:()=>a,x:()=>o});var r=i(6540);const s={},t=r.createContext(s);function a(e){const n=r.useContext(t);return r.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:a(e.components),r.createElement(t.Provider,{value:n},e.children)}},8610:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-register-job-details-075436efba1df107ac7e42164ff6494a.png"},9013:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-register-entity-fe6449f47304e0377107d8e5b3ce1d30.png"},9352:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-register-store-details-a36537beae9ac91576186b193e858112.png"},9992:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-edit-entity-0c3bb1263b53ed678ae2f9310441f3d7.png"}}]); \ No newline at end of file diff --git a/docs/assets/js/176d210f.cd62be36.js b/docs/assets/js/176d210f.cd62be36.js new file mode 100644 index 00000000..96438f5c --- /dev/null +++ b/docs/assets/js/176d210f.cd62be36.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[6100],{73:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-add-features-details-278a519cdfe25bead880d7a18e0b858e.png"},351:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-register-entity-details-016ab5c5b2fef9f58bde75e6a07c9823.png"},753:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>l,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>r,toc:()=>c});const r=JSON.parse('{"id":"trufflebox-ui/v1.0.0/userguide","title":"User Manual","description":"This guide covers the complete setup and usage of the Online Feature Store system, including the core services (Online Feature Store and Horizon) and the TruffleBox UI for feature management.","source":"@site/docs/trufflebox-ui/v1.0.0/userguide.md","sourceDirName":"trufflebox-ui/v1.0.0","slug":"/trufflebox-ui/v1.0.0/userguide","permalink":"/BharatMLStack/trufflebox-ui/v1.0.0/userguide","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/trufflebox-ui/v1.0.0/userguide.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"User Manual","sidebar_position":1},"sidebar":"tutorialSidebar","previous":{"title":"v1.0.0","permalink":"/BharatMLStack/trufflebox-ui/v1.0.0"},"next":{"title":"SDKs","permalink":"/BharatMLStack/category/sdks"}}');var s=i(4848),t=i(8453);const a={title:"User Manual",sidebar_position:1},o="Usage Guide",l={},c=[{value:"Table of Contents",id:"table-of-contents",level:2},{value:"System Overview",id:"system-overview",level:2},{value:"Environment Setup",id:"environment-setup",level:2},{value:"Online Feature Store Configuration",id:"online-feature-store-configuration",level:3},{value:"Core Application Settings",id:"core-application-settings",level:4},{value:"Storage Configuration",id:"storage-configuration",level:4},{value:"Caching Configuration",id:"caching-configuration",level:4},{value:"Service Discovery and Configuration",id:"service-discovery-and-configuration",level:4},{value:"Horizon Configuration",id:"horizon-configuration",level:3},{value:"Core Application Settings",id:"core-application-settings-1",level:4},{value:"Database Configuration",id:"database-configuration",level:4},{value:"ScyllaDB Configuration",id:"scylladb-configuration",level:4},{value:"Service Integration",id:"service-integration",level:4},{value:"Key Constructs",id:"key-constructs",level:2},{value:"Store ID",id:"store-id",level:3},{value:"Entity",id:"entity",level:3},{value:"Feature Group",id:"feature-group",level:3},{value:"Feature",id:"feature",level:3},{value:"Job",id:"job",level:3},{value:"Configuration Hierarchy",id:"configuration-hierarchy",level:3},{value:"Table of Contents",id:"table-of-contents-1",level:2},{value:"User Flow",id:"user-flow",level:2},{value:"Getting Started with TruffleBox",id:"getting-started-with-trufflebox",level:3},{value:"Authentication",id:"authentication",level:4},{value:"User Management",id:"user-management",level:4},{value:"Navigation",id:"navigation",level:4},{value:"Feature Discovery",id:"feature-discovery",level:3},{value:"Entity Management",id:"entity-management",level:4},{value:"Feature Group Management",id:"feature-group-management",level:4},{value:"Feature Management",id:"feature-management",level:4},{value:"Store Discovery",id:"store-discovery",level:4},{value:"Job Discovery",id:"job-discovery",level:4},{value:"Feature Registry",id:"feature-registry",level:3},{value:"Request Status Tracking",id:"request-status-tracking",level:4},{value:"Step-by-Step Registration Guide",id:"step-by-step-registration-guide",level:4},{value:"Store Registry",id:"store-registry",level:4},{value:"Job Registry",id:"job-registry",level:4},{value:"Entity Registry",id:"entity-registry",level:4},{value:"Feature Group Registry",id:"feature-group-registry",level:4},{value:"Feature Addition",id:"feature-addition",level:4},{value:"Need Help?",id:"need-help",level:4},{value:"Admin Approval Flow",id:"admin-approval-flow",level:2},{value:"Request Management",id:"request-management",level:3},{value:"Viewing All Requests",id:"viewing-all-requests",level:4},{value:"Request Approval Process",id:"request-approval-process",level:4},{value:"Admin Support",id:"admin-support",level:4},{value:"Contributing",id:"contributing",level:2},{value:"Community & Support",id:"community--support",level:2},{value:"License",id:"license",level:2}];function d(e){const n={a:"a",br:"br",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",hr:"hr",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,t.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.header,{children:(0,s.jsx)(n.h1,{id:"usage-guide",children:"Usage Guide"})}),"\n",(0,s.jsx)(n.p,{children:"This guide covers the complete setup and usage of the Online Feature Store system, including the core services (Online Feature Store and Horizon) and the TruffleBox UI for feature management."}),"\n",(0,s.jsx)(n.h2,{id:"table-of-contents",children:"Table of Contents"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"#system-overview",children:"System Overview"})}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"#environment-setup",children:"Environment Setup"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"#online-feature-store-configuration",children:"Online Feature Store Configuration"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"#horizon-configuration",children:"Horizon Configuration"})}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"#key-constructs",children:"Key Constructs"})}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"#trufflebox-ui-guide",children:"TruffleBox UI Guide"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"#user-flow",children:"User Flow"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"#admin-approval-flow",children:"Admin Approval Flow"})}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"system-overview",children:"System Overview"}),"\n",(0,s.jsx)(n.p,{children:"The Online Feature Store is a comprehensive feature management system consisting of two main components:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Online Feature Store"}),": The core feature serving service that provides real-time feature retrieval with multiple storage backends and caching layers"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Horizon"}),": The configuration and metadata management service that handles feature definitions, stores, and job configurations"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"These services work together to provide a scalable, high-performance feature store for machine learning applications."}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"environment-setup",children:"Environment Setup"}),"\n",(0,s.jsx)(n.h3,{id:"online-feature-store-configuration",children:"Online Feature Store Configuration"}),"\n",(0,s.jsx)(n.p,{children:"The Online Feature Store requires several environment variables to configure storage backends, caching, and service settings."}),"\n",(0,s.jsx)(n.h4,{id:"core-application-settings",children:"Core Application Settings"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"APP_ENV=prod\nAPP_LOG_LEVEL=DEBUG\nAPP_METRIC_SAMPLING_RATE=1\nAPP_NAME=online-feature-store\nAPP_PORT=8005\nAUTH_TOKEN=ofs-token\n"})}),"\n",(0,s.jsx)(n.h4,{id:"storage-configuration",children:"Storage Configuration"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"ScyllaDB Storage (Primary Storage)"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"# Primary ScyllaDB cluster\nSTORAGE_SCYLLA_1_CONTACT_POINTS=localhost\nSTORAGE_SCYLLA_1_KEYSPACE=ofs\nSTORAGE_SCYLLA_1_NUM_CONNS=1\nSTORAGE_SCYLLA_1_PORT=9042\nSTORAGE_SCYLLA_1_TIMEOUT_IN_MS=300000\nSTORAGE_SCYLLA_1_PASSWORD=\nSTORAGE_SCYLLA_1_USERNAME=ofs\n\n# Secondary ScyllaDB cluster\nSTORAGE_SCYLLA_5_CONTACT_POINTS=localhost\nSTORAGE_SCYLLA_5_KEYSPACE=onfs\nSTORAGE_SCYLLA_5_NUM_CONNS=1\nSTORAGE_SCYLLA_5_PASSWORD=\nSTORAGE_SCYLLA_5_PORT=9042\nSTORAGE_SCYLLA_5_TIMEOUT_IN_MS=300000\nSTORAGE_SCYLLA_5_USERNAME=\n\n# Active ScyllaDB configurations\nSTORAGE_SCYLLA_ACTIVE_CONFIG_IDS=1,5\n"})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"Redis Storage Configuration"})}),"\n",(0,s.jsx)(n.p,{children:"Redis serves dual purposes in the Online Feature Store:"}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Primary Storage Backend"}),": For fast feature retrieval and storage"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Distributed Cache Layer"}),": For improved performance and reduced latency"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Redis configurations can be referenced by their IDs in Store configurations, similar to ScyllaDB. Each Redis configuration can be independently used as either a storage backend or cache layer."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"# Redis Failover Configuration 1 (ID: 2)\nSTORAGE_REDIS_FAILOVER_2_SENTINEL_ADDRESSES=localhost:26379\nSTORAGE_REDIS_FAILOVER_2_DB=0\nSTORAGE_REDIS_FAILOVER_2_DISABLE_IDENTITY=true\nSTORAGE_REDIS_FAILOVER_2_MASTER_NAME=mymaster\nSTORAGE_REDIS_FAILOVER_2_MAX_IDLE_CONN=32\nSTORAGE_REDIS_FAILOVER_2_MIN_IDLE_CONN=20\nSTORAGE_REDIS_FAILOVER_2_MAX_ACTIVE_CONN=32\nSTORAGE_REDIS_FAILOVER_2_MAX_RETRY=-1\nSTORAGE_REDIS_FAILOVER_2_POOL_FIFO=false\nSTORAGE_REDIS_FAILOVER_2_READ_TIMEOUT_IN_MS=3000\nSTORAGE_REDIS_FAILOVER_2_WRITE_TIMEOUT_IN_MS=3000\nSTORAGE_REDIS_FAILOVER_2_POOL_TIMEOUT_IN_MS=3000\nSTORAGE_REDIS_FAILOVER_2_POOL_SIZE=32\nSTORAGE_REDIS_FAILOVER_2_CONN_MAX_IDLE_TIMEOUT_IN_MINUTES=15\nSTORAGE_REDIS_FAILOVER_2_CONN_MAX_AGE_IN_MINUTES=30\n\n# Redis Failover Configuration 2 (ID: 4)\nSTORAGE_REDIS_FAILOVER_4_SENTINEL_ADDRESSES=localhost:26379\nSTORAGE_REDIS_FAILOVER_4_DB=0\nSTORAGE_REDIS_FAILOVER_4_DISABLE_IDENTITY=true\nSTORAGE_REDIS_FAILOVER_4_MASTER_NAME=mymaster\nSTORAGE_REDIS_FAILOVER_4_MAX_IDLE_CONN=32\nSTORAGE_REDIS_FAILOVER_4_MIN_IDLE_CONN=20\nSTORAGE_REDIS_FAILOVER_4_MAX_ACTIVE_CONN=32\nSTORAGE_REDIS_FAILOVER_4_MAX_RETRY=-1\nSTORAGE_REDIS_FAILOVER_4_POOL_FIFO=false\nSTORAGE_REDIS_FAILOVER_4_READ_TIMEOUT_IN_MS=3000\nSTORAGE_REDIS_FAILOVER_4_WRITE_TIMEOUT_IN_MS=3000\nSTORAGE_REDIS_FAILOVER_4_POOL_TIMEOUT_IN_MS=3000\nSTORAGE_REDIS_FAILOVER_4_POOL_SIZE=32\nSTORAGE_REDIS_FAILOVER_4_CONN_MAX_IDLE_TIMEOUT_IN_MINUTES=15\nSTORAGE_REDIS_FAILOVER_4_CONN_MAX_AGE_IN_MINUTES=30\n\n# High-Performance Redis Configuration (ID: 6)\nSTORAGE_REDIS_FAILOVER_6_CONN_MAX_AGE_IN_MINUTES=-1\nSTORAGE_REDIS_FAILOVER_6_CONN_MAX_IDLE_TIMEOUT_IN_MINUTES=30\nSTORAGE_REDIS_FAILOVER_6_DB=0\nSTORAGE_REDIS_FAILOVER_6_DISABLE_IDENTITY=true\nSTORAGE_REDIS_FAILOVER_6_MASTER_NAME=mymaster\nSTORAGE_REDIS_FAILOVER_6_MAX_ACTIVE_CONN=202\nSTORAGE_REDIS_FAILOVER_6_MAX_IDLE_CONN=157\nSTORAGE_REDIS_FAILOVER_6_MAX_RETRY=-1\nSTORAGE_REDIS_FAILOVER_6_MIN_IDLE_CONN=52\nSTORAGE_REDIS_FAILOVER_6_PASSWORD=\nSTORAGE_REDIS_FAILOVER_6_POOL_FIFO=false\nSTORAGE_REDIS_FAILOVER_6_POOL_SIZE=202\nSTORAGE_REDIS_FAILOVER_6_POOL_TIMEOUT_IN_MS=2\nSTORAGE_REDIS_FAILOVER_6_READ_TIMEOUT_IN_MS=75\nSTORAGE_REDIS_FAILOVER_6_ROUTE_RANDOM=true\nSTORAGE_REDIS_FAILOVER_6_SENTINEL_ADDRESSES=localhost:26379\nSTORAGE_REDIS_FAILOVER_6_WRITE_TIMEOUT_IN_MS=300\n\n# Active Redis configurations\nSTORAGE_REDIS_FAILOVER_ACTIVE_CONFIG_IDS=2,4,6\n"})}),"\n",(0,s.jsx)(n.h4,{id:"caching-configuration",children:"Caching Configuration"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"# In-Memory Cache\nIN_MEM_CACHE_3_ENABLED=true\nIN_MEM_CACHE_3_NAME=onfs\nIN_MEM_CACHE_3_SIZE_IN_BYTES=10000000\nIN_MEM_CACHE_ACTIVE_CONFIG_IDS=3\n\n# Distributed Cache (uses Redis configurations)\n# Redis configurations (IDs: 2,4,6) can be used for distributed caching\nDISTRIBUTED_CACHE_CONF_IDS=2\n"})}),"\n",(0,s.jsx)(n.h4,{id:"service-discovery-and-configuration",children:"Service Discovery and Configuration"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"# ETCD Configuration for service discovery\nETCD_SERVER=0.0.0.0:2379\nETCD_WATCHER_ENABLED=true\n"})}),"\n",(0,s.jsx)(n.h3,{id:"horizon-configuration",children:"Horizon Configuration"}),"\n",(0,s.jsx)(n.p,{children:"Horizon manages the metadata and configuration for the Online Feature Store system."}),"\n",(0,s.jsx)(n.h4,{id:"core-application-settings-1",children:"Core Application Settings"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"APP_NAME=horizon\nAPP_ENVIRONMENT=PROD\nAPP_ENV=production\nAPP_PORT=8082\nAPP_LOG_LEVEL=DEBUG\nAPP_METRIC_SAMPLING_RATE=1\nAPP_GC_PERCENTAGE=1\n"})}),"\n",(0,s.jsx)(n.h4,{id:"database-configuration",children:"Database Configuration"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"# MySQL Master Configuration\nMYSQL_MASTER_MAX_POOL_SIZE=5\nMYSQL_MASTER_MIN_POOL_SIZE=2\nMYSQL_MASTER_PASSWORD=\nMYSQL_MASTER_HOST=127.0.0.1\nMYSQL_MASTER_PORT=3306\nMYSQL_DB_NAME=ml_config\nMYSQL_MASTER_USERNAME=root\n\n# MySQL Slave Configuration\nMYSQL_SLAVE_MAX_POOL_SIZE=5\nMYSQL_SLAVE_MIN_POOL_SIZE=2\nMYSQL_SLAVE_PASSWORD=\nMYSQL_SLAVE_HOST=127.0.0.1\nMYSQL_SLAVE_USERNAME=root\nMYSQL_SLAVE_PORT=3306\n"})}),"\n",(0,s.jsx)(n.h4,{id:"scylladb-configuration",children:"ScyllaDB Configuration"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"# ScyllaDB for Horizon\nSCYLLA_1_CONTACT_POINTS=localhost\nSCYLLA_1_KEYSPACE=onfs\nSCYLLA_1_NUM_CONNS=1\nSCYLLA_1_PORT=9042\nSCYLLA_1_TIMEOUT_IN_MS=300000\nSCYLLA_1_PASSWORD=\nSCYLLA_1_USERNAME=\nSCYLLA_ACTIVE_CONFIG_IDS=1\n"})}),"\n",(0,s.jsx)(n.h4,{id:"service-integration",children:"Service Integration"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"# ETCD Configuration\nETCD_WATCHER_ENABLED=true\nETCD_SERVER=localhost:2379\n\n# Integration with Online Feature Store\nONLINE_FEATURE_STORE_APP_NAME=online-feature-store\n"})}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"key-constructs",children:"Key Constructs"}),"\n",(0,s.jsx)(n.p,{children:"Understanding these key constructs is essential for effectively using the Online Feature Store:"}),"\n",(0,s.jsx)(n.h3,{id:"store-id",children:"Store ID"}),"\n",(0,s.jsxs)(n.p,{children:["A ",(0,s.jsx)(n.strong,{children:"Store ID"})," is a unique identifier that represents a data storage configuration within the system. It defines:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Storage Backend"}),": Which underlying storage system (ScyllaDB, Redis, etc.) to use"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Configuration Parameters"}),": Connection settings, timeouts, pool sizes"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Access Patterns"}),": How data is read from and written to the store"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Store IDs are referenced throughout the system to:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Route feature requests to the appropriate storage backend"}),"\n",(0,s.jsx)(n.li,{children:"Apply specific caching strategies"}),"\n",(0,s.jsx)(n.li,{children:"Manage data lifecycle and retention policies"}),"\n",(0,s.jsx)(n.li,{children:"Configure stores in TruffleBox UI for feature groups and entities"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"Storage Backend Configuration:"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"ScyllaDB Store IDs"}),": ",(0,s.jsx)(n.code,{children:"STORAGE_SCYLLA_ACTIVE_CONFIG_IDS=1,5"})," indicates ScyllaDB configurations with IDs 1 and 5 are active"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Redis Store IDs"}),": ",(0,s.jsx)(n.code,{children:"STORAGE_REDIS_FAILOVER_ACTIVE_CONFIG_IDS=2,4,6"})," indicates Redis configurations with IDs 2, 4, and 6 are active"]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Dual Usage of Redis:"}),"\nRedis configurations can serve dual purposes:"]}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"As Storage Backend"}),": Redis IDs (2,4,6) can be configured as primary storage in Store configurations"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"As Distributed Cache"}),": Same Redis IDs can be used for caching via ",(0,s.jsx)(n.code,{children:"DISTRIBUTED_CACHE_CONF_IDS=2"})]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"When creating stores in TruffleBox, you can reference these storage configuration IDs to determine which backend (ScyllaDB ID 1/5 or Redis ID 2/4/6) will be used for your feature data."}),"\n",(0,s.jsx)(n.h3,{id:"entity",children:"Entity"}),"\n",(0,s.jsxs)(n.p,{children:["An ",(0,s.jsx)(n.strong,{children:"Entity"})," represents a logical grouping of related features, typically corresponding to a business object (e.g., User, Product, Transaction). Entities provide:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Namespace"}),": Logical separation of feature groups"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Identity"}),": Primary key definition for feature lookup"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Configuration"}),": Cache settings and storage preferences"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"feature-group",children:"Feature Group"}),"\n",(0,s.jsxs)(n.p,{children:["A ",(0,s.jsx)(n.strong,{children:"Feature Group"})," is a collection of related features that share:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Common Entity"}),": All features belong to the same entity"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Storage Configuration"}),": Same underlying storage and caching strategy"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Data Lifecycle"}),": Shared TTL and retention policies"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Access Patterns"}),": Similar read/write characteristics"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"feature",children:"Feature"}),"\n",(0,s.jsxs)(n.p,{children:["A ",(0,s.jsx)(n.strong,{children:"Feature"})," is an individual data point that can be retrieved for machine learning models. Each feature has:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Name"}),": Unique identifier within its feature group"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Data Type"}),": The type of data stored (string, integer, float, etc.)"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Default Value"}),": Value returned when feature data is not available"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Source Mapping"}),": How the feature maps to underlying storage columns"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"job",children:"Job"}),"\n",(0,s.jsxs)(n.p,{children:["A ",(0,s.jsx)(n.strong,{children:"Job"})," represents a data processing pipeline that:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Ingests Data"}),": Processes raw data from various sources"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Transforms Features"}),": Applies business logic and computations"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Updates Storage"}),": Writes processed features to the feature store"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Scheduling"}),": Defines when and how often the job runs"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"configuration-hierarchy",children:"Configuration Hierarchy"}),"\n",(0,s.jsx)(n.p,{children:"The system uses a hierarchical configuration approach:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"Store \u2192 Entity \u2192 Feature Group \u2192 Feature\n \u2193 \u2193 \u2193 \u2193\nConfig Identity Collection Individual\nLevel Level Level Level\n"})}),"\n",(0,s.jsx)(n.p,{children:"This hierarchy allows for:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Inheritance"}),": Lower levels inherit settings from higher levels"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Override"}),": Specific configurations can be overridden at each level"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Flexibility"}),": Different storage strategies for different use cases"]}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h1,{id:"trufflebox-ui-guide",children:"TruffleBox UI Guide"}),"\n",(0,s.jsx)(n.p,{children:"TruffleBox is a comprehensive and intuitive UI to help users onboard new features, models and related entities easily. We will build iteratively and add support overtime for entire feature lifecycle management."}),"\n",(0,s.jsx)(n.h2,{id:"table-of-contents-1",children:"Table of Contents"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"#user-flow",children:"User Flow"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"#getting-started-with-trufflebox",children:"Getting Started with TruffleBox"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"#feature-discovery",children:"Feature Discovery"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"#feature-registry",children:"Feature Registry"})}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"#admin-approval-flow",children:"Admin Approval Flow"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"#request-management",children:"Request Management"})}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"user-flow",children:"User Flow"}),"\n",(0,s.jsx)(n.h3,{id:"getting-started-with-trufflebox",children:"Getting Started with TruffleBox"}),"\n",(0,s.jsx)(n.h4,{id:"authentication",children:"Authentication"}),"\n",(0,s.jsx)(n.p,{children:"Users can access TruffleBox through registration or login:"}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Registration"}),":"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"New users should fill in all details and click Register."}),"\n",(0,s.jsx)(n.li,{children:"Once Registered, Please wait for an admin to activate your User"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Registration Screen",src:i(7184).A+"",width:"3438",height:"1690"})}),"\n",(0,s.jsx)(n.h4,{id:"user-management",children:"User Management"}),"\n",(0,s.jsx)(n.p,{children:"Admin users can manage other users through the User Management interface:"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"User Management",src:i(8224).A+"",width:"3398",height:"1676"})}),"\n",(0,s.jsx)(n.p,{children:"In the User Management page, admins can:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"View all registered users"}),"\n",(0,s.jsx)(n.li,{children:"Activate/deactivate user accounts"}),"\n",(0,s.jsx)(n.li,{children:"Modify user roles"}),"\n",(0,s.jsx)(n.li,{children:"Manage user permissions"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"This is a crucial step in the user onboarding process as new users must be activated by an admin before they can log in to the system."}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Login"}),": Existing users can login with their registered email and password."]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Login Screen",src:i(3620).A+"",width:"3438",height:"1690"})}),"\n",(0,s.jsx)(n.h4,{id:"navigation",children:"Navigation"}),"\n",(0,s.jsx)(n.p,{children:"After logging in, you'll be redirected to the feature-discovery page. Access the Control Center by clicking the hamburger icon in the top left corner."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Control Center Navigation",src:i(4525).A+"",width:"3450",height:"1700"})}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h3,{id:"feature-discovery",children:"Feature Discovery"}),"\n",(0,s.jsx)(n.p,{children:"The Feature Discovery page displays approved entities, feature groups, and features."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Feature Discovery Landing Page",src:i(2904).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsx)(n.p,{children:"You can:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"View details by clicking the info icon"}),"\n",(0,s.jsx)(n.li,{children:"Edit entities, feature groups, and features as needed"}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"entity-management",children:"Entity Management"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Entity Details",src:i(2399).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsx)(n.p,{children:'View entity details and edit them (limited to In Memory Cache and Distributed Cache details excluding config ID). Submit changes via "Save Changes" to raise an edit request.'}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Edit Entity",src:i(911).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsx)(n.h4,{id:"feature-group-management",children:"Feature Group Management"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Feature Group Details",src:i(7035).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsx)(n.p,{children:"Edit feature groups (TTL, In-Memory Cache Enabled, Distributed Cache Enabled, Layout Version) and submit changes to raise an edit request."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Edit Feature Group",src:i(3963).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsx)(n.h4,{id:"feature-management",children:"Feature Management"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Feature Details",src:i(6092).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsx)(n.p,{children:"Edit features (Default Value, Source Base Path, Source Data Column, Storage Provider) and submit changes to raise an edit request."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Edit Features",src:i(7943).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsx)(n.h4,{id:"store-discovery",children:"Store Discovery"}),"\n",(0,s.jsx)(n.p,{children:"Access Store Discovery from the Control Center to view all stores in the database."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Store Discovery",src:i(8689).A+"",width:"3456",height:"1680"})}),"\n",(0,s.jsx)(n.p,{children:"You can search for specific stores but have view-only access."}),"\n",(0,s.jsx)(n.h4,{id:"job-discovery",children:"Job Discovery"}),"\n",(0,s.jsx)(n.p,{children:"Access Job Discovery from the Control Center to view all jobs in the database."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Job Discovery",src:i(9095).A+"",width:"3456",height:"1680"})}),"\n",(0,s.jsx)(n.p,{children:"You can search for specific jobs but have view-only access."}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h3,{id:"feature-registry",children:"Feature Registry"}),"\n",(0,s.jsx)(n.p,{children:"In the Control Center, find the 'Feature Registry' accordion to access various registry options for component registration."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Feature Registry Accordion",src:i(4525).A+"",width:"3450",height:"1700"})}),"\n",(0,s.jsx)(n.h4,{id:"request-status-tracking",children:"Request Status Tracking"}),"\n",(0,s.jsx)(n.p,{children:"After raising a request, track its status in the respective registry page. For rejected requests, view the rejection reason by clicking the info icon in the Actions column."}),"\n",(0,s.jsx)(n.h4,{id:"step-by-step-registration-guide",children:"Step-by-Step Registration Guide"}),"\n",(0,s.jsx)(n.p,{children:"For proper feature lifecycle management, register components in this order:"}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsx)(n.li,{children:"Store"}),"\n",(0,s.jsx)(n.li,{children:"Job"}),"\n",(0,s.jsx)(n.li,{children:"Entity"}),"\n",(0,s.jsx)(n.li,{children:"Feature Group"}),"\n",(0,s.jsx)(n.li,{children:"Features (if not added during Feature Group registration)"}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"store-registry",children:"Store Registry"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Register Store",src:i(1644).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsx)(n.p,{children:"Access Store Registry from the Control Center to view raised requests and register new stores. Fill required data and submit to raise a request."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Store Details",src:i(1481).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"Important Considerations:"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Always add primary keys for proper data identification"}),"\n",(0,s.jsx)(n.li,{children:"Accurate store configuration is crucial as changes later can be complex"}),"\n",(0,s.jsx)(n.li,{children:"Admin approval creates a database table with your configuration"}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"job-registry",children:"Job Registry"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Create Job",src:i(3366).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsx)(n.p,{children:"Access Job Registry from the Control Center to view raised requests and create new jobs. Fill required data and submit your request."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Job Details",src:i(2791).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsx)(n.p,{children:"Ensure job details are accurate before proceeding to Entity Registry."}),"\n",(0,s.jsx)(n.h4,{id:"entity-registry",children:"Entity Registry"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Create Entity",src:i(9214).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsx)(n.p,{children:"Access Entity Registry from the Control Center to view raised requests and create new entities. Fill required data and submit your request."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Entity Detail View",src:i(351).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"Important Considerations:"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Ensure entity details align with your data model"}),"\n",(0,s.jsx)(n.li,{children:"The entity serves as a logical container for feature groups"}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"feature-group-registry",children:"Feature Group Registry"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Create Feature Group",src:i(1954).A+"",width:"3456",height:"1680"})}),"\n",(0,s.jsx)(n.p,{children:"Access Feature Group Registry from the Control Center to view raised requests and create new feature groups. Fill required data and submit your request."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Feature Group Detail View",src:i(2955).A+"",width:"3456",height:"1680"})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"Important Considerations:"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Primary keys must match the store primary keys"}),"\n",(0,s.jsx)(n.li,{children:"TTL settings determine how long feature data is stored"}),"\n",(0,s.jsx)(n.li,{children:"Configure cache settings based on access patterns"}),"\n",(0,s.jsx)(n.li,{children:"Approved feature groups automatically add necessary columns to the database table"}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"feature-addition",children:"Feature Addition"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Add Features",src:i(3532).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsx)(n.p,{children:"Access Feature Addition from the Control Center to view raised requests and add new features. Fill required data and submit your request."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Feature Detail View",src:i(73).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"Important Considerations:"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Ensure feature data types are compatible with source data"}),"\n",(0,s.jsx)(n.li,{children:"Set appropriate default values and correct source data column mapping"}),"\n",(0,s.jsx)(n.li,{children:"Approved features automatically add columns to the database table"}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"need-help",children:"Need Help?"}),"\n",(0,s.jsx)(n.p,{children:"Please reach out to the BharatMLStack core team for any questions about using TruffleBox."}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"admin-approval-flow",children:"Admin Approval Flow"}),"\n",(0,s.jsx)(n.p,{children:"As an admin, you're responsible for reviewing and managing user requests."}),"\n",(0,s.jsx)(n.h3,{id:"request-management",children:"Request Management"}),"\n",(0,s.jsx)(n.h4,{id:"viewing-all-requests",children:"Viewing All Requests"}),"\n",(0,s.jsx)(n.p,{children:"After logging in as an admin, you can see all pending requests across different components (Stores, Jobs, Entities, Feature Groups, Features)."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Admin Dashboard",src:i(6500).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsx)(n.h4,{id:"request-approval-process",children:"Request Approval Process"}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Review Details"}),": Click the info icon to view complete request details"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Request Details",src:i(6500).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsxs)(n.ol,{start:"2",children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Approval Option"}),": After review, use the approve/reject buttons"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Approval Buttons",src:i(6500).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsxs)(n.ol,{start:"3",children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Approval Process"}),":",(0,s.jsx)(n.br,{}),"\n",'Click "Approve" to process the request. The system will create database tables or add columns as needed. A success message confirms completion.']}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Approval Success",src:i(6500).A+"",width:"3450",height:"1690"})}),"\n",(0,s.jsxs)(n.ol,{start:"4",children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Rejection Process"}),":",(0,s.jsx)(n.br,{}),"\n",'Click "Reject" to deny a request. Provide a rejection reason to help users understand why their request wasn\'t approved.']}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Rejection Reason",src:i(6863).A+"",width:"3456",height:"1680"})}),"\n",(0,s.jsx)(n.p,{children:"Users can view the rejection reason in their respective registry page."}),"\n",(0,s.jsx)(n.h4,{id:"admin-support",children:"Admin Support"}),"\n",(0,s.jsx)(n.p,{children:"If you need assistance with admin functions, please contact the BharatMLStack core team."}),"\n",(0,s.jsx)(n.h2,{id:"contributing",children:"Contributing"}),"\n",(0,s.jsxs)(n.p,{children:["We welcome contributions from the community! Please see our ",(0,s.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTING.md",children:"Contributing Guide"})," for details on how to get started."]}),"\n",(0,s.jsx)(n.h2,{id:"community--support",children:"Community & Support"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["\ud83d\udcac ",(0,s.jsx)(n.strong,{children:"Discord"}),": Join our ",(0,s.jsx)(n.a,{href:"https://discord.gg/XkT7XsV2AU",children:"community chat"})]}),"\n",(0,s.jsxs)(n.li,{children:["\ud83d\udc1b ",(0,s.jsx)(n.strong,{children:"Issues"}),": Report bugs and request features on ",(0,s.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/issues",children:"GitHub Issues"})]}),"\n",(0,s.jsxs)(n.li,{children:["\ud83d\udce7 ",(0,s.jsx)(n.strong,{children:"Email"}),": Contact us at ",(0,s.jsx)(n.a,{href:"mailto:ml-oss@meesho.com",children:"ml-oss@meesho.com"})]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"license",children:"License"}),"\n",(0,s.jsxs)(n.p,{children:["BharatMLStack is open-source software licensed under the ",(0,s.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md",children:"BharatMLStack Business Source License 1.1"}),"."]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)("div",{align:"center",children:(0,s.jsx)("strong",{children:"Built with \u2764\ufe0f for the ML community from Meesho"})}),"\n",(0,s.jsx)("div",{align:"center",children:(0,s.jsx)("strong",{children:"If you find this useful, \u2b50\ufe0f the repo \u2014 your support means the world to us!"})})]})}function h(e={}){const{wrapper:n}={...(0,t.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(d,{...e})}):d(e)}},911:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-edit-entity-0c3bb1263b53ed678ae2f9310441f3d7.png"},1481:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-register-store-details-a36537beae9ac91576186b193e858112.png"},1644:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-register-store-d6f80ceb9a6570b225bba4653ac22dd8.png"},1954:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-register-fg-9c3b22e62b389f2c1baf968a6e201964.png"},2399:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-feature-discovery-entity-details-839bb44b2cd99129eeb0ee785d19152c.png"},2791:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-register-job-details-075436efba1df107ac7e42164ff6494a.png"},2904:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-feature-discovery-c3a8456bb04479842666120a0ec082e6.png"},2955:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-register-fg-details-1b1100bbb5d23fac31414b15f2a59366.png"},3366:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-register-job-e45c350f42a09adaeea50ef00d53df55.png"},3532:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-add-features-6cb39960d91af3ee1c896492188cfcb5.png"},3620:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-login-de1cbf15b2daa5c532875a94a4ad1a47.png"},3963:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-edit-fg-edc1a8999700e5c1e9ff023fe9f6413f.png"},4525:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-navigation-0e472fd13ccdae9448011eb9aebb990e.png"},6092:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-feature-discovery-feature-details-b780eb1ede246eb257862a46f0fdb53e.png"},6500:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-approve-store-1057c0853f92becfa9b1f87d165a72f9.png"},6863:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-reject-popup-9941183f1128e19034f41970d218d72f.png"},7035:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-feature-discovery-fg-details-a2dda4f72568878138e3b2d50fa20e8f.png"},7184:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-registration-aed7738afc652b6418bdc00966850ec0.png"},7943:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-edit-features-41cb78c09d70203c166fce91976d2ba0.png"},8224:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-user-management-2c50fa8488f21ff07b9925c48a10f7cd.png"},8453:(e,n,i)=>{i.d(n,{R:()=>a,x:()=>o});var r=i(6540);const s={},t=r.createContext(s);function a(e){const n=r.useContext(t);return r.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:a(e.components),r.createElement(t.Provider,{value:n},e.children)}},8689:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-store-discovery-8c9042352255fff36b35b4aa193583f7.png"},9095:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-job-discovery-3fac78c4b09b6c76a7bc1dd0738cc93d.png"},9214:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/v1.0.0-trufflebox-register-entity-fe6449f47304e0377107d8e5b3ce1d30.png"}}]); \ No newline at end of file diff --git a/docs/assets/js/17896441.72377930.js b/docs/assets/js/17896441.72377930.js new file mode 100644 index 00000000..df71920b --- /dev/null +++ b/docs/assets/js/17896441.72377930.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[8401],{594:(e,n,t)=>{t.d(n,{A:()=>j});t(6540);var s=t(4164),a=t(7559),i=t(6972),l=t(9169),o=t(8774),r=t(1312),c=t(6025),d=t(4848);function u(e){return(0,d.jsx)("svg",{viewBox:"0 0 24 24",...e,children:(0,d.jsx)("path",{d:"M10 19v-5h4v5c0 .55.45 1 1 1h3c.55 0 1-.45 1-1v-7h1.7c.46 0 .68-.57.33-.87L12.67 3.6c-.38-.34-.96-.34-1.34 0l-8.36 7.53c-.34.3-.13.87.33.87H5v7c0 .55.45 1 1 1h3c.55 0 1-.45 1-1z",fill:"currentColor"})})}const m={breadcrumbHomeIcon:"breadcrumbHomeIcon_YNFT"};function h(){const e=(0,c.Ay)("/");return(0,d.jsx)("li",{className:"breadcrumbs__item",children:(0,d.jsx)(o.A,{"aria-label":(0,r.T)({id:"theme.docs.breadcrumbs.home",message:"Home page",description:"The ARIA label for the home page in the breadcrumbs"}),className:"breadcrumbs__link",href:e,children:(0,d.jsx)(u,{className:m.breadcrumbHomeIcon})})})}var b=t(5260),v=t(4586);function x(e){const n=function({breadcrumbs:e}){const{siteConfig:n}=(0,v.A)();return{"@context":"https://schema.org","@type":"BreadcrumbList",itemListElement:e.filter(e=>e.href).map((e,t)=>({"@type":"ListItem",position:t+1,name:e.label,item:`${n.url}${e.href}`}))}}({breadcrumbs:e.breadcrumbs});return(0,d.jsx)(b.A,{children:(0,d.jsx)("script",{type:"application/ld+json",children:JSON.stringify(n)})})}const g={breadcrumbsContainer:"breadcrumbsContainer_Z_bl"};function f({children:e,href:n,isLast:t}){const s="breadcrumbs__link";return t?(0,d.jsx)("span",{className:s,children:e}):n?(0,d.jsx)(o.A,{className:s,href:n,children:(0,d.jsx)("span",{children:e})}):(0,d.jsx)("span",{className:s,children:e})}function p({children:e,active:n}){return(0,d.jsx)("li",{className:(0,s.A)("breadcrumbs__item",{"breadcrumbs__item--active":n}),children:e})}function j(){const e=(0,i.OF)(),n=(0,l.Dt)();return e?(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)(x,{breadcrumbs:e}),(0,d.jsx)("nav",{className:(0,s.A)(a.G.docs.docBreadcrumbs,g.breadcrumbsContainer),"aria-label":(0,r.T)({id:"theme.docs.breadcrumbs.navAriaLabel",message:"Breadcrumbs",description:"The ARIA label for the breadcrumbs"}),children:(0,d.jsxs)("ul",{className:"breadcrumbs",children:[n&&(0,d.jsx)(h,{}),e.map((n,t)=>{const s=t===e.length-1,a="category"===n.type&&n.linkUnlisted?void 0:n.href;return(0,d.jsx)(p,{active:s,children:(0,d.jsx)(f,{href:a,isLast:s,children:n.label})},t)})]})})]}):null}},833:(e,n,t)=>{t.r(n),t.d(n,{default:()=>F});var s=t(6540),a=t(5500),i=t(9532),l=t(4848);const o=s.createContext(null);function r({children:e,content:n}){const t=function(e){return(0,s.useMemo)(()=>({metadata:e.metadata,frontMatter:e.frontMatter,assets:e.assets,contentTitle:e.contentTitle,toc:e.toc}),[e])}(n);return(0,l.jsx)(o.Provider,{value:t,children:e})}function c(){const e=(0,s.useContext)(o);if(null===e)throw new i.dV("DocProvider");return e}function d(){const{metadata:e,frontMatter:n,assets:t}=c();return(0,l.jsx)(a.be,{title:e.title,description:e.description,keywords:n.keywords,image:t.image??n.image})}var u=t(4164),m=t(4581),h=t(7719);function b(){const{metadata:e}=c();return(0,l.jsx)(h.A,{className:"docusaurus-mt-lg",previous:e.previous,next:e.next})}var v=t(1878),x=t(4267),g=t(7559),f=t(4434),p=t(4336);function j(){const{metadata:e}=c(),{editUrl:n,lastUpdatedAt:t,lastUpdatedBy:s,tags:a}=e,i=a.length>0,o=!!(n||t||s);return i||o?(0,l.jsxs)("footer",{className:(0,u.A)(g.G.docs.docFooter,"docusaurus-mt-lg"),children:[i&&(0,l.jsx)("div",{className:(0,u.A)("row margin-top--sm",g.G.docs.docFooterTagsRow),children:(0,l.jsx)("div",{className:"col",children:(0,l.jsx)(f.A,{tags:a})})}),o&&(0,l.jsx)(p.A,{className:(0,u.A)("margin-top--sm",g.G.docs.docFooterEditMetaRow),editUrl:n,lastUpdatedAt:t,lastUpdatedBy:s})]}):null}var A=t(1422),N=t(5195),C=t(1312);const L={tocCollapsibleButton:"tocCollapsibleButton_TO0P",tocCollapsibleButtonExpanded:"tocCollapsibleButtonExpanded_MG3E"};function _({collapsed:e,...n}){return(0,l.jsx)("button",{type:"button",...n,className:(0,u.A)("clean-btn",L.tocCollapsibleButton,!e&&L.tocCollapsibleButtonExpanded,n.className),children:(0,l.jsx)(C.A,{id:"theme.TOCCollapsible.toggleButtonLabel",description:"The label used by the button on the collapsible TOC component",children:"On this page"})})}const T={tocCollapsible:"tocCollapsible_ETCw",tocCollapsibleContent:"tocCollapsibleContent_vkbj",tocCollapsibleExpanded:"tocCollapsibleExpanded_sAul"};function k({toc:e,className:n,minHeadingLevel:t,maxHeadingLevel:s}){const{collapsed:a,toggleCollapsed:i}=(0,A.u)({initialState:!0});return(0,l.jsxs)("div",{className:(0,u.A)(T.tocCollapsible,!a&&T.tocCollapsibleExpanded,n),children:[(0,l.jsx)(_,{collapsed:a,onClick:i}),(0,l.jsx)(A.N,{lazy:!0,className:T.tocCollapsibleContent,collapsed:a,children:(0,l.jsx)(N.A,{toc:e,minHeadingLevel:t,maxHeadingLevel:s})})]})}const H={tocMobile:"tocMobile_ITEo"};function y(){const{toc:e,frontMatter:n}=c();return(0,l.jsx)(k,{toc:e,minHeadingLevel:n.toc_min_heading_level,maxHeadingLevel:n.toc_max_heading_level,className:(0,u.A)(g.G.docs.docTocMobile,H.tocMobile)})}var M=t(7763);function B(){const{toc:e,frontMatter:n}=c();return(0,l.jsx)(M.A,{toc:e,minHeadingLevel:n.toc_min_heading_level,maxHeadingLevel:n.toc_max_heading_level,className:g.G.docs.docTocDesktop})}var I=t(1107),w=t(3253);function E({children:e}){const n=function(){const{metadata:e,frontMatter:n,contentTitle:t}=c();return n.hide_title||void 0!==t?null:e.title}();return(0,l.jsxs)("div",{className:(0,u.A)(g.G.docs.docMarkdown,"markdown"),children:[n&&(0,l.jsx)("header",{children:(0,l.jsx)(I.A,{as:"h1",children:n})}),(0,l.jsx)(w.A,{children:e})]})}var V=t(594),O=t(1689);const R={docItemContainer:"docItemContainer_Djhp",docItemCol:"docItemCol_VOVn"};function G({children:e}){const n=function(){const{frontMatter:e,toc:n}=c(),t=(0,m.l)(),s=e.hide_table_of_contents,a=!s&&n.length>0;return{hidden:s,mobile:a?(0,l.jsx)(y,{}):void 0,desktop:!a||"desktop"!==t&&"ssr"!==t?void 0:(0,l.jsx)(B,{})}}(),{metadata:t}=c();return(0,l.jsxs)("div",{className:"row",children:[(0,l.jsxs)("div",{className:(0,u.A)("col",!n.hidden&&R.docItemCol),children:[(0,l.jsx)(O.A,{metadata:t}),(0,l.jsx)(v.A,{}),(0,l.jsxs)("div",{className:R.docItemContainer,children:[(0,l.jsxs)("article",{children:[(0,l.jsx)(V.A,{}),(0,l.jsx)(x.A,{}),n.mobile,(0,l.jsx)(E,{children:e}),(0,l.jsx)(j,{})]}),(0,l.jsx)(b,{})]})]}),n.desktop&&(0,l.jsx)("div",{className:"col col--3",children:n.desktop})]})}function F(e){const n=`docs-doc-id-${e.content.metadata.id}`,t=e.content;return(0,l.jsx)(r,{content:e.content,children:(0,l.jsxs)(a.e3,{className:n,children:[(0,l.jsx)(d,{}),(0,l.jsx)(G,{children:(0,l.jsx)(t,{})})]})})}},1689:(e,n,t)=>{t.d(n,{A:()=>d});t(6540);var s=t(4164),a=t(4084),i=t(7559),l=t(7293),o=t(4848);function r({className:e}){return(0,o.jsx)(l.A,{type:"caution",title:(0,o.jsx)(a.Yh,{}),className:(0,s.A)(e,i.G.common.draftBanner),children:(0,o.jsx)(a.TT,{})})}var c=t(2234);function d({metadata:e}){const{unlisted:n,frontMatter:t}=e;return(0,o.jsxs)(o.Fragment,{children:[(n||t.unlisted)&&(0,o.jsx)(c.A,{}),t.draft&&(0,o.jsx)(r,{})]})}},1878:(e,n,t)=>{t.d(n,{A:()=>x});t(6540);var s=t(4164),a=t(4586),i=t(8774),l=t(1312),o=t(4070),r=t(7559),c=t(3886),d=t(3025),u=t(4848);const m={unreleased:function({siteTitle:e,versionMetadata:n}){return(0,u.jsx)(l.A,{id:"theme.docs.versions.unreleasedVersionLabel",description:"The label used to tell the user that he's browsing an unreleased doc version",values:{siteTitle:e,versionLabel:(0,u.jsx)("b",{children:n.label})},children:"This is unreleased documentation for {siteTitle} {versionLabel} version."})},unmaintained:function({siteTitle:e,versionMetadata:n}){return(0,u.jsx)(l.A,{id:"theme.docs.versions.unmaintainedVersionLabel",description:"The label used to tell the user that he's browsing an unmaintained doc version",values:{siteTitle:e,versionLabel:(0,u.jsx)("b",{children:n.label})},children:"This is documentation for {siteTitle} {versionLabel}, which is no longer actively maintained."})}};function h(e){const n=m[e.versionMetadata.banner];return(0,u.jsx)(n,{...e})}function b({versionLabel:e,to:n,onClick:t}){return(0,u.jsx)(l.A,{id:"theme.docs.versions.latestVersionSuggestionLabel",description:"The label used to tell the user to check the latest version",values:{versionLabel:e,latestVersionLink:(0,u.jsx)("b",{children:(0,u.jsx)(i.A,{to:n,onClick:t,children:(0,u.jsx)(l.A,{id:"theme.docs.versions.latestVersionLinkLabel",description:"The label used for the latest version suggestion link label",children:"latest version"})})})},children:"For up-to-date documentation, see the {latestVersionLink} ({versionLabel})."})}function v({className:e,versionMetadata:n}){const{siteConfig:{title:t}}=(0,a.A)(),{pluginId:i}=(0,o.vT)({failfast:!0}),{savePreferredVersionName:l}=(0,c.g1)(i),{latestDocSuggestion:d,latestVersionSuggestion:m}=(0,o.HW)(i),v=d??(x=m).docs.find(e=>e.id===x.mainDocId);var x;return(0,u.jsxs)("div",{className:(0,s.A)(e,r.G.docs.docVersionBanner,"alert alert--warning margin-bottom--md"),role:"alert",children:[(0,u.jsx)("div",{children:(0,u.jsx)(h,{siteTitle:t,versionMetadata:n})}),(0,u.jsx)("div",{className:"margin-top--md",children:(0,u.jsx)(b,{versionLabel:m.label,to:v.path,onClick:()=>l(m.name)})})]})}function x({className:e}){const n=(0,d.r)();return n.banner?(0,u.jsx)(v,{className:e,versionMetadata:n}):null}},2234:(e,n,t)=>{t.d(n,{A:()=>c});t(6540);var s=t(4164),a=t(7559),i=t(4084),l=t(7293),o=t(4848);function r({className:e}){return(0,o.jsx)(l.A,{type:"caution",title:(0,o.jsx)(i.Rc,{}),className:(0,s.A)(e,a.G.common.unlistedBanner),children:(0,o.jsx)(i.Uh,{})})}function c(e){return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(i.AE,{}),(0,o.jsx)(r,{...e})]})}},4084:(e,n,t)=>{t.d(n,{AE:()=>r,Rc:()=>l,TT:()=>d,Uh:()=>o,Yh:()=>c});t(6540);var s=t(1312),a=t(5260),i=t(4848);function l(){return(0,i.jsx)(s.A,{id:"theme.contentVisibility.unlistedBanner.title",description:"The unlisted content banner title",children:"Unlisted page"})}function o(){return(0,i.jsx)(s.A,{id:"theme.contentVisibility.unlistedBanner.message",description:"The unlisted content banner message",children:"This page is unlisted. Search engines will not index it, and only users having a direct link can access it."})}function r(){return(0,i.jsx)(a.A,{children:(0,i.jsx)("meta",{name:"robots",content:"noindex, nofollow"})})}function c(){return(0,i.jsx)(s.A,{id:"theme.contentVisibility.draftBanner.title",description:"The draft content banner title",children:"Draft page"})}function d(){return(0,i.jsx)(s.A,{id:"theme.contentVisibility.draftBanner.message",description:"The draft content banner message",children:"This page is a draft. It will only be visible in dev and be excluded from the production build."})}},4267:(e,n,t)=>{t.d(n,{A:()=>r});t(6540);var s=t(4164),a=t(1312),i=t(7559),l=t(3025),o=t(4848);function r({className:e}){const n=(0,l.r)();return n.badge?(0,o.jsx)("span",{className:(0,s.A)(e,i.G.docs.docVersionBadge,"badge badge--secondary"),children:(0,o.jsx)(a.A,{id:"theme.docs.versionBadge.label",values:{versionLabel:n.label},children:"Version: {versionLabel}"})}):null}},4434:(e,n,t)=>{t.d(n,{A:()=>r});t(6540);var s=t(4164),a=t(1312),i=t(6133);const l={tags:"tags_jXut",tag:"tag_QGVx"};var o=t(4848);function r({tags:e}){return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)("b",{children:(0,o.jsx)(a.A,{id:"theme.tags.tagsListLabel",description:"The label alongside a tag list",children:"Tags:"})}),(0,o.jsx)("ul",{className:(0,s.A)(l.tags,"padding--none","margin-left--sm"),children:e.map(e=>(0,o.jsx)("li",{className:l.tag,children:(0,o.jsx)(i.A,{...e})},e.permalink))})]})}},5195:(e,n,t)=>{t.d(n,{A:()=>v});var s=t(6540),a=t(6342);function i(e){const n=e.map(e=>({...e,parentIndex:-1,children:[]})),t=Array(7).fill(-1);n.forEach((e,n)=>{const s=t.slice(2,e.level);e.parentIndex=Math.max(...s),t[e.level]=n});const s=[];return n.forEach(e=>{const{parentIndex:t,...a}=e;t>=0?n[t].children.push(a):s.push(a)}),s}function l({toc:e,minHeadingLevel:n,maxHeadingLevel:t}){return e.flatMap(e=>{const s=l({toc:e.children,minHeadingLevel:n,maxHeadingLevel:t});return function(e){return e.level>=n&&e.level<=t}(e)?[{...e,children:s}]:s})}function o(e){const n=e.getBoundingClientRect();return n.top===n.bottom?o(e.parentNode):n}function r(e,{anchorTopOffset:n}){const t=e.find(e=>o(e).top>=n);if(t){return function(e){return e.top>0&&e.bottom{e.current=n?0:document.querySelector(".navbar").clientHeight},[n]),e}function d(e){const n=(0,s.useRef)(void 0),t=c();(0,s.useEffect)(()=>{if(!e)return()=>{};const{linkClassName:s,linkActiveClassName:a,minHeadingLevel:i,maxHeadingLevel:l}=e;function o(){const e=function(e){return Array.from(document.getElementsByClassName(e))}(s),o=function({minHeadingLevel:e,maxHeadingLevel:n}){const t=[];for(let s=e;s<=n;s+=1)t.push(`h${s}.anchor`);return Array.from(document.querySelectorAll(t.join()))}({minHeadingLevel:i,maxHeadingLevel:l}),c=r(o,{anchorTopOffset:t.current}),d=e.find(e=>c&&c.id===function(e){return decodeURIComponent(e.href.substring(e.href.indexOf("#")+1))}(e));e.forEach(e=>{!function(e,t){t?(n.current&&n.current!==e&&n.current.classList.remove(a),e.classList.add(a),n.current=e):e.classList.remove(a)}(e,e===d)})}return document.addEventListener("scroll",o),document.addEventListener("resize",o),o(),()=>{document.removeEventListener("scroll",o),document.removeEventListener("resize",o)}},[e,t])}var u=t(8774),m=t(4848);function h({toc:e,className:n,linkClassName:t,isChild:s}){return e.length?(0,m.jsx)("ul",{className:s?void 0:n,children:e.map(e=>(0,m.jsxs)("li",{children:[(0,m.jsx)(u.A,{to:`#${e.id}`,className:t??void 0,dangerouslySetInnerHTML:{__html:e.value}}),(0,m.jsx)(h,{isChild:!0,toc:e.children,className:n,linkClassName:t})]},e.id))}):null}const b=s.memo(h);function v({toc:e,className:n="table-of-contents table-of-contents__left-border",linkClassName:t="table-of-contents__link",linkActiveClassName:o,minHeadingLevel:r,maxHeadingLevel:c,...u}){const h=(0,a.p)(),v=r??h.tableOfContents.minHeadingLevel,x=c??h.tableOfContents.maxHeadingLevel,g=function({toc:e,minHeadingLevel:n,maxHeadingLevel:t}){return(0,s.useMemo)(()=>l({toc:i(e),minHeadingLevel:n,maxHeadingLevel:t}),[e,n,t])}({toc:e,minHeadingLevel:v,maxHeadingLevel:x});return d((0,s.useMemo)(()=>{if(t&&o)return{linkClassName:t,linkActiveClassName:o,minHeadingLevel:v,maxHeadingLevel:x}},[t,o,v,x])),(0,m.jsx)(b,{toc:g,className:n,linkClassName:t,...u})}},6133:(e,n,t)=>{t.d(n,{A:()=>o});t(6540);var s=t(4164),a=t(8774);const i={tag:"tag_zVej",tagRegular:"tagRegular_sFm0",tagWithCount:"tagWithCount_h2kH"};var l=t(4848);function o({permalink:e,label:n,count:t,description:o}){return(0,l.jsxs)(a.A,{rel:"tag",href:e,title:o,className:(0,s.A)(i.tag,t?i.tagWithCount:i.tagRegular),children:[n,t&&(0,l.jsx)("span",{children:t})]})}},7719:(e,n,t)=>{t.d(n,{A:()=>o});t(6540);var s=t(4164),a=t(1312),i=t(9022),l=t(4848);function o(e){const{className:n,previous:t,next:o}=e;return(0,l.jsxs)("nav",{className:(0,s.A)(n,"pagination-nav"),"aria-label":(0,a.T)({id:"theme.docs.paginator.navAriaLabel",message:"Docs pages",description:"The ARIA label for the docs pagination"}),children:[t&&(0,l.jsx)(i.A,{...t,subLabel:(0,l.jsx)(a.A,{id:"theme.docs.paginator.previous",description:"The label used to navigate to the previous doc",children:"Previous"})}),o&&(0,l.jsx)(i.A,{...o,subLabel:(0,l.jsx)(a.A,{id:"theme.docs.paginator.next",description:"The label used to navigate to the next doc",children:"Next"}),isNext:!0})]})}},7763:(e,n,t)=>{t.d(n,{A:()=>c});t(6540);var s=t(4164),a=t(5195);const i={tableOfContents:"tableOfContents_bqdL",docItemContainer:"docItemContainer_F8PC"};var l=t(4848);const o="table-of-contents__link toc-highlight",r="table-of-contents__link--active";function c({className:e,...n}){return(0,l.jsx)("div",{className:(0,s.A)(i.tableOfContents,"thin-scrollbar",e),children:(0,l.jsx)(a.A,{...n,linkClassName:o,linkActiveClassName:r})})}},9022:(e,n,t)=>{t.d(n,{A:()=>l});t(6540);var s=t(4164),a=t(8774),i=t(4848);function l(e){const{permalink:n,title:t,subLabel:l,isNext:o}=e;return(0,i.jsxs)(a.A,{className:(0,s.A)("pagination-nav__link",o?"pagination-nav__link--next":"pagination-nav__link--prev"),to:n,children:[l&&(0,i.jsx)("div",{className:"pagination-nav__sublabel",children:l}),(0,i.jsx)("div",{className:"pagination-nav__label",children:t})]})}}}]); \ No newline at end of file diff --git a/docs/assets/js/17896441.e023d99a.js b/docs/assets/js/17896441.e023d99a.js deleted file mode 100644 index 5e076aa5..00000000 --- a/docs/assets/js/17896441.e023d99a.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[8401],{594:(e,n,t)=>{t.d(n,{A:()=>j});t(6540);var s=t(4164),a=t(7559),i=t(6972),l=t(9169),o=t(8774),r=t(1312),c=t(6025),d=t(4848);function u(e){return(0,d.jsx)("svg",{viewBox:"0 0 24 24",...e,children:(0,d.jsx)("path",{d:"M10 19v-5h4v5c0 .55.45 1 1 1h3c.55 0 1-.45 1-1v-7h1.7c.46 0 .68-.57.33-.87L12.67 3.6c-.38-.34-.96-.34-1.34 0l-8.36 7.53c-.34.3-.13.87.33.87H5v7c0 .55.45 1 1 1h3c.55 0 1-.45 1-1z",fill:"currentColor"})})}const m={breadcrumbHomeIcon:"breadcrumbHomeIcon_YNFT"};function h(){const e=(0,c.Ay)("/");return(0,d.jsx)("li",{className:"breadcrumbs__item",children:(0,d.jsx)(o.A,{"aria-label":(0,r.T)({id:"theme.docs.breadcrumbs.home",message:"Home page",description:"The ARIA label for the home page in the breadcrumbs"}),className:"breadcrumbs__link",href:e,children:(0,d.jsx)(u,{className:m.breadcrumbHomeIcon})})})}var b=t(5260),v=t(4586);function x(e){const n=function({breadcrumbs:e}){const{siteConfig:n}=(0,v.A)();return{"@context":"https://schema.org","@type":"BreadcrumbList",itemListElement:e.filter((e=>e.href)).map(((e,t)=>({"@type":"ListItem",position:t+1,name:e.label,item:`${n.url}${e.href}`})))}}({breadcrumbs:e.breadcrumbs});return(0,d.jsx)(b.A,{children:(0,d.jsx)("script",{type:"application/ld+json",children:JSON.stringify(n)})})}const g={breadcrumbsContainer:"breadcrumbsContainer_Z_bl"};function f({children:e,href:n,isLast:t}){const s="breadcrumbs__link";return t?(0,d.jsx)("span",{className:s,children:e}):n?(0,d.jsx)(o.A,{className:s,href:n,children:(0,d.jsx)("span",{children:e})}):(0,d.jsx)("span",{className:s,children:e})}function p({children:e,active:n}){return(0,d.jsx)("li",{className:(0,s.A)("breadcrumbs__item",{"breadcrumbs__item--active":n}),children:e})}function j(){const e=(0,i.OF)(),n=(0,l.Dt)();return e?(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)(x,{breadcrumbs:e}),(0,d.jsx)("nav",{className:(0,s.A)(a.G.docs.docBreadcrumbs,g.breadcrumbsContainer),"aria-label":(0,r.T)({id:"theme.docs.breadcrumbs.navAriaLabel",message:"Breadcrumbs",description:"The ARIA label for the breadcrumbs"}),children:(0,d.jsxs)("ul",{className:"breadcrumbs",children:[n&&(0,d.jsx)(h,{}),e.map(((n,t)=>{const s=t===e.length-1,a="category"===n.type&&n.linkUnlisted?void 0:n.href;return(0,d.jsx)(p,{active:s,children:(0,d.jsx)(f,{href:a,isLast:s,children:n.label})},t)}))]})})]}):null}},833:(e,n,t)=>{t.r(n),t.d(n,{default:()=>F});var s=t(6540),a=t(5500),i=t(9532),l=t(4848);const o=s.createContext(null);function r({children:e,content:n}){const t=function(e){return(0,s.useMemo)((()=>({metadata:e.metadata,frontMatter:e.frontMatter,assets:e.assets,contentTitle:e.contentTitle,toc:e.toc})),[e])}(n);return(0,l.jsx)(o.Provider,{value:t,children:e})}function c(){const e=(0,s.useContext)(o);if(null===e)throw new i.dV("DocProvider");return e}function d(){const{metadata:e,frontMatter:n,assets:t}=c();return(0,l.jsx)(a.be,{title:e.title,description:e.description,keywords:n.keywords,image:t.image??n.image})}var u=t(4164),m=t(4581),h=t(7719);function b(){const{metadata:e}=c();return(0,l.jsx)(h.A,{className:"docusaurus-mt-lg",previous:e.previous,next:e.next})}var v=t(1878),x=t(4267),g=t(7559),f=t(2053),p=t(4336);function j(){const{metadata:e}=c(),{editUrl:n,lastUpdatedAt:t,lastUpdatedBy:s,tags:a}=e,i=a.length>0,o=!!(n||t||s);return i||o?(0,l.jsxs)("footer",{className:(0,u.A)(g.G.docs.docFooter,"docusaurus-mt-lg"),children:[i&&(0,l.jsx)("div",{className:(0,u.A)("row margin-top--sm",g.G.docs.docFooterTagsRow),children:(0,l.jsx)("div",{className:"col",children:(0,l.jsx)(f.A,{tags:a})})}),o&&(0,l.jsx)(p.A,{className:(0,u.A)("margin-top--sm",g.G.docs.docFooterEditMetaRow),editUrl:n,lastUpdatedAt:t,lastUpdatedBy:s})]}):null}var A=t(1422),N=t(5195),C=t(1312);const L={tocCollapsibleButton:"tocCollapsibleButton_TO0P",tocCollapsibleButtonExpanded:"tocCollapsibleButtonExpanded_MG3E"};function _({collapsed:e,...n}){return(0,l.jsx)("button",{type:"button",...n,className:(0,u.A)("clean-btn",L.tocCollapsibleButton,!e&&L.tocCollapsibleButtonExpanded,n.className),children:(0,l.jsx)(C.A,{id:"theme.TOCCollapsible.toggleButtonLabel",description:"The label used by the button on the collapsible TOC component",children:"On this page"})})}const T={tocCollapsible:"tocCollapsible_ETCw",tocCollapsibleContent:"tocCollapsibleContent_vkbj",tocCollapsibleExpanded:"tocCollapsibleExpanded_sAul"};function k({toc:e,className:n,minHeadingLevel:t,maxHeadingLevel:s}){const{collapsed:a,toggleCollapsed:i}=(0,A.u)({initialState:!0});return(0,l.jsxs)("div",{className:(0,u.A)(T.tocCollapsible,!a&&T.tocCollapsibleExpanded,n),children:[(0,l.jsx)(_,{collapsed:a,onClick:i}),(0,l.jsx)(A.N,{lazy:!0,className:T.tocCollapsibleContent,collapsed:a,children:(0,l.jsx)(N.A,{toc:e,minHeadingLevel:t,maxHeadingLevel:s})})]})}const H={tocMobile:"tocMobile_ITEo"};function y(){const{toc:e,frontMatter:n}=c();return(0,l.jsx)(k,{toc:e,minHeadingLevel:n.toc_min_heading_level,maxHeadingLevel:n.toc_max_heading_level,className:(0,u.A)(g.G.docs.docTocMobile,H.tocMobile)})}var M=t(7763);function B(){const{toc:e,frontMatter:n}=c();return(0,l.jsx)(M.A,{toc:e,minHeadingLevel:n.toc_min_heading_level,maxHeadingLevel:n.toc_max_heading_level,className:g.G.docs.docTocDesktop})}var I=t(1107),w=t(3253);function E({children:e}){const n=function(){const{metadata:e,frontMatter:n,contentTitle:t}=c();return n.hide_title||void 0!==t?null:e.title}();return(0,l.jsxs)("div",{className:(0,u.A)(g.G.docs.docMarkdown,"markdown"),children:[n&&(0,l.jsx)("header",{children:(0,l.jsx)(I.A,{as:"h1",children:n})}),(0,l.jsx)(w.A,{children:e})]})}var V=t(594),O=t(1689);const R={docItemContainer:"docItemContainer_Djhp",docItemCol:"docItemCol_VOVn"};function G({children:e}){const n=function(){const{frontMatter:e,toc:n}=c(),t=(0,m.l)(),s=e.hide_table_of_contents,a=!s&&n.length>0;return{hidden:s,mobile:a?(0,l.jsx)(y,{}):void 0,desktop:!a||"desktop"!==t&&"ssr"!==t?void 0:(0,l.jsx)(B,{})}}(),{metadata:t}=c();return(0,l.jsxs)("div",{className:"row",children:[(0,l.jsxs)("div",{className:(0,u.A)("col",!n.hidden&&R.docItemCol),children:[(0,l.jsx)(O.A,{metadata:t}),(0,l.jsx)(v.A,{}),(0,l.jsxs)("div",{className:R.docItemContainer,children:[(0,l.jsxs)("article",{children:[(0,l.jsx)(V.A,{}),(0,l.jsx)(x.A,{}),n.mobile,(0,l.jsx)(E,{children:e}),(0,l.jsx)(j,{})]}),(0,l.jsx)(b,{})]})]}),n.desktop&&(0,l.jsx)("div",{className:"col col--3",children:n.desktop})]})}function F(e){const n=`docs-doc-id-${e.content.metadata.id}`,t=e.content;return(0,l.jsx)(r,{content:e.content,children:(0,l.jsxs)(a.e3,{className:n,children:[(0,l.jsx)(d,{}),(0,l.jsx)(G,{children:(0,l.jsx)(t,{})})]})})}},1689:(e,n,t)=>{t.d(n,{A:()=>d});t(6540);var s=t(4164),a=t(4084),i=t(7559),l=t(7293),o=t(4848);function r({className:e}){return(0,o.jsx)(l.A,{type:"caution",title:(0,o.jsx)(a.Yh,{}),className:(0,s.A)(e,i.G.common.draftBanner),children:(0,o.jsx)(a.TT,{})})}var c=t(2234);function d({metadata:e}){const{unlisted:n,frontMatter:t}=e;return(0,o.jsxs)(o.Fragment,{children:[(n||t.unlisted)&&(0,o.jsx)(c.A,{}),t.draft&&(0,o.jsx)(r,{})]})}},1878:(e,n,t)=>{t.d(n,{A:()=>x});t(6540);var s=t(4164),a=t(4586),i=t(8774),l=t(1312),o=t(4070),r=t(7559),c=t(3886),d=t(3025),u=t(4848);const m={unreleased:function({siteTitle:e,versionMetadata:n}){return(0,u.jsx)(l.A,{id:"theme.docs.versions.unreleasedVersionLabel",description:"The label used to tell the user that he's browsing an unreleased doc version",values:{siteTitle:e,versionLabel:(0,u.jsx)("b",{children:n.label})},children:"This is unreleased documentation for {siteTitle} {versionLabel} version."})},unmaintained:function({siteTitle:e,versionMetadata:n}){return(0,u.jsx)(l.A,{id:"theme.docs.versions.unmaintainedVersionLabel",description:"The label used to tell the user that he's browsing an unmaintained doc version",values:{siteTitle:e,versionLabel:(0,u.jsx)("b",{children:n.label})},children:"This is documentation for {siteTitle} {versionLabel}, which is no longer actively maintained."})}};function h(e){const n=m[e.versionMetadata.banner];return(0,u.jsx)(n,{...e})}function b({versionLabel:e,to:n,onClick:t}){return(0,u.jsx)(l.A,{id:"theme.docs.versions.latestVersionSuggestionLabel",description:"The label used to tell the user to check the latest version",values:{versionLabel:e,latestVersionLink:(0,u.jsx)("b",{children:(0,u.jsx)(i.A,{to:n,onClick:t,children:(0,u.jsx)(l.A,{id:"theme.docs.versions.latestVersionLinkLabel",description:"The label used for the latest version suggestion link label",children:"latest version"})})})},children:"For up-to-date documentation, see the {latestVersionLink} ({versionLabel})."})}function v({className:e,versionMetadata:n}){const{siteConfig:{title:t}}=(0,a.A)(),{pluginId:i}=(0,o.vT)({failfast:!0}),{savePreferredVersionName:l}=(0,c.g1)(i),{latestDocSuggestion:d,latestVersionSuggestion:m}=(0,o.HW)(i),v=d??(x=m).docs.find((e=>e.id===x.mainDocId));var x;return(0,u.jsxs)("div",{className:(0,s.A)(e,r.G.docs.docVersionBanner,"alert alert--warning margin-bottom--md"),role:"alert",children:[(0,u.jsx)("div",{children:(0,u.jsx)(h,{siteTitle:t,versionMetadata:n})}),(0,u.jsx)("div",{className:"margin-top--md",children:(0,u.jsx)(b,{versionLabel:m.label,to:v.path,onClick:()=>l(m.name)})})]})}function x({className:e}){const n=(0,d.r)();return n.banner?(0,u.jsx)(v,{className:e,versionMetadata:n}):null}},2053:(e,n,t)=>{t.d(n,{A:()=>r});t(6540);var s=t(4164),a=t(1312),i=t(6133);const l={tags:"tags_jXut",tag:"tag_QGVx"};var o=t(4848);function r({tags:e}){return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)("b",{children:(0,o.jsx)(a.A,{id:"theme.tags.tagsListLabel",description:"The label alongside a tag list",children:"Tags:"})}),(0,o.jsx)("ul",{className:(0,s.A)(l.tags,"padding--none","margin-left--sm"),children:e.map((e=>(0,o.jsx)("li",{className:l.tag,children:(0,o.jsx)(i.A,{...e})},e.permalink)))})]})}},2234:(e,n,t)=>{t.d(n,{A:()=>c});t(6540);var s=t(4164),a=t(4084),i=t(7559),l=t(7293),o=t(4848);function r({className:e}){return(0,o.jsx)(l.A,{type:"caution",title:(0,o.jsx)(a.Rc,{}),className:(0,s.A)(e,i.G.common.unlistedBanner),children:(0,o.jsx)(a.Uh,{})})}function c(e){return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(a.AE,{}),(0,o.jsx)(r,{...e})]})}},4084:(e,n,t)=>{t.d(n,{AE:()=>r,Rc:()=>l,TT:()=>d,Uh:()=>o,Yh:()=>c});t(6540);var s=t(1312),a=t(5260),i=t(4848);function l(){return(0,i.jsx)(s.A,{id:"theme.contentVisibility.unlistedBanner.title",description:"The unlisted content banner title",children:"Unlisted page"})}function o(){return(0,i.jsx)(s.A,{id:"theme.contentVisibility.unlistedBanner.message",description:"The unlisted content banner message",children:"This page is unlisted. Search engines will not index it, and only users having a direct link can access it."})}function r(){return(0,i.jsx)(a.A,{children:(0,i.jsx)("meta",{name:"robots",content:"noindex, nofollow"})})}function c(){return(0,i.jsx)(s.A,{id:"theme.contentVisibility.draftBanner.title",description:"The draft content banner title",children:"Draft page"})}function d(){return(0,i.jsx)(s.A,{id:"theme.contentVisibility.draftBanner.message",description:"The draft content banner message",children:"This page is a draft. It will only be visible in dev and be excluded from the production build."})}},4267:(e,n,t)=>{t.d(n,{A:()=>r});t(6540);var s=t(4164),a=t(1312),i=t(7559),l=t(3025),o=t(4848);function r({className:e}){const n=(0,l.r)();return n.badge?(0,o.jsx)("span",{className:(0,s.A)(e,i.G.docs.docVersionBadge,"badge badge--secondary"),children:(0,o.jsx)(a.A,{id:"theme.docs.versionBadge.label",values:{versionLabel:n.label},children:"Version: {versionLabel}"})}):null}},5195:(e,n,t)=>{t.d(n,{A:()=>v});var s=t(6540),a=t(6342);function i(e){const n=e.map((e=>({...e,parentIndex:-1,children:[]}))),t=Array(7).fill(-1);n.forEach(((e,n)=>{const s=t.slice(2,e.level);e.parentIndex=Math.max(...s),t[e.level]=n}));const s=[];return n.forEach((e=>{const{parentIndex:t,...a}=e;t>=0?n[t].children.push(a):s.push(a)})),s}function l({toc:e,minHeadingLevel:n,maxHeadingLevel:t}){return e.flatMap((e=>{const s=l({toc:e.children,minHeadingLevel:n,maxHeadingLevel:t});return function(e){return e.level>=n&&e.level<=t}(e)?[{...e,children:s}]:s}))}function o(e){const n=e.getBoundingClientRect();return n.top===n.bottom?o(e.parentNode):n}function r(e,{anchorTopOffset:n}){const t=e.find((e=>o(e).top>=n));if(t){return function(e){return e.top>0&&e.bottom{e.current=n?0:document.querySelector(".navbar").clientHeight}),[n]),e}function d(e){const n=(0,s.useRef)(void 0),t=c();(0,s.useEffect)((()=>{if(!e)return()=>{};const{linkClassName:s,linkActiveClassName:a,minHeadingLevel:i,maxHeadingLevel:l}=e;function o(){const e=function(e){return Array.from(document.getElementsByClassName(e))}(s),o=function({minHeadingLevel:e,maxHeadingLevel:n}){const t=[];for(let s=e;s<=n;s+=1)t.push(`h${s}.anchor`);return Array.from(document.querySelectorAll(t.join()))}({minHeadingLevel:i,maxHeadingLevel:l}),c=r(o,{anchorTopOffset:t.current}),d=e.find((e=>c&&c.id===function(e){return decodeURIComponent(e.href.substring(e.href.indexOf("#")+1))}(e)));e.forEach((e=>{!function(e,t){t?(n.current&&n.current!==e&&n.current.classList.remove(a),e.classList.add(a),n.current=e):e.classList.remove(a)}(e,e===d)}))}return document.addEventListener("scroll",o),document.addEventListener("resize",o),o(),()=>{document.removeEventListener("scroll",o),document.removeEventListener("resize",o)}}),[e,t])}var u=t(8774),m=t(4848);function h({toc:e,className:n,linkClassName:t,isChild:s}){return e.length?(0,m.jsx)("ul",{className:s?void 0:n,children:e.map((e=>(0,m.jsxs)("li",{children:[(0,m.jsx)(u.A,{to:`#${e.id}`,className:t??void 0,dangerouslySetInnerHTML:{__html:e.value}}),(0,m.jsx)(h,{isChild:!0,toc:e.children,className:n,linkClassName:t})]},e.id)))}):null}const b=s.memo(h);function v({toc:e,className:n="table-of-contents table-of-contents__left-border",linkClassName:t="table-of-contents__link",linkActiveClassName:o,minHeadingLevel:r,maxHeadingLevel:c,...u}){const h=(0,a.p)(),v=r??h.tableOfContents.minHeadingLevel,x=c??h.tableOfContents.maxHeadingLevel,g=function({toc:e,minHeadingLevel:n,maxHeadingLevel:t}){return(0,s.useMemo)((()=>l({toc:i(e),minHeadingLevel:n,maxHeadingLevel:t})),[e,n,t])}({toc:e,minHeadingLevel:v,maxHeadingLevel:x});return d((0,s.useMemo)((()=>{if(t&&o)return{linkClassName:t,linkActiveClassName:o,minHeadingLevel:v,maxHeadingLevel:x}}),[t,o,v,x])),(0,m.jsx)(b,{toc:g,className:n,linkClassName:t,...u})}},6133:(e,n,t)=>{t.d(n,{A:()=>o});t(6540);var s=t(4164),a=t(8774);const i={tag:"tag_zVej",tagRegular:"tagRegular_sFm0",tagWithCount:"tagWithCount_h2kH"};var l=t(4848);function o({permalink:e,label:n,count:t,description:o}){return(0,l.jsxs)(a.A,{rel:"tag",href:e,title:o,className:(0,s.A)(i.tag,t?i.tagWithCount:i.tagRegular),children:[n,t&&(0,l.jsx)("span",{children:t})]})}},7719:(e,n,t)=>{t.d(n,{A:()=>o});t(6540);var s=t(4164),a=t(1312),i=t(9022),l=t(4848);function o(e){const{className:n,previous:t,next:o}=e;return(0,l.jsxs)("nav",{className:(0,s.A)(n,"pagination-nav"),"aria-label":(0,a.T)({id:"theme.docs.paginator.navAriaLabel",message:"Docs pages",description:"The ARIA label for the docs pagination"}),children:[t&&(0,l.jsx)(i.A,{...t,subLabel:(0,l.jsx)(a.A,{id:"theme.docs.paginator.previous",description:"The label used to navigate to the previous doc",children:"Previous"})}),o&&(0,l.jsx)(i.A,{...o,subLabel:(0,l.jsx)(a.A,{id:"theme.docs.paginator.next",description:"The label used to navigate to the next doc",children:"Next"}),isNext:!0})]})}},7763:(e,n,t)=>{t.d(n,{A:()=>c});t(6540);var s=t(4164),a=t(5195);const i={tableOfContents:"tableOfContents_bqdL",docItemContainer:"docItemContainer_F8PC"};var l=t(4848);const o="table-of-contents__link toc-highlight",r="table-of-contents__link--active";function c({className:e,...n}){return(0,l.jsx)("div",{className:(0,s.A)(i.tableOfContents,"thin-scrollbar",e),children:(0,l.jsx)(a.A,{...n,linkClassName:o,linkActiveClassName:r})})}},9022:(e,n,t)=>{t.d(n,{A:()=>l});t(6540);var s=t(4164),a=t(8774),i=t(4848);function l(e){const{permalink:n,title:t,subLabel:l,isNext:o}=e;return(0,i.jsxs)(a.A,{className:(0,s.A)("pagination-nav__link",o?"pagination-nav__link--next":"pagination-nav__link--prev"),to:n,children:[l&&(0,i.jsx)("div",{className:"pagination-nav__sublabel",children:l}),(0,i.jsx)("div",{className:"pagination-nav__label",children:t})]})}}}]); \ No newline at end of file diff --git a/docs/assets/js/1a4fe2b7.286ed6de.js b/docs/assets/js/1a4fe2b7.286ed6de.js new file mode 100644 index 00000000..3fa409b6 --- /dev/null +++ b/docs/assets/js/1a4fe2b7.286ed6de.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[6027],{1691:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>l,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>i,toc:()=>c});var i=t(5069),r=t(4848),s=t(8453);const a={title:"Building Meesho\u2019s ML Platform: Lessons from the First-Gen System (Part 2)",description:"Lessons from scaling Meesho's first-gen ML platform\u2014building Inferflow for no-code feature retrieval, migrating from Cassandra to ScyllaDB, optimizing the Interaction Store with tiered storage, and cutting infra costs by 60% while hitting 1M QPS.",authors:["bhawani","jigar","adarsha"],slug:"building-meeshos-mlplatform-lessons-from-first-gen",date:"2023-4-10",tags:["inferflow","interaction-store","mlplatform","meesho","bharatmlstack"]},o=void 0,l={authorsImageUrls:[void 0,void 0,void 0]},c=[{value:"The Cost of Success",id:"the-cost-of-success",level:3},{value:"Scaling Pains (and Cassandra\u2019s Limits)",id:"scaling-pains-and-cassandras-limits",level:3},{value:"Interaction Store Woes",id:"interaction-store-woes",level:3},{value:"Silver Linings",id:"silver-linings",level:3},{value:"Round Two: Solving the Top 2 Bottlenecks",id:"round-two-solving-the-top-2-bottlenecks",level:3},{value:"Problem 1: No-Code Feature Retrieval for Model Inference",id:"problem-1-no-code-feature-retrieval-for-model-inference",level:4},{value:"Problem 2: Scaling Without Breaking the Bank",id:"problem-2-scaling-without-breaking-the-bank",level:4},{value:"Optimizing the Online Feature Store",id:"optimizing-the-online-feature-store",level:4},{value:"Optimizing the Interaction Store",id:"optimizing-the-interaction-store",level:4},{value:"Results",id:"results",level:4},{value:"The Catch: Our ML Hosting Hit a Hard Limit",id:"the-catch-our-ml-hosting-hit-a-hard-limit",level:4},{value:"Conclusion: From Firefighting to Future-Proofing",id:"conclusion-from-firefighting-to-future-proofing",level:3}];function d(e){const n={h3:"h3",h4:"h4",img:"img",li:"li",ol:"ol",p:"p",ul:"ul",...(0,s.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.img,{alt:"BharatMLStack",src:t(5542).A+"",width:"1396",height:"460"}),"\nBy late 2022, we had built something we were truly proud of\u2014a real-time ML serving system with a DAG-based executor, a feature store, and an interaction store powering key ranking and personalization models. It was a major milestone, the culmination of months of effort from data scientists, ML engineers, and backend teams. Our system was live, and we were ready to push the boundaries of experimentation.\nAnd it worked. Mostly.\nBut soon, cracks appeared. Every new model needed custom feature retrieval logic, DAGs became dense and unmanageable, and scaling turned into a constant firefight. Costs surged, and infra bottlenecks slowed experimentation. Our system worked, but it wasn\u2019t built for scale.\nThis is the story of how we tackled these challenges\u2014building Inferflow for seamless feature retrieval, optimizing real-time infra, and cutting costs while scaling to millions of QPS."]}),"\n",(0,r.jsx)(n.h3,{id:"the-cost-of-success",children:"The Cost of Success"}),"\n",(0,r.jsx)(n.p,{children:"Every new Ranker model required its own feature set, often pulling from different entities. Each addition meant:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Adding new DAG nodes in IOP"}),"\n",(0,r.jsx)(n.li,{children:"Writing custom logic to fetch features from multiple sources (e.g., user, product, user \xd7 category)"}),"\n",(0,r.jsx)(n.li,{children:"Inferring intermediate features (e.g., extracting category from a product to fetch user \xd7 category data)"}),"\n",(0,r.jsx)(n.li,{children:"Optimizing I/O and dealing with the inevitable bugs"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"What began as clean DAGs soon turned into a tangled web of cross-dependent graphs. Every experimentation cycle meant new nodes, new dependencies, and slower iterations."}),"\n",(0,r.jsx)(n.h3,{id:"scaling-pains-and-cassandras-limits",children:"Scaling Pains (and Cassandra\u2019s Limits)"}),"\n",(0,r.jsx)(n.p,{children:"At some point, we were hitting:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"250\u2013300K reads/sec"}),"\n",(0,r.jsx)(n.li,{children:"1M writes/sec (during lean hours)"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"All of this ran on Cassandra. While its distributed architecture had been proven in production, operating large-scale clusters came with considerable infrastructure overhead. Our proof-of-concept (POC) demonstrated throughput of around 100K ops/sec, but as we scaled further, the challenges grew. Ensuring node health, optimizing compaction, and maintaining storage balance became increasingly demanding. We also observed latency spikes under heavy load, alongside a sharp increase in total cost of ownership."}),"\n",(0,r.jsx)(n.h3,{id:"interaction-store-woes",children:"Interaction Store Woes"}),"\n",(0,r.jsx)(n.p,{children:"Our interaction store was another ticking time bomb:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"\ud83d\udea8 Clusters kept growing in size and cost"}),"\n",(0,r.jsx)(n.li,{children:"\ud83d\udea8 Latency spikes became increasingly frequent"}),"\n",(0,r.jsx)(n.li,{children:"\ud83d\udea8 The DMC proxy occasionally lost locality of nodes against shards, causing cross-node communication and degraded performance"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"Each time this happened, we had to manually rebalance shards just to restore stable latency, making operations unsustainable at scale."}),"\n",(0,r.jsx)(n.h3,{id:"silver-linings",children:"Silver Linings"}),"\n",(0,r.jsx)(n.p,{children:"Despite the chaos, the system was live and delivering value:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Real-time infrastructure was in production"}),"\n",(0,r.jsx)(n.li,{children:"Costs dropped by 60\u201370% compared to offline personalization"}),"\n",(0,r.jsx)(n.li,{children:"New experiments rolled out faster and more successfully"}),"\n",(0,r.jsx)(n.li,{children:"User engagement metrics improved"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"It wasn\u2019t perfect. It was far from easy. But it worked\u2014and that counted for a lot."}),"\n",(0,r.jsx)(n.h3,{id:"round-two-solving-the-top-2-bottlenecks",children:"Round Two: Solving the Top 2 Bottlenecks"}),"\n",(0,r.jsx)(n.p,{children:"With the first-gen system stretched to its limits, we stepped back. Conversations with data scientists and backend engineers revealed three recurring pain points:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsx)(n.li,{children:"Coding feature retrieval logic for every new model was becoming unsustainable"}),"\n",(0,r.jsx)(n.li,{children:"ML scale was exploding\u2014bringing rising infra costs with it"}),"\n",(0,r.jsx)(n.li,{children:"Real-time embedding search was the next big unlock"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"We tackled them one by one\u2014starting with the biggest pain point."}),"\n",(0,r.jsx)(n.h4,{id:"problem-1-no-code-feature-retrieval-for-model-inference",children:"Problem 1: No-Code Feature Retrieval for Model Inference"}),"\n",(0,r.jsx)(n.p,{children:"We noticed a pattern: for personalized ranking, models needed features from:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"\u2705 Product"}),"\n",(0,r.jsx)(n.li,{children:"\u2705 User"}),"\n",(0,r.jsx)(n.li,{children:"\u2705 User \xd7 Category"}),"\n",(0,r.jsx)(n.li,{children:"\u2705 Region, cohort, sub-category, etc."}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"A key insight emerged: Entities that contribute features for a model always map back to the context entities."}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.img,{alt:"MP Dag",src:t(5754).A+"",width:"1272",height:"512"})}),"\n",(0,r.jsx)(n.p,{children:"With this, we designed Inferflow, a graph-driven feature retrieval and model orchestration system:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"1\ufe0f\u20e3 Inferflow takes a modelId and context IDs (e.g., userId, productIds)"}),"\n",(0,r.jsx)(n.li,{children:"2\ufe0f\u20e3 Loads a pre-defined feature retrieval graph from ZooKeeper"}),"\n",(0,r.jsx)(n.li,{children:"3\ufe0f\u20e3 Executes the graph to resolve entity relationships dynamically"}),"\n",(0,r.jsx)(n.li,{children:"4\ufe0f\u20e3 Outputs a 2D matrix of feature vectors"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"\ud83d\udca1 The impact?"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"\ud83d\ude80 No more custom feature retrieval code\u2014just graph updates in config"}),"\n",(0,r.jsx)(n.li,{children:"\ud83d\ude80 Feature consistency across experiments"}),"\n",(0,r.jsx)(n.li,{children:"\ud83d\ude80 Faster iteration cycles for ranking, fraud detection, and beyond"}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["Here\u2019s a visual example that shows how this graph plays out during execution. We further extended the graph to call multiple models as needed:\n",(0,r.jsx)(n.img,{alt:"MP matrix",src:t(8247).A+"",width:"1262",height:"768"}),"\nWe built Inferflow in GoLang, using gRPC and Proto3 serialization for efficiency."]}),"\n",(0,r.jsx)(n.h4,{id:"problem-2-scaling-without-breaking-the-bank",children:"Problem 2: Scaling Without Breaking the Bank"}),"\n",(0,r.jsx)(n.p,{children:"With more ML use cases coming online, we needed to cut costs without compromising performance. We focused on:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"\ud83d\udd39 Online Feature Store"}),"\n",(0,r.jsx)(n.li,{children:"\ud83d\udd39 Interaction Store"}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"optimizing-the-online-feature-store",children:"Optimizing the Online Feature Store"}),"\n",(0,r.jsx)(n.p,{children:"Our costs were concentrated in:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"\ud83d\udccc Database (Cassandra)"}),"\n",(0,r.jsx)(n.li,{children:"\ud83d\udccc Cache (Redis)"}),"\n",(0,r.jsx)(n.li,{children:"\ud83d\udccc Running Pods (Java services)"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"1\ufe0f\u20e3 Replacing Cassandra with ScyllaDB\nAs we hit the operational limits of large Cassandra clusters, we transitioned to ScyllaDB, which offered a seamless drop-in replacement without major code changes. The switch brought significant benefits:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Throughput: Matched or exceeded Cassandra's performance under identical workloads, even under high concurrency."}),"\n",(0,r.jsx)(n.li,{children:"Latency: Achieved consistently lower P99 latencies due to ScyllaDB's shard-per-core architecture and better I/O utilization."}),"\n",(0,r.jsx)(n.li,{children:"Cost Efficiency: Reduced infra footprint by ~70% through better CPU and memory efficiency, eliminating the need for over-provisioned nodes."}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"2\ufe0f\u20e3 Finding the Right Cache\nTo reduce backend load and improve response times, we benchmarked multiple caching solutions\u2014Memcached, KeyDB, and Dragonfly\u2014under real production traffic patterns. Dragonfly stood out due to its robust architecture and operational simplicity:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Data Skew Handling: Efficiently managed extreme key hotness and uneven access patterns without performance degradation."}),"\n",(0,r.jsx)(n.li,{children:"Throughput: Delivered consistently high throughput, even with large object sizes and concurrent access."}),"\n",(0,r.jsx)(n.li,{children:"Ease of Adoption: Acted as a drop-in Redis replacement with full protocol compatibility\u2014no changes needed in application code or client libraries."}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"3\ufe0f\u20e3 Moving to GoLang for Cost-Efficient Serving\nJava services were memory-heavy\u2014so we rewrote core services in GoLang. The results?"}),"\n",(0,r.jsx)(n.p,{children:"\u2705 Memory usage dropped by ~80%\n\u2705 CPU utilization was significantly lower\n\u2705 Faster, more efficient deployments"}),"\n",(0,r.jsx)(n.h4,{id:"optimizing-the-interaction-store",children:"Optimizing the Interaction Store"}),"\n",(0,r.jsx)(n.p,{children:"We realized that we only need a user\u2019s interaction data in Redis when they open the app. So, we implemented a tiered storage approach:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"\ud83d\udccc Cold Tier (ScyllaDB)\u2014Stores click, order, wishlist events"}),"\n",(0,r.jsx)(n.li,{children:"\ud83d\udccc Hot Tier (Redis)\u2014Loads a user\u2019s past interactions only when they open the app"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"Smart Offloading: We introduced an inactivity tracker to detect when a user session ends. At that point, Redis data was flushed back to Scylla, reducing unnecessary writes."}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.img,{alt:"InteractionStore",src:t(8406).A+"",width:"1242",height:"572"})}),"\n",(0,r.jsx)(n.h4,{id:"results",children:"Results"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Online Feature Store hit 1M QPS for the first time during the 2023 Mega Blockbuster Sale\u2014without breaking a sweat"}),"\n",(0,r.jsx)(n.li,{children:"Infra costs for Online Feature Store and Interaction Store dropped by ~60%"}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"the-catch-our-ml-hosting-hit-a-hard-limit",children:"The Catch: Our ML Hosting Hit a Hard Limit"}),"\n",(0,r.jsx)(n.p,{children:"While planning for 2023 MBS, we ran into a critical scalability bottleneck:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"\u274c Insufficient compute availability in our region for ML instances"}),"\n",(0,r.jsx)(n.li,{children:"\u274c Couldn\u2019t provision enough nodes to handle real-time inference at scale"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"This forced us to rethink where and how we hosted our models. The existing setup was great for prototyping\u2014but it wasn\u2019t built to handle the bursty, high-QPS demands of real-world production workloads."}),"\n",(0,r.jsx)(n.h3,{id:"conclusion-from-firefighting-to-future-proofing",children:"Conclusion: From Firefighting to Future-Proofing"}),"\n",(0,r.jsx)(n.p,{children:"What started as an ambitious experiment turned into a real-time ML infrastructure that powered millions of requests per second. We battled scaling pains, rethought feature retrieval with Inferflow, and rebuilt our infra stack for efficiency\u2014driving down costs while improving experimentation velocity.\nBut new challenges emerged. Our infrastructure could now handle scale, but our ML model hosting setup hit a hard limit. With compute availability bottlenecks threatening real-time inference, we faced a critical decision: how do we make model serving as scalable and cost-efficient as the rest of our stack? That\u2019s the next piece of the puzzle\u2014and the story of Part 3."})]})}function h(e={}){const{wrapper:n}={...(0,s.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(d,{...e})}):d(e)}},5069:e=>{e.exports=JSON.parse('{"permalink":"/BharatMLStack/blog/building-meeshos-mlplatform-lessons-from-first-gen","editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/blog/bharatmlstack-history/building-meeshos-mlplatform-lessons-from-first-gen/index.md","source":"@site/blog/bharatmlstack-history/building-meeshos-mlplatform-lessons-from-first-gen/index.md","title":"Building Meesho\u2019s ML Platform: Lessons from the First-Gen System (Part 2)","description":"Lessons from scaling Meesho\'s first-gen ML platform\u2014building Inferflow for no-code feature retrieval, migrating from Cassandra to ScyllaDB, optimizing the Interaction Store with tiered storage, and cutting infra costs by 60% while hitting 1M QPS.","date":"2023-04-10T00:00:00.000Z","tags":[{"inline":true,"label":"inferflow","permalink":"/BharatMLStack/blog/tags/inferflow"},{"inline":true,"label":"interaction-store","permalink":"/BharatMLStack/blog/tags/interaction-store"},{"inline":true,"label":"mlplatform","permalink":"/BharatMLStack/blog/tags/mlplatform"},{"inline":true,"label":"meesho","permalink":"/BharatMLStack/blog/tags/meesho"},{"inline":true,"label":"bharatmlstack","permalink":"/BharatMLStack/blog/tags/bharatmlstack"}],"readingTime":6.25,"hasTruncateMarker":false,"authors":[{"name":"Bhawani Singh","title":"Architect @ Meesho","url":"https://github.com/singh-bhawani","imageURL":"https://github.com/singh-bhawani.png","key":"bhawani","page":null},{"name":"Jigar Dave","title":"Lead Software Engineer @ Meesho","url":"https://github.com/jigarpatel26","imageURL":"https://github.com/jigarpatel26.png","key":"jigar","page":null},{"name":"Adarsha Das","title":"Senior Architect @ Meesho","url":"https://github.com/a0d00kc","imageURL":"https://github.com/a0d00kc.png","key":"adarsha","page":null}],"frontMatter":{"title":"Building Meesho\u2019s ML Platform: Lessons from the First-Gen System (Part 2)","description":"Lessons from scaling Meesho\'s first-gen ML platform\u2014building Inferflow for no-code feature retrieval, migrating from Cassandra to ScyllaDB, optimizing the Interaction Store with tiered storage, and cutting infra costs by 60% while hitting 1M QPS.","authors":["bhawani","jigar","adarsha"],"slug":"building-meeshos-mlplatform-lessons-from-first-gen","date":"2023-4-10","tags":["inferflow","interaction-store","mlplatform","meesho","bharatmlstack"]},"unlisted":false,"prevItem":{"title":"Cracking the Code: Scaling Model Inference & Real-Time Embedding Search","permalink":"/BharatMLStack/blog/scaling-model-inference-and-embedding-search"},"nextItem":{"title":"Building Meesho\u2019s ML Platform: From Chaos to Cutting-Edge (Part 1)","permalink":"/BharatMLStack/blog/building-meeshos-mlplatform"}}')},5542:(e,n,t)=>{t.d(n,{A:()=>i});const i=t.p+"assets/images/bms-7399e8796d2cd24617c432518ce3f312.png"},5754:(e,n,t)=>{t.d(n,{A:()=>i});const i=t.p+"assets/images/mp-dag-976ff51caf25f09d977ccc10e70918f3.png"},8247:(e,n,t)=>{t.d(n,{A:()=>i});const i=t.p+"assets/images/mp-matrix-43994f433f78905ccbd10cfe284f3c9f.png"},8406:(e,n,t)=>{t.d(n,{A:()=>i});const i=t.p+"assets/images/interaction-str-d9e7aefea121aefb4e94c6c9f060d016.png"},8453:(e,n,t)=>{t.d(n,{R:()=>a,x:()=>o});var i=t(6540);const r={},s=i.createContext(r);function a(e){const n=i.useContext(s);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:a(e.components),i.createElement(s.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/1a64de69.22893b6e.js b/docs/assets/js/1a64de69.22893b6e.js deleted file mode 100644 index 4e9a6ac1..00000000 --- a/docs/assets/js/1a64de69.22893b6e.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[3645],{1694:a=>{a.exports=JSON.parse('{"tag":{"label":"meesho","permalink":"/BharatMLStack/blog/tags/meesho","allTagsPath":"/BharatMLStack/blog/tags","count":1,"unlisted":false},"listMetadata":{"permalink":"/BharatMLStack/blog/tags/meesho","page":1,"postsPerPage":10,"totalPages":1,"totalCount":1,"blogDescription":"Blog","blogTitle":"Blog"}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/1a64de69.844e372c.js b/docs/assets/js/1a64de69.844e372c.js new file mode 100644 index 00000000..e55cfef9 --- /dev/null +++ b/docs/assets/js/1a64de69.844e372c.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[3645],{1694:a=>{a.exports=JSON.parse('{"tag":{"label":"meesho","permalink":"/BharatMLStack/blog/tags/meesho","allTagsPath":"/BharatMLStack/blog/tags","count":5,"unlisted":false},"listMetadata":{"permalink":"/BharatMLStack/blog/tags/meesho","page":1,"postsPerPage":10,"totalPages":1,"totalCount":5,"blogDescription":"Blog","blogTitle":"Blog"}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/1f391b9e.4acd5995.js b/docs/assets/js/1f391b9e.4acd5995.js new file mode 100644 index 00000000..7ffe71ca --- /dev/null +++ b/docs/assets/js/1f391b9e.4acd5995.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[6061],{1689:(e,n,t)=>{t.d(n,{A:()=>d});t(6540);var a=t(4164),i=t(4084),s=t(7559),r=t(7293),l=t(4848);function c({className:e}){return(0,l.jsx)(r.A,{type:"caution",title:(0,l.jsx)(i.Yh,{}),className:(0,a.A)(e,s.G.common.draftBanner),children:(0,l.jsx)(i.TT,{})})}var o=t(2234);function d({metadata:e}){const{unlisted:n,frontMatter:t}=e;return(0,l.jsxs)(l.Fragment,{children:[(n||t.unlisted)&&(0,l.jsx)(o.A,{}),t.draft&&(0,l.jsx)(c,{})]})}},2234:(e,n,t)=>{t.d(n,{A:()=>o});t(6540);var a=t(4164),i=t(7559),s=t(4084),r=t(7293),l=t(4848);function c({className:e}){return(0,l.jsx)(r.A,{type:"caution",title:(0,l.jsx)(s.Rc,{}),className:(0,a.A)(e,i.G.common.unlistedBanner),children:(0,l.jsx)(s.Uh,{})})}function o(e){return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(s.AE,{}),(0,l.jsx)(c,{...e})]})}},4084:(e,n,t)=>{t.d(n,{AE:()=>c,Rc:()=>r,TT:()=>d,Uh:()=>l,Yh:()=>o});t(6540);var a=t(1312),i=t(5260),s=t(4848);function r(){return(0,s.jsx)(a.A,{id:"theme.contentVisibility.unlistedBanner.title",description:"The unlisted content banner title",children:"Unlisted page"})}function l(){return(0,s.jsx)(a.A,{id:"theme.contentVisibility.unlistedBanner.message",description:"The unlisted content banner message",children:"This page is unlisted. Search engines will not index it, and only users having a direct link can access it."})}function c(){return(0,s.jsx)(i.A,{children:(0,s.jsx)("meta",{name:"robots",content:"noindex, nofollow"})})}function o(){return(0,s.jsx)(a.A,{id:"theme.contentVisibility.draftBanner.title",description:"The draft content banner title",children:"Draft page"})}function d(){return(0,s.jsx)(a.A,{id:"theme.contentVisibility.draftBanner.message",description:"The draft content banner message",children:"This page is a draft. It will only be visible in dev and be excluded from the production build."})}},5195:(e,n,t)=>{t.d(n,{A:()=>v});var a=t(6540),i=t(6342);function s(e){const n=e.map(e=>({...e,parentIndex:-1,children:[]})),t=Array(7).fill(-1);n.forEach((e,n)=>{const a=t.slice(2,e.level);e.parentIndex=Math.max(...a),t[e.level]=n});const a=[];return n.forEach(e=>{const{parentIndex:t,...i}=e;t>=0?n[t].children.push(i):a.push(i)}),a}function r({toc:e,minHeadingLevel:n,maxHeadingLevel:t}){return e.flatMap(e=>{const a=r({toc:e.children,minHeadingLevel:n,maxHeadingLevel:t});return function(e){return e.level>=n&&e.level<=t}(e)?[{...e,children:a}]:a})}function l(e){const n=e.getBoundingClientRect();return n.top===n.bottom?l(e.parentNode):n}function c(e,{anchorTopOffset:n}){const t=e.find(e=>l(e).top>=n);if(t){return function(e){return e.top>0&&e.bottom{e.current=n?0:document.querySelector(".navbar").clientHeight},[n]),e}function d(e){const n=(0,a.useRef)(void 0),t=o();(0,a.useEffect)(()=>{if(!e)return()=>{};const{linkClassName:a,linkActiveClassName:i,minHeadingLevel:s,maxHeadingLevel:r}=e;function l(){const e=function(e){return Array.from(document.getElementsByClassName(e))}(a),l=function({minHeadingLevel:e,maxHeadingLevel:n}){const t=[];for(let a=e;a<=n;a+=1)t.push(`h${a}.anchor`);return Array.from(document.querySelectorAll(t.join()))}({minHeadingLevel:s,maxHeadingLevel:r}),o=c(l,{anchorTopOffset:t.current}),d=e.find(e=>o&&o.id===function(e){return decodeURIComponent(e.href.substring(e.href.indexOf("#")+1))}(e));e.forEach(e=>{!function(e,t){t?(n.current&&n.current!==e&&n.current.classList.remove(i),e.classList.add(i),n.current=e):e.classList.remove(i)}(e,e===d)})}return document.addEventListener("scroll",l),document.addEventListener("resize",l),l(),()=>{document.removeEventListener("scroll",l),document.removeEventListener("resize",l)}},[e,t])}var m=t(8774),u=t(4848);function f({toc:e,className:n,linkClassName:t,isChild:a}){return e.length?(0,u.jsx)("ul",{className:a?void 0:n,children:e.map(e=>(0,u.jsxs)("li",{children:[(0,u.jsx)(m.A,{to:`#${e.id}`,className:t??void 0,dangerouslySetInnerHTML:{__html:e.value}}),(0,u.jsx)(f,{isChild:!0,toc:e.children,className:n,linkClassName:t})]},e.id))}):null}const h=a.memo(f);function v({toc:e,className:n="table-of-contents table-of-contents__left-border",linkClassName:t="table-of-contents__link",linkActiveClassName:l,minHeadingLevel:c,maxHeadingLevel:o,...m}){const f=(0,i.p)(),v=c??f.tableOfContents.minHeadingLevel,x=o??f.tableOfContents.maxHeadingLevel,g=function({toc:e,minHeadingLevel:n,maxHeadingLevel:t}){return(0,a.useMemo)(()=>r({toc:s(e),minHeadingLevel:n,maxHeadingLevel:t}),[e,n,t])}({toc:e,minHeadingLevel:v,maxHeadingLevel:x});return d((0,a.useMemo)(()=>{if(t&&l)return{linkClassName:t,linkActiveClassName:l,minHeadingLevel:v,maxHeadingLevel:x}},[t,l,v,x])),(0,u.jsx)(h,{toc:g,className:n,linkClassName:t,...m})}},7763:(e,n,t)=>{t.d(n,{A:()=>o});t(6540);var a=t(4164),i=t(5195);const s={tableOfContents:"tableOfContents_bqdL",docItemContainer:"docItemContainer_F8PC"};var r=t(4848);const l="table-of-contents__link toc-highlight",c="table-of-contents__link--active";function o({className:e,...n}){return(0,r.jsx)("div",{className:(0,a.A)(s.tableOfContents,"thin-scrollbar",e),children:(0,r.jsx)(i.A,{...n,linkClassName:l,linkActiveClassName:c})})}},7973:(e,n,t)=>{t.r(n),t.d(n,{default:()=>f});t(6540);var a=t(4164),i=t(5500),s=t(7559),r=t(1656),l=t(3253),c=t(7763),o=t(1689),d=t(4336);const m={mdxPageWrapper:"mdxPageWrapper_j9I6"};var u=t(4848);function f(e){const{content:n}=e,{metadata:t,assets:f}=n,{title:h,editUrl:v,description:x,frontMatter:g,lastUpdatedBy:p,lastUpdatedAt:j}=t,{keywords:A,wrapperClassName:b,hide_table_of_contents:L}=g,N=f.image??g.image,C=!!(v||j||p);return(0,u.jsx)(i.e3,{className:(0,a.A)(b??s.G.wrapper.mdxPages,s.G.page.mdxPage),children:(0,u.jsxs)(r.A,{children:[(0,u.jsx)(i.be,{title:h,description:x,keywords:A,image:N}),(0,u.jsx)("main",{className:"container container--fluid margin-vert--lg",children:(0,u.jsxs)("div",{className:(0,a.A)("row",m.mdxPageWrapper),children:[(0,u.jsxs)("div",{className:(0,a.A)("col",!L&&"col--8"),children:[(0,u.jsx)(o.A,{metadata:t}),(0,u.jsx)("article",{children:(0,u.jsx)(l.A,{children:(0,u.jsx)(n,{})})}),C&&(0,u.jsx)(d.A,{className:(0,a.A)("margin-top--sm",s.G.pages.pageFooterEditMetaRow),editUrl:v,lastUpdatedAt:j,lastUpdatedBy:p})]}),!L&&n.toc.length>0&&(0,u.jsx)("div",{className:"col col--2",children:(0,u.jsx)(c.A,{toc:n.toc,minHeadingLevel:g.toc_min_heading_level,maxHeadingLevel:g.toc_max_heading_level})})]})})]})})}}}]); \ No newline at end of file diff --git a/docs/assets/js/1f391b9e.8e4379a0.js b/docs/assets/js/1f391b9e.8e4379a0.js deleted file mode 100644 index 76c76713..00000000 --- a/docs/assets/js/1f391b9e.8e4379a0.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[6061],{1689:(e,n,t)=>{t.d(n,{A:()=>d});t(6540);var a=t(4164),i=t(4084),s=t(7559),r=t(7293),l=t(4848);function c({className:e}){return(0,l.jsx)(r.A,{type:"caution",title:(0,l.jsx)(i.Yh,{}),className:(0,a.A)(e,s.G.common.draftBanner),children:(0,l.jsx)(i.TT,{})})}var o=t(2234);function d({metadata:e}){const{unlisted:n,frontMatter:t}=e;return(0,l.jsxs)(l.Fragment,{children:[(n||t.unlisted)&&(0,l.jsx)(o.A,{}),t.draft&&(0,l.jsx)(c,{})]})}},2234:(e,n,t)=>{t.d(n,{A:()=>o});t(6540);var a=t(4164),i=t(4084),s=t(7559),r=t(7293),l=t(4848);function c({className:e}){return(0,l.jsx)(r.A,{type:"caution",title:(0,l.jsx)(i.Rc,{}),className:(0,a.A)(e,s.G.common.unlistedBanner),children:(0,l.jsx)(i.Uh,{})})}function o(e){return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(i.AE,{}),(0,l.jsx)(c,{...e})]})}},4084:(e,n,t)=>{t.d(n,{AE:()=>c,Rc:()=>r,TT:()=>d,Uh:()=>l,Yh:()=>o});t(6540);var a=t(1312),i=t(5260),s=t(4848);function r(){return(0,s.jsx)(a.A,{id:"theme.contentVisibility.unlistedBanner.title",description:"The unlisted content banner title",children:"Unlisted page"})}function l(){return(0,s.jsx)(a.A,{id:"theme.contentVisibility.unlistedBanner.message",description:"The unlisted content banner message",children:"This page is unlisted. Search engines will not index it, and only users having a direct link can access it."})}function c(){return(0,s.jsx)(i.A,{children:(0,s.jsx)("meta",{name:"robots",content:"noindex, nofollow"})})}function o(){return(0,s.jsx)(a.A,{id:"theme.contentVisibility.draftBanner.title",description:"The draft content banner title",children:"Draft page"})}function d(){return(0,s.jsx)(a.A,{id:"theme.contentVisibility.draftBanner.message",description:"The draft content banner message",children:"This page is a draft. It will only be visible in dev and be excluded from the production build."})}},5195:(e,n,t)=>{t.d(n,{A:()=>v});var a=t(6540),i=t(6342);function s(e){const n=e.map((e=>({...e,parentIndex:-1,children:[]}))),t=Array(7).fill(-1);n.forEach(((e,n)=>{const a=t.slice(2,e.level);e.parentIndex=Math.max(...a),t[e.level]=n}));const a=[];return n.forEach((e=>{const{parentIndex:t,...i}=e;t>=0?n[t].children.push(i):a.push(i)})),a}function r({toc:e,minHeadingLevel:n,maxHeadingLevel:t}){return e.flatMap((e=>{const a=r({toc:e.children,minHeadingLevel:n,maxHeadingLevel:t});return function(e){return e.level>=n&&e.level<=t}(e)?[{...e,children:a}]:a}))}function l(e){const n=e.getBoundingClientRect();return n.top===n.bottom?l(e.parentNode):n}function c(e,{anchorTopOffset:n}){const t=e.find((e=>l(e).top>=n));if(t){return function(e){return e.top>0&&e.bottom{e.current=n?0:document.querySelector(".navbar").clientHeight}),[n]),e}function d(e){const n=(0,a.useRef)(void 0),t=o();(0,a.useEffect)((()=>{if(!e)return()=>{};const{linkClassName:a,linkActiveClassName:i,minHeadingLevel:s,maxHeadingLevel:r}=e;function l(){const e=function(e){return Array.from(document.getElementsByClassName(e))}(a),l=function({minHeadingLevel:e,maxHeadingLevel:n}){const t=[];for(let a=e;a<=n;a+=1)t.push(`h${a}.anchor`);return Array.from(document.querySelectorAll(t.join()))}({minHeadingLevel:s,maxHeadingLevel:r}),o=c(l,{anchorTopOffset:t.current}),d=e.find((e=>o&&o.id===function(e){return decodeURIComponent(e.href.substring(e.href.indexOf("#")+1))}(e)));e.forEach((e=>{!function(e,t){t?(n.current&&n.current!==e&&n.current.classList.remove(i),e.classList.add(i),n.current=e):e.classList.remove(i)}(e,e===d)}))}return document.addEventListener("scroll",l),document.addEventListener("resize",l),l(),()=>{document.removeEventListener("scroll",l),document.removeEventListener("resize",l)}}),[e,t])}var m=t(8774),u=t(4848);function f({toc:e,className:n,linkClassName:t,isChild:a}){return e.length?(0,u.jsx)("ul",{className:a?void 0:n,children:e.map((e=>(0,u.jsxs)("li",{children:[(0,u.jsx)(m.A,{to:`#${e.id}`,className:t??void 0,dangerouslySetInnerHTML:{__html:e.value}}),(0,u.jsx)(f,{isChild:!0,toc:e.children,className:n,linkClassName:t})]},e.id)))}):null}const h=a.memo(f);function v({toc:e,className:n="table-of-contents table-of-contents__left-border",linkClassName:t="table-of-contents__link",linkActiveClassName:l,minHeadingLevel:c,maxHeadingLevel:o,...m}){const f=(0,i.p)(),v=c??f.tableOfContents.minHeadingLevel,x=o??f.tableOfContents.maxHeadingLevel,g=function({toc:e,minHeadingLevel:n,maxHeadingLevel:t}){return(0,a.useMemo)((()=>r({toc:s(e),minHeadingLevel:n,maxHeadingLevel:t})),[e,n,t])}({toc:e,minHeadingLevel:v,maxHeadingLevel:x});return d((0,a.useMemo)((()=>{if(t&&l)return{linkClassName:t,linkActiveClassName:l,minHeadingLevel:v,maxHeadingLevel:x}}),[t,l,v,x])),(0,u.jsx)(h,{toc:g,className:n,linkClassName:t,...m})}},7763:(e,n,t)=>{t.d(n,{A:()=>o});t(6540);var a=t(4164),i=t(5195);const s={tableOfContents:"tableOfContents_bqdL",docItemContainer:"docItemContainer_F8PC"};var r=t(4848);const l="table-of-contents__link toc-highlight",c="table-of-contents__link--active";function o({className:e,...n}){return(0,r.jsx)("div",{className:(0,a.A)(s.tableOfContents,"thin-scrollbar",e),children:(0,r.jsx)(i.A,{...n,linkClassName:l,linkActiveClassName:c})})}},7973:(e,n,t)=>{t.r(n),t.d(n,{default:()=>f});t(6540);var a=t(4164),i=t(5500),s=t(7559),r=t(1656),l=t(3253),c=t(7763),o=t(1689),d=t(4336);const m={mdxPageWrapper:"mdxPageWrapper_j9I6"};var u=t(4848);function f(e){const{content:n}=e,{metadata:t,assets:f}=n,{title:h,editUrl:v,description:x,frontMatter:g,lastUpdatedBy:p,lastUpdatedAt:j}=t,{keywords:A,wrapperClassName:b,hide_table_of_contents:L}=g,N=f.image??g.image,C=!!(v||j||p);return(0,u.jsx)(i.e3,{className:(0,a.A)(b??s.G.wrapper.mdxPages,s.G.page.mdxPage),children:(0,u.jsxs)(r.A,{children:[(0,u.jsx)(i.be,{title:h,description:x,keywords:A,image:N}),(0,u.jsx)("main",{className:"container container--fluid margin-vert--lg",children:(0,u.jsxs)("div",{className:(0,a.A)("row",m.mdxPageWrapper),children:[(0,u.jsxs)("div",{className:(0,a.A)("col",!L&&"col--8"),children:[(0,u.jsx)(o.A,{metadata:t}),(0,u.jsx)("article",{children:(0,u.jsx)(l.A,{children:(0,u.jsx)(n,{})})}),C&&(0,u.jsx)(d.A,{className:(0,a.A)("margin-top--sm",s.G.pages.pageFooterEditMetaRow),editUrl:v,lastUpdatedAt:j,lastUpdatedBy:p})]}),!L&&n.toc.length>0&&(0,u.jsx)("div",{className:"col col--2",children:(0,u.jsx)(c.A,{toc:n.toc,minHeadingLevel:g.toc_min_heading_level,maxHeadingLevel:g.toc_max_heading_level})})]})})]})})}}}]); \ No newline at end of file diff --git a/docs/assets/js/2303959d.3e6bfee0.js b/docs/assets/js/2303959d.3e6bfee0.js new file mode 100644 index 00000000..3397089a --- /dev/null +++ b/docs/assets/js/2303959d.3e6bfee0.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[2576],{2199:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/bms-7399e8796d2cd24617c432518ce3f312.png"},5376:e=>{e.exports=JSON.parse('{"permalink":"/BharatMLStack/blog/scaling-model-inference-and-embedding-search","editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/blog/bharatmlstack-history/scaling-model-inference-and-embedding-search/index.md","source":"@site/blog/bharatmlstack-history/scaling-model-inference-and-embedding-search/index.md","title":"Cracking the Code: Scaling Model Inference & Real-Time Embedding Search","description":"How Meesho scaled model inference with self-hosted Triton on GKE\u2014slashing latency and costs by 65%\u2014and built a real-time embedding search system on Qdrant to power personalized recommendations at scale.","date":"2024-05-21T00:00:00.000Z","tags":[{"inline":true,"label":"model-inference","permalink":"/BharatMLStack/blog/tags/model-inference"},{"inline":true,"label":"embedding-search","permalink":"/BharatMLStack/blog/tags/embedding-search"},{"inline":true,"label":"mlplatform","permalink":"/BharatMLStack/blog/tags/mlplatform"},{"inline":true,"label":"meesho","permalink":"/BharatMLStack/blog/tags/meesho"},{"inline":true,"label":"bharatmlstack","permalink":"/BharatMLStack/blog/tags/bharatmlstack"}],"readingTime":3.55,"hasTruncateMarker":false,"authors":[{"name":"Aditya Kumar","title":"Lead Software Engineer @ Meesho","url":"https://github.com/Adit2607","imageURL":"https://github.com/Adit2607.png","key":"aditya","page":null},{"name":"Jaya Kumar","title":"Lead ML Engineer @ Meesho","url":"https://github.com/jayakommuru","imageURL":"https://github.com/jayakommuru.png","key":"jaya","page":null},{"name":"Adarsha Das","title":"Senior Architect @ Meesho","url":"https://github.com/a0d00kc","imageURL":"https://github.com/a0d00kc.png","key":"adarsha","page":null}],"frontMatter":{"title":"Cracking the Code: Scaling Model Inference & Real-Time Embedding Search","description":"How Meesho scaled model inference with self-hosted Triton on GKE\u2014slashing latency and costs by 65%\u2014and built a real-time embedding search system on Qdrant to power personalized recommendations at scale.","authors":["aditya","jaya","adarsha"],"slug":"scaling-model-inference-and-embedding-search","date":"2024-05-21T00:00:00.000Z","tags":["model-inference","embedding-search","mlplatform","meesho","bharatmlstack"]},"unlisted":false,"prevItem":{"title":"Designing a Production-Grade LLM Inference Platform: From Model Weights to Scalable GPU Serving","permalink":"/BharatMLStack/blog/multi-engine-llm-inferencing-platform"},"nextItem":{"title":"Building Meesho\u2019s ML Platform: Lessons from the First-Gen System (Part 2)","permalink":"/BharatMLStack/blog/building-meeshos-mlplatform-lessons-from-first-gen"}}')},5869:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/vss-c482f6eac4c68b3219e4c562a6b717ec.png"},8453:(e,n,i)=>{i.d(n,{R:()=>r,x:()=>l});var t=i(6540);const a={},s=t.createContext(a);function r(e){const n=t.useContext(s);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(a):e.components||a:r(e.components),t.createElement(s.Provider,{value:n},e.children)}},9494:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>o,contentTitle:()=>l,default:()=>h,frontMatter:()=>r,metadata:()=>t,toc:()=>d});var t=i(5376),a=i(4848),s=i(8453);const r={title:"Cracking the Code: Scaling Model Inference & Real-Time Embedding Search",description:"How Meesho scaled model inference with self-hosted Triton on GKE\u2014slashing latency and costs by 65%\u2014and built a real-time embedding search system on Qdrant to power personalized recommendations at scale.",authors:["aditya","jaya","adarsha"],slug:"scaling-model-inference-and-embedding-search",date:new Date("2024-05-21T00:00:00.000Z"),tags:["model-inference","embedding-search","mlplatform","meesho","bharatmlstack"]},l=void 0,o={authorsImageUrls:[void 0,void 0,void 0]},d=[{value:"Breaking Free from the Scalability Ceiling",id:"breaking-free-from-the-scalability-ceiling",level:2},{value:"The Model Serving Bottleneck\u2014A Wake-Up Call",id:"the-model-serving-bottlenecka-wake-up-call",level:3},{value:"Scaling Triton on GKE",id:"scaling-triton-on-gke",level:3},{value:"Fixing the Cold Start Problem",id:"fixing-the-cold-start-problem",level:3},{value:"Embedding Search: The Last Piece of the Puzzle",id:"embedding-search-the-last-piece-of-the-puzzle",level:2},{value:"Choosing the Right Vector Database",id:"choosing-the-right-vector-database",level:3},{value:"Embedding Freshness & Real-Time Updates",id:"embedding-freshness--real-time-updates",level:3},{value:"Final Takeaways: Scaling Smartly for Real-Time ML",id:"final-takeaways-scaling-smartly-for-real-time-ml",level:2}];function c(e){const n={h2:"h2",h3:"h3",img:"img",li:"li",p:"p",ul:"ul",...(0,s.R)(),...e.components};return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsxs)(n.p,{children:[(0,a.jsx)(n.img,{alt:"BharatMLStack",src:i(2199).A+"",width:"1396",height:"460"}),"\nBy mid-2023, we had transformed our ML stack\u2014building a real-time feature store, optimizing model retrieval, and fine-tuning ranking. But two critical gaps remained:"]}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:"\ud83d\udd39 Scaling model inference without hitting infrastructure roadblocks"}),"\n",(0,a.jsx)(n.li,{children:"\ud83d\udd39 Moving embedding search from batch to real-time for candidate generation"}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:"Here\u2019s how we tackled these last-mile challenges, broke free from infrastructure constraints, and built a cost-efficient, high-performance system."}),"\n",(0,a.jsx)(n.h2,{id:"breaking-free-from-the-scalability-ceiling",children:"Breaking Free from the Scalability Ceiling"}),"\n",(0,a.jsx)(n.h3,{id:"the-model-serving-bottlenecka-wake-up-call",children:"The Model Serving Bottleneck\u2014A Wake-Up Call"}),"\n",(0,a.jsx)(n.p,{children:"July 2023. With just months left for the Mega Blockbuster Sale (MBS), we noticed a serious issue\u2014scaling our model-serving infrastructure was taking 10\u201315 minutes. In real-time ML, that\u2019s an eternity.\nIn one of our war rooms, we ran a quick experiment:"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:"\ud83d\ude80 We deployed an XGBoost model on a self-hosted Triton Inference Server running on a 16-core machine."}),"\n",(0,a.jsx)(n.li,{children:"\ud83d\ude80 Fired requests and compared the outputs with our existing cloud-hosted setup."}),"\n",(0,a.jsx)(n.li,{children:"\ud83d\ude80 The results matched\u2014perfectly."}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:'That moment changed everything. We prepped a backup Triton setup on EKS, just in case our cloud provider couldn\'t allocate enough compute resources in time. Luckily, they did\u2014but the seed was planted.\nThen in October, just two weeks before MBS, we got an alarming response from our infrastructure team:\n"Node availability may be an issue."\nWith no time to waste, we moved 30% of real-time ML traffic to our self-hosted Triton cluster. The results?'}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:"\u2705 p99 latency dropped from 90\u2013100ms to 30\u201340ms"}),"\n",(0,a.jsx)(n.li,{children:"\u2705 Triton handled significantly higher throughput on fewer resources"}),"\n",(0,a.jsx)(n.li,{children:"\u2705 No model changes were needed"}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:"MBS ran without a hitch, proving that self-hosted inference was the way forward."}),"\n",(0,a.jsx)(n.h3,{id:"scaling-triton-on-gke",children:"Scaling Triton on GKE"}),"\n",(0,a.jsx)(n.p,{children:"This left us with two choices:"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:"1\ufe0f\u20e3 Port models to a managed cloud inference service, investing time in learning a new deployment stack"}),"\n",(0,a.jsx)(n.li,{children:"2\ufe0f\u20e3 Scale our existing Triton setup on GKE, optimizing for cost and performance"}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:"We went with Option 2\u2014and it slashed inference costs to 35% of what we previously paid, while giving us full control over scaling and optimizations."}),"\n",(0,a.jsx)(n.h3,{id:"fixing-the-cold-start-problem",children:"Fixing the Cold Start Problem"}),"\n",(0,a.jsx)(n.p,{children:"As we onboarded more deep learning (DL) models, we hit a new bottleneck, new inference pods took 7\u20139 minutes to spin up."}),"\n",(0,a.jsx)(n.p,{children:"After profiling, we found the culprits:"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:"Triton\u2019s base image\u2014a massive 5GB"}),"\n",(0,a.jsx)(n.li,{children:"Model binaries\u2014often 1GB+"}),"\n",(0,a.jsx)(n.li,{children:"Startup delay\u2014mostly due to downloading and initializing these assets"}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:"To fix this, we built a lightweight Triton image, stripping unused components and shrinking the size to 900MB. This cut cold start times drastically, making auto-scaling faster and smoother."}),"\n",(0,a.jsx)(n.h2,{id:"embedding-search-the-last-piece-of-the-puzzle",children:"Embedding Search: The Last Piece of the Puzzle"}),"\n",(0,a.jsx)(n.p,{children:"By mid-2023, most of our ML stack had gone real-time\u2014except for Candidate Generation (CG), which still ran in batch mode. To truly power real-time recommendations, we needed an online embedding search system."}),"\n",(0,a.jsx)(n.h3,{id:"choosing-the-right-vector-database",children:"Choosing the Right Vector Database"}),"\n",(0,a.jsx)(n.p,{children:"We benchmarked three production-ready vector DBs across key parameters:"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:"Milvus"}),"\n",(0,a.jsx)(n.li,{children:"Qdrant"}),"\n",(0,a.jsx)(n.li,{children:"Weaviate"}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:"After extensive POCs, Qdrant stood out for its:"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:"\u2705 Blazing-fast search latency on high-dimensional vectors"}),"\n",(0,a.jsx)(n.li,{children:"\u2705 Efficient memory usage, crucial for in-memory workloads"}),"\n",(0,a.jsx)(n.li,{children:"\u2705 Support for upserts and soft deletes, vital for Ads use cases"}),"\n",(0,a.jsx)(n.li,{children:"\u2705 gRPC + REST APIs, making integration seamless"}),"\n",(0,a.jsx)(n.li,{children:"\u2705 Powerful filtering, allowing fine-tuned retrieval (e.g., filtering Ads by category, active status, etc.)"}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:"At its core, Qdrant uses HNSW indexing, delivering both high recall and low-latency nearest-neighbor search\u2014a perfect fit for our needs."}),"\n",(0,a.jsx)(n.h3,{id:"embedding-freshness--real-time-updates",children:"Embedding Freshness & Real-Time Updates"}),"\n",(0,a.jsx)(n.p,{children:"To ensure embeddings stayed up to date, we built a dual ingestion pipeline:"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:"\ud83d\udccc Daily Refresh: A bulk pipeline updated embeddings overnight"}),"\n",(0,a.jsx)(n.li,{children:"\ud83d\udccc Real-Time Updates: Ads events triggered immediate upserts/deletes"}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:'This setup powered real-time "Similar Products" recommendations on the product page and became the foundation for Ads Candidate Generation, ensuring the right ads surfaced in milliseconds.'}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.img,{alt:"Skye",src:i(5869).A+"",width:"1260",height:"644"})}),"\n",(0,a.jsx)(n.h2,{id:"final-takeaways-scaling-smartly-for-real-time-ml",children:"Final Takeaways: Scaling Smartly for Real-Time ML"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:"\ud83d\ude80 Self-hosted inference on Triton gave us lower cost, faster scaling, and better performance than managed services"}),"\n",(0,a.jsx)(n.li,{children:"\ud83d\ude80 Building a custom Triton image reduced cold starts, improving responsiveness"}),"\n",(0,a.jsx)(n.li,{children:"\ud83d\ude80 Qdrant-based embedding search enabled real-time personalization at scale"}),"\n",(0,a.jsx)(n.li,{children:"\ud83d\ude80 Real-time updates for embeddings unlocked dynamic, up-to-date recommendations"}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:"By early 2024, Meesho\u2019s ML stack had evolved into a fully real-time, scalable, and cost-efficient system, setting the foundation for even bigger leaps ahead."})]})}function h(e={}){const{wrapper:n}={...(0,s.R)(),...e.components};return n?(0,a.jsx)(n,{...e,children:(0,a.jsx)(c,{...e})}):c(e)}}}]); \ No newline at end of file diff --git a/docs/assets/js/23a1b8fc.29176600.js b/docs/assets/js/23a1b8fc.29176600.js new file mode 100644 index 00000000..6e8296cd --- /dev/null +++ b/docs/assets/js/23a1b8fc.29176600.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[8439],{211:(e,n,r)=>{r.r(n),r.d(n,{assets:()=>d,contentTitle:()=>l,default:()=>h,frontMatter:()=>o,metadata:()=>i,toc:()=>c});const i=JSON.parse('{"id":"predator/v1.0.0/architecture","title":"Architecture","description":"Predator is a scalable, high-performance model inference service built as a wrapper around the NVIDIA Triton Inference Server. It is designed to serve a variety of machine learning models (Deep Learning, Tree-based, etc.) with low latency in a Kubernetes (K8s) environment.","source":"@site/docs/predator/v1.0.0/architecture.md","sourceDirName":"predator/v1.0.0","slug":"/predator/v1.0.0/architecture","permalink":"/BharatMLStack/predator/v1.0.0/architecture","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/predator/v1.0.0/architecture.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"Architecture","sidebar_position":1},"sidebar":"tutorialSidebar","previous":{"title":"v1.0.0","permalink":"/BharatMLStack/predator/v1.0.0"},"next":{"title":"Key Functionalities","permalink":"/BharatMLStack/predator/v1.0.0/functionalities"}}');var s=r(4848),t=r(8453);const o={title:"Architecture",sidebar_position:1},l="BharatMLStack - Predator",d={},c=[{value:"High-Level Design",id:"high-level-design",level:2},{value:"End-to-End Flow",id:"end-to-end-flow",level:3},{value:"Key Design Principles",id:"key-design-principles",level:3},{value:"Inference Engine: Triton Inference Server",id:"inference-engine-triton-inference-server",level:2},{value:"Core Components",id:"core-components",level:3},{value:"Backends",id:"backends",level:3},{value:"Key Features",id:"key-features",level:3},{value:"Model Repository Structure",id:"model-repository-structure",level:2},{value:"Sample config.pbtxt",id:"sample-configpbtxt",level:3},{value:"Kubernetes Deployment Architecture",id:"kubernetes-deployment-architecture",level:2},{value:"Pod Architecture",id:"pod-architecture",level:3},{value:"Init Container",id:"init-container",level:4},{value:"Triton Inference Server Container",id:"triton-inference-server-container",level:4},{value:"Triton Server Image Strategy",id:"triton-server-image-strategy",level:3},{value:"Image Distribution Optimization",id:"image-distribution-optimization",level:3},{value:"Health Probes",id:"health-probes",level:3},{value:"Resource Configuration",id:"resource-configuration",level:3},{value:"Autoscaling Architecture",id:"autoscaling-architecture",level:3},{value:"Contributing",id:"contributing",level:2},{value:"Community & Support",id:"community--support",level:2},{value:"License",id:"license",level:2}];function a(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",hr:"hr",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,t.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.header,{children:(0,s.jsx)(n.h1,{id:"bharatmlstack---predator",children:"BharatMLStack - Predator"})}),"\n",(0,s.jsxs)(n.p,{children:["Predator is a scalable, high-performance model inference service built as a wrapper around the ",(0,s.jsx)(n.strong,{children:"NVIDIA Triton Inference Server"}),". It is designed to serve a variety of machine learning models (Deep Learning, Tree-based, etc.) with low latency in a ",(0,s.jsx)(n.strong,{children:"Kubernetes (K8s)"})," environment."]}),"\n",(0,s.jsxs)(n.p,{children:["The system integrates seamlessly with the ",(0,s.jsx)(n.strong,{children:"Online Feature Store (OnFS)"})," for real-time feature retrieval and uses ",(0,s.jsx)(n.strong,{children:"Horizon"})," as the deployment orchestration layer. Deployments follow a ",(0,s.jsx)(n.strong,{children:"GitOps"})," pipeline \u2014 Horizon generates Helm configurations, commits them to GitHub, and ",(0,s.jsx)(n.strong,{children:"Argo Sync"})," reconciles the desired state onto Kubernetes."]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"high-level-design",children:"High-Level Design"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Predator HLD - End-to-end deployment and inference architecture",src:r(9843).A+"",width:"1824",height:"1124"})}),"\n",(0,s.jsx)(n.h3,{id:"end-to-end-flow",children:"End-to-End Flow"}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Model Deployment Trigger"}),": An actor initiates deployment through ",(0,s.jsx)(n.strong,{children:"Trufflebox UI"}),", specifying the GCS path (",(0,s.jsx)(n.code,{children:"gcs://"}),") of the trained model. Separately, post-training pipelines write model artifacts to ",(0,s.jsx)(n.strong,{children:"GCS Artifactory"}),"."]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Orchestration via Horizon"}),": Trufflebox UI communicates with ",(0,s.jsx)(n.strong,{children:"Horizon"}),", the deployment orchestration layer. Horizon generates the appropriate ",(0,s.jsx)(n.strong,{children:"Helm"})," chart configuration for the inference service."]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"GitOps Pipeline"}),": Horizon commits the Helm values to a ",(0,s.jsx)(n.strong,{children:"GitHub"})," repository. ",(0,s.jsx)(n.strong,{children:"Argo Sync"})," watches the repo and reconciles the desired state onto the Kubernetes cluster, creating or updating deployable units."]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Deployable Units (Deployable 1 \u2026 N)"}),": Each deployable is an independent Kubernetes deployment that:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Downloads model artifacts from ",(0,s.jsx)(n.strong,{children:"GCS"})," at startup via an ",(0,s.jsx)(n.code,{children:"init.sh"})," script."]}),"\n",(0,s.jsxs)(n.li,{children:["Launches a ",(0,s.jsx)(n.strong,{children:"Triton Inference Server"})," instance loaded with the model."]}),"\n",(0,s.jsx)(n.li,{children:"Runs one or more pods, each containing the inference runtime and configured backends."}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Triton Backends"}),": Each Triton instance supports pluggable backends based on the model type:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"FIL"})," \u2014 GPU-accelerated tree-based models (XGBoost, LightGBM, Random Forest)."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"PyTorch"})," \u2014 Native PyTorch models via LibTorch."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Python"})," \u2014 Custom preprocessing/postprocessing or unsupported model formats."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"TRT (TensorRT)"})," \u2014 GPU-optimized serialized TensorRT engines."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"ONNX"})," \u2014 Framework-agnostic execution via ONNX Runtime."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"DALI"})," \u2014 GPU-accelerated data preprocessing (image, audio, video)."]}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Autoscaling with KEDA"}),": The cluster uses ",(0,s.jsx)(n.strong,{children:"KEDA"})," (Kubernetes Event-Driven Autoscaling) to scale deployable pods based on custom metrics (CPU utilization, GPU utilization via DCGM, queue depth, etc.). The underlying ",(0,s.jsx)(n.strong,{children:"Kubernetes"})," scheduler places pods across GPU/CPU node pools."]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"key-design-principles",children:"Key Design Principles"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"GitOps-driven"}),": All deployment state is version-controlled in Git; Argo Sync ensures cluster state matches the declared configuration."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Isolation per deployable"}),": Each model or model group gets its own deployable unit, preventing noisy-neighbor interference."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Init-based model loading"}),": Models are materialized to local disk before Triton starts, ensuring deterministic startup and no runtime dependency on remote storage."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Pluggable backends"}),": The same infrastructure serves deep learning, tree-based, and custom models through Triton's backend abstraction."]}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"inference-engine-triton-inference-server",children:"Inference Engine: Triton Inference Server"}),"\n",(0,s.jsx)(n.p,{children:"NVIDIA Triton Inference Server is a high-performance model serving system designed to deploy ML and deep learning models at scale across CPUs and GPUs. It provides a unified inference runtime that supports multiple frameworks, optimized execution, and production-grade scheduling."}),"\n",(0,s.jsxs)(n.p,{children:["Triton operates as a standalone server that loads models from a model repository and exposes standardized HTTP/gRPC APIs. Predator uses ",(0,s.jsx)(n.strong,{children:"gRPC"})," for efficient request and response handling via the ",(0,s.jsx)(n.strong,{children:"helix client"}),"."]}),"\n",(0,s.jsx)(n.h3,{id:"core-components",children:"Core Components"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Model Repository"}),": Central directory where models are stored. Predator typically materializes the model repository onto local disk via an init container, enabling fast model loading and eliminating runtime dependency on remote storage during inference."]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"backends",children:"Backends"}),"\n",(0,s.jsx)(n.p,{children:"A backend is the runtime responsible for executing a model. Each model specifies which backend runs it via configuration."}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Backend"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"TensorRT"})}),(0,s.jsx)(n.td,{children:"GPU-optimized; executes serialized TensorRT engines (kernel fusion, FP16/INT8)."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"PyTorch"})}),(0,s.jsx)(n.td,{children:"Serves native PyTorch models via LibTorch."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"ONNX Runtime"})}),(0,s.jsx)(n.td,{children:"Framework-agnostic ONNX execution with TensorRT and other accelerators."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"TensorFlow"})}),(0,s.jsx)(n.td,{children:"Runs TensorFlow SavedModel format."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"Python backend"})}),(0,s.jsx)(n.td,{children:"Custom Python code for preprocessing, postprocessing, or unsupported models."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"Custom backends"})}),(0,s.jsx)(n.td,{children:"C++/Python backends for specialized or proprietary runtimes."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"DALI"})}),(0,s.jsx)(n.td,{children:"GPU-accelerated data preprocessing (image, audio, video)."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"FIL (Forest Inference Library)"})}),(0,s.jsx)(n.td,{children:"GPU-accelerated tree-based models (XGBoost, LightGBM, Random Forest)."})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"key-features",children:"Key Features"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Dynamic batching"}),": Combines multiple requests into a single batch at runtime \u2014 higher GPU utilization, improved throughput, reduced latency variance."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Concurrent model execution"}),": Run multiple models or multiple instances of the same model; distribute load across GPUs."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Model versioning"}),": Support multiple versions per model."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Ensemble models"}),": Pipeline of models as an ensemble; eliminates intermediate network hops, reduces latency."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Model instance scaling"}),": Multiple copies of a model for parallel inference and load isolation."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Observability"}),": Prometheus metrics, granular latency, throughput, GPU utilization."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Warmup requests"}),": Preload kernels and avoid cold-start latency."]}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"model-repository-structure",children:"Model Repository Structure"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"model_repository/\n\u251c\u2500\u2500 model_A/\n\u2502 \u251c\u2500\u2500 config.pbtxt\n\u2502 \u251c\u2500\u2500 1/\n\u2502 \u2502 \u2514\u2500\u2500 model.plan\n\u2502 \u251c\u2500\u2500 2/\n\u2502 \u2502 \u2514\u2500\u2500 model.plan\n\u251c\u2500\u2500 model_B/\n\u2502 \u251c\u2500\u2500 config.pbtxt\n\u2502 \u251c\u2500\u2500 1/\n\u2502 \u2514\u2500\u2500 model.py\n"})}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.code,{children:"config.pbtxt"})," file defines how Triton loads and executes a model: input/output tensors, batch settings, hardware execution, backend runtime, and optimization parameters. At minimum it defines: ",(0,s.jsx)(n.code,{children:"backend/platform"}),", ",(0,s.jsx)(n.code,{children:"max_batch_size"}),", ",(0,s.jsx)(n.code,{children:"inputs"}),", ",(0,s.jsx)(n.code,{children:"outputs"}),"."]}),"\n",(0,s.jsx)(n.h3,{id:"sample-configpbtxt",children:"Sample config.pbtxt"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-text",children:'name: "product_ranking_model"\nplatform: "tensorrt_plan"\nmax_batch_size: 64\ninput [ { name: "input_embeddings" data_type: TYPE_FP16 dims: [ 128 ] }, { name: "context_features" data_type: TYPE_FP32 dims: [ 32 ] } ]\noutput [ { name: "scores" data_type: TYPE_FP32 dims: [ 1 ] } ]\ninstance_group [ { kind: KIND_GPU count: 2 gpus: [0] } ]\ndynamic_batching { preferred_batch_size: [8,16,32,64] max_queue_delay_microseconds: 2000 }\n'})}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"kubernetes-deployment-architecture",children:"Kubernetes Deployment Architecture"}),"\n",(0,s.jsxs)(n.p,{children:["Predator inference services are deployed on Kubernetes using ",(0,s.jsx)(n.strong,{children:"Helm-based"})," deployments for standardized, scalable, GPU-optimized model serving. Each deployment consists of Triton Inference Server wrapped within a Predator runtime, with autoscaling driven by CPU and GPU utilization."]}),"\n",(0,s.jsx)(n.h3,{id:"pod-architecture",children:"Pod Architecture"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"Predator Pod\n\u251c\u2500\u2500 Init Container (Model Sync)\n\u251c\u2500\u2500 Triton Inference Server Container\n"})}),"\n",(0,s.jsx)(n.p,{children:"Model artifacts and runtime are initialized before inference traffic is accepted."}),"\n",(0,s.jsx)(n.h4,{id:"init-container",children:"Init Container"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Download model artifacts from cloud storage (GCS)."}),"\n",(0,s.jsx)(n.li,{children:"Populate the Triton model repository directory."}),"\n",(0,s.jsxs)(n.li,{children:["Example: ",(0,s.jsx)(n.code,{children:"gcloud storage cp -r gs://.../model-path/* /models"})]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Benefits: deterministic startup (Triton starts only after models are available), separation of concerns (image = runtime, repository = data)."}),"\n",(0,s.jsx)(n.h4,{id:"triton-inference-server-container",children:"Triton Inference Server Container"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Load model artifacts from local repository."}),"\n",(0,s.jsx)(n.li,{children:"Manage inference scheduling, request/response handling, and expose inference endpoints."}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"triton-server-image-strategy",children:"Triton Server Image Strategy"}),"\n",(0,s.jsxs)(n.p,{children:["The Helm chart uses the Triton container image from the internal ",(0,s.jsx)(n.strong,{children:"artifact registry"}),". Production uses ",(0,s.jsx)(n.strong,{children:"custom-built"})," images (only required backends, e.g. TensorRT, Python) to reduce size and startup time. Unnecessary components are excluded; images are built internally and pushed to the registry."]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Response Caching"}),": Custom cache plugins can be added at image build time for optional inference response caching \u2014 reducing redundant execution and GPU use for repeated inputs."]}),"\n",(0,s.jsx)(n.h3,{id:"image-distribution-optimization",children:"Image Distribution Optimization"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Secondary boot disk image caching"}),": Images are pre-cached on GPU node pool secondary boot disks to avoid repeated pulls during scale-up and reduce pod startup time and cold-start latency."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Image streaming"}),": Can be used to progressively pull layers for faster time-to-readiness during scaling."]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"health-probes",children:"Health Probes"}),"\n",(0,s.jsxs)(n.p,{children:["Readiness and liveness use ",(0,s.jsx)(n.code,{children:"/v2/health/ready"}),". Triton receives traffic only after model loading; failed instances are restarted automatically."]}),"\n",(0,s.jsx)(n.h3,{id:"resource-configuration",children:"Resource Configuration"}),"\n",(0,s.jsx)(n.p,{children:"Sample GPU resource config:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-yaml",children:"limits:\n cpu: 7000m\n memory: 28Gi\n gpu: 1\n"})}),"\n",(0,s.jsx)(n.h3,{id:"autoscaling-architecture",children:"Autoscaling Architecture"}),"\n",(0,s.jsxs)(n.p,{children:["Predator uses ",(0,s.jsx)(n.strong,{children:"KEDA"})," (Kubernetes Event-Driven Autoscaling) for scaling deployable pods. KEDA supports custom metric sources including:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"CPU / Memory utilization"})," for CPU-based deployments."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"GPU utilization"})," via ",(0,s.jsx)(n.strong,{children:"DCGM"})," (Data Center GPU Manager) for GPU pods \u2014 covering utilization, memory, power, etc."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Custom Prometheus queries"})," for application-level scaling signals (e.g., inference queue depth, request latency)."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"KEDA ScaledObjects are configured per deployable, enabling fine-grained, independent scaling for each model or model group."}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"contributing",children:"Contributing"}),"\n",(0,s.jsxs)(n.p,{children:["We welcome contributions! See the ",(0,s.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTING.md",children:"Contributing Guide"}),"."]}),"\n",(0,s.jsx)(n.h2,{id:"community--support",children:"Community & Support"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Discord"}),": ",(0,s.jsx)(n.a,{href:"https://discord.gg/XkT7XsV2AU",children:"community chat"})]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Issues"}),": ",(0,s.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/issues",children:"GitHub Issues"})]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Email"}),": ",(0,s.jsx)(n.a,{href:"mailto:ml-oss@meesho.com",children:"ml-oss@meesho.com"})]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"license",children:"License"}),"\n",(0,s.jsxs)(n.p,{children:["BharatMLStack is open-source under the ",(0,s.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md",children:"BharatMLStack Business Source License 1.1"}),"."]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)("div",{align:"center",children:(0,s.jsx)("strong",{children:"Built with \u2764\ufe0f for the ML community from Meesho"})}),"\n",(0,s.jsx)("div",{align:"center",children:(0,s.jsx)("strong",{children:"If you find this useful, \u2b50\ufe0f the repo \u2014 your support means the world to us!"})})]})}function h(e={}){const{wrapper:n}={...(0,t.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(a,{...e})}):a(e)}},8453:(e,n,r)=>{r.d(n,{R:()=>o,x:()=>l});var i=r(6540);const s={},t=i.createContext(s);function o(e){const n=i.useContext(t);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:o(e.components),i.createElement(t.Provider,{value:n},e.children)}},9843:(e,n,r)=>{r.d(n,{A:()=>i});const i=r.p+"assets/images/v1.0.0-predator-hld-949215d6604ae103e724c3978e803443.png"}}]); \ No newline at end of file diff --git a/docs/assets/js/23d02069.48cf6bc2.js b/docs/assets/js/23d02069.48cf6bc2.js new file mode 100644 index 00000000..c971bfce --- /dev/null +++ b/docs/assets/js/23d02069.48cf6bc2.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[3239],{2377:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>l,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>i,toc:()=>c});var i=t(5069),r=t(4848),s=t(8453);const a={title:"Building Meesho\u2019s ML Platform: Lessons from the First-Gen System (Part 2)",description:"Lessons from scaling Meesho's first-gen ML platform\u2014building Inferflow for no-code feature retrieval, migrating from Cassandra to ScyllaDB, optimizing the Interaction Store with tiered storage, and cutting infra costs by 60% while hitting 1M QPS.",authors:["bhawani","jigar","adarsha"],slug:"building-meeshos-mlplatform-lessons-from-first-gen",date:"2023-4-10",tags:["inferflow","interaction-store","mlplatform","meesho","bharatmlstack"]},o=void 0,l={authorsImageUrls:[void 0,void 0,void 0]},c=[{value:"The Cost of Success",id:"the-cost-of-success",level:3},{value:"Scaling Pains (and Cassandra\u2019s Limits)",id:"scaling-pains-and-cassandras-limits",level:3},{value:"Interaction Store Woes",id:"interaction-store-woes",level:3},{value:"Silver Linings",id:"silver-linings",level:3},{value:"Round Two: Solving the Top 2 Bottlenecks",id:"round-two-solving-the-top-2-bottlenecks",level:3},{value:"Problem 1: No-Code Feature Retrieval for Model Inference",id:"problem-1-no-code-feature-retrieval-for-model-inference",level:4},{value:"Problem 2: Scaling Without Breaking the Bank",id:"problem-2-scaling-without-breaking-the-bank",level:4},{value:"Optimizing the Online Feature Store",id:"optimizing-the-online-feature-store",level:4},{value:"Optimizing the Interaction Store",id:"optimizing-the-interaction-store",level:4},{value:"Results",id:"results",level:4},{value:"The Catch: Our ML Hosting Hit a Hard Limit",id:"the-catch-our-ml-hosting-hit-a-hard-limit",level:4},{value:"Conclusion: From Firefighting to Future-Proofing",id:"conclusion-from-firefighting-to-future-proofing",level:3}];function d(e){const n={h3:"h3",h4:"h4",img:"img",li:"li",ol:"ol",p:"p",ul:"ul",...(0,s.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.img,{alt:"BharatMLStack",src:t(5542).A+"",width:"1396",height:"460"}),"\nBy late 2022, we had built something we were truly proud of\u2014a real-time ML serving system with a DAG-based executor, a feature store, and an interaction store powering key ranking and personalization models. It was a major milestone, the culmination of months of effort from data scientists, ML engineers, and backend teams. Our system was live, and we were ready to push the boundaries of experimentation.\nAnd it worked. Mostly.\nBut soon, cracks appeared. Every new model needed custom feature retrieval logic, DAGs became dense and unmanageable, and scaling turned into a constant firefight. Costs surged, and infra bottlenecks slowed experimentation. Our system worked, but it wasn\u2019t built for scale.\nThis is the story of how we tackled these challenges\u2014building Inferflow for seamless feature retrieval, optimizing real-time infra, and cutting costs while scaling to millions of QPS."]}),"\n",(0,r.jsx)(n.h3,{id:"the-cost-of-success",children:"The Cost of Success"}),"\n",(0,r.jsx)(n.p,{children:"Every new Ranker model required its own feature set, often pulling from different entities. Each addition meant:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Adding new DAG nodes in IOP"}),"\n",(0,r.jsx)(n.li,{children:"Writing custom logic to fetch features from multiple sources (e.g., user, product, user \xd7 category)"}),"\n",(0,r.jsx)(n.li,{children:"Inferring intermediate features (e.g., extracting category from a product to fetch user \xd7 category data)"}),"\n",(0,r.jsx)(n.li,{children:"Optimizing I/O and dealing with the inevitable bugs"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"What began as clean DAGs soon turned into a tangled web of cross-dependent graphs. Every experimentation cycle meant new nodes, new dependencies, and slower iterations."}),"\n",(0,r.jsx)(n.h3,{id:"scaling-pains-and-cassandras-limits",children:"Scaling Pains (and Cassandra\u2019s Limits)"}),"\n",(0,r.jsx)(n.p,{children:"At some point, we were hitting:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"250\u2013300K reads/sec"}),"\n",(0,r.jsx)(n.li,{children:"1M writes/sec (during lean hours)"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"All of this ran on Cassandra. While its distributed architecture had been proven in production, operating large-scale clusters came with considerable infrastructure overhead. Our proof-of-concept (POC) demonstrated throughput of around 100K ops/sec, but as we scaled further, the challenges grew. Ensuring node health, optimizing compaction, and maintaining storage balance became increasingly demanding. We also observed latency spikes under heavy load, alongside a sharp increase in total cost of ownership."}),"\n",(0,r.jsx)(n.h3,{id:"interaction-store-woes",children:"Interaction Store Woes"}),"\n",(0,r.jsx)(n.p,{children:"Our interaction store was another ticking time bomb:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"\ud83d\udea8 Clusters kept growing in size and cost"}),"\n",(0,r.jsx)(n.li,{children:"\ud83d\udea8 Latency spikes became increasingly frequent"}),"\n",(0,r.jsx)(n.li,{children:"\ud83d\udea8 The DMC proxy occasionally lost locality of nodes against shards, causing cross-node communication and degraded performance"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"Each time this happened, we had to manually rebalance shards just to restore stable latency, making operations unsustainable at scale."}),"\n",(0,r.jsx)(n.h3,{id:"silver-linings",children:"Silver Linings"}),"\n",(0,r.jsx)(n.p,{children:"Despite the chaos, the system was live and delivering value:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Real-time infrastructure was in production"}),"\n",(0,r.jsx)(n.li,{children:"Costs dropped by 60\u201370% compared to offline personalization"}),"\n",(0,r.jsx)(n.li,{children:"New experiments rolled out faster and more successfully"}),"\n",(0,r.jsx)(n.li,{children:"User engagement metrics improved"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"It wasn\u2019t perfect. It was far from easy. But it worked\u2014and that counted for a lot."}),"\n",(0,r.jsx)(n.h3,{id:"round-two-solving-the-top-2-bottlenecks",children:"Round Two: Solving the Top 2 Bottlenecks"}),"\n",(0,r.jsx)(n.p,{children:"With the first-gen system stretched to its limits, we stepped back. Conversations with data scientists and backend engineers revealed three recurring pain points:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsx)(n.li,{children:"Coding feature retrieval logic for every new model was becoming unsustainable"}),"\n",(0,r.jsx)(n.li,{children:"ML scale was exploding\u2014bringing rising infra costs with it"}),"\n",(0,r.jsx)(n.li,{children:"Real-time embedding search was the next big unlock"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"We tackled them one by one\u2014starting with the biggest pain point."}),"\n",(0,r.jsx)(n.h4,{id:"problem-1-no-code-feature-retrieval-for-model-inference",children:"Problem 1: No-Code Feature Retrieval for Model Inference"}),"\n",(0,r.jsx)(n.p,{children:"We noticed a pattern: for personalized ranking, models needed features from:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"\u2705 Product"}),"\n",(0,r.jsx)(n.li,{children:"\u2705 User"}),"\n",(0,r.jsx)(n.li,{children:"\u2705 User \xd7 Category"}),"\n",(0,r.jsx)(n.li,{children:"\u2705 Region, cohort, sub-category, etc."}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"A key insight emerged: Entities that contribute features for a model always map back to the context entities."}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.img,{alt:"MP Dag",src:t(5754).A+"",width:"1272",height:"512"})}),"\n",(0,r.jsx)(n.p,{children:"With this, we designed Inferflow, a graph-driven feature retrieval and model orchestration system:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"1\ufe0f\u20e3 Inferflow takes a modelId and context IDs (e.g., userId, productIds)"}),"\n",(0,r.jsx)(n.li,{children:"2\ufe0f\u20e3 Loads a pre-defined feature retrieval graph from ZooKeeper"}),"\n",(0,r.jsx)(n.li,{children:"3\ufe0f\u20e3 Executes the graph to resolve entity relationships dynamically"}),"\n",(0,r.jsx)(n.li,{children:"4\ufe0f\u20e3 Outputs a 2D matrix of feature vectors"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"\ud83d\udca1 The impact?"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"\ud83d\ude80 No more custom feature retrieval code\u2014just graph updates in config"}),"\n",(0,r.jsx)(n.li,{children:"\ud83d\ude80 Feature consistency across experiments"}),"\n",(0,r.jsx)(n.li,{children:"\ud83d\ude80 Faster iteration cycles for ranking, fraud detection, and beyond"}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["Here\u2019s a visual example that shows how this graph plays out during execution. We further extended the graph to call multiple models as needed:\n",(0,r.jsx)(n.img,{alt:"MP matrix",src:t(8247).A+"",width:"1262",height:"768"}),"\nWe built Inferflow in GoLang, using gRPC and Proto3 serialization for efficiency."]}),"\n",(0,r.jsx)(n.h4,{id:"problem-2-scaling-without-breaking-the-bank",children:"Problem 2: Scaling Without Breaking the Bank"}),"\n",(0,r.jsx)(n.p,{children:"With more ML use cases coming online, we needed to cut costs without compromising performance. We focused on:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"\ud83d\udd39 Online Feature Store"}),"\n",(0,r.jsx)(n.li,{children:"\ud83d\udd39 Interaction Store"}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"optimizing-the-online-feature-store",children:"Optimizing the Online Feature Store"}),"\n",(0,r.jsx)(n.p,{children:"Our costs were concentrated in:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"\ud83d\udccc Database (Cassandra)"}),"\n",(0,r.jsx)(n.li,{children:"\ud83d\udccc Cache (Redis)"}),"\n",(0,r.jsx)(n.li,{children:"\ud83d\udccc Running Pods (Java services)"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"1\ufe0f\u20e3 Replacing Cassandra with ScyllaDB\nAs we hit the operational limits of large Cassandra clusters, we transitioned to ScyllaDB, which offered a seamless drop-in replacement without major code changes. The switch brought significant benefits:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Throughput: Matched or exceeded Cassandra's performance under identical workloads, even under high concurrency."}),"\n",(0,r.jsx)(n.li,{children:"Latency: Achieved consistently lower P99 latencies due to ScyllaDB's shard-per-core architecture and better I/O utilization."}),"\n",(0,r.jsx)(n.li,{children:"Cost Efficiency: Reduced infra footprint by ~70% through better CPU and memory efficiency, eliminating the need for over-provisioned nodes."}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"2\ufe0f\u20e3 Finding the Right Cache\nTo reduce backend load and improve response times, we benchmarked multiple caching solutions\u2014Memcached, KeyDB, and Dragonfly\u2014under real production traffic patterns. Dragonfly stood out due to its robust architecture and operational simplicity:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Data Skew Handling: Efficiently managed extreme key hotness and uneven access patterns without performance degradation."}),"\n",(0,r.jsx)(n.li,{children:"Throughput: Delivered consistently high throughput, even with large object sizes and concurrent access."}),"\n",(0,r.jsx)(n.li,{children:"Ease of Adoption: Acted as a drop-in Redis replacement with full protocol compatibility\u2014no changes needed in application code or client libraries."}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"3\ufe0f\u20e3 Moving to GoLang for Cost-Efficient Serving\nJava services were memory-heavy\u2014so we rewrote core services in GoLang. The results?"}),"\n",(0,r.jsx)(n.p,{children:"\u2705 Memory usage dropped by ~80%\n\u2705 CPU utilization was significantly lower\n\u2705 Faster, more efficient deployments"}),"\n",(0,r.jsx)(n.h4,{id:"optimizing-the-interaction-store",children:"Optimizing the Interaction Store"}),"\n",(0,r.jsx)(n.p,{children:"We realized that we only need a user\u2019s interaction data in Redis when they open the app. So, we implemented a tiered storage approach:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"\ud83d\udccc Cold Tier (ScyllaDB)\u2014Stores click, order, wishlist events"}),"\n",(0,r.jsx)(n.li,{children:"\ud83d\udccc Hot Tier (Redis)\u2014Loads a user\u2019s past interactions only when they open the app"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"Smart Offloading: We introduced an inactivity tracker to detect when a user session ends. At that point, Redis data was flushed back to Scylla, reducing unnecessary writes."}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.img,{alt:"InteractionStore",src:t(8406).A+"",width:"1242",height:"572"})}),"\n",(0,r.jsx)(n.h4,{id:"results",children:"Results"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Online Feature Store hit 1M QPS for the first time during the 2023 Mega Blockbuster Sale\u2014without breaking a sweat"}),"\n",(0,r.jsx)(n.li,{children:"Infra costs for Online Feature Store and Interaction Store dropped by ~60%"}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"the-catch-our-ml-hosting-hit-a-hard-limit",children:"The Catch: Our ML Hosting Hit a Hard Limit"}),"\n",(0,r.jsx)(n.p,{children:"While planning for 2023 MBS, we ran into a critical scalability bottleneck:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"\u274c Insufficient compute availability in our region for ML instances"}),"\n",(0,r.jsx)(n.li,{children:"\u274c Couldn\u2019t provision enough nodes to handle real-time inference at scale"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"This forced us to rethink where and how we hosted our models. The existing setup was great for prototyping\u2014but it wasn\u2019t built to handle the bursty, high-QPS demands of real-world production workloads."}),"\n",(0,r.jsx)(n.h3,{id:"conclusion-from-firefighting-to-future-proofing",children:"Conclusion: From Firefighting to Future-Proofing"}),"\n",(0,r.jsx)(n.p,{children:"What started as an ambitious experiment turned into a real-time ML infrastructure that powered millions of requests per second. We battled scaling pains, rethought feature retrieval with Inferflow, and rebuilt our infra stack for efficiency\u2014driving down costs while improving experimentation velocity.\nBut new challenges emerged. Our infrastructure could now handle scale, but our ML model hosting setup hit a hard limit. With compute availability bottlenecks threatening real-time inference, we faced a critical decision: how do we make model serving as scalable and cost-efficient as the rest of our stack? That\u2019s the next piece of the puzzle\u2014and the story of Part 3."})]})}function h(e={}){const{wrapper:n}={...(0,s.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(d,{...e})}):d(e)}},5069:e=>{e.exports=JSON.parse('{"permalink":"/BharatMLStack/blog/building-meeshos-mlplatform-lessons-from-first-gen","editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/blog/bharatmlstack-history/building-meeshos-mlplatform-lessons-from-first-gen/index.md","source":"@site/blog/bharatmlstack-history/building-meeshos-mlplatform-lessons-from-first-gen/index.md","title":"Building Meesho\u2019s ML Platform: Lessons from the First-Gen System (Part 2)","description":"Lessons from scaling Meesho\'s first-gen ML platform\u2014building Inferflow for no-code feature retrieval, migrating from Cassandra to ScyllaDB, optimizing the Interaction Store with tiered storage, and cutting infra costs by 60% while hitting 1M QPS.","date":"2023-04-10T00:00:00.000Z","tags":[{"inline":true,"label":"inferflow","permalink":"/BharatMLStack/blog/tags/inferflow"},{"inline":true,"label":"interaction-store","permalink":"/BharatMLStack/blog/tags/interaction-store"},{"inline":true,"label":"mlplatform","permalink":"/BharatMLStack/blog/tags/mlplatform"},{"inline":true,"label":"meesho","permalink":"/BharatMLStack/blog/tags/meesho"},{"inline":true,"label":"bharatmlstack","permalink":"/BharatMLStack/blog/tags/bharatmlstack"}],"readingTime":6.25,"hasTruncateMarker":false,"authors":[{"name":"Bhawani Singh","title":"Architect @ Meesho","url":"https://github.com/singh-bhawani","imageURL":"https://github.com/singh-bhawani.png","key":"bhawani","page":null},{"name":"Jigar Dave","title":"Lead Software Engineer @ Meesho","url":"https://github.com/jigarpatel26","imageURL":"https://github.com/jigarpatel26.png","key":"jigar","page":null},{"name":"Adarsha Das","title":"Senior Architect @ Meesho","url":"https://github.com/a0d00kc","imageURL":"https://github.com/a0d00kc.png","key":"adarsha","page":null}],"frontMatter":{"title":"Building Meesho\u2019s ML Platform: Lessons from the First-Gen System (Part 2)","description":"Lessons from scaling Meesho\'s first-gen ML platform\u2014building Inferflow for no-code feature retrieval, migrating from Cassandra to ScyllaDB, optimizing the Interaction Store with tiered storage, and cutting infra costs by 60% while hitting 1M QPS.","authors":["bhawani","jigar","adarsha"],"slug":"building-meeshos-mlplatform-lessons-from-first-gen","date":"2023-4-10","tags":["inferflow","interaction-store","mlplatform","meesho","bharatmlstack"]},"unlisted":false,"prevItem":{"title":"Cracking the Code: Scaling Model Inference & Real-Time Embedding Search","permalink":"/BharatMLStack/blog/scaling-model-inference-and-embedding-search"},"nextItem":{"title":"Building Meesho\u2019s ML Platform: From Chaos to Cutting-Edge (Part 1)","permalink":"/BharatMLStack/blog/building-meeshos-mlplatform"}}')},5542:(e,n,t)=>{t.d(n,{A:()=>i});const i=t.p+"assets/images/bms-7399e8796d2cd24617c432518ce3f312.png"},5754:(e,n,t)=>{t.d(n,{A:()=>i});const i=t.p+"assets/images/mp-dag-976ff51caf25f09d977ccc10e70918f3.png"},8247:(e,n,t)=>{t.d(n,{A:()=>i});const i=t.p+"assets/images/mp-matrix-43994f433f78905ccbd10cfe284f3c9f.png"},8406:(e,n,t)=>{t.d(n,{A:()=>i});const i=t.p+"assets/images/interaction-str-d9e7aefea121aefb4e94c6c9f060d016.png"},8453:(e,n,t)=>{t.d(n,{R:()=>a,x:()=>o});var i=t(6540);const r={},s=i.createContext(r);function a(e){const n=i.useContext(s);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:a(e.components),i.createElement(s.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/252a9097.294bfc5c.js b/docs/assets/js/252a9097.294bfc5c.js new file mode 100644 index 00000000..b5f0950d --- /dev/null +++ b/docs/assets/js/252a9097.294bfc5c.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[4424],{248:(e,n,r)=>{r.r(n),r.d(n,{assets:()=>c,contentTitle:()=>l,default:()=>h,frontMatter:()=>o,metadata:()=>t,toc:()=>d});const t=JSON.parse('{"id":"inferflow/v1.0.0/architecture","title":"Architecture","description":"Inferflow is part of BharatMLStack, a graph-driven feature retrieval and model inference orchestration engine built in Go. It eliminates the need for custom feature retrieval code by using configurable DAG topologies to dynamically resolve entity relationships, fetch features from the Online Feature Store, and orchestrate model scoring \u2014 all driven by configuration stored in etcd.","source":"@site/docs/inferflow/v1.0.0/architecture.md","sourceDirName":"inferflow/v1.0.0","slug":"/inferflow/v1.0.0/architecture","permalink":"/BharatMLStack/inferflow/v1.0.0/architecture","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/inferflow/v1.0.0/architecture.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"Architecture","sidebar_position":1},"sidebar":"tutorialSidebar","previous":{"title":"v1.0.0","permalink":"/BharatMLStack/inferflow/v1.0.0"},"next":{"title":"Key Functionalities","permalink":"/BharatMLStack/inferflow/v1.0.0/functionalities"}}');var i=r(4848),s=r(8453);const o={title:"Architecture",sidebar_position:1},l="BharatMLStack - Inferflow",c={},d=[{value:"Overview",id:"overview",level:2},{value:"High-Level Architecture",id:"high-level-architecture",level:2},{value:"Core Components",id:"core-components",level:2},{value:"1. gRPC Server",id:"1-grpc-server",level:3},{value:"2. DAG Topology Executor",id:"2-dag-topology-executor",level:3},{value:"3. Component Types",id:"3-component-types",level:3},{value:"4. ComponentMatrix \u2014 The 2D Result Matrix",id:"4-componentmatrix--the-2d-result-matrix",level:3},{value:"How the matrix evolves through the DAG",id:"how-the-matrix-evolves-through-the-dag",level:4},{value:"Matrix structure",id:"matrix-structure",level:4},{value:"5. Configuration Management (etcd)",id:"5-configuration-management-etcd",level:3},{value:"6. External Integrations",id:"6-external-integrations",level:3},{value:"Online Feature Store (OnFS)",id:"online-feature-store-onfs",level:4},{value:"Predator (Model Serving)",id:"predator-model-serving",level:4},{value:"Numerix (Compute Engine)",id:"numerix-compute-engine",level:4},{value:"Kafka (Inference Logging)",id:"kafka-inference-logging",level:4},{value:"Request Flow",id:"request-flow",level:2},{value:"Observability",id:"observability",level:2},{value:"Metrics (StatsD / Telegraf)",id:"metrics-statsd--telegraf",level:3},{value:"Logging",id:"logging",level:3},{value:"Deployment",id:"deployment",level:2},{value:"Docker",id:"docker",level:3},{value:"Supported Environments",id:"supported-environments",level:3},{value:"Configuration",id:"configuration",level:3},{value:"Target Users",id:"target-users",level:2},{value:"Benefits",id:"benefits",level:2},{value:"Contributing",id:"contributing",level:2},{value:"Community & Support",id:"community--support",level:2},{value:"License",id:"license",level:2}];function a(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",hr:"hr",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,s.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.header,{children:(0,i.jsx)(n.h1,{id:"bharatmlstack---inferflow",children:"BharatMLStack - Inferflow"})}),"\n",(0,i.jsxs)(n.p,{children:["Inferflow is part of ",(0,i.jsx)(n.strong,{children:"BharatMLStack"}),", a graph-driven feature retrieval and model inference orchestration engine built in ",(0,i.jsx)(n.strong,{children:"Go"}),". It eliminates the need for custom feature retrieval code by using configurable DAG topologies to dynamically resolve entity relationships, fetch features from the Online Feature Store, and orchestrate model scoring \u2014 all driven by configuration stored in ",(0,i.jsx)(n.strong,{children:"etcd"}),"."]}),"\n",(0,i.jsx)(n.hr,{}),"\n",(0,i.jsx)(n.h2,{id:"overview",children:"Overview"}),"\n",(0,i.jsx)(n.p,{children:"In a typical ML serving pipeline, every new model requires bespoke code to:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Fetch features from multiple entities (user, product, user x category, etc.)"}),"\n",(0,i.jsx)(n.li,{children:"Infer intermediate entity relationships (e.g., extract category from product to fetch user x category data)"}),"\n",(0,i.jsx)(n.li,{children:"Orchestrate one or more model inference calls"}),"\n",(0,i.jsx)(n.li,{children:"Handle I/O, batching, and error propagation"}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["Inferflow abstracts all of this behind a ",(0,i.jsx)(n.strong,{children:"config-driven DAG executor"}),". Given a ",(0,i.jsx)(n.code,{children:"model_config_id"})," and context entities (e.g., ",(0,i.jsx)(n.code,{children:"userId"}),", ",(0,i.jsx)(n.code,{children:"productIds"}),"), it:"]}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsx)(n.li,{children:"Loads a pre-defined feature retrieval and inference graph from etcd"}),"\n",(0,i.jsx)(n.li,{children:"Executes the graph to resolve entity relationships dynamically"}),"\n",(0,i.jsx)(n.li,{children:"Retrieves features from the Online Feature Store (OnFS) in parallel"}),"\n",(0,i.jsx)(n.li,{children:"Calls model serving endpoints (Predator) and compute services (Numerix)"}),"\n",(0,i.jsx)(n.li,{children:"Returns scored results as a structured response"}),"\n"]}),"\n",(0,i.jsx)(n.hr,{}),"\n",(0,i.jsx)(n.h2,{id:"high-level-architecture",children:"High-Level Architecture"}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.img,{alt:"Inferflow Architecture - DAG Topology Executor",src:r(7748).A+"",width:"2036",height:"1212"})}),"\n",(0,i.jsxs)(n.p,{children:["The diagram shows the internal DAG structure of Inferflow's topology executor. gRPC APIs (Pair, Point, Slate) feed into the DAG, where ",(0,i.jsx)(n.strong,{children:"Feature Init"})," bootstraps the ComponentMatrix. Feature components (FS User, FS Product, FS Region, FS User Cat, FS Region Scat) fetch features from ",(0,i.jsx)(n.strong,{children:"OnFS"})," in parallel and populate columns in the shared ",(0,i.jsx)(n.strong,{children:"2D Result Matrix"}),". Model components (Model A, Model B) call ",(0,i.jsx)(n.strong,{children:"Predator"})," for inference, and compute components call ",(0,i.jsx)(n.strong,{children:"Numerix"})," for operations like reranking. The entire DAG topology is driven by config loaded from ",(0,i.jsx)(n.strong,{children:"etcd"}),"."]}),"\n",(0,i.jsx)(n.hr,{}),"\n",(0,i.jsx)(n.h2,{id:"core-components",children:"Core Components"}),"\n",(0,i.jsx)(n.h3,{id:"1-grpc-server",children:"1. gRPC Server"}),"\n",(0,i.jsxs)(n.p,{children:["Inferflow exposes its APIs via a gRPC server, with HTTP health endpoints multiplexed on the same port using ",(0,i.jsx)(n.strong,{children:"cmux"}),". The server provides:"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Inferflow API"})," \u2014 ",(0,i.jsx)(n.code,{children:"RetrieveModelScore"}),": entity-based feature retrieval and scoring"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Predict API"})," \u2014 ",(0,i.jsx)(n.code,{children:"InferPointWise"}),", ",(0,i.jsx)(n.code,{children:"InferPairWise"}),", ",(0,i.jsx)(n.code,{children:"InferSlateWise"}),": structured inference with targets, pairs, and slates"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"2-dag-topology-executor",children:"2. DAG Topology Executor"}),"\n",(0,i.jsxs)(n.p,{children:["The heart of Inferflow. Each model configuration defines a ",(0,i.jsx)(n.code,{children:"component_dependency"})," map that describes a Directed Acyclic Graph (DAG) of components."]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Execution model:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["Uses ",(0,i.jsx)(n.strong,{children:"Kahn's algorithm"})," for topological ordering"]}),"\n",(0,i.jsxs)(n.li,{children:["Components at the same level run ",(0,i.jsx)(n.strong,{children:"concurrently"})," in goroutines"]}),"\n",(0,i.jsxs)(n.li,{children:["All components share a mutable ",(0,i.jsx)(n.code,{children:"ComponentMatrix"})," (rows = entity IDs, columns = features/scores)"]}),"\n",(0,i.jsxs)(n.li,{children:["DAG topologies are ",(0,i.jsx)(n.strong,{children:"cached"})," using Murmur3 hashing with Ristretto cache"]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Validation:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Cycle detection via in-degree analysis"}),"\n",(0,i.jsxs)(n.li,{children:["Component existence verification against the ",(0,i.jsx)(n.code,{children:"ComponentProvider"})]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"3-component-types",children:"3. Component Types"}),"\n",(0,i.jsx)(n.p,{children:"Inferflow defines four types of DAG components:"}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Component"}),(0,i.jsx)(n.th,{children:"Role"}),(0,i.jsx)(n.th,{children:"External Dependency"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"FeatureInitComponent"})}),(0,i.jsxs)(n.td,{children:["Root node \u2014 initializes the ",(0,i.jsx)(n.code,{children:"ComponentMatrix"})," with entity IDs and schema"]}),(0,i.jsx)(n.td,{children:"None"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"FeatureComponent"})}),(0,i.jsx)(n.td,{children:"Fetches features from the Online Feature Store for a specific entity type"}),(0,i.jsx)(n.td,{children:"OnFS (gRPC)"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"PredatorComponent"})}),(0,i.jsx)(n.td,{children:"Calls model serving endpoints for inference scoring"}),(0,i.jsx)(n.td,{children:"Predator / Helix (gRPC)"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"NumerixComponent"})}),(0,i.jsx)(n.td,{children:"Calls compute engine for operations like reranking"}),(0,i.jsx)(n.td,{children:"Numerix (gRPC)"})]})]})]}),"\n",(0,i.jsx)(n.h3,{id:"4-componentmatrix--the-2d-result-matrix",children:"4. ComponentMatrix \u2014 The 2D Result Matrix"}),"\n",(0,i.jsx)(n.p,{children:"The ComponentMatrix is a shared, mutable 2D data structure that flows through the entire DAG. Every component reads from and writes to this matrix, progressively building a complete feature + score row for each entity."}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.img,{alt:"DAG Execution & 2D Matrix Flow",src:r(3066).A+"",width:"672",height:"778"})}),"\n",(0,i.jsx)(n.h4,{id:"how-the-matrix-evolves-through-the-dag",children:"How the matrix evolves through the DAG"}),"\n",(0,i.jsx)(n.p,{children:"The diagram above illustrates the three execution phases and how the 2D matrix grows at each stage:"}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Phase 1 \u2014 Feature Retrieval"})}),"\n",(0,i.jsxs)(n.p,{children:["The ",(0,i.jsx)(n.strong,{children:"init"})," node creates an empty matrix with one row per target entity ID. Feature components then execute \u2014 first the top-level entities (entity A, entity B) fetch their features from OnFS and populate their columns (shown as colored blocks). Derived entities (entity C, D, E) resolve their keys from the already-populated columns and add more feature columns. At this point the matrix contains all feature data, with each color representing features from a different entity."]}),"\n",(0,i.jsxs)(n.p,{children:["The right side of the diagram shows the matrix being ",(0,i.jsx)(n.strong,{children:"decomposed"})," \u2014 feature columns from different entities are separated into per-model input groups, selecting only the features each model needs."]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Phase 2 \u2014 Model Invocation"})}),"\n",(0,i.jsxs)(n.p,{children:["Model X and Model Y each receive their decomposed feature slices, call ",(0,i.jsx)(n.strong,{children:"Predator"})," for inference, and write score columns back into the matrix (shown as new colored columns appended to the right). Multiple models can run in parallel if they don't depend on each other's outputs."]}),"\n",(0,i.jsx)(n.p,{children:"The scores are then decomposed again to prepare inputs for the compute stage."}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Phase 3 \u2014 Numerix Compute"})}),"\n",(0,i.jsxs)(n.p,{children:["The ",(0,i.jsx)(n.strong,{children:"Score Comb"})," node takes score columns from both models, calls ",(0,i.jsx)(n.strong,{children:"Numerix"})," for a final compute operation (e.g., score combination, reranking), and writes the final score column (shown in dark red) into the matrix. The result is a complete row per entity with all features and all scores."]}),"\n",(0,i.jsx)(n.h4,{id:"matrix-structure",children:"Matrix structure"}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Property"}),(0,i.jsx)(n.th,{children:"Description"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"Rows"})}),(0,i.jsx)(n.td,{children:"One per target entity ID (e.g., each product being scored)"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"String columns"})}),(0,i.jsx)(n.td,{children:"Human-readable values used in responses"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"Byte columns"})}),(0,i.jsx)(n.td,{children:"Binary-encoded feature values used for model inputs"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"Column naming"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"entity_label:feature_group:feature_name"})})]})]})]}),"\n",(0,i.jsx)(n.p,{children:"Each component only reads the columns it needs and writes to its own columns, enabling safe concurrent execution across independent branches of the DAG."}),"\n",(0,i.jsxs)(n.p,{children:["For slate-based APIs, a companion ",(0,i.jsx)(n.code,{children:"SlateData"})," structure holds per-slate matrices and scores, with ",(0,i.jsx)(n.code,{children:"slate_target_indices"})," mapping slates to rows in the main matrix."]}),"\n",(0,i.jsx)(n.h3,{id:"5-configuration-management-etcd",children:"5. Configuration Management (etcd)"}),"\n",(0,i.jsx)(n.p,{children:"Model configurations are stored in etcd and hot-reloaded via watchers:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Config paths"}),": ",(0,i.jsx)(n.code,{children:"/config/inferflow/services/"}),", ",(0,i.jsx)(n.code,{children:"/model-config"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Watch mechanism"}),": etcd watchers trigger ",(0,i.jsx)(n.code,{children:"ReloadModelConfigMapAndRegisterComponents"})," on any change"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"On reload"}),": Updates ",(0,i.jsx)(n.code,{children:"ConfigMap"}),", re-initializes feature schemas, and re-registers DAG components"]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["This means new models or configuration changes go live ",(0,i.jsx)(n.strong,{children:"without redeployment"}),"."]}),"\n",(0,i.jsx)(n.h3,{id:"6-external-integrations",children:"6. External Integrations"}),"\n",(0,i.jsx)(n.h4,{id:"online-feature-store-onfs",children:"Online Feature Store (OnFS)"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["gRPC client calling ",(0,i.jsx)(n.code,{children:"FeatureService.RetrieveFeatures"})]}),"\n",(0,i.jsx)(n.li,{children:"Batched retrieval with configurable batch size and deadline"}),"\n",(0,i.jsxs)(n.li,{children:["Auth via ",(0,i.jsx)(n.code,{children:"CALLER_ID"})," and ",(0,i.jsx)(n.code,{children:"CALLER_TOKEN"})," metadata"]}),"\n"]}),"\n",(0,i.jsx)(n.h4,{id:"predator-model-serving",children:"Predator (Model Serving)"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["Uses ",(0,i.jsx)(n.code,{children:"go-sdk"})," for model inference"]}),"\n",(0,i.jsxs)(n.li,{children:["Supports ",(0,i.jsx)(n.strong,{children:"percentage-based traffic routing"})," across multiple model endpoints"]}),"\n",(0,i.jsx)(n.li,{children:"Configurable calibration and batch sizing"}),"\n"]}),"\n",(0,i.jsx)(n.h4,{id:"numerix-compute-engine",children:"Numerix (Compute Engine)"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["Uses ",(0,i.jsx)(n.code,{children:"go-sdk"})," Numerix client"]}),"\n",(0,i.jsxs)(n.li,{children:["RPC: ",(0,i.jsx)(n.code,{children:"NumerixService.Compute"})," with entity score data"]}),"\n",(0,i.jsx)(n.li,{children:"Used for compute operations like reranking"}),"\n"]}),"\n",(0,i.jsx)(n.h4,{id:"kafka-inference-logging",children:"Kafka (Inference Logging)"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["Async inference log publishing using ",(0,i.jsx)(n.code,{children:"segmentio/kafka-go"})]}),"\n",(0,i.jsxs)(n.li,{children:["Supports ",(0,i.jsx)(n.strong,{children:"Proto"}),", ",(0,i.jsx)(n.strong,{children:"Arrow"}),", and ",(0,i.jsx)(n.strong,{children:"Parquet"})," serialization formats"]}),"\n",(0,i.jsxs)(n.li,{children:["Configurable sampling via ",(0,i.jsx)(n.code,{children:"LoggingPerc"})," and user-based daily sampling"]}),"\n"]}),"\n",(0,i.jsx)(n.hr,{}),"\n",(0,i.jsx)(n.h2,{id:"request-flow",children:"Request Flow"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{children:"1. Client sends gRPC request with model_config_id + entity IDs\n \u2502\n2. Load ModelConfig from etcd-backed ConfigMap\n \u2502\n3. Adapt proto request \u2192 ComponentRequest\n (build ComponentMatrix with entity schema)\n \u2502\n4. Resolve DAG topology from component_dependency config\n \u2502\n5. Execute DAG (Kahn's algorithm, concurrent):\n \u2502\n \u251c\u2500 FeatureInitComponent: populate matrix with entity IDs + schema\n \u2502\n \u251c\u2500 FeatureComponents (parallel): fetch features from OnFS \u2192 fill matrix columns\n \u2502\n \u251c\u2500 PredatorComponent: build feature payloads from matrix \u2192 call model \u2192 write scores\n \u2502\n \u2514\u2500 NumerixComponent: read scores from matrix \u2192 call compute \u2192 write final scores\n \u2502\n6. Build response from matrix columns per ResponseConfig\n \u2502\n7. (Optional) Async Kafka logging of inference features and scores\n \u2502\n8. Return gRPC response to client\n"})}),"\n",(0,i.jsx)(n.hr,{}),"\n",(0,i.jsx)(n.h2,{id:"observability",children:"Observability"}),"\n",(0,i.jsx)(n.h3,{id:"metrics-statsd--telegraf",children:"Metrics (StatsD / Telegraf)"}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Metric"}),(0,i.jsx)(n.th,{children:"Description"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"inferflow.retrievemodelscore.request.total"})}),(0,i.jsx)(n.td,{children:"Total RetrieveModelScore requests"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"inferflow.retrievemodelscore.latency"})}),(0,i.jsx)(n.td,{children:"End-to-end latency"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"inferflow.retrievemodelscore.batch.size"})}),(0,i.jsx)(n.td,{children:"Batch size per request"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"predict.infer.request.total"})}),(0,i.jsx)(n.td,{children:"Total Predict API requests"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"predict.infer.latency"})}),(0,i.jsx)(n.td,{children:"Predict API latency"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"inferflow.component.execution.total"})}),(0,i.jsx)(n.td,{children:"Per-component execution count"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"inferflow.component.execution.latency"})}),(0,i.jsx)(n.td,{children:"Per-component latency"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"inferflow.component.execution.error"})}),(0,i.jsx)(n.td,{children:"Component-level errors"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"inferflow.component.feature.count"})}),(0,i.jsx)(n.td,{children:"Feature count per component"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"inferflow.external.api.request.total"})}),(0,i.jsx)(n.td,{children:"External API call count"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"inferflow.external.api.latency"})}),(0,i.jsx)(n.td,{children:"External API latency"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"inferflow.component.inmemorycache.request.total"})}),(0,i.jsx)(n.td,{children:"Cache hit/miss total"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"inferflow.component.inmemorycache.miss"})}),(0,i.jsx)(n.td,{children:"Cache misses"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"inferflow.logging.kafka_sent"})}),(0,i.jsx)(n.td,{children:"Kafka log messages sent"})]})]})]}),"\n",(0,i.jsx)(n.h3,{id:"logging",children:"Logging"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["Structured JSON logging via ",(0,i.jsx)(n.strong,{children:"zerolog"})]}),"\n",(0,i.jsx)(n.li,{children:"Configurable log levels"}),"\n"]}),"\n",(0,i.jsx)(n.hr,{}),"\n",(0,i.jsx)(n.h2,{id:"deployment",children:"Deployment"}),"\n",(0,i.jsx)(n.h3,{id:"docker",children:"Docker"}),"\n",(0,i.jsx)(n.p,{children:"Inferflow ships as a multi-stage Docker image:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Builder"}),": Go 1.19 Alpine with optional Kafka support (librdkafka)"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Runtime"}),": Debian 10 slim"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Build command"}),": ",(0,i.jsx)(n.code,{children:'go build -tags musl -ldflags "-extldflags -static" -o server cmd/${module}/main.go'})]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"supported-environments",children:"Supported Environments"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Kubernetes (K8s)"}),"\n",(0,i.jsx)(n.li,{children:"Google Kubernetes Engine (GKE)"}),"\n",(0,i.jsx)(n.li,{children:"Amazon EKS"}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"configuration",children:"Configuration"}),"\n",(0,i.jsx)(n.p,{children:"All configuration is driven via environment variables (loaded by Viper) and etcd. No config files are required at deployment time."}),"\n",(0,i.jsx)(n.hr,{}),"\n",(0,i.jsx)(n.h2,{id:"target-users",children:"Target Users"}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"User"}),(0,i.jsx)(n.th,{children:"Role"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Data Scientists"}),(0,i.jsx)(n.td,{children:"Define model configs and feature retrieval graphs via config \u2014 no code needed"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"ML Engineers"}),(0,i.jsx)(n.td,{children:"Onboard new models by updating etcd config; manage DAG topologies"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Backend Developers"}),(0,i.jsx)(n.td,{children:"Integrate via gRPC SDKs for real-time scoring in application services"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Platform Engineers"}),(0,i.jsx)(n.td,{children:"Deploy, scale, and monitor Inferflow clusters"})]})]})]}),"\n",(0,i.jsx)(n.hr,{}),"\n",(0,i.jsx)(n.h2,{id:"benefits",children:"Benefits"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"No-code feature retrieval"})," \u2014 new models need only a config change, not custom code"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Feature consistency"})," \u2014 same graph-driven retrieval ensures identical features across experiments"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Faster iteration"})," \u2014 experiment with new models in minutes, not days"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Concurrent execution"})," \u2014 DAG components run in parallel for minimal latency"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Hot reloading"})," \u2014 model config changes via etcd go live without redeployment"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Multi-API support"})," \u2014 PointWise, PairWise, and SlateWise inference patterns out of the box"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Production-grade"})," \u2014 built in Go with gRPC, designed for millions of QPS"]}),"\n"]}),"\n",(0,i.jsx)(n.hr,{}),"\n",(0,i.jsx)(n.h2,{id:"contributing",children:"Contributing"}),"\n",(0,i.jsxs)(n.p,{children:["We welcome contributions from the community! Please see our ",(0,i.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTING.md",children:"Contributing Guide"})," for details on how to get started."]}),"\n",(0,i.jsx)(n.h2,{id:"community--support",children:"Community & Support"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Discord"}),": Join our ",(0,i.jsx)(n.a,{href:"https://discord.gg/XkT7XsV2AU",children:"community chat"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Issues"}),": Report bugs and request features on ",(0,i.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/issues",children:"GitHub Issues"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Email"}),": Contact us at ",(0,i.jsx)(n.a,{href:"mailto:ml-oss@meesho.com",children:"ml-oss@meesho.com"})]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"license",children:"License"}),"\n",(0,i.jsxs)(n.p,{children:["BharatMLStack is open-source software licensed under the ",(0,i.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md",children:"BharatMLStack Business Source License 1.1"}),"."]}),"\n",(0,i.jsx)(n.hr,{}),"\n",(0,i.jsx)("div",{align:"center",children:(0,i.jsx)("strong",{children:"Built with \u2764\ufe0f for the ML community from Meesho"})}),"\n",(0,i.jsx)("div",{align:"center",children:(0,i.jsx)("strong",{children:"If you find this useful, \u2b50\ufe0f the repo \u2014 your support means the world to us!"})})]})}function h(e={}){const{wrapper:n}={...(0,s.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(a,{...e})}):a(e)}},3066:(e,n,r)=>{r.d(n,{A:()=>t});const t=r.p+"assets/images/v1.0.0-inferflow-dag-matrix-0f13b51422587e6099cf4ee783844db1.png"},7748:(e,n,r)=>{r.d(n,{A:()=>t});const t=r.p+"assets/images/v1.0.0-inferflow-arch-bce54b3b4f7d3be68fa22dc204529f53.png"},8453:(e,n,r)=>{r.d(n,{R:()=>o,x:()=>l});var t=r(6540);const i={},s=t.createContext(i);function o(e){const n=t.useContext(s);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:o(e.components),t.createElement(s.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/2c62ead1.fc5c1b17.js b/docs/assets/js/2c62ead1.fc5c1b17.js new file mode 100644 index 00000000..83e25c6b --- /dev/null +++ b/docs/assets/js/2c62ead1.fc5c1b17.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[5801],{1688:(e,i,n)=>{n.r(i),n.d(i,{assets:()=>a,contentTitle:()=>o,default:()=>u,frontMatter:()=>l,metadata:()=>s,toc:()=>c});const s=JSON.parse('{"id":"numerix/v1.0.0/functionalities","title":"Key Functionalities","description":"Overview","source":"@site/docs/numerix/v1.0.0/functionalities.md","sourceDirName":"numerix/v1.0.0","slug":"/numerix/v1.0.0/functionalities","permalink":"/BharatMLStack/numerix/v1.0.0/functionalities","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/numerix/v1.0.0/functionalities.md","tags":[],"version":"current","sidebarPosition":3,"frontMatter":{"title":"Key Functionalities","sidebar_position":3},"sidebar":"tutorialSidebar","previous":{"title":"Benchmarks","permalink":"/BharatMLStack/numerix/v1.0.0/benchmarks"},"next":{"title":"Release Notes","permalink":"/BharatMLStack/numerix/v1.0.0/release-notes"}}');var r=n(4848),t=n(8453);const l={title:"Key Functionalities",sidebar_position:3},o="Numerix \u2014 Key Functionalities",a={},c=[{value:"Overview",id:"overview",level:2},{value:"\ud83d\ude80 Core Capabilities",id:"-core-capabilities",level:2},{value:"Expression Evaluation",id:"expression-evaluation",level:3},{value:"Input Formats",id:"input-formats",level:3},{value:"Request Patterns",id:"request-patterns",level:3},{value:"\ud83c\udfaf Developer Experience",id:"-developer-experience",level:2},{value:"gRPC Service",id:"grpc-service",level:3},{value:"Example Call (grpcurl)",id:"example-call-grpcurl",level:3},{value:"\ud83d\udcca Observability",id:"-observability",level:2},{value:"\u2699\ufe0f Configuration & Registry",id:"\ufe0f-configuration--registry",level:2},{value:"\ud83e\uddea Example Scenarios",id:"-example-scenarios",level:2},{value:"Batched evaluation",id:"batched-evaluation",level:3},{value:"Mixed input formats",id:"mixed-input-formats",level:3},{value:"\ud83d\udd27 Tuning Knobs",id:"-tuning-knobs",level:2},{value:"Contributing",id:"contributing",level:2},{value:"Community & Support",id:"community--support",level:2},{value:"License",id:"license",level:2}];function d(e){const i={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,t.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(i.header,{children:(0,r.jsx)(i.h1,{id:"numerix--key-functionalities",children:"Numerix \u2014 Key Functionalities"})}),"\n",(0,r.jsx)(i.h2,{id:"overview",children:"Overview"}),"\n",(0,r.jsxs)(i.p,{children:["Numerix evaluates mathematical expressions over feature matrices with a simple, low-latency gRPC surface. Each request references a ",(0,r.jsx)(i.code,{children:"compute_id"}),"; Numerix resolves a postfix expression, maps variables to input columns, and evaluates it over fp32/fp64 vectors with compiler-assisted SIMD."]}),"\n",(0,r.jsx)(i.h2,{id:"-core-capabilities",children:"\ud83d\ude80 Core Capabilities"}),"\n",(0,r.jsx)(i.h3,{id:"expression-evaluation",children:"Expression Evaluation"}),"\n",(0,r.jsxs)(i.ul,{children:["\n",(0,r.jsxs)(i.li,{children:[(0,r.jsx)(i.strong,{children:"Postfix execution"}),": Linear-time, stack-based evaluation over aligned vectors."]}),"\n",(0,r.jsxs)(i.li,{children:[(0,r.jsx)(i.strong,{children:"Vectorized math"}),": Compiler autovectorization (NEON/SVE on ARM, SSE/AVX on x86) \u2014 no handwritten intrinsics."]}),"\n",(0,r.jsxs)(i.li,{children:[(0,r.jsx)(i.strong,{children:"Typed compute"}),": Inputs converted to ",(0,r.jsx)(i.code,{children:"fp32"})," or ",(0,r.jsx)(i.code,{children:"fp64"})," for predictable performance."]}),"\n"]}),"\n",(0,r.jsx)(i.h3,{id:"input-formats",children:"Input Formats"}),"\n",(0,r.jsxs)(i.ul,{children:["\n",(0,r.jsxs)(i.li,{children:[(0,r.jsx)(i.strong,{children:"Strings"}),": Easy-to-produce feature values (converted internally)."]}),"\n",(0,r.jsxs)(i.li,{children:[(0,r.jsx)(i.strong,{children:"Bytes"}),": Efficient wire format for high-throughput paths."]}),"\n"]}),"\n",(0,r.jsx)(i.h3,{id:"request-patterns",children:"Request Patterns"}),"\n",(0,r.jsxs)(i.ul,{children:["\n",(0,r.jsxs)(i.li,{children:[(0,r.jsx)(i.strong,{children:"Single entity or batch"}),": Multiple ",(0,r.jsx)(i.code,{children:"entity_scores"})," per call."]}),"\n",(0,r.jsxs)(i.li,{children:[(0,r.jsx)(i.strong,{children:"Schema-driven"}),": Column order in ",(0,r.jsx)(i.code,{children:"schema"})," drives variable mapping in expressions."]}),"\n"]}),"\n",(0,r.jsx)(i.h2,{id:"-developer-experience",children:"\ud83c\udfaf Developer Experience"}),"\n",(0,r.jsxs)(i.ul,{children:["\n",(0,r.jsxs)(i.li,{children:[(0,r.jsx)(i.strong,{children:"gRPC API"}),": Simple single RPC \u2014 ",(0,r.jsx)(i.code,{children:"Numerix/Compute"}),"."]}),"\n",(0,r.jsxs)(i.li,{children:[(0,r.jsx)(i.strong,{children:"Protobuf schema"}),": Language-agnostic client generation."]}),"\n",(0,r.jsxs)(i.li,{children:[(0,r.jsx)(i.strong,{children:"Deterministic behavior"}),": No parsing at request time; expression resolved from registry."]}),"\n"]}),"\n",(0,r.jsx)(i.h3,{id:"grpc-service",children:"gRPC Service"}),"\n",(0,r.jsx)(i.pre,{children:(0,r.jsx)(i.code,{className:"language-protobuf",children:"service Numerix {\n rpc Compute(NumerixRequestProto) returns (NumerixResponseProto);\n}\n"})}),"\n",(0,r.jsx)(i.h3,{id:"example-call-grpcurl",children:"Example Call (grpcurl)"}),"\n",(0,r.jsx)(i.pre,{children:(0,r.jsx)(i.code,{className:"language-bash",children:'grpcurl -plaintext \\\n -import-path ./numerix/src/protos/proto \\\n -proto numerix.proto \\\n -d \'{\n "entityScoreData": {\n "schema": ["feature1", "feature2"],\n "entityScores": [ { "stringData": { "values": ["1.0", "2.0"] } } ],\n "computeId": "1001",\n "dataType": "fp32"\n }\n }\' \\\n localhost:8080 numerix.Numerix/Compute\n'})}),"\n",(0,r.jsx)(i.h2,{id:"-observability",children:"\ud83d\udcca Observability"}),"\n",(0,r.jsxs)(i.ul,{children:["\n",(0,r.jsxs)(i.li,{children:[(0,r.jsx)(i.strong,{children:"Datadog/DogStatsD"}),": Latency (P50/P95/P99), RPS, error rate via UDP client."]}),"\n",(0,r.jsxs)(i.li,{children:["Optional ",(0,r.jsx)(i.code,{children:"/metrics"})," endpoint for local/adhoc debugging."]}),"\n"]}),"\n",(0,r.jsx)(i.h2,{id:"\ufe0f-configuration--registry",children:"\u2699\ufe0f Configuration & Registry"}),"\n",(0,r.jsxs)(i.ul,{children:["\n",(0,r.jsxs)(i.li,{children:[(0,r.jsx)(i.strong,{children:"etcd registry"}),": ",(0,r.jsx)(i.code,{children:"compute_id (int) \u2192 postfix expression"})," mapping."]}),"\n",(0,r.jsxs)(i.li,{children:[(0,r.jsx)(i.strong,{children:"Environment-driven config"}),": endpoints, timeouts, sampling rate."]}),"\n"]}),"\n",(0,r.jsx)(i.h2,{id:"-example-scenarios",children:"\ud83e\uddea Example Scenarios"}),"\n",(0,r.jsx)(i.h3,{id:"batched-evaluation",children:"Batched evaluation"}),"\n",(0,r.jsxs)(i.ul,{children:["\n",(0,r.jsxs)(i.li,{children:["Submit multiple entities in one call to reduce RPC overhead; evaluate the same ",(0,r.jsx)(i.code,{children:"compute_id"})," across all rows."]}),"\n"]}),"\n",(0,r.jsx)(i.h3,{id:"mixed-input-formats",children:"Mixed input formats"}),"\n",(0,r.jsxs)(i.ul,{children:["\n",(0,r.jsx)(i.li,{children:"Start with string inputs for ease; migrate to bytes for performance without changing the expression or API."}),"\n"]}),"\n",(0,r.jsx)(i.h2,{id:"-tuning-knobs",children:"\ud83d\udd27 Tuning Knobs"}),"\n",(0,r.jsxs)(i.ul,{children:["\n",(0,r.jsxs)(i.li,{children:[(0,r.jsx)(i.strong,{children:"Data type"}),": choose ",(0,r.jsx)(i.code,{children:"fp32"})," (speed) vs ",(0,r.jsx)(i.code,{children:"fp64"})," (precision)."]}),"\n",(0,r.jsxs)(i.li,{children:[(0,r.jsx)(i.strong,{children:"Batch size"}),": tune number of entities per call for your p99 vs throughput goals."]}),"\n",(0,r.jsxs)(i.li,{children:[(0,r.jsx)(i.strong,{children:"Sampling rate"}),": adjust Datadog metric sampling to balance signal vs cost."]}),"\n"]}),"\n",(0,r.jsx)(i.hr,{}),"\n",(0,r.jsx)(i.h2,{id:"contributing",children:"Contributing"}),"\n",(0,r.jsxs)(i.p,{children:["We welcome contributions from the community! Please see our ",(0,r.jsx)(i.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTING.md",children:"Contributing Guide"})," for details on how to get started."]}),"\n",(0,r.jsx)(i.h2,{id:"community--support",children:"Community & Support"}),"\n",(0,r.jsxs)(i.ul,{children:["\n",(0,r.jsxs)(i.li,{children:["\ud83d\udcac ",(0,r.jsx)(i.strong,{children:"Discord"}),": Join our ",(0,r.jsx)(i.a,{href:"https://discord.gg/XkT7XsV2AU",children:"community chat"})]}),"\n",(0,r.jsxs)(i.li,{children:["\ud83d\udc1b ",(0,r.jsx)(i.strong,{children:"Issues"}),": Report bugs and request features on ",(0,r.jsx)(i.a,{href:"https://github.com/Meesho/BharatMLStack/issues",children:"GitHub Issues"})]}),"\n",(0,r.jsxs)(i.li,{children:["\ud83d\udce7 ",(0,r.jsx)(i.strong,{children:"Email"}),": Contact us at ",(0,r.jsx)(i.a,{href:"mailto:ml-oss@meesho.com",children:"ml-oss@meesho.com"})]}),"\n"]}),"\n",(0,r.jsx)(i.h2,{id:"license",children:"License"}),"\n",(0,r.jsxs)(i.p,{children:["BharatMLStack is open-source software licensed under the ",(0,r.jsx)(i.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md",children:"BharatMLStack Business Source License 1.1"}),"."]}),"\n",(0,r.jsx)(i.hr,{}),"\n",(0,r.jsx)("div",{align:"center",children:(0,r.jsx)("strong",{children:"Built with \u2764\ufe0f for the ML community from Meesho"})}),"\n",(0,r.jsx)("div",{align:"center",children:(0,r.jsx)("strong",{children:"If you find this useful, \u2b50\ufe0f the repo \u2014 your support means the world to us!"})})]})}function u(e={}){const{wrapper:i}={...(0,t.R)(),...e.components};return i?(0,r.jsx)(i,{...e,children:(0,r.jsx)(d,{...e})}):d(e)}},8453:(e,i,n)=>{n.d(i,{R:()=>l,x:()=>o});var s=n(6540);const r={},t=s.createContext(r);function l(e){const i=s.useContext(t);return s.useMemo(function(){return"function"==typeof e?e(i):{...i,...e}},[i,e])}function o(e){let i;return i=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:l(e.components),s.createElement(t.Provider,{value:i},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/2d865531.30aab314.js b/docs/assets/js/2d865531.30aab314.js new file mode 100644 index 00000000..f34105fc --- /dev/null +++ b/docs/assets/js/2d865531.30aab314.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[9197],{4153:t=>{t.exports=JSON.parse('{"authors":[{"name":"Adarsha Das","title":"Senior Architect @ Meesho","url":"https://github.com/a0d00kc","imageURL":"https://github.com/a0d00kc.png","key":"adarsha","page":null,"count":4},{"name":"Aditya Kumar","title":"Lead Software Engineer @ Meesho","url":"https://github.com/Adit2607","imageURL":"https://github.com/Adit2607.png","key":"aditya","page":null,"count":2},{"name":"Jigar Dave","title":"Lead Software Engineer @ Meesho","url":"https://github.com/jigarpatel26","imageURL":"https://github.com/jigarpatel26.png","key":"jigar","page":null,"count":2},{"name":"Jaya Kumar","title":"Lead ML Engineer @ Meesho","url":"https://github.com/jayakommuru","imageURL":"https://github.com/jayakommuru.png","key":"jaya","page":null,"count":3},{"name":"Bhawani Singh","title":"Architect @ Meesho","url":"https://github.com/singh-bhawani","imageURL":"https://github.com/singh-bhawani.png","key":"bhawani","page":null,"count":2},{"name":"Mohit Kumar","title":"SDE-III @ Meesho","url":"https://github.com/kmohit00","imageURL":"https://github.com/kmohit00.png","key":"mohit","page":null,"count":0}]}')}}]); \ No newline at end of file diff --git a/docs/assets/js/2d865531.a5005531.js b/docs/assets/js/2d865531.a5005531.js deleted file mode 100644 index f959c03e..00000000 --- a/docs/assets/js/2d865531.a5005531.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[9197],{4153:t=>{t.exports=JSON.parse('{"authors":[{"name":"Adarsha Das","title":"Senior Architect @ Meesho","url":"https://github.com/a0d00kc","imageURL":"https://github.com/a0d00kc.png","key":"adarsha","page":null,"count":1},{"name":"Aditya Kumar","title":"SDE-III @ Meesho","url":"https://github.com/Adit2607","imageURL":"https://github.com/Adit2607.png","key":"aditya","page":null,"count":1},{"name":"Jigar Dave","title":"SDE-IV @ Meesho","url":"https://github.com/jigarpatel26","imageURL":"https://github.com/jigarpatel26.png","key":"jigar","page":null,"count":1},{"name":"Jaya Kumar","title":"MLE-III @ Meesho","url":"https://github.com/jayakommuru","imageURL":"https://github.com/jayakommuru.png","key":"jaya","page":null,"count":0},{"name":"Bhawani Singh","title":"SDE-IV @ Meesho","url":"https://github.com/singh-bhawani","imageURL":"https://github.com/singh-bhawani.png","key":"bhawani","page":null,"count":1},{"name":"Mohit Kumar","title":"SDE-III @ Meesho","url":"https://github.com/kmohit00","imageURL":"https://github.com/kmohit00.png","key":"mohit","page":null,"count":0}]}')}}]); \ No newline at end of file diff --git a/docs/assets/js/3039fa8c.6e21a8e8.js b/docs/assets/js/3039fa8c.6e21a8e8.js new file mode 100644 index 00000000..82454f01 --- /dev/null +++ b/docs/assets/js/3039fa8c.6e21a8e8.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[1966],{474:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>o,contentTitle:()=>l,default:()=>h,frontMatter:()=>r,metadata:()=>t,toc:()=>d});var t=i(5376),a=i(4848),s=i(8453);const r={title:"Cracking the Code: Scaling Model Inference & Real-Time Embedding Search",description:"How Meesho scaled model inference with self-hosted Triton on GKE\u2014slashing latency and costs by 65%\u2014and built a real-time embedding search system on Qdrant to power personalized recommendations at scale.",authors:["aditya","jaya","adarsha"],slug:"scaling-model-inference-and-embedding-search",date:new Date("2024-05-21T00:00:00.000Z"),tags:["model-inference","embedding-search","mlplatform","meesho","bharatmlstack"]},l=void 0,o={authorsImageUrls:[void 0,void 0,void 0]},d=[{value:"Breaking Free from the Scalability Ceiling",id:"breaking-free-from-the-scalability-ceiling",level:2},{value:"The Model Serving Bottleneck\u2014A Wake-Up Call",id:"the-model-serving-bottlenecka-wake-up-call",level:3},{value:"Scaling Triton on GKE",id:"scaling-triton-on-gke",level:3},{value:"Fixing the Cold Start Problem",id:"fixing-the-cold-start-problem",level:3},{value:"Embedding Search: The Last Piece of the Puzzle",id:"embedding-search-the-last-piece-of-the-puzzle",level:2},{value:"Choosing the Right Vector Database",id:"choosing-the-right-vector-database",level:3},{value:"Embedding Freshness & Real-Time Updates",id:"embedding-freshness--real-time-updates",level:3},{value:"Final Takeaways: Scaling Smartly for Real-Time ML",id:"final-takeaways-scaling-smartly-for-real-time-ml",level:2}];function c(e){const n={h2:"h2",h3:"h3",img:"img",li:"li",p:"p",ul:"ul",...(0,s.R)(),...e.components};return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsxs)(n.p,{children:[(0,a.jsx)(n.img,{alt:"BharatMLStack",src:i(2199).A+"",width:"1396",height:"460"}),"\nBy mid-2023, we had transformed our ML stack\u2014building a real-time feature store, optimizing model retrieval, and fine-tuning ranking. But two critical gaps remained:"]}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:"\ud83d\udd39 Scaling model inference without hitting infrastructure roadblocks"}),"\n",(0,a.jsx)(n.li,{children:"\ud83d\udd39 Moving embedding search from batch to real-time for candidate generation"}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:"Here\u2019s how we tackled these last-mile challenges, broke free from infrastructure constraints, and built a cost-efficient, high-performance system."}),"\n",(0,a.jsx)(n.h2,{id:"breaking-free-from-the-scalability-ceiling",children:"Breaking Free from the Scalability Ceiling"}),"\n",(0,a.jsx)(n.h3,{id:"the-model-serving-bottlenecka-wake-up-call",children:"The Model Serving Bottleneck\u2014A Wake-Up Call"}),"\n",(0,a.jsx)(n.p,{children:"July 2023. With just months left for the Mega Blockbuster Sale (MBS), we noticed a serious issue\u2014scaling our model-serving infrastructure was taking 10\u201315 minutes. In real-time ML, that\u2019s an eternity.\nIn one of our war rooms, we ran a quick experiment:"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:"\ud83d\ude80 We deployed an XGBoost model on a self-hosted Triton Inference Server running on a 16-core machine."}),"\n",(0,a.jsx)(n.li,{children:"\ud83d\ude80 Fired requests and compared the outputs with our existing cloud-hosted setup."}),"\n",(0,a.jsx)(n.li,{children:"\ud83d\ude80 The results matched\u2014perfectly."}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:'That moment changed everything. We prepped a backup Triton setup on EKS, just in case our cloud provider couldn\'t allocate enough compute resources in time. Luckily, they did\u2014but the seed was planted.\nThen in October, just two weeks before MBS, we got an alarming response from our infrastructure team:\n"Node availability may be an issue."\nWith no time to waste, we moved 30% of real-time ML traffic to our self-hosted Triton cluster. The results?'}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:"\u2705 p99 latency dropped from 90\u2013100ms to 30\u201340ms"}),"\n",(0,a.jsx)(n.li,{children:"\u2705 Triton handled significantly higher throughput on fewer resources"}),"\n",(0,a.jsx)(n.li,{children:"\u2705 No model changes were needed"}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:"MBS ran without a hitch, proving that self-hosted inference was the way forward."}),"\n",(0,a.jsx)(n.h3,{id:"scaling-triton-on-gke",children:"Scaling Triton on GKE"}),"\n",(0,a.jsx)(n.p,{children:"This left us with two choices:"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:"1\ufe0f\u20e3 Port models to a managed cloud inference service, investing time in learning a new deployment stack"}),"\n",(0,a.jsx)(n.li,{children:"2\ufe0f\u20e3 Scale our existing Triton setup on GKE, optimizing for cost and performance"}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:"We went with Option 2\u2014and it slashed inference costs to 35% of what we previously paid, while giving us full control over scaling and optimizations."}),"\n",(0,a.jsx)(n.h3,{id:"fixing-the-cold-start-problem",children:"Fixing the Cold Start Problem"}),"\n",(0,a.jsx)(n.p,{children:"As we onboarded more deep learning (DL) models, we hit a new bottleneck, new inference pods took 7\u20139 minutes to spin up."}),"\n",(0,a.jsx)(n.p,{children:"After profiling, we found the culprits:"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:"Triton\u2019s base image\u2014a massive 5GB"}),"\n",(0,a.jsx)(n.li,{children:"Model binaries\u2014often 1GB+"}),"\n",(0,a.jsx)(n.li,{children:"Startup delay\u2014mostly due to downloading and initializing these assets"}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:"To fix this, we built a lightweight Triton image, stripping unused components and shrinking the size to 900MB. This cut cold start times drastically, making auto-scaling faster and smoother."}),"\n",(0,a.jsx)(n.h2,{id:"embedding-search-the-last-piece-of-the-puzzle",children:"Embedding Search: The Last Piece of the Puzzle"}),"\n",(0,a.jsx)(n.p,{children:"By mid-2023, most of our ML stack had gone real-time\u2014except for Candidate Generation (CG), which still ran in batch mode. To truly power real-time recommendations, we needed an online embedding search system."}),"\n",(0,a.jsx)(n.h3,{id:"choosing-the-right-vector-database",children:"Choosing the Right Vector Database"}),"\n",(0,a.jsx)(n.p,{children:"We benchmarked three production-ready vector DBs across key parameters:"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:"Milvus"}),"\n",(0,a.jsx)(n.li,{children:"Qdrant"}),"\n",(0,a.jsx)(n.li,{children:"Weaviate"}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:"After extensive POCs, Qdrant stood out for its:"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:"\u2705 Blazing-fast search latency on high-dimensional vectors"}),"\n",(0,a.jsx)(n.li,{children:"\u2705 Efficient memory usage, crucial for in-memory workloads"}),"\n",(0,a.jsx)(n.li,{children:"\u2705 Support for upserts and soft deletes, vital for Ads use cases"}),"\n",(0,a.jsx)(n.li,{children:"\u2705 gRPC + REST APIs, making integration seamless"}),"\n",(0,a.jsx)(n.li,{children:"\u2705 Powerful filtering, allowing fine-tuned retrieval (e.g., filtering Ads by category, active status, etc.)"}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:"At its core, Qdrant uses HNSW indexing, delivering both high recall and low-latency nearest-neighbor search\u2014a perfect fit for our needs."}),"\n",(0,a.jsx)(n.h3,{id:"embedding-freshness--real-time-updates",children:"Embedding Freshness & Real-Time Updates"}),"\n",(0,a.jsx)(n.p,{children:"To ensure embeddings stayed up to date, we built a dual ingestion pipeline:"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:"\ud83d\udccc Daily Refresh: A bulk pipeline updated embeddings overnight"}),"\n",(0,a.jsx)(n.li,{children:"\ud83d\udccc Real-Time Updates: Ads events triggered immediate upserts/deletes"}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:'This setup powered real-time "Similar Products" recommendations on the product page and became the foundation for Ads Candidate Generation, ensuring the right ads surfaced in milliseconds.'}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.img,{alt:"Skye",src:i(5869).A+"",width:"1260",height:"644"})}),"\n",(0,a.jsx)(n.h2,{id:"final-takeaways-scaling-smartly-for-real-time-ml",children:"Final Takeaways: Scaling Smartly for Real-Time ML"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:"\ud83d\ude80 Self-hosted inference on Triton gave us lower cost, faster scaling, and better performance than managed services"}),"\n",(0,a.jsx)(n.li,{children:"\ud83d\ude80 Building a custom Triton image reduced cold starts, improving responsiveness"}),"\n",(0,a.jsx)(n.li,{children:"\ud83d\ude80 Qdrant-based embedding search enabled real-time personalization at scale"}),"\n",(0,a.jsx)(n.li,{children:"\ud83d\ude80 Real-time updates for embeddings unlocked dynamic, up-to-date recommendations"}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:"By early 2024, Meesho\u2019s ML stack had evolved into a fully real-time, scalable, and cost-efficient system, setting the foundation for even bigger leaps ahead."})]})}function h(e={}){const{wrapper:n}={...(0,s.R)(),...e.components};return n?(0,a.jsx)(n,{...e,children:(0,a.jsx)(c,{...e})}):c(e)}},2199:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/bms-7399e8796d2cd24617c432518ce3f312.png"},5376:e=>{e.exports=JSON.parse('{"permalink":"/BharatMLStack/blog/scaling-model-inference-and-embedding-search","editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/blog/bharatmlstack-history/scaling-model-inference-and-embedding-search/index.md","source":"@site/blog/bharatmlstack-history/scaling-model-inference-and-embedding-search/index.md","title":"Cracking the Code: Scaling Model Inference & Real-Time Embedding Search","description":"How Meesho scaled model inference with self-hosted Triton on GKE\u2014slashing latency and costs by 65%\u2014and built a real-time embedding search system on Qdrant to power personalized recommendations at scale.","date":"2024-05-21T00:00:00.000Z","tags":[{"inline":true,"label":"model-inference","permalink":"/BharatMLStack/blog/tags/model-inference"},{"inline":true,"label":"embedding-search","permalink":"/BharatMLStack/blog/tags/embedding-search"},{"inline":true,"label":"mlplatform","permalink":"/BharatMLStack/blog/tags/mlplatform"},{"inline":true,"label":"meesho","permalink":"/BharatMLStack/blog/tags/meesho"},{"inline":true,"label":"bharatmlstack","permalink":"/BharatMLStack/blog/tags/bharatmlstack"}],"readingTime":3.55,"hasTruncateMarker":false,"authors":[{"name":"Aditya Kumar","title":"Lead Software Engineer @ Meesho","url":"https://github.com/Adit2607","imageURL":"https://github.com/Adit2607.png","key":"aditya","page":null},{"name":"Jaya Kumar","title":"Lead ML Engineer @ Meesho","url":"https://github.com/jayakommuru","imageURL":"https://github.com/jayakommuru.png","key":"jaya","page":null},{"name":"Adarsha Das","title":"Senior Architect @ Meesho","url":"https://github.com/a0d00kc","imageURL":"https://github.com/a0d00kc.png","key":"adarsha","page":null}],"frontMatter":{"title":"Cracking the Code: Scaling Model Inference & Real-Time Embedding Search","description":"How Meesho scaled model inference with self-hosted Triton on GKE\u2014slashing latency and costs by 65%\u2014and built a real-time embedding search system on Qdrant to power personalized recommendations at scale.","authors":["aditya","jaya","adarsha"],"slug":"scaling-model-inference-and-embedding-search","date":"2024-05-21T00:00:00.000Z","tags":["model-inference","embedding-search","mlplatform","meesho","bharatmlstack"]},"unlisted":false,"prevItem":{"title":"Designing a Production-Grade LLM Inference Platform: From Model Weights to Scalable GPU Serving","permalink":"/BharatMLStack/blog/multi-engine-llm-inferencing-platform"},"nextItem":{"title":"Building Meesho\u2019s ML Platform: Lessons from the First-Gen System (Part 2)","permalink":"/BharatMLStack/blog/building-meeshos-mlplatform-lessons-from-first-gen"}}')},5869:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/vss-c482f6eac4c68b3219e4c562a6b717ec.png"},8453:(e,n,i)=>{i.d(n,{R:()=>r,x:()=>l});var t=i(6540);const a={},s=t.createContext(a);function r(e){const n=t.useContext(s);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(a):e.components||a:r(e.components),t.createElement(s.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/340c7c5f.a496fe54.js b/docs/assets/js/340c7c5f.a496fe54.js new file mode 100644 index 00000000..eb9e9e7a --- /dev/null +++ b/docs/assets/js/340c7c5f.a496fe54.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[74],{4795:(e,t,r)=>{r.d(t,{A:()=>N});r(6540);var n=r(4164),s=r(6972),o=r(8774),i=r(5846),a=r(6654),c=r(1312),l=r(1107);const u={cardContainer:"cardContainer_fWXF",cardTitle:"cardTitle_rnsV",cardDescription:"cardDescription_PWke"};var d=r(4848);function m({className:e,href:t,children:r}){return(0,d.jsx)(o.A,{href:t,className:(0,n.A)("card padding--lg",u.cardContainer,e),children:r})}function f({className:e,href:t,icon:r,title:s,description:o}){return(0,d.jsxs)(m,{href:t,className:e,children:[(0,d.jsxs)(l.A,{as:"h2",className:(0,n.A)("text--truncate",u.cardTitle),title:s,children:[r," ",s]}),o&&(0,d.jsx)("p",{className:(0,n.A)("text--truncate",u.cardDescription),title:o,children:o})]})}function h({item:e}){const t=(0,s.Nr)(e),r=function(){const{selectMessage:e}=(0,i.W)();return t=>e(t,(0,c.T)({message:"1 item|{count} items",id:"theme.docs.DocCard.categoryDescription.plurals",description:"The default description for a category card in the generated index about how many items this category includes"},{count:t}))}();return t?(0,d.jsx)(f,{className:e.className,href:t,icon:"\ud83d\uddc3\ufe0f",title:e.label,description:e.description??r(e.items.length)}):null}function p({item:e}){const t=(0,a.A)(e.href)?"\ud83d\udcc4\ufe0f":"\ud83d\udd17",r=(0,s.cC)(e.docId??void 0);return(0,d.jsx)(f,{className:e.className,href:e.href,icon:t,title:e.label,description:e.description??r?.description})}function x({item:e}){switch(e.type){case"link":return(0,d.jsx)(p,{item:e});case"category":return(0,d.jsx)(h,{item:e});default:throw new Error(`unknown item type ${JSON.stringify(e)}`)}}const g={docCardListItem:"docCardListItem_W1sv"};function v({className:e}){const t=(0,s.a4)();return(0,d.jsx)(N,{items:t,className:e})}function j({item:e}){return(0,d.jsx)("article",{className:(0,n.A)(g.docCardListItem,"col col--6"),children:(0,d.jsx)(x,{item:e})})}function N(e){const{items:t,className:r}=e;if(!t)return(0,d.jsx)(v,{...e});const o=(0,s.d1)(t);return(0,d.jsx)("section",{className:(0,n.A)("row",r),children:o.map((e,t)=>(0,d.jsx)(j,{item:e},t))})}},5846:(e,t,r)=>{r.d(t,{W:()=>l});var n=r(6540),s=r(4586);const o=["zero","one","two","few","many","other"];function i(e){return o.filter(t=>e.includes(t))}const a={locale:"en",pluralForms:i(["one","other"]),select:e=>1===e?"one":"other"};function c(){const{i18n:{currentLocale:e}}=(0,s.A)();return(0,n.useMemo)(()=>{try{return function(e){const t=new Intl.PluralRules(e);return{locale:e,pluralForms:i(t.resolvedOptions().pluralCategories),select:e=>t.select(e)}}(e)}catch(t){return console.error(`Failed to use Intl.PluralRules for locale "${e}".\nDocusaurus will fallback to the default (English) implementation.\nError: ${t.message}\n`),a}},[e])}function l(){const e=c();return{selectMessage:(t,r)=>function(e,t,r){const n=e.split("|");if(1===n.length)return n[0];n.length>r.pluralForms.length&&console.error(`For locale=${r.locale}, a maximum of ${r.pluralForms.length} plural forms are expected (${r.pluralForms.join(",")}), but the message contains ${n.length}: ${e}`);const s=r.select(t),o=r.pluralForms.indexOf(s);return n[Math.min(o,n.length-1)]}(r,t,e)}}},7642:(e,t,r)=>{r.r(t),r.d(t,{assets:()=>l,contentTitle:()=>c,default:()=>m,frontMatter:()=>a,metadata:()=>n,toc:()=>u});const n=JSON.parse('{"id":"online-feature-store/v1.0.0/index","title":"v1.0.0","description":"Online Feature Store v1.0.0","source":"@site/docs/online-feature-store/v1.0.0/index.md","sourceDirName":"online-feature-store/v1.0.0","slug":"/online-feature-store/v1.0.0","permalink":"/BharatMLStack/online-feature-store/v1.0.0","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/online-feature-store/v1.0.0/index.md","tags":[],"version":"current","sidebarPosition":0,"frontMatter":{"title":"v1.0.0","description":"Online Feature Store v1.0.0","sidebar_position":0,"slug":"/online-feature-store/v1.0.0"},"sidebar":"tutorialSidebar","previous":{"title":"Online Feature Store","permalink":"/BharatMLStack/category/online-feature-store"},"next":{"title":"Architecture","permalink":"/BharatMLStack/online-feature-store/v1.0.0/architecture"}}');var s=r(4848),o=r(8453),i=r(4795);const a={title:"v1.0.0",description:"Online Feature Store v1.0.0",sidebar_position:0,slug:"/online-feature-store/v1.0.0"},c="Online Feature Store v1.0.0",l={},u=[];function d(e){const t={h1:"h1",header:"header",p:"p",...(0,o.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(t.header,{children:(0,s.jsx)(t.h1,{id:"online-feature-store-v100",children:"Online Feature Store v1.0.0"})}),"\n",(0,s.jsx)(t.p,{children:"A high-performance, scalable, and production-grade feature store built for modern machine learning systems. It supports both real-time and batch workflows, with low-latency feature retrieval."}),"\n",(0,s.jsx)(i.A,{})]})}function m(e={}){const{wrapper:t}={...(0,o.R)(),...e.components};return t?(0,s.jsx)(t,{...e,children:(0,s.jsx)(d,{...e})}):d(e)}},8453:(e,t,r)=>{r.d(t,{R:()=>i,x:()=>a});var n=r(6540);const s={},o=n.createContext(s);function i(e){const t=n.useContext(o);return n.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function a(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:i(e.components),n.createElement(o.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/3650a837.fd1a89f8.js b/docs/assets/js/3650a837.fd1a89f8.js new file mode 100644 index 00000000..d3f5b92d --- /dev/null +++ b/docs/assets/js/3650a837.fd1a89f8.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[1782],{4795:(e,t,n)=>{n.d(t,{A:()=>v});n(6540);var r=n(4164),s=n(6972),c=n(8774),i=n(5846),a=n(6654),o=n(1312),l=n(1107);const u={cardContainer:"cardContainer_fWXF",cardTitle:"cardTitle_rnsV",cardDescription:"cardDescription_PWke"};var d=n(4848);function m({className:e,href:t,children:n}){return(0,d.jsx)(c.A,{href:t,className:(0,r.A)("card padding--lg",u.cardContainer,e),children:n})}function h({className:e,href:t,icon:n,title:s,description:c}){return(0,d.jsxs)(m,{href:t,className:e,children:[(0,d.jsxs)(l.A,{as:"h2",className:(0,r.A)("text--truncate",u.cardTitle),title:s,children:[n," ",s]}),c&&(0,d.jsx)("p",{className:(0,r.A)("text--truncate",u.cardDescription),title:c,children:c})]})}function p({item:e}){const t=(0,s.Nr)(e),n=function(){const{selectMessage:e}=(0,i.W)();return t=>e(t,(0,o.T)({message:"1 item|{count} items",id:"theme.docs.DocCard.categoryDescription.plurals",description:"The default description for a category card in the generated index about how many items this category includes"},{count:t}))}();return t?(0,d.jsx)(h,{className:e.className,href:t,icon:"\ud83d\uddc3\ufe0f",title:e.label,description:e.description??n(e.items.length)}):null}function f({item:e}){const t=(0,a.A)(e.href)?"\ud83d\udcc4\ufe0f":"\ud83d\udd17",n=(0,s.cC)(e.docId??void 0);return(0,d.jsx)(h,{className:e.className,href:e.href,icon:t,title:e.label,description:e.description??n?.description})}function x({item:e}){switch(e.type){case"link":return(0,d.jsx)(f,{item:e});case"category":return(0,d.jsx)(p,{item:e});default:throw new Error(`unknown item type ${JSON.stringify(e)}`)}}const y={docCardListItem:"docCardListItem_W1sv"};function g({className:e}){const t=(0,s.a4)();return(0,d.jsx)(v,{items:t,className:e})}function k({item:e}){return(0,d.jsx)("article",{className:(0,r.A)(y.docCardListItem,"col col--6"),children:(0,d.jsx)(x,{item:e})})}function v(e){const{items:t,className:n}=e;if(!t)return(0,d.jsx)(g,{...e});const c=(0,s.d1)(t);return(0,d.jsx)("section",{className:(0,r.A)("row",n),children:c.map((e,t)=>(0,d.jsx)(k,{item:e},t))})}},4927:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>m,frontMatter:()=>a,metadata:()=>r,toc:()=>u});const r=JSON.parse('{"id":"skye/v1.0.0/index","title":"v1.0.0","description":"Skye v1.0.0","source":"@site/docs/skye/v1.0.0/index.md","sourceDirName":"skye/v1.0.0","slug":"/skye/v1.0.0","permalink":"/BharatMLStack/skye/v1.0.0","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/skye/v1.0.0/index.md","tags":[],"version":"current","sidebarPosition":0,"frontMatter":{"title":"v1.0.0","description":"Skye v1.0.0","sidebar_position":0,"slug":"/skye/v1.0.0"},"sidebar":"tutorialSidebar","previous":{"title":"Skye","permalink":"/BharatMLStack/category/skye"},"next":{"title":"Architecture","permalink":"/BharatMLStack/skye/v1.0.0/architecture"}}');var s=n(4848),c=n(8453),i=n(4795);const a={title:"v1.0.0",description:"Skye v1.0.0",sidebar_position:0,slug:"/skye/v1.0.0"},o="Skye v1.0.0",l={},u=[];function d(e){const t={h1:"h1",header:"header",p:"p",...(0,c.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(t.header,{children:(0,s.jsx)(t.h1,{id:"skye-v100",children:"Skye v1.0.0"})}),"\n",(0,s.jsx)(t.p,{children:"Skye is a high-performance vector similarity search platform that enables fast semantic retrieval by representing data as vectors and querying nearest matches in high-dimensional space."}),"\n",(0,s.jsx)(i.A,{})]})}function m(e={}){const{wrapper:t}={...(0,c.R)(),...e.components};return t?(0,s.jsx)(t,{...e,children:(0,s.jsx)(d,{...e})}):d(e)}},5846:(e,t,n)=>{n.d(t,{W:()=>l});var r=n(6540),s=n(4586);const c=["zero","one","two","few","many","other"];function i(e){return c.filter(t=>e.includes(t))}const a={locale:"en",pluralForms:i(["one","other"]),select:e=>1===e?"one":"other"};function o(){const{i18n:{currentLocale:e}}=(0,s.A)();return(0,r.useMemo)(()=>{try{return function(e){const t=new Intl.PluralRules(e);return{locale:e,pluralForms:i(t.resolvedOptions().pluralCategories),select:e=>t.select(e)}}(e)}catch(t){return console.error(`Failed to use Intl.PluralRules for locale "${e}".\nDocusaurus will fallback to the default (English) implementation.\nError: ${t.message}\n`),a}},[e])}function l(){const e=o();return{selectMessage:(t,n)=>function(e,t,n){const r=e.split("|");if(1===r.length)return r[0];r.length>n.pluralForms.length&&console.error(`For locale=${n.locale}, a maximum of ${n.pluralForms.length} plural forms are expected (${n.pluralForms.join(",")}), but the message contains ${r.length}: ${e}`);const s=n.select(t),c=n.pluralForms.indexOf(s);return r[Math.min(c,r.length-1)]}(n,t,e)}}},8453:(e,t,n)=>{n.d(t,{R:()=>i,x:()=>a});var r=n(6540);const s={},c=r.createContext(s);function i(e){const t=r.useContext(c);return r.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function a(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:i(e.components),r.createElement(c.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/393be207.6e979fd2.js b/docs/assets/js/393be207.6e979fd2.js new file mode 100644 index 00000000..ff548d9e --- /dev/null +++ b/docs/assets/js/393be207.6e979fd2.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[4134],{591:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>c,default:()=>l,frontMatter:()=>s,metadata:()=>a,toc:()=>d});const a=JSON.parse('{"type":"mdx","permalink":"/BharatMLStack/markdown-page","source":"@site/src/pages/markdown-page.md","title":"Markdown page example","description":"You don\'t need React to write simple standalone pages.","frontMatter":{"title":"Markdown page example"},"unlisted":false}');var o=n(4848),r=n(8453);const s={title:"Markdown page example"},c="Markdown page example",p={},d=[];function i(e){const t={h1:"h1",header:"header",p:"p",...(0,r.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(t.header,{children:(0,o.jsx)(t.h1,{id:"markdown-page-example",children:"Markdown page example"})}),"\n",(0,o.jsx)(t.p,{children:"You don't need React to write simple standalone pages."})]})}function l(e={}){const{wrapper:t}={...(0,r.R)(),...e.components};return t?(0,o.jsx)(t,{...e,children:(0,o.jsx)(i,{...e})}):i(e)}},8453:(e,t,n)=>{n.d(t,{R:()=>s,x:()=>c});var a=n(6540);const o={},r=a.createContext(o);function s(e){const t=a.useContext(r);return a.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function c(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:s(e.components),a.createElement(r.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/393be207.81b456e5.js b/docs/assets/js/393be207.81b456e5.js deleted file mode 100644 index 044303c5..00000000 --- a/docs/assets/js/393be207.81b456e5.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[4134],{591:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>c,default:()=>l,frontMatter:()=>s,metadata:()=>a,toc:()=>d});const a=JSON.parse('{"type":"mdx","permalink":"/BharatMLStack/markdown-page","source":"@site/src/pages/markdown-page.md","title":"Markdown page example","description":"You don\'t need React to write simple standalone pages.","frontMatter":{"title":"Markdown page example"},"unlisted":false}');var o=n(4848),r=n(8453);const s={title:"Markdown page example"},c="Markdown page example",p={},d=[];function i(e){const t={h1:"h1",header:"header",p:"p",...(0,r.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(t.header,{children:(0,o.jsx)(t.h1,{id:"markdown-page-example",children:"Markdown page example"})}),"\n",(0,o.jsx)(t.p,{children:"You don't need React to write simple standalone pages."})]})}function l(e={}){const{wrapper:t}={...(0,r.R)(),...e.components};return t?(0,o.jsx)(t,{...e,children:(0,o.jsx)(i,{...e})}):i(e)}},8453:(e,t,n)=>{n.d(t,{R:()=>s,x:()=>c});var a=n(6540);const o={},r=a.createContext(o);function s(e){const t=a.useContext(r);return a.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function c(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:s(e.components),a.createElement(r.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/3980073a.43116f8b.js b/docs/assets/js/3980073a.43116f8b.js new file mode 100644 index 00000000..482c5b6e --- /dev/null +++ b/docs/assets/js/3980073a.43116f8b.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[940],{3840:t=>{t.exports=JSON.parse('{"tag":{"label":"interaction-store","permalink":"/BharatMLStack/blog/tags/interaction-store","allTagsPath":"/BharatMLStack/blog/tags","count":2,"unlisted":false},"listMetadata":{"permalink":"/BharatMLStack/blog/tags/interaction-store","page":1,"postsPerPage":10,"totalPages":1,"totalCount":2,"blogDescription":"Blog","blogTitle":"Blog"}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/3980073a.4ab85476.js b/docs/assets/js/3980073a.4ab85476.js deleted file mode 100644 index a2b5d33b..00000000 --- a/docs/assets/js/3980073a.4ab85476.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[940],{3840:t=>{t.exports=JSON.parse('{"tag":{"label":"interaction-store","permalink":"/BharatMLStack/blog/tags/interaction-store","allTagsPath":"/BharatMLStack/blog/tags","count":1,"unlisted":false},"listMetadata":{"permalink":"/BharatMLStack/blog/tags/interaction-store","page":1,"postsPerPage":10,"totalPages":1,"totalCount":1,"blogDescription":"Blog","blogTitle":"Blog"}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/3c208a5b.9fedc403.js b/docs/assets/js/3c208a5b.9fedc403.js new file mode 100644 index 00000000..3a442305 --- /dev/null +++ b/docs/assets/js/3c208a5b.9fedc403.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[7795],{1467:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>l,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>t,toc:()=>d});var t=i(7882),s=i(4848),r=i(8453);const a={title:"Building Meesho\u2019s ML Platform: From Chaos to Cutting-Edge (Part 1)",description:"How Meesho transitioned from batch-based recommendations to a real-time ML platform\u2014building an Online Feature Store, Interaction Store, and DAG execution framework that became BharatMLStack.",slug:"building-meeshos-mlplatform",authors:["adarsha","aditya","bhawani","jigar"],date:new Date("2022-11-15T00:00:00.000Z"),tags:["online-feature-store","interaction-store","mlplatform","meesho"]},o=void 0,l={authorsImageUrls:[void 0,void 0,void 0,void 0]},d=[{value:"The Turning Point: From Batch to Real-Time",id:"the-turning-point-from-batch-to-real-time",level:2},{value:"First Generation Design",id:"first-generation-design",level:2},{value:"1. IOP Framework: A Real-Time DAG Executor",id:"1-iop-framework-a-real-time-dag-executor",level:3},{value:"2. Online Feature Store - 0th Version",id:"2-online-feature-store---0th-version",level:3},{value:"3. Interaction Store - 0th Version",id:"3-interaction-store---0th-version",level:3},{value:"Building the Online Feature Store - 0th Version",id:"building-the-online-feature-store---0th-version",level:2},{value:"Choosing the Right Tech Stack",id:"choosing-the-right-tech-stack",level:3},{value:"Streamlining the Data Flow",id:"streamlining-the-data-flow",level:3},{value:"The Challenges: Data Format and Storage",id:"the-challenges-data-format-and-storage",level:2},{value:"Feature Consistency",id:"feature-consistency",level:3},{value:"TTL Granularity",id:"ttl-granularity",level:3},{value:"Extensibility Across Databases",id:"extensibility-across-databases",level:3},{value:"Overcoming Technical Constraints",id:"overcoming-technical-constraints",level:2},{value:"The Solution: Schema Separation",id:"the-solution-schema-separation",level:2},{value:"Tracking Changes in Feature Groups",id:"tracking-changes-in-feature-groups",level:2},{value:"Common Real-World Scenarios:",id:"common-real-world-scenarios",level:3},{value:"The Solution: Schema Versioning",id:"the-solution-schema-versioning",level:2},{value:"Backward Compatibility",id:"backward-compatibility",level:3},{value:"Partial Availability Handling",id:"partial-availability-handling",level:3},{value:"Safe Writes Without Pipeline Pauses",id:"safe-writes-without-pipeline-pauses",level:3},{value:"Interaction Store - 0th Version",id:"interaction-store---0th-version",level:2},{value:"Event Ingestion",id:"event-ingestion",level:2},{value:"Storage Design",id:"storage-design",level:2},{value:"Why Redis?",id:"why-redis",level:3},{value:"Storage Structure",id:"storage-structure",level:3},{value:"Built-in Guardrails",id:"built-in-guardrails",level:3},{value:"Conclusion: Laying the Foundation for Real-Time ML",id:"conclusion-laying-the-foundation-for-real-time-ml",level:2}];function c(e){const n={a:"a",br:"br",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",hr:"hr",img:"img",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,r.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.img,{alt:"BharatMLStack",src:i(6496).A+"",width:"1396",height:"460"}),"\nIt all started in early 2022, over a casual Friday evening catch-up. Like many great origin stories, this one began with friendly banter between a group of backend engineers and data scientists. As the conversations unfolded, so did the roasting\u2014until one remark hit a little too close to home:"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.em,{children:'"Why are we still crunching data for Monthly Active Users (MAU) when the next day it\u2019s all about Daily Active Users (DAU)?"'})}),"\n",(0,s.jsx)(n.p,{children:"The laughter died down, and the question lingered. When we regrouped on Monday\u2014clear-headed and slightly reflective\u2014we decided to dig into the numbers. What they discovered was quite revealing: a large portion of compute resources wasn\u2019t being put to good use.\nMuch of the system\u2019s effort was spent supporting users who weren\u2019t actively engaging, and even for new users, the experience wasn\u2019t optimized to make a meaningful impact."}),"\n",(0,s.jsxs)(n.p,{children:["At the same time, Meesho had just launched a company-wide initiative to reduce costs\u2014and every team had to contribute. This realization sparked the journey that would eventually lead to the ",(0,s.jsx)(n.strong,{children:"Meesho ML Platform"}),", known today as ",(0,s.jsx)(n.strong,{children:"BharatMLStack"}),"."]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Alt Text",src:i(7121).A+"",width:"1600",height:"1078"})}),"\n",(0,s.jsx)(n.p,{children:"Before the ML Platform, our recommendation and ranking pipelines followed a batch processing approach:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Data Ingestion"}),": The Data Platform team executed ETL jobs to ingest raw user data\u2014including user profiles, interaction logs, and product impressions\u2014into designated S3 buckets."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Layer 1"}),": Embedding Generation: On the Data Science side, Spark jobs pulled data from multiple S3 sources, cleaned and preprocessed it, and applied matrix factorization to generate user and item embeddings. The processed data and embeddings were then stored back in S3 in a structured format."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Layer 2"}),": Candidate Generation (CG): In this stage, Spark jobs leveraged embeddings and historical interaction data to generate candidate recommendations for users. These candidate lists were subsequently written to S3."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Layer 3"}),": Ranking and Merging \u2013 A final round of processing ranked the generated candidates using ML models, combined different candidate lists, and stored the final ranked recommendations in a caching system."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Serving"}),': A microservice retrieved ranked recommendations from an in-memory data store via exposed APIs, delivering personalized listings across key surfaces such as "For You" and Category Landing Pages (CLP).']}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"This approach held up well\u2014until Meesho started seeing a significant surge in traffic."}),"\n",(0,s.jsx)(n.h2,{id:"the-turning-point-from-batch-to-real-time",children:"The Turning Point: From Batch to Real-Time"}),"\n",(0,s.jsxs)(n.p,{children:["At this time, the team was iterating on new ",(0,s.jsx)(n.strong,{children:"Ranker models"}),", and real-time inference seemed like the next logical step. But Rankers needed ",(0,s.jsx)(n.strong,{children:"real-time feature retrieval"}),", which meant an ",(0,s.jsx)(n.strong,{children:"online feature store"})," had to be built first."]}),"\n",(0,s.jsxs)(n.p,{children:["Exploring open-source options led to ",(0,s.jsx)(n.strong,{children:"cost vs. performance trade-offs"}),", but Meesho\u2019s surging traffic meant that ",(0,s.jsx)(n.strong,{children:"latency and stability were non-negotiable"}),". After multiple debates and stakeholder discussions, a bold decision was made:"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.em,{children:"We would build our own feature store."})}),"\n",(0,s.jsxs)(n.p,{children:["Meanwhile, efforts began to bring ",(0,s.jsx)(n.strong,{children:"Candidate Generators (CGs)"})," to real-time. The challenge? ",(0,s.jsx)(n.strong,{children:"Storing and retrieving user interactions quickly enough"})," to power real-time recommendations."]}),"\n",(0,s.jsxs)(n.p,{children:["As the team dove deeper, a new roadblock emerged:",(0,s.jsx)(n.br,{}),"\n","Our ML jobs were orchestrated using ",(0,s.jsx)(n.strong,{children:"Airflow DAGs"}),", giving data scientists flexibility in experimentation. But transitioning to real-time execution threatened this agility. Every change would now require backend engineering support, ",(0,s.jsx)(n.strong,{children:"slowing down iteration cycles"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["That\u2019s when the idea struck:",(0,s.jsx)(n.br,{}),"\n","We needed a ",(0,s.jsx)(n.strong,{children:"framework for real-time DAG execution"}),"\u2014one that preserved the same flexibility as Airflow but worked for ",(0,s.jsx)(n.strong,{children:"streaming data"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["This moment shaped the ",(0,s.jsx)(n.strong,{children:"next phase of our journey"}),"."]}),"\n",(0,s.jsx)(n.h2,{id:"first-generation-design",children:"First Generation Design"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Alt Text",src:i(1828).A+"",width:"1600",height:"1006"})}),"\n",(0,s.jsx)(n.h1,{id:"laying-the-groundwork-the-first-gen-ml-platform",children:"Laying the Groundwork: The First-Gen ML Platform"}),"\n",(0,s.jsx)(n.p,{children:"To solve these challenges, the team built three foundational components:"}),"\n",(0,s.jsx)(n.h3,{id:"1-iop-framework-a-real-time-dag-executor",children:"1. IOP Framework: A Real-Time DAG Executor"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Reusable Nodes"}),": Each DAG node (e.g., an invocation to a CG service, a ranker, or a filter) had to be implemented only once. After that, it could be reused across any workflow by referencing it in config."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Config-driven Dynamic Graphs"}),": Execution graphs were defined as adjacency lists stored in ",(0,s.jsx)(n.strong,{children:"ZooKeeper"}),", allowing teams to modify the sequence or structure of operations without touching application code."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Plug-and-play CGs"}),": The Candidate Generator interface was preserved, so a single CG node could call any CG service by passing ",(0,s.jsx)(n.code,{children:"cg_name"})," in the request. This drastically reduced the code surface area and improved maintainability."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Production-Grade DAGs"}),": DAGs were designed to execute in ",(0,s.jsx)(n.strong,{children:"low-latency real-time environments"}),", with support for ",(0,s.jsx)(n.strong,{children:"parallel execution, retries, and branching"}),"."]}),"\n"]}),"\n",(0,s.jsx)("u",{children:(0,s.jsx)(n.a,{href:"https://www.meesho.io/blog/rebuilding-meeshos-ranking-platform",children:"More about IOP DAG"})}),"\n",(0,s.jsx)(n.h3,{id:"2-online-feature-store---0th-version",children:"2. Online Feature Store - 0th Version"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Used ",(0,s.jsx)(n.strong,{children:"Cassandra"})," and ",(0,s.jsx)(n.strong,{children:"Redis"})," for low-latency feature serving."]}),"\n",(0,s.jsxs)(n.li,{children:["Maintained feature consistency using ",(0,s.jsx)(n.strong,{children:"Feature Groups"})," with TTL-based expiry."]}),"\n",(0,s.jsxs)(n.li,{children:["A hybrid schema was used: feature keys stored in ",(0,s.jsx)(n.strong,{children:"ZooKeeper"}),", data stored in ",(0,s.jsx)(n.strong,{children:"compact arrays"}),"."]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"3-interaction-store---0th-version",children:"3. Interaction Store - 0th Version"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Captured real-time user interactions like clicks, orders, and add-to-cart events."}),"\n",(0,s.jsxs)(n.li,{children:["Stored event data in ",(0,s.jsx)(n.strong,{children:"Redis ZSETs (sorted sets)"})," to enable fast lookups for recommendation engines."]}),"\n",(0,s.jsxs)(n.li,{children:["Provided an API to fetch a user's ",(0,s.jsxs)(n.strong,{children:["last ",(0,s.jsx)(n.em,{children:"k"})," interactions"]})," or ",(0,s.jsx)(n.strong,{children:"interactions within a time window"}),"."]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["With these components in place, ",(0,s.jsx)(n.strong,{children:"real-time ML at Meesho became a reality"}),"."]}),"\n",(0,s.jsx)(n.p,{children:"This was just the beginning."}),"\n",(0,s.jsx)(n.h2,{id:"building-the-online-feature-store---0th-version",children:"Building the Online Feature Store - 0th Version"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Alt text",src:i(1965).A+"",width:"1574",height:"562"})}),"\n",(0,s.jsx)(n.h3,{id:"choosing-the-right-tech-stack",children:"Choosing the Right Tech Stack"}),"\n",(0,s.jsxs)(n.p,{children:["We spent considerable time evaluating various databases, caches, and communication protocols for our ",(0,s.jsx)(n.strong,{children:"online feature store"}),". After carefully weighing ",(0,s.jsx)(n.strong,{children:"cost, latency, throughput"}),", and ",(0,s.jsx)(n.strong,{children:"operational stability"}),", we settled on a combination of:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Cassandra"})," and ",(0,s.jsx)(n.strong,{children:"Redis"})," for storage"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"gRPC + Proto3"})," as our communication layer"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"streamlining-the-data-flow",children:"Streamlining the Data Flow"}),"\n",(0,s.jsx)(n.p,{children:"To keep things simple in the initial version:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Feature engineering jobs"})," wrote raw outputs to an ",(0,s.jsx)(n.strong,{children:"S3 bucket"})]}),"\n",(0,s.jsxs)(n.li,{children:["A ",(0,s.jsx)(n.strong,{children:"daily feature push job"}),":","\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Read from S3"}),"\n",(0,s.jsxs)(n.li,{children:["Grouped related features into ",(0,s.jsx)(n.strong,{children:"Feature Groups"})," (ensuring consistency)"]}),"\n",(0,s.jsxs)(n.li,{children:["Pushed them to ",(0,s.jsx)(n.strong,{children:"Kafka"})]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"For features requiring frequent updates:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Ad-hoc jobs"})," computed features in higher frequency"]}),"\n",(0,s.jsxs)(n.li,{children:["These jobs pushed to both ",(0,s.jsx)(n.strong,{children:"Kafka"})," and ",(0,s.jsx)(n.strong,{children:"S3"})," (S3 preserved historical data for future model training)"]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"the-challenges-data-format-and-storage",children:"The Challenges: Data Format and Storage"}),"\n",(0,s.jsxs)(n.p,{children:["One of the most critical design challenges was how to store feature data ",(0,s.jsx)(n.strong,{children:"efficiently and consistently"}),", especially in databases like ",(0,s.jsx)(n.strong,{children:"Cassandra"})," and ",(0,s.jsx)(n.strong,{children:"Redis"}),", which come with unique storage constraints."]}),"\n",(0,s.jsx)(n.p,{children:"We had to solve for three key requirements:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.h3,{id:"feature-consistency",children:"Feature Consistency"}),"\n",(0,s.jsxs)(n.p,{children:["When a feature group contains features like ",(0,s.jsx)(n.code,{children:"order_count_1h"})," and ",(0,s.jsx)(n.code,{children:"click_count_1h"}),", both must reflect the ",(0,s.jsx)(n.strong,{children:"same time window"}),". Inconsistent updates would lead to ",(0,s.jsx)(n.strong,{children:"unreliable model predictions"}),"."]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.h3,{id:"ttl-granularity",children:"TTL Granularity"}),"\n",(0,s.jsxs)(n.p,{children:["Each feature group required an ",(0,s.jsx)(n.strong,{children:"expiry timestamp"}),", so that ",(0,s.jsx)(n.strong,{children:"all features within it expired together"}),"\u2014preserving consistency during reads."]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.h3,{id:"extensibility-across-databases",children:"Extensibility Across Databases"}),"\n",(0,s.jsxs)(n.p,{children:["We anticipated that infra needs would evolve. To future-proof our system, the data format was designed to be ",(0,s.jsx)(n.strong,{children:"decoupled from DB-specific layouts"}),", enabling portability to systems like ",(0,s.jsx)(n.strong,{children:"ScyllaDB"}),", ",(0,s.jsx)(n.strong,{children:"DynamoDB"}),", ",(0,s.jsx)(n.strong,{children:"HBase"}),", or ",(0,s.jsx)(n.strong,{children:"BigTable"}),"."]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"overcoming-technical-constraints",children:"Overcoming Technical Constraints"}),"\n",(0,s.jsx)(n.p,{children:'At the time, we were using Cassandra, which not only imposed a soft limit of 75 columns per row, but also exhibited significant performance degradation as the number of columns increased further, particularly in memory constrained machines. Wide rows caused high memory usage during reads, unpredictable latencies due to heavy deserialization overhead, and inefficiencies during compactions and repairs. This ruled out the naive "one column per feature" approach. We needed a format that was compact, minimized the number of columns, and remained efficient and portable across different storage systems.'}),"\n",(0,s.jsx)(n.h2,{id:"the-solution-schema-separation",children:"The Solution: Schema Separation"}),"\n",(0,s.jsx)(n.p,{children:"We introduced the concept of Feature Groups\u2014logical groupings of features that must remain consistent with one another.\nTo represent these groups efficiently, we adopted a layered storage approach:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Feature Labels (Keys)"})," were stored in ZooKeeper, serving as the schema."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Feature Values"})," were stored as a comma-separated string array in Cassandra or Redis."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Expiry Timestamp and Schema Version"})," were appended using a semi-colon delimiter at the end of the string."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Example:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"feature_1_value,feature_2_value,feature_3_value;expiry_ts\n"})}),"\n",(0,s.jsx)(n.p,{children:"This format allowed:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Consistent writes and reads at the group level"}),"\n",(0,s.jsx)(n.li,{children:"Easy parsing of feature values using the schema lookup from ZooKeeper"}),"\n",(0,s.jsx)(n.li,{children:"Efficient storage with minimal DB column usage"}),"\n",(0,s.jsx)(n.li,{children:"Support for per-group TTLs and schema evolution"}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"tracking-changes-in-feature-groups",children:"Tracking Changes in Feature Groups"}),"\n",(0,s.jsx)(n.p,{children:"Feature groups don\u2019t stay static. As models evolve, features get added, renamed, or removed. But schema changes often go live before the data is ready\u2014and stopping ingestion just to wait for everything to align isn't feasible."}),"\n",(0,s.jsx)(n.h3,{id:"common-real-world-scenarios",children:"Common Real-World Scenarios:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"A new feature is added to the schema, but ingestion jobs still use the older schema version."}),"\n",(0,s.jsx)(n.li,{children:"Ongoing writes don\u2019t include the newly added feature, and stopping ingestion would break freshness for existing features."}),"\n",(0,s.jsx)(n.li,{children:"During serving, models request a mix of old and new features, depending on rollout stages."}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"the-solution-schema-versioning",children:"The Solution: Schema Versioning"}),"\n",(0,s.jsx)(n.p,{children:"We solved this with versioned feature group schemas, which unlocked several capabilities:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.h3,{id:"backward-compatibility",children:"Backward Compatibility"}),"\n","Older ingestion jobs can continue writing using older schema versions. During reads, the system uses the schema version embedded in the value to interpret the data correctly."]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.h3,{id:"partial-availability-handling",children:"Partial Availability Handling"}),"\n","During inference, if some features in the request aren\u2019t available (due to rollout delays or missing data), the system serves default values, ensuring the inference call doesn\u2019t fail."]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.h3,{id:"safe-writes-without-pipeline-pauses",children:"Safe Writes Without Pipeline Pauses"}),"\n","With schema versioning, we no longer had to stop ingestion pipelines for schema updates. Writes using previous versions can continue safely, and downstream consumers evolve independently.\nThis design gave us the flexibility to move fast without breaking things\u2014preserving data quality, enabling experimentation, and ensuring reliability at scale."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Alt Text",src:i(8449).A+"",width:"1600",height:"599"})}),"\n",(0,s.jsx)(n.h2,{id:"interaction-store---0th-version",children:"Interaction Store - 0th Version"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Alt Text",src:i(4959).A+"",width:"1600",height:"518"})}),"\n",(0,s.jsxs)(n.p,{children:["To power real-time Candidate Generators (CGs), we needed fast access to user behavior signals\u2014like what a user recently clicked, ordered, or added to their cart. These interactions form the basis for many real-time recommendations, such as ",(0,s.jsx)(n.strong,{children:"Similar Products"}),", ",(0,s.jsx)(n.strong,{children:"People Also Viewed"}),", or ",(0,s.jsx)(n.strong,{children:"Recently Ordered Again"}),".\nFor the ",(0,s.jsx)(n.strong,{children:"0th version"})," of the Interaction Store, we focused on a design that was ",(0,s.jsx)(n.strong,{children:"simple, fast, and reliable"})," \u2014 optimized for high-throughput ingestion and low-latency lookups."]}),"\n",(0,s.jsx)(n.h2,{id:"event-ingestion",children:"Event Ingestion"}),"\n",(0,s.jsx)(n.p,{children:"We instrumented our backend services to emit key user interaction events to Kafka in real time. These included:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Click"}),"\n",(0,s.jsx)(n.li,{children:"Order"}),"\n",(0,s.jsx)(n.li,{children:"Add to Cart"}),"\n",(0,s.jsx)(n.li,{children:"Wishlist"}),"\n",(0,s.jsx)(n.li,{children:"Share"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Each event carried essential metadata:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"userId \u2014 uniquely identifies the user"}),"\n",(0,s.jsx)(n.li,{children:"productId \u2014 the item being interacted with"}),"\n",(0,s.jsx)(n.li,{children:"timestamp \u2014 the moment the interaction occurred"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"This decoupled the interaction logging from storage, allowing ingestion and consumption to scale independently."}),"\n",(0,s.jsx)(n.h2,{id:"storage-design",children:"Storage Design"}),"\n",(0,s.jsx)(n.p,{children:"To store these events, we built Kafka consumers that processed the incoming streams and wrote the data into Redis, using sorted sets (ZSETs) as the primary data structure."}),"\n",(0,s.jsx)(n.h3,{id:"why-redis",children:"Why Redis?"}),"\n",(0,s.jsx)(n.p,{children:"Redis gave us:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Low-latency"})," reads and writes"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Time-ordered data"})," using ZSETs (via score = timestamp)"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Native TTL support"}),", if needed in later versions"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"In-memory performance"})," \u2014ideal for real-time CGs"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"storage-structure",children:"Storage Structure"}),"\n",(0,s.jsx)(n.p,{children:"Each user\u2019s interactions were stored using a composite key format, uniquely identifying the user and interaction type. This structure allowed efficient organization and quick retrieval of recent activity for recommendation generation:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"userId_eventType \u2192 ZSET[...(pid, ts)...]\n"})}),"\n",(0,s.jsx)(n.p,{children:"Within each ZSET:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["The ",(0,s.jsx)(n.strong,{children:"timestamp"})," served as the score, maintaining temporal order"]}),"\n",(0,s.jsxs)(n.li,{children:["The ",(0,s.jsx)(n.strong,{children:"productId"})," (optionally with metadata) was the ",(0,s.jsx)(n.strong,{children:"value"})]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"This allowed us to efficiently retrieve the interactions with HTTP-based API server with two query modes:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Fetch the ",(0,s.jsx)(n.strong,{children:"last k interactions"})," of a specific type for a given user with ",(0,s.jsx)(n.code,{children:"ZREVRANGE(userId_eventType, count)"})]}),"\n",(0,s.jsxs)(n.li,{children:["Retrieve ",(0,s.jsx)(n.strong,{children:"all interactions within a time range"})," (e.g., last 24 hours) with ",(0,s.jsx)(n.code,{children:"ZREVRANGEBYSCORE(userId_eventType, timeRange)"})]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"built-in-guardrails",children:"Built-in Guardrails"}),"\n",(0,s.jsx)(n.p,{children:"Since Redis was the sole store, we implemented High Availability (HA) to prevent data loss. To optimize memory usage, we also enforced size limits per event type\u2014only storing the last k interactions per user, with older entries getting truncated."}),"\n",(0,s.jsx)(n.h2,{id:"conclusion-laying-the-foundation-for-real-time-ml",children:"Conclusion: Laying the Foundation for Real-Time ML"}),"\n",(0,s.jsxs)(n.p,{children:["In this first phase, we tackled the ",(0,s.jsx)(n.strong,{children:"fundamentals"}),"\u2014shifting from batch-based recommendations to a ",(0,s.jsx)(n.strong,{children:"real-time Recommendation"})," using ML platform that could keep up with Meesho\u2019s growth."]}),"\n",(0,s.jsxs)(n.p,{children:["With the ",(0,s.jsx)(n.strong,{children:"IOP Framework"}),", ",(0,s.jsx)(n.strong,{children:"Online Feature Store"}),", and ",(0,s.jsx)(n.strong,{children:"Interaction Store"}),", we built the core infrastructure to support real-time personalization at scale. These wins have already unlocked:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"\u2705 Faster, more dynamic recommendations for millions of users."}),"\n",(0,s.jsx)(n.li,{children:"\u2705 Better infrastructure efficiency, reducing wasted compute power."}),"\n",(0,s.jsx)(n.li,{children:"\u2705 A flexible, modular system that allows for further experimentation."}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["But this is just the beginning. While we've solved key challenges, ",(0,s.jsx)(n.strong,{children:"certain roadblocks remain"})," \u2014from optimizing ",(0,s.jsx)(n.strong,{children:"cost-performance trade-offs"})," to ",(0,s.jsx)(n.strong,{children:"seamlessly evolving schemas"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["This foundational work laid the path for a reliable and scalable ",(0,s.jsx)(n.strong,{children:"real-time feature serving layer"}),"."]})]})}function h(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(c,{...e})}):c(e)}},1828:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/first-gen-arch-7c0b286810aecb7eff42b48f51caee1f.png"},1965:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/online-feature-store-v0-86ec0010947ae24621f39ebd0d1729ca.png"},4959:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/interaction-store-v0-68167b64c6e462ef2f177f0f86d55bda.png"},6496:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/bms-7399e8796d2cd24617c432518ce3f312.png"},7121:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/old-batch-arch-bc2cedbc1fed0fc6f08479ba8fe52996.png"},7882:e=>{e.exports=JSON.parse('{"permalink":"/BharatMLStack/blog/building-meeshos-mlplatform","editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/blog/bharatmlstack-history/building-meeshos-mlplatform-from-chaos-to-cutting-edge/index.md","source":"@site/blog/bharatmlstack-history/building-meeshos-mlplatform-from-chaos-to-cutting-edge/index.md","title":"Building Meesho\u2019s ML Platform: From Chaos to Cutting-Edge (Part 1)","description":"How Meesho transitioned from batch-based recommendations to a real-time ML platform\u2014building an Online Feature Store, Interaction Store, and DAG execution framework that became BharatMLStack.","date":"2022-11-15T00:00:00.000Z","tags":[{"inline":true,"label":"online-feature-store","permalink":"/BharatMLStack/blog/tags/online-feature-store"},{"inline":true,"label":"interaction-store","permalink":"/BharatMLStack/blog/tags/interaction-store"},{"inline":true,"label":"mlplatform","permalink":"/BharatMLStack/blog/tags/mlplatform"},{"inline":true,"label":"meesho","permalink":"/BharatMLStack/blog/tags/meesho"}],"readingTime":10.19,"hasTruncateMarker":false,"authors":[{"name":"Adarsha Das","title":"Senior Architect @ Meesho","url":"https://github.com/a0d00kc","imageURL":"https://github.com/a0d00kc.png","key":"adarsha","page":null},{"name":"Aditya Kumar","title":"Lead Software Engineer @ Meesho","url":"https://github.com/Adit2607","imageURL":"https://github.com/Adit2607.png","key":"aditya","page":null},{"name":"Bhawani Singh","title":"Architect @ Meesho","url":"https://github.com/singh-bhawani","imageURL":"https://github.com/singh-bhawani.png","key":"bhawani","page":null},{"name":"Jigar Dave","title":"Lead Software Engineer @ Meesho","url":"https://github.com/jigarpatel26","imageURL":"https://github.com/jigarpatel26.png","key":"jigar","page":null}],"frontMatter":{"title":"Building Meesho\u2019s ML Platform: From Chaos to Cutting-Edge (Part 1)","description":"How Meesho transitioned from batch-based recommendations to a real-time ML platform\u2014building an Online Feature Store, Interaction Store, and DAG execution framework that became BharatMLStack.","slug":"building-meeshos-mlplatform","authors":["adarsha","aditya","bhawani","jigar"],"date":"2022-11-15T00:00:00.000Z","tags":["online-feature-store","interaction-store","mlplatform","meesho"]},"unlisted":false,"prevItem":{"title":"Building Meesho\u2019s ML Platform: Lessons from the First-Gen System (Part 2)","permalink":"/BharatMLStack/blog/building-meeshos-mlplatform-lessons-from-first-gen"}}')},8449:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/schema-d699efc400ed0f83bba421c1f55ab211.png"},8453:(e,n,i)=>{i.d(n,{R:()=>a,x:()=>o});var t=i(6540);const s={},r=t.createContext(s);function a(e){const n=t.useContext(r);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:a(e.components),t.createElement(r.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/4137b431.2e5cd4ca.js b/docs/assets/js/4137b431.2e5cd4ca.js deleted file mode 100644 index 125ba260..00000000 --- a/docs/assets/js/4137b431.2e5cd4ca.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[6054],{4019:e=>{e.exports=JSON.parse('{"version":{"pluginId":"default","version":"current","label":"Next","banner":null,"badge":false,"noIndex":false,"className":"docs-version-current","isLast":true,"docsSidebars":{"tutorialSidebar":[{"type":"category","label":"Online Feature Store","collapsible":true,"collapsed":true,"items":[{"type":"category","label":"v1.0.0","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Architecture","href":"/BharatMLStack/online-feature-store/v1.0.0/architecture","docId":"online-feature-store/v1.0.0/architecture","unlisted":false},{"type":"link","label":"Data Formats","href":"/BharatMLStack/online-feature-store/v1.0.0/data-formats","docId":"online-feature-store/v1.0.0/data-formats","unlisted":false},{"type":"link","label":"Benchmarks","href":"/BharatMLStack/online-feature-store/v1.0.0/benchmarks","docId":"online-feature-store/v1.0.0/benchmarks","unlisted":false},{"type":"link","label":"Key Functionalities","href":"/BharatMLStack/online-feature-store/v1.0.0/functionalities","docId":"online-feature-store/v1.0.0/functionalities","unlisted":false},{"type":"link","label":"Release Notes","href":"/BharatMLStack/online-feature-store/v1.0.0/release-notes","docId":"online-feature-store/v1.0.0/release-notes","unlisted":false}],"href":"/BharatMLStack/online-feature-store/v1.0.0"}],"href":"/BharatMLStack/category/online-feature-store"},{"type":"category","label":"Quick Start","collapsible":true,"collapsed":true,"items":[{"type":"category","label":"v1.0.0","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Quick Start","href":"/BharatMLStack/quick-start/v1.0.0/quick-start","docId":"quick-start/v1.0.0/quick-start","unlisted":false}]}],"href":"/BharatMLStack/category/quick-start"},{"type":"category","label":"Trufflebox UI","collapsible":true,"collapsed":true,"items":[{"type":"category","label":"v1.0.0","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"User Manual","href":"/BharatMLStack/trufflebox-ui/v1.0.0/userguide","docId":"trufflebox-ui/v1.0.0/userguide","unlisted":false}]}],"href":"/BharatMLStack/category/trufflebox-ui"},{"type":"category","label":"SDKs","collapsible":true,"collapsed":true,"items":[{"type":"category","label":"Go SDK","collapsible":true,"collapsed":true,"items":[{"type":"category","label":"v1.0.0","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"GRPC Feature client","href":"/BharatMLStack/sdks/go/v1.0.0/feature_client","docId":"sdks/go/v1.0.0/feature_client","unlisted":false}]}],"href":"/BharatMLStack/category/go-sdk"},{"type":"category","label":"Python SDK","collapsible":true,"collapsed":true,"items":[{"type":"category","label":"v1.0.0","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"GRPC Feature client","href":"/BharatMLStack/sdks/python/v1.0.0/grpc_feature_client","docId":"sdks/python/v1.0.0/grpc_feature_client","unlisted":false},{"type":"link","label":"Spark client","href":"/BharatMLStack/sdks/python/v1.0.0/spark_feature_push_client","docId":"sdks/python/v1.0.0/spark_feature_push_client","unlisted":false}],"href":"/BharatMLStack/category/v100"}],"href":"/BharatMLStack/category/python-sdk"}],"href":"/BharatMLStack/category/sdks"}]},"docs":{"online-feature-store/v1.0.0/architecture":{"id":"online-feature-store/v1.0.0/architecture","title":"Architecture","description":"The Online Feature Store (OnFS) is part of BharatMLStack, designed to support real-time ML workloads through low-latency feature retrieval and flexible feature ingestion pipelines. It ensures that features generated offline or online are immediately accessible for inference.","sidebar":"tutorialSidebar"},"online-feature-store/v1.0.0/benchmarks":{"id":"online-feature-store/v1.0.0/benchmarks","title":"Benchmarks","description":"Summary","sidebar":"tutorialSidebar"},"online-feature-store/v1.0.0/data-formats":{"id":"online-feature-store/v1.0.0/data-formats","title":"Data Formats","description":"In this section we will go through the data-formats which is at the hear of online-feature-store, it\'s inspired form other storage efficient formats like parquet & arrow, but custom made to deliver in constraint environment. The two key data-formats are:","sidebar":"tutorialSidebar"},"online-feature-store/v1.0.0/functionalities":{"id":"online-feature-store/v1.0.0/functionalities","title":"Key Functionalities","description":"Overview","sidebar":"tutorialSidebar"},"online-feature-store/v1.0.0/release-notes":{"id":"online-feature-store/v1.0.0/release-notes","title":"Release Notes","description":"Version 1.0.0 \ud83d\ude80","sidebar":"tutorialSidebar"},"quick-start/v1.0.0/quick-start":{"id":"quick-start/v1.0.0/quick-start","title":"Quick Start","description":"Discord","sidebar":"tutorialSidebar"},"sdks/go/v1.0.0/feature_client":{"id":"sdks/go/v1.0.0/feature_client","title":"GRPC Feature client","description":"Build Status","sidebar":"tutorialSidebar"},"sdks/python/v1.0.0/grpc_feature_client":{"id":"sdks/python/v1.0.0/grpc_feature_client","title":"GRPC Feature client","description":"PyPI version","sidebar":"tutorialSidebar"},"sdks/python/v1.0.0/spark_feature_push_client":{"id":"sdks/python/v1.0.0/spark_feature_push_client","title":"Spark client","description":"PyPI version","sidebar":"tutorialSidebar"},"trufflebox-ui/v1.0.0/userguide":{"id":"trufflebox-ui/v1.0.0/userguide","title":"User Manual","description":"This guide covers the complete setup and usage of the Online Feature Store system, including the core services (Online Feature Store and Horizon) and the TruffleBox UI for feature management.","sidebar":"tutorialSidebar"}}}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/4137b431.eda97697.js b/docs/assets/js/4137b431.eda97697.js new file mode 100644 index 00000000..f28d4a83 --- /dev/null +++ b/docs/assets/js/4137b431.eda97697.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[6054],{4019:e=>{e.exports=JSON.parse('{"version":{"pluginId":"default","version":"current","label":"Next","banner":null,"badge":false,"noIndex":false,"className":"docs-version-current","isLast":true,"docsSidebars":{"tutorialSidebar":[{"type":"link","label":"BharatMLStack Documentation","href":"/BharatMLStack/intro","docId":"intro","unlisted":false},{"type":"category","label":"Online Feature Store","collapsible":true,"collapsed":true,"items":[{"type":"category","label":"v1.0.0","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Architecture","href":"/BharatMLStack/online-feature-store/v1.0.0/architecture","docId":"online-feature-store/v1.0.0/architecture","unlisted":false},{"type":"link","label":"Data Formats","href":"/BharatMLStack/online-feature-store/v1.0.0/data-formats","docId":"online-feature-store/v1.0.0/data-formats","unlisted":false},{"type":"link","label":"Benchmarks","href":"/BharatMLStack/online-feature-store/v1.0.0/benchmarks","docId":"online-feature-store/v1.0.0/benchmarks","unlisted":false},{"type":"link","label":"Key Functionalities","href":"/BharatMLStack/online-feature-store/v1.0.0/functionalities","docId":"online-feature-store/v1.0.0/functionalities","unlisted":false},{"type":"link","label":"Release Notes","href":"/BharatMLStack/online-feature-store/v1.0.0/release-notes","docId":"online-feature-store/v1.0.0/release-notes","unlisted":false}],"href":"/BharatMLStack/online-feature-store/v1.0.0"}],"href":"/BharatMLStack/category/online-feature-store"},{"type":"category","label":"Inferflow","collapsible":true,"collapsed":true,"items":[{"type":"category","label":"v1.0.0","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Architecture","href":"/BharatMLStack/inferflow/v1.0.0/architecture","docId":"inferflow/v1.0.0/architecture","unlisted":false},{"type":"link","label":"Key Functionalities","href":"/BharatMLStack/inferflow/v1.0.0/functionalities","docId":"inferflow/v1.0.0/functionalities","unlisted":false},{"type":"link","label":"Configuration Guide","href":"/BharatMLStack/inferflow/v1.0.0/configuration","docId":"inferflow/v1.0.0/configuration","unlisted":false},{"type":"link","label":"Release Notes","href":"/BharatMLStack/inferflow/v1.0.0/release-notes","docId":"inferflow/v1.0.0/release-notes","unlisted":false}],"href":"/BharatMLStack/inferflow/v1.0.0"}],"href":"/BharatMLStack/category/inferflow"},{"type":"category","label":"Quick Start","collapsible":true,"collapsed":true,"items":[{"type":"category","label":"v1.0.0","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Quick Start","href":"/BharatMLStack/quick-start/v1.0.0/quick-start","docId":"quick-start/v1.0.0/quick-start","unlisted":false}],"href":"/BharatMLStack/quick-start/v1.0.0"}],"href":"/BharatMLStack/category/quick-start"},{"type":"category","label":"Trufflebox UI","collapsible":true,"collapsed":true,"items":[{"type":"category","label":"v1.0.0","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"User Manual","href":"/BharatMLStack/trufflebox-ui/v1.0.0/userguide","docId":"trufflebox-ui/v1.0.0/userguide","unlisted":false}],"href":"/BharatMLStack/trufflebox-ui/v1.0.0"}],"href":"/BharatMLStack/category/trufflebox-ui"},{"type":"category","label":"SDKs","collapsible":true,"collapsed":true,"items":[{"type":"category","label":"Go SDK","collapsible":true,"collapsed":true,"items":[{"type":"category","label":"v1.0.0","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"GRPC Feature client","href":"/BharatMLStack/sdks/go/v1.0.0/feature_client","docId":"sdks/go/v1.0.0/feature_client","unlisted":false}],"href":"/BharatMLStack/sdks/go/v1.0.0"}],"href":"/BharatMLStack/category/go-sdk"},{"type":"category","label":"Python SDK","collapsible":true,"collapsed":true,"items":[{"type":"category","label":"v1.0.0","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"GRPC Feature client","href":"/BharatMLStack/sdks/python/v1.0.0/grpc_feature_client","docId":"sdks/python/v1.0.0/grpc_feature_client","unlisted":false},{"type":"link","label":"Spark client","href":"/BharatMLStack/sdks/python/v1.0.0/spark_feature_push_client","docId":"sdks/python/v1.0.0/spark_feature_push_client","unlisted":false}],"href":"/BharatMLStack/sdks/python/v1.0.0"}],"href":"/BharatMLStack/category/python-sdk"}],"href":"/BharatMLStack/category/sdks"},{"type":"category","label":"Skye","collapsible":true,"collapsed":true,"items":[{"type":"category","label":"v1.0.0","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Architecture","href":"/BharatMLStack/skye/v1.0.0/architecture","docId":"skye/v1.0.0/architecture","unlisted":false},{"type":"link","label":"Functionalities","href":"/BharatMLStack/skye/v1.0.0/functionalities","docId":"skye/v1.0.0/functionalities","unlisted":false},{"type":"link","label":"Release Notes","href":"/BharatMLStack/skye/v1.0.0/release-notes","docId":"skye/v1.0.0/release-notes","unlisted":false}],"href":"/BharatMLStack/skye/v1.0.0"}],"href":"/BharatMLStack/category/skye"},{"type":"category","label":"Numerix","collapsible":true,"collapsed":true,"items":[{"type":"category","label":"v1.0.0","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Architecture","href":"/BharatMLStack/numerix/v1.0.0/architecture","docId":"numerix/v1.0.0/architecture","unlisted":false},{"type":"link","label":"Benchmarks","href":"/BharatMLStack/numerix/v1.0.0/benchmarks","docId":"numerix/v1.0.0/benchmarks","unlisted":false},{"type":"link","label":"Key Functionalities","href":"/BharatMLStack/numerix/v1.0.0/functionalities","docId":"numerix/v1.0.0/functionalities","unlisted":false},{"type":"link","label":"Release Notes","href":"/BharatMLStack/numerix/v1.0.0/release-notes","docId":"numerix/v1.0.0/release-notes","unlisted":false}],"href":"/BharatMLStack/numerix/v1.0.0"}],"href":"/BharatMLStack/category/numerix"},{"type":"category","label":"Predator","collapsible":true,"collapsed":true,"items":[{"type":"category","label":"v1.0.0","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Architecture","href":"/BharatMLStack/predator/v1.0.0/architecture","docId":"predator/v1.0.0/architecture","unlisted":false},{"type":"link","label":"Key Functionalities","href":"/BharatMLStack/predator/v1.0.0/functionalities","docId":"predator/v1.0.0/functionalities","unlisted":false},{"type":"link","label":"Release Notes","href":"/BharatMLStack/predator/v1.0.0/release-notes","docId":"predator/v1.0.0/release-notes","unlisted":false}],"href":"/BharatMLStack/predator/v1.0.0"}],"href":"/BharatMLStack/category/predator"}]},"docs":{"inferflow/v1.0.0/architecture":{"id":"inferflow/v1.0.0/architecture","title":"Architecture","description":"Inferflow is part of BharatMLStack, a graph-driven feature retrieval and model inference orchestration engine built in Go. It eliminates the need for custom feature retrieval code by using configurable DAG topologies to dynamically resolve entity relationships, fetch features from the Online Feature Store, and orchestrate model scoring \u2014 all driven by configuration stored in etcd.","sidebar":"tutorialSidebar"},"inferflow/v1.0.0/configuration":{"id":"inferflow/v1.0.0/configuration","title":"Configuration Guide","description":"Inferflow is fully config-driven. All model onboarding, feature retrieval logic, DAG topology, and inference behavior are controlled through configuration stored in etcd \u2014 with zero code changes required.","sidebar":"tutorialSidebar"},"inferflow/v1.0.0/functionalities":{"id":"inferflow/v1.0.0/functionalities","title":"Key Functionalities","description":"Overview","sidebar":"tutorialSidebar"},"inferflow/v1.0.0/index":{"id":"inferflow/v1.0.0/index","title":"v1.0.0","description":"Inferflow v1.0.0","sidebar":"tutorialSidebar"},"inferflow/v1.0.0/release-notes":{"id":"inferflow/v1.0.0/release-notes","title":"Release Notes","description":"Version 1.0.0","sidebar":"tutorialSidebar"},"intro":{"id":"intro","title":"BharatMLStack Documentation","description":"Welcome to the BharatMLStack documentation. BharatMLStack is an open-source, end-to-end ML infrastructure stack built for scale, speed, and simplicity. Explore the components below to get started.","sidebar":"tutorialSidebar"},"numerix/v1.0.0/architecture":{"id":"numerix/v1.0.0/architecture","title":"Architecture","description":"---","sidebar":"tutorialSidebar"},"numerix/v1.0.0/benchmarks":{"id":"numerix/v1.0.0/benchmarks","title":"Benchmarks","description":"This PoC measures the performance of vector addition in Rust with and without compiler SIMD optimizations. Requests consist of repeated fixed-size vector addition operations processed in parallel by the CPU. These results provide perspective on how much faster SIMD makes vectorized computations, and similar improvements are expected for other vectorized operations in Numerix.","sidebar":"tutorialSidebar"},"numerix/v1.0.0/functionalities":{"id":"numerix/v1.0.0/functionalities","title":"Key Functionalities","description":"Overview","sidebar":"tutorialSidebar"},"numerix/v1.0.0/index":{"id":"numerix/v1.0.0/index","title":"v1.0.0","description":"Numerix v1.0.0","sidebar":"tutorialSidebar"},"numerix/v1.0.0/release-notes":{"id":"numerix/v1.0.0/release-notes","title":"Release Notes","description":"Version 1.0.0 \ud83d\ude80","sidebar":"tutorialSidebar"},"online-feature-store/v1.0.0/architecture":{"id":"online-feature-store/v1.0.0/architecture","title":"Architecture","description":"The Online Feature Store (OnFS) is part of BharatMLStack, designed to support real-time ML workloads through low-latency feature retrieval and flexible feature ingestion pipelines. It ensures that features generated offline or online are immediately accessible for inference.","sidebar":"tutorialSidebar"},"online-feature-store/v1.0.0/benchmarks":{"id":"online-feature-store/v1.0.0/benchmarks","title":"Benchmarks","description":"Summary","sidebar":"tutorialSidebar"},"online-feature-store/v1.0.0/data-formats":{"id":"online-feature-store/v1.0.0/data-formats","title":"Data Formats","description":"In this section we will go through the data-formats which is at the hear of online-feature-store, it\'s inspired form other storage efficient formats like parquet & arrow, but custom made to deliver in constraint environment. The two key data-formats are:","sidebar":"tutorialSidebar"},"online-feature-store/v1.0.0/functionalities":{"id":"online-feature-store/v1.0.0/functionalities","title":"Key Functionalities","description":"Overview","sidebar":"tutorialSidebar"},"online-feature-store/v1.0.0/index":{"id":"online-feature-store/v1.0.0/index","title":"v1.0.0","description":"Online Feature Store v1.0.0","sidebar":"tutorialSidebar"},"online-feature-store/v1.0.0/release-notes":{"id":"online-feature-store/v1.0.0/release-notes","title":"Release Notes","description":"Version 1.0.0 \ud83d\ude80","sidebar":"tutorialSidebar"},"predator/v1.0.0/architecture":{"id":"predator/v1.0.0/architecture","title":"Architecture","description":"Predator is a scalable, high-performance model inference service built as a wrapper around the NVIDIA Triton Inference Server. It is designed to serve a variety of machine learning models (Deep Learning, Tree-based, etc.) with low latency in a Kubernetes (K8s) environment.","sidebar":"tutorialSidebar"},"predator/v1.0.0/functionalities":{"id":"predator/v1.0.0/functionalities","title":"Key Functionalities","description":"Overview","sidebar":"tutorialSidebar"},"predator/v1.0.0/index":{"id":"predator/v1.0.0/index","title":"v1.0.0","description":"Predator v1.0.0","sidebar":"tutorialSidebar"},"predator/v1.0.0/release-notes":{"id":"predator/v1.0.0/release-notes","title":"Release Notes","description":"Version 1.0.0","sidebar":"tutorialSidebar"},"quick-start/v1.0.0/index":{"id":"quick-start/v1.0.0/index","title":"v1.0.0","description":"Quick Start v1.0.0","sidebar":"tutorialSidebar"},"quick-start/v1.0.0/quick-start":{"id":"quick-start/v1.0.0/quick-start","title":"Quick Start","description":"Discord","sidebar":"tutorialSidebar"},"sdks/go/v1.0.0/feature_client":{"id":"sdks/go/v1.0.0/feature_client","title":"GRPC Feature client","description":"Build Status","sidebar":"tutorialSidebar"},"sdks/go/v1.0.0/index":{"id":"sdks/go/v1.0.0/index","title":"v1.0.0","description":"Go SDK v1.0.0","sidebar":"tutorialSidebar"},"sdks/python/v1.0.0/grpc_feature_client":{"id":"sdks/python/v1.0.0/grpc_feature_client","title":"GRPC Feature client","description":"PyPI version","sidebar":"tutorialSidebar"},"sdks/python/v1.0.0/index":{"id":"sdks/python/v1.0.0/index","title":"v1.0.0","description":"Python SDK v1.0.0","sidebar":"tutorialSidebar"},"sdks/python/v1.0.0/spark_feature_push_client":{"id":"sdks/python/v1.0.0/spark_feature_push_client","title":"Spark client","description":"PyPI version","sidebar":"tutorialSidebar"},"skye/v1.0.0/architecture":{"id":"skye/v1.0.0/architecture","title":"Architecture","description":"Skye is BharatMLStack\'s vector similarity search platform that enables fast semantic retrieval by representing data as vectors and querying nearest matches in high-dimensional space. It is composed of three runnable components: skye-admin, skye-consumers, and skye-serving.","sidebar":"tutorialSidebar"},"skye/v1.0.0/functionalities":{"id":"skye/v1.0.0/functionalities","title":"Functionalities","description":"Core Capabilities","sidebar":"tutorialSidebar"},"skye/v1.0.0/index":{"id":"skye/v1.0.0/index","title":"v1.0.0","description":"Skye v1.0.0","sidebar":"tutorialSidebar"},"skye/v1.0.0/release-notes":{"id":"skye/v1.0.0/release-notes","title":"Release Notes","description":"v1.0.0","sidebar":"tutorialSidebar"},"trufflebox-ui/v1.0.0/index":{"id":"trufflebox-ui/v1.0.0/index","title":"v1.0.0","description":"Trufflebox UI v1.0.0","sidebar":"tutorialSidebar"},"trufflebox-ui/v1.0.0/userguide":{"id":"trufflebox-ui/v1.0.0/userguide","title":"User Manual","description":"This guide covers the complete setup and usage of the Online Feature Store system, including the core services (Online Feature Store and Horizon) and the TruffleBox UI for feature management.","sidebar":"tutorialSidebar"}}}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/44d1c015.4db6c425.js b/docs/assets/js/44d1c015.4db6c425.js deleted file mode 100644 index 00df5e29..00000000 --- a/docs/assets/js/44d1c015.4db6c425.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[1065],{6725:t=>{t.exports=JSON.parse('{"categoryGeneratedIndex":{"title":"Python SDK","description":"Python SDK for BharatML Stack. Provides Python client libraries and utilities for interacting with the online feature store, including gRPC clients, Spark integration, and common utilities.","slug":"/category/python-sdk","permalink":"/BharatMLStack/category/python-sdk","sidebar":"tutorialSidebar","navigation":{"previous":{"title":"GRPC Feature client","permalink":"/BharatMLStack/sdks/go/v1.0.0/feature_client"},"next":{"title":"v1.0.0","permalink":"/BharatMLStack/category/v100"}}}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/44d1c015.880095c2.js b/docs/assets/js/44d1c015.880095c2.js new file mode 100644 index 00000000..8d256db2 --- /dev/null +++ b/docs/assets/js/44d1c015.880095c2.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[1065],{6725:t=>{t.exports=JSON.parse('{"categoryGeneratedIndex":{"title":"Python SDK","description":"Python SDK for BharatML Stack. Provides Python client libraries and utilities for interacting with the online feature store, including gRPC clients, Spark integration, and common utilities.","slug":"/category/python-sdk","permalink":"/BharatMLStack/category/python-sdk","sidebar":"tutorialSidebar","navigation":{"previous":{"title":"GRPC Feature client","permalink":"/BharatMLStack/sdks/go/v1.0.0/feature_client"},"next":{"title":"v1.0.0","permalink":"/BharatMLStack/sdks/python/v1.0.0"}}}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/479eb034.0c88dc68.js b/docs/assets/js/479eb034.0c88dc68.js deleted file mode 100644 index 560af3f1..00000000 --- a/docs/assets/js/479eb034.0c88dc68.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[5425],{9341:a=>{a.exports=JSON.parse('{"tag":{"label":"mlplatform","permalink":"/BharatMLStack/blog/tags/mlplatform","allTagsPath":"/BharatMLStack/blog/tags","count":1,"unlisted":false},"listMetadata":{"permalink":"/BharatMLStack/blog/tags/mlplatform","page":1,"postsPerPage":10,"totalPages":1,"totalCount":1,"blogDescription":"Blog","blogTitle":"Blog"}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/479eb034.fc01692f.js b/docs/assets/js/479eb034.fc01692f.js new file mode 100644 index 00000000..7a5850f1 --- /dev/null +++ b/docs/assets/js/479eb034.fc01692f.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[5425],{9341:a=>{a.exports=JSON.parse('{"tag":{"label":"mlplatform","permalink":"/BharatMLStack/blog/tags/mlplatform","allTagsPath":"/BharatMLStack/blog/tags","count":5,"unlisted":false},"listMetadata":{"permalink":"/BharatMLStack/blog/tags/mlplatform","page":1,"postsPerPage":10,"totalPages":1,"totalCount":5,"blogDescription":"Blog","blogTitle":"Blog"}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/4af50aac.59b38fde.js b/docs/assets/js/4af50aac.59b38fde.js new file mode 100644 index 00000000..f26cb200 --- /dev/null +++ b/docs/assets/js/4af50aac.59b38fde.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[1964],{6220:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>a,contentTitle:()=>o,default:()=>h,frontMatter:()=>l,metadata:()=>s,toc:()=>c});const s=JSON.parse('{"id":"sdks/go/v1.0.0/feature_client","title":"GRPC Feature client","description":"Build Status","source":"@site/docs/sdks/go/v1.0.0/feature_client.md","sourceDirName":"sdks/go/v1.0.0","slug":"/sdks/go/v1.0.0/feature_client","permalink":"/BharatMLStack/sdks/go/v1.0.0/feature_client","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/sdks/go/v1.0.0/feature_client.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"GRPC Feature client","sidebar_position":1},"sidebar":"tutorialSidebar","previous":{"title":"v1.0.0","permalink":"/BharatMLStack/sdks/go/v1.0.0"},"next":{"title":"Python SDK","permalink":"/BharatMLStack/category/python-sdk"}}');var i=t(4848),r=t(8453);const l={title:"GRPC Feature client",sidebar_position:1},o="BharatMLStack Go SDK",a={},c=[{value:"Features",id:"features",level:2},{value:"Installation",id:"installation",level:2},{value:"Configuration",id:"configuration",level:2},{value:"Usage",id:"usage",level:2},{value:"Basic Usage",id:"basic-usage",level:3},{value:"Complete Example",id:"complete-example",level:3},{value:"Development",id:"development",level:2},{value:"Prerequisites",id:"prerequisites",level:3},{value:"Building",id:"building",level:3},{value:"Testing",id:"testing",level:3},{value:"Contributing",id:"contributing",level:2},{value:"Community & Support",id:"community--support",level:2},{value:"License",id:"license",level:2}];function d(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",img:"img",li:"li",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,r.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.img,{src:"https://github.com/Meesho/BharatMLStack/actions/workflows/go-sdk.yml/badge.svg",alt:"Build Status"}),"\n",(0,i.jsx)(n.img,{src:"https://img.shields.io/badge/release-v1.0.0-blue?style=flat",alt:"Static Badge"}),"\n",(0,i.jsx)(n.a,{href:"https://discord.gg/XkT7XsV2AU",children:(0,i.jsx)(n.img,{src:"https://img.shields.io/badge/Discord-Join%20Chat-7289da?style=flat&logo=discord&logoColor=white",alt:"Discord"})})]}),"\n",(0,i.jsx)(n.header,{children:(0,i.jsx)(n.h1,{id:"bharatmlstack-go-sdk",children:"BharatMLStack Go SDK"})}),"\n",(0,i.jsx)(n.p,{children:"A Go SDK for interacting with BharatMLStack components, providing easy-to-use client libraries for the Online Feature Store and other services."}),"\n",(0,i.jsx)(n.h2,{id:"features",children:"Features"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Online Feature Store Client"}),": Complete gRPC client for feature retrieval and persistence"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Multiple API Methods"}),": Support for ",(0,i.jsx)(n.code,{children:"RetrieveFeatures"}),", ",(0,i.jsx)(n.code,{children:"RetrieveDecodedFeatures"}),", and ",(0,i.jsx)(n.code,{children:"PersistFeatures"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Protocol Buffer Support"}),": Generated clients from proto definitions with full type safety"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Batch Processing"}),": Configurable batch sizes for efficient bulk operations"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Authentication"}),": Built-in support for caller ID and token-based authentication"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Connection Management"}),": Configurable timeouts, TLS, and connection pooling"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Metrics Integration"}),": Built-in timing and count metrics for monitoring"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Type-Safe API"}),": Strongly typed Go interfaces and data structures"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Test Coverage"}),": Comprehensive test suite with mocking support"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"installation",children:"Installation"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"go get github.com/Meesho/BharatMLStack/go-sdk\n"})}),"\n",(0,i.jsx)(n.h2,{id:"configuration",children:"Configuration"}),"\n",(0,i.jsx)(n.p,{children:"The SDK requires a configuration object with the following fields:"}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Field"}),(0,i.jsx)(n.th,{children:"Type"}),(0,i.jsx)(n.th,{children:"Required"}),(0,i.jsx)(n.th,{children:"Description"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"Host"})}),(0,i.jsx)(n.td,{children:"string"}),(0,i.jsx)(n.td,{children:"Yes"}),(0,i.jsx)(n.td,{children:'Server hostname (e.g., "localhost", "feature-store.example.com")'})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"Port"})}),(0,i.jsx)(n.td,{children:"string"}),(0,i.jsx)(n.td,{children:"Yes"}),(0,i.jsx)(n.td,{children:'Server port (e.g., "8080", "443")'})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"CallerId"})}),(0,i.jsx)(n.td,{children:"string"}),(0,i.jsx)(n.td,{children:"Yes"}),(0,i.jsx)(n.td,{children:"Unique identifier for your service/application"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"CallerToken"})}),(0,i.jsx)(n.td,{children:"string"}),(0,i.jsx)(n.td,{children:"Yes"}),(0,i.jsx)(n.td,{children:"Authentication token for API access"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"DeadLine"})}),(0,i.jsx)(n.td,{children:"int"}),(0,i.jsx)(n.td,{children:"No"}),(0,i.jsx)(n.td,{children:"Request timeout in milliseconds (default: 5000)"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"PlainText"})}),(0,i.jsx)(n.td,{children:"bool"}),(0,i.jsx)(n.td,{children:"No"}),(0,i.jsx)(n.td,{children:"Use plaintext connection instead of TLS (default: false)"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"BatchSize"})}),(0,i.jsx)(n.td,{children:"int"}),(0,i.jsx)(n.td,{children:"No"}),(0,i.jsx)(n.td,{children:"Maximum batch size for bulk operations (default: 50)"})]})]})]}),"\n",(0,i.jsx)(n.h2,{id:"usage",children:"Usage"}),"\n",(0,i.jsx)(n.h3,{id:"basic-usage",children:"Basic Usage"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-go",children:'package main\n\nimport (\n "context"\n "log"\n \n "github.com/Meesho/BharatMLStack/go-sdk/pkg/onfs"\n)\n\nfunc main() {\n config := &onfs.Config{\n Host: "localhost",\n Port: "8080",\n PlainText: true, // For local development\n CallerId: "my-service",\n CallerToken: "my-token",\n }\n\n // Initialize client (timing and count can be nil)\n client := onfs.NewClientV1(config, nil, nil)\n \n // Your feature operations here...\n}\n'})}),"\n",(0,i.jsx)(n.h3,{id:"complete-example",children:"Complete Example"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-go",children:'package main\n\nimport (\n "context"\n "log"\n "time"\n \n "github.com/Meesho/BharatMLStack/go-sdk/pkg/onfs"\n)\n\nfunc main() {\n // Create configuration\n config := &onfs.Config{\n Host: "localhost",\n Port: "8080",\n DeadLine: 5000, // 5 seconds timeout in milliseconds\n PlainText: true, // Use plaintext connection for local development\n BatchSize: 50, // Optional: batch size for requests\n CallerId: "your-service-id",\n CallerToken: "your-auth-token",\n }\n\n // Timing and count functions (can be nil for basic usage)\n timing := func(name string, value time.Duration, tags []string) {\n log.Printf("Timing: %s took %v with tags %v", name, value, tags)\n }\n count := func(name string, value int64, tags []string) {\n log.Printf("Count: %s = %d with tags %v", name, value, tags)\n }\n\n // Initialize the client\n client := onfs.InitClient(onfs.Version1, config, timing, count)\n // Or alternatively use: client := onfs.NewClientV1(config, timing, count)\n\n ctx := context.Background()\n\n // Example: Retrieve features\n query := &onfs.Query{\n EntityLabel: "user",\n FeatureGroups: []onfs.FeatureGroup{\n {\n Label: "user_features",\n FeatureLabels: []string{"age", "location", "preferences"},\n },\n },\n KeysSchema: []string{"user_id"},\n Keys: []onfs.Keys{\n {Cols: []string{"12345"}},\n {Cols: []string{"67890"}},\n },\n }\n\n result, err := client.RetrieveFeatures(ctx, query)\n if err != nil {\n log.Fatalf("Failed to retrieve features: %v", err)\n }\n\n log.Printf("Retrieved %d rows for entity %s", len(result.Rows), result.EntityLabel)\n\n // Example: Retrieve decoded features (string values)\n decodedResult, err := client.RetrieveDecodedFeatures(ctx, query)\n if err != nil {\n log.Fatalf("Failed to retrieve decoded features: %v", err)\n }\n\n log.Printf("Retrieved %d decoded rows", len(decodedResult.Rows))\n\n // Example: Persist features\n persistRequest := &onfs.PersistFeaturesRequest{\n EntityLabel: "user",\n KeysSchema: []string{"user_id"},\n FeatureGroups: []onfs.FeatureGroupSchema{\n {\n Label: "user_features",\n FeatureLabels: []string{"age", "location"},\n },\n },\n Data: []onfs.Data{\n {\n KeyValues: []string{"12345"},\n FeatureValues: []onfs.FeatureValues{\n {\n Values: onfs.Values{\n Int32Values: []int32{25},\n StringValues: []string{"New York"},\n },\n },\n },\n },\n },\n }\n\n persistResponse, err := client.PersistFeatures(ctx, persistRequest)\n if err != nil {\n log.Fatalf("Failed to persist features: %v", err)\n }\n\n log.Printf("Persist result: %s", persistResponse.Message)\n}\n'})}),"\n",(0,i.jsx)(n.h2,{id:"development",children:"Development"}),"\n",(0,i.jsx)(n.h3,{id:"prerequisites",children:"Prerequisites"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Go 1.22 or later (as specified in go.mod)"}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"building",children:"Building"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"# Build all packages\ngo build ./...\n\n# Run tests\ngo test ./...\n\n# Run tests with coverage\ngo test -v -coverprofile=coverage.out ./...\ngo tool cover -html=coverage.out\n"})}),"\n",(0,i.jsx)(n.h3,{id:"testing",children:"Testing"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"# Run all tests\ngo test -v ./...\n\n# Run specific package tests\ngo test -v ./pkg/onfs\n\n# Run with race detection\ngo test -race ./...\n"})}),"\n",(0,i.jsx)(n.h2,{id:"contributing",children:"Contributing"}),"\n",(0,i.jsxs)(n.p,{children:["We welcome contributions from the community! Please see our ",(0,i.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTING.md",children:"Contributing Guide"})," for details on how to get started."]}),"\n",(0,i.jsx)(n.h2,{id:"community--support",children:"Community & Support"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["\ud83d\udcac ",(0,i.jsx)(n.strong,{children:"Discord"}),": Join our ",(0,i.jsx)(n.a,{href:"https://discord.gg/XkT7XsV2AU",children:"community chat"})]}),"\n",(0,i.jsxs)(n.li,{children:["\ud83d\udc1b ",(0,i.jsx)(n.strong,{children:"Issues"}),": Report bugs and request features on ",(0,i.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/issues",children:"GitHub Issues"})]}),"\n",(0,i.jsxs)(n.li,{children:["\ud83d\udce7 ",(0,i.jsx)(n.strong,{children:"Email"}),": Contact us at ",(0,i.jsx)(n.a,{href:"mailto:ml-oss@meesho.com",children:"ml-oss@meesho.com"})]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"license",children:"License"}),"\n",(0,i.jsxs)(n.p,{children:["BharatMLStack is open-source software licensed under the ",(0,i.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md",children:"BharatMLStack Business Source License 1.1"}),"."]}),"\n",(0,i.jsx)(n.hr,{}),"\n",(0,i.jsx)("div",{align:"center",children:(0,i.jsx)("strong",{children:"Built with \u2764\ufe0f for the ML community from Meesho"})}),"\n",(0,i.jsx)("div",{align:"center",children:(0,i.jsx)("strong",{children:"If you find this useful, \u2b50\ufe0f the repo \u2014 your support means the world to us!"})})]})}function h(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(d,{...e})}):d(e)}},8453:(e,n,t)=>{t.d(n,{R:()=>l,x:()=>o});var s=t(6540);const i={},r=s.createContext(i);function l(e){const n=s.useContext(r);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:l(e.components),s.createElement(r.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/4af50aac.a113ce2d.js b/docs/assets/js/4af50aac.a113ce2d.js deleted file mode 100644 index a6058d52..00000000 --- a/docs/assets/js/4af50aac.a113ce2d.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[1964],{6220:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>a,contentTitle:()=>o,default:()=>h,frontMatter:()=>l,metadata:()=>s,toc:()=>c});const s=JSON.parse('{"id":"sdks/go/v1.0.0/feature_client","title":"GRPC Feature client","description":"Build Status","source":"@site/docs/sdks/go/v1.0.0/feature_client.md","sourceDirName":"sdks/go/v1.0.0","slug":"/sdks/go/v1.0.0/feature_client","permalink":"/BharatMLStack/sdks/go/v1.0.0/feature_client","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/sdks/go/v1.0.0/feature_client.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"GRPC Feature client","sidebar_position":1},"sidebar":"tutorialSidebar","previous":{"title":"Go SDK","permalink":"/BharatMLStack/category/go-sdk"},"next":{"title":"Python SDK","permalink":"/BharatMLStack/category/python-sdk"}}');var i=t(4848),r=t(8453);const l={title:"GRPC Feature client",sidebar_position:1},o="BharatMLStack Go SDK",a={},c=[{value:"Features",id:"features",level:2},{value:"Installation",id:"installation",level:2},{value:"Configuration",id:"configuration",level:2},{value:"Usage",id:"usage",level:2},{value:"Basic Usage",id:"basic-usage",level:3},{value:"Complete Example",id:"complete-example",level:3},{value:"Development",id:"development",level:2},{value:"Prerequisites",id:"prerequisites",level:3},{value:"Building",id:"building",level:3},{value:"Testing",id:"testing",level:3},{value:"Contributing",id:"contributing",level:2},{value:"Community & Support",id:"community--support",level:2},{value:"License",id:"license",level:2}];function d(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",img:"img",li:"li",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,r.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.img,{src:"https://github.com/Meesho/BharatMLStack/actions/workflows/go-sdk.yml/badge.svg",alt:"Build Status"}),"\n",(0,i.jsx)(n.img,{src:"https://img.shields.io/badge/release-v1.0.0-blue?style=flat",alt:"Static Badge"}),"\n",(0,i.jsx)(n.a,{href:"https://discord.gg/XkT7XsV2AU",children:(0,i.jsx)(n.img,{src:"https://img.shields.io/badge/Discord-Join%20Chat-7289da?style=flat&logo=discord&logoColor=white",alt:"Discord"})})]}),"\n",(0,i.jsx)(n.header,{children:(0,i.jsx)(n.h1,{id:"bharatmlstack-go-sdk",children:"BharatMLStack Go SDK"})}),"\n",(0,i.jsx)(n.p,{children:"A Go SDK for interacting with BharatMLStack components, providing easy-to-use client libraries for the Online Feature Store and other services."}),"\n",(0,i.jsx)(n.h2,{id:"features",children:"Features"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Online Feature Store Client"}),": Complete gRPC client for feature retrieval and persistence"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Multiple API Methods"}),": Support for ",(0,i.jsx)(n.code,{children:"RetrieveFeatures"}),", ",(0,i.jsx)(n.code,{children:"RetrieveDecodedFeatures"}),", and ",(0,i.jsx)(n.code,{children:"PersistFeatures"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Protocol Buffer Support"}),": Generated clients from proto definitions with full type safety"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Batch Processing"}),": Configurable batch sizes for efficient bulk operations"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Authentication"}),": Built-in support for caller ID and token-based authentication"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Connection Management"}),": Configurable timeouts, TLS, and connection pooling"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Metrics Integration"}),": Built-in timing and count metrics for monitoring"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Type-Safe API"}),": Strongly typed Go interfaces and data structures"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Test Coverage"}),": Comprehensive test suite with mocking support"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"installation",children:"Installation"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"go get github.com/Meesho/BharatMLStack/go-sdk\n"})}),"\n",(0,i.jsx)(n.h2,{id:"configuration",children:"Configuration"}),"\n",(0,i.jsx)(n.p,{children:"The SDK requires a configuration object with the following fields:"}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Field"}),(0,i.jsx)(n.th,{children:"Type"}),(0,i.jsx)(n.th,{children:"Required"}),(0,i.jsx)(n.th,{children:"Description"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"Host"})}),(0,i.jsx)(n.td,{children:"string"}),(0,i.jsx)(n.td,{children:"Yes"}),(0,i.jsx)(n.td,{children:'Server hostname (e.g., "localhost", "feature-store.example.com")'})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"Port"})}),(0,i.jsx)(n.td,{children:"string"}),(0,i.jsx)(n.td,{children:"Yes"}),(0,i.jsx)(n.td,{children:'Server port (e.g., "8080", "443")'})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"CallerId"})}),(0,i.jsx)(n.td,{children:"string"}),(0,i.jsx)(n.td,{children:"Yes"}),(0,i.jsx)(n.td,{children:"Unique identifier for your service/application"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"CallerToken"})}),(0,i.jsx)(n.td,{children:"string"}),(0,i.jsx)(n.td,{children:"Yes"}),(0,i.jsx)(n.td,{children:"Authentication token for API access"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"DeadLine"})}),(0,i.jsx)(n.td,{children:"int"}),(0,i.jsx)(n.td,{children:"No"}),(0,i.jsx)(n.td,{children:"Request timeout in milliseconds (default: 5000)"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"PlainText"})}),(0,i.jsx)(n.td,{children:"bool"}),(0,i.jsx)(n.td,{children:"No"}),(0,i.jsx)(n.td,{children:"Use plaintext connection instead of TLS (default: false)"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"BatchSize"})}),(0,i.jsx)(n.td,{children:"int"}),(0,i.jsx)(n.td,{children:"No"}),(0,i.jsx)(n.td,{children:"Maximum batch size for bulk operations (default: 50)"})]})]})]}),"\n",(0,i.jsx)(n.h2,{id:"usage",children:"Usage"}),"\n",(0,i.jsx)(n.h3,{id:"basic-usage",children:"Basic Usage"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-go",children:'package main\n\nimport (\n "context"\n "log"\n \n "github.com/Meesho/BharatMLStack/go-sdk/pkg/onfs"\n)\n\nfunc main() {\n config := &onfs.Config{\n Host: "localhost",\n Port: "8080",\n PlainText: true, // For local development\n CallerId: "my-service",\n CallerToken: "my-token",\n }\n\n // Initialize client (timing and count can be nil)\n client := onfs.NewClientV1(config, nil, nil)\n \n // Your feature operations here...\n}\n'})}),"\n",(0,i.jsx)(n.h3,{id:"complete-example",children:"Complete Example"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-go",children:'package main\n\nimport (\n "context"\n "log"\n "time"\n \n "github.com/Meesho/BharatMLStack/go-sdk/pkg/onfs"\n)\n\nfunc main() {\n // Create configuration\n config := &onfs.Config{\n Host: "localhost",\n Port: "8080",\n DeadLine: 5000, // 5 seconds timeout in milliseconds\n PlainText: true, // Use plaintext connection for local development\n BatchSize: 50, // Optional: batch size for requests\n CallerId: "your-service-id",\n CallerToken: "your-auth-token",\n }\n\n // Timing and count functions (can be nil for basic usage)\n timing := func(name string, value time.Duration, tags []string) {\n log.Printf("Timing: %s took %v with tags %v", name, value, tags)\n }\n count := func(name string, value int64, tags []string) {\n log.Printf("Count: %s = %d with tags %v", name, value, tags)\n }\n\n // Initialize the client\n client := onfs.InitClient(onfs.Version1, config, timing, count)\n // Or alternatively use: client := onfs.NewClientV1(config, timing, count)\n\n ctx := context.Background()\n\n // Example: Retrieve features\n query := &onfs.Query{\n EntityLabel: "user",\n FeatureGroups: []onfs.FeatureGroup{\n {\n Label: "user_features",\n FeatureLabels: []string{"age", "location", "preferences"},\n },\n },\n KeysSchema: []string{"user_id"},\n Keys: []onfs.Keys{\n {Cols: []string{"12345"}},\n {Cols: []string{"67890"}},\n },\n }\n\n result, err := client.RetrieveFeatures(ctx, query)\n if err != nil {\n log.Fatalf("Failed to retrieve features: %v", err)\n }\n\n log.Printf("Retrieved %d rows for entity %s", len(result.Rows), result.EntityLabel)\n\n // Example: Retrieve decoded features (string values)\n decodedResult, err := client.RetrieveDecodedFeatures(ctx, query)\n if err != nil {\n log.Fatalf("Failed to retrieve decoded features: %v", err)\n }\n\n log.Printf("Retrieved %d decoded rows", len(decodedResult.Rows))\n\n // Example: Persist features\n persistRequest := &onfs.PersistFeaturesRequest{\n EntityLabel: "user",\n KeysSchema: []string{"user_id"},\n FeatureGroups: []onfs.FeatureGroupSchema{\n {\n Label: "user_features",\n FeatureLabels: []string{"age", "location"},\n },\n },\n Data: []onfs.Data{\n {\n KeyValues: []string{"12345"},\n FeatureValues: []onfs.FeatureValues{\n {\n Values: onfs.Values{\n Int32Values: []int32{25},\n StringValues: []string{"New York"},\n },\n },\n },\n },\n },\n }\n\n persistResponse, err := client.PersistFeatures(ctx, persistRequest)\n if err != nil {\n log.Fatalf("Failed to persist features: %v", err)\n }\n\n log.Printf("Persist result: %s", persistResponse.Message)\n}\n'})}),"\n",(0,i.jsx)(n.h2,{id:"development",children:"Development"}),"\n",(0,i.jsx)(n.h3,{id:"prerequisites",children:"Prerequisites"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Go 1.22 or later (as specified in go.mod)"}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"building",children:"Building"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"# Build all packages\ngo build ./...\n\n# Run tests\ngo test ./...\n\n# Run tests with coverage\ngo test -v -coverprofile=coverage.out ./...\ngo tool cover -html=coverage.out\n"})}),"\n",(0,i.jsx)(n.h3,{id:"testing",children:"Testing"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"# Run all tests\ngo test -v ./...\n\n# Run specific package tests\ngo test -v ./pkg/onfs\n\n# Run with race detection\ngo test -race ./...\n"})}),"\n",(0,i.jsx)(n.h2,{id:"contributing",children:"Contributing"}),"\n",(0,i.jsxs)(n.p,{children:["We welcome contributions from the community! Please see our ",(0,i.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTING.md",children:"Contributing Guide"})," for details on how to get started."]}),"\n",(0,i.jsx)(n.h2,{id:"community--support",children:"Community & Support"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["\ud83d\udcac ",(0,i.jsx)(n.strong,{children:"Discord"}),": Join our ",(0,i.jsx)(n.a,{href:"https://discord.gg/XkT7XsV2AU",children:"community chat"})]}),"\n",(0,i.jsxs)(n.li,{children:["\ud83d\udc1b ",(0,i.jsx)(n.strong,{children:"Issues"}),": Report bugs and request features on ",(0,i.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/issues",children:"GitHub Issues"})]}),"\n",(0,i.jsxs)(n.li,{children:["\ud83d\udce7 ",(0,i.jsx)(n.strong,{children:"Email"}),": Contact us at ",(0,i.jsx)(n.a,{href:"mailto:ml-oss@meesho.com",children:"ml-oss@meesho.com"})]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"license",children:"License"}),"\n",(0,i.jsxs)(n.p,{children:["BharatMLStack is open-source software licensed under the ",(0,i.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md",children:"BharatMLStack Business Source License 1.1"}),"."]}),"\n",(0,i.jsx)(n.hr,{}),"\n",(0,i.jsx)("div",{align:"center",children:(0,i.jsx)("strong",{children:"Built with \u2764\ufe0f for the ML community from Meesho"})}),"\n",(0,i.jsx)("div",{align:"center",children:(0,i.jsx)("strong",{children:"If you find this useful, \u2b50\ufe0f the repo \u2014 your support means the world to us!"})})]})}function h(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(d,{...e})}):d(e)}},8453:(e,n,t)=>{t.d(n,{R:()=>l,x:()=>o});var s=t(6540);const i={},r=s.createContext(i);function l(e){const n=s.useContext(r);return s.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:l(e.components),s.createElement(r.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/4b01b88a.e818c95d.js b/docs/assets/js/4b01b88a.e818c95d.js new file mode 100644 index 00000000..10572260 --- /dev/null +++ b/docs/assets/js/4b01b88a.e818c95d.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[9095],{762:(e,t,n)=>{n.d(t,{A:()=>i});const i=n.p+"assets/images/bms-7399e8796d2cd24617c432518ce3f312.png"},1737:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>i,toc:()=>d});var i=n(3306),s=n(4848),r=n(8453);const a={title:"Beyond Vector RAG: Building Agent Memory That Learns From Experience.",description:"Current agent memory is just search. We built an episodic memory system that tracks outcomes, forms causal links, extracts reasoning heuristics, and actually learns from failure \u2014 without retraining the model.",slug:"episodic-memory-for-agents",authors:["adarsha"],date:new Date("2026-02-19T00:00:00.000Z"),tags:["ai-agents","memory","architecture","llm","episodic-memory"]},o=void 0,c={authorsImageUrls:[void 0]},d=[{value:"The Gap Nobody Talks About",id:"the-gap-nobody-talks-about",level:2},{value:"What's Wrong With Vector RAG as Memory",id:"whats-wrong-with-vector-rag-as-memory",level:2},{value:"The Architecture: Episodic Memory",id:"the-architecture-episodic-memory",level:2},{value:"Layer 1: Immutable Timeline",id:"layer-1-immutable-timeline",level:3},{value:"Layer 2: Episode Segmentation",id:"layer-2-episode-segmentation",level:3},{value:"Layer 3: Episodic Graph",id:"layer-3-episodic-graph",level:3},{value:"Layer 4: Generalized Facts",id:"layer-4-generalized-facts",level:3},{value:"The Reinforcement Loop",id:"the-reinforcement-loop",level:3},{value:"The Experiment",id:"the-experiment",level:2},{value:"Results",id:"results",level:2},{value:"Decision Accuracy",id:"decision-accuracy",level:3},{value:"Where the Gap Opened",id:"where-the-gap-opened",level:3},{value:"Retrieval Quality",id:"retrieval-quality",level:3},{value:"What Didn't Work",id:"what-didnt-work",level:2},{value:"What This Means",id:"what-this-means",level:2},{value:"How It Compares to Existing Solutions",id:"how-it-compares-to-existing-solutions",level:2},{value:"Try It Yourself",id:"try-it-yourself",level:2},{value:"Conclusion",id:"conclusion",level:2}];function l(e){const t={blockquote:"blockquote",code:"code",em:"em",h2:"h2",h3:"h3",hr:"hr",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,r.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.img,{alt:"BharatMLStack",src:n(762).A+"",width:"1396",height:"460"}),"\nAgent memory has come a long way. Persistent context, vector retrieval, knowledge graphs \u2014 the building blocks are real and getting better fast."]}),"\n",(0,s.jsx)(t.p,{children:'But most of what we call "memory" today is still closer to search: chunk text, embed it, retrieve whatever looks similar at query time. That works well for recalling facts and preferences. It starts to break down when you need an agent to recall what happened last time, learn from a mistake, or avoid repeating a failed approach.'}),"\n",(0,s.jsx)(t.p,{children:"We are trying to experiment something different. An episodic memory system where a frozen LLM \u2014 same weights, no retraining \u2014 produces increasingly better decisions over time because the memory feeding it context is continuously evolving.\nThen we tested it. The results were interesting."}),"\n",(0,s.jsx)(t.h2,{id:"the-gap-nobody-talks-about",children:"The Gap Nobody Talks About"}),"\n",(0,s.jsx)(t.p,{children:"Here's a scenario every engineering team has encountered: AI agent hits a Redis connection pool exhaustion issue. It misdiagnoses it as a database problem. You correct it. Next week, a different service has the exact same failure pattern. The agent makes the exact same mistake."}),"\n",(0,s.jsx)(t.p,{children:"Why? Because LLMs don't learn at inference time. Corrections adjust behavior within a conversation. Once the session ends, the lesson is gone. The model weights haven't changed. The next conversation starts from zero."}),"\n",(0,s.jsx)(t.p,{children:'Current "memory" systems don\'t fully address this. They store facts \u2014 user preferences, document chunks, conversation summaries. But facts aren\'t experience. Knowing that "Redis connection pools can exhaust under load" is different from remembering "last time I saw 500 errors under load, I assumed it was the database, I was wrong, it was actually the connection pool, and here\'s the correction I received."'}),"\n",(0,s.jsx)(t.p,{children:"The first is a fact. The second is an episode. The difference matters."}),"\n",(0,s.jsx)(t.h2,{id:"whats-wrong-with-vector-rag-as-memory",children:"What's Wrong With Vector RAG as Memory"}),"\n",(0,s.jsx)(t.p,{children:"We identified five structural gaps in how current agent frameworks handle memory:"}),"\n",(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.strong,{children:"No concept of time."})," Two events are either semantically similar or they're not. The system can't represent \"this happened after that\" without distorting similarity scores. An agent can't reason about sequence or causality."]}),"\n",(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.strong,{children:"No concept of situation."})," A production incident and a design review might use the same technical vocabulary. Flat vector search can't distinguish them. Your agent retrieves planning notes when it should be retrieving incident postmortems."]}),"\n",(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.strong,{children:"No outcome tracking."})," The system stores ",(0,s.jsx)(t.em,{children:"what happened"})," but not ",(0,s.jsx)(t.em,{children:"whether it worked"}),". A failed approach and a successful one are equally retrievable. The agent has no way to prefer strategies that worked over strategies that didn't."]}),"\n",(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.strong,{children:"Summaries destroy evidence."})," Summarization-based memory compresses experience but discards the reasoning chain. The agent loses the ability to explain ",(0,s.jsx)(t.em,{children:"how"})," it arrived at a conclusion. The audit trail is gone."]}),"\n",(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.strong,{children:"No causal links."})," Each memory chunk is independent. There's no way to express that incident A caused decision B, which led to outcome C, which was corrected by approach D. Without this structure, the agent can't traverse chains of reasoning."]}),"\n",(0,s.jsx)(t.p,{children:"These gaps compound. As an agent accumulates more experience, flat vector memory gets noisier, more contradictory, and less useful. The system degrades precisely when it should be improving."}),"\n",(0,s.jsx)(t.h2,{id:"the-architecture-episodic-memory",children:"The Architecture: Episodic Memory"}),"\n",(0,s.jsx)(t.p,{children:"We are building a memory system modeled on how human episodic memory works \u2014 not as a metaphor, but as an engineering specification."}),"\n",(0,s.jsx)(t.p,{children:"The system has four layers:"}),"\n",(0,s.jsx)(t.h3,{id:"layer-1-immutable-timeline",children:"Layer 1: Immutable Timeline"}),"\n",(0,s.jsx)(t.p,{children:"Every piece of agent experience is recorded as an append-only timeline entry. Each entry carries a semantic embedding (what it means), a timestamp (when it happened), and a state label (what situation the agent was in \u2014 debugging, planning, code review, incident response). Entries are never modified, never deleted, never summarized. This is the source of truth."}),"\n",(0,s.jsx)(t.h3,{id:"layer-2-episode-segmentation",children:"Layer 2: Episode Segmentation"}),"\n",(0,s.jsx)(t.p,{children:"The system watches the timeline and detects when one coherent unit of experience ends and another begins \u2014 via state transitions, semantic shifts, temporal gaps, or explicit signals. Each episode is a reference into the timeline (not a copy) with a generated summary, an outcome (SUCCESS, FAILURE, PARTIAL, UNKNOWN), decisions made, assumptions held, and corrections received."}),"\n",(0,s.jsx)(t.p,{children:"The outcome field is the most important thing that doesn't exist in any current memory system. Without it, you can't learn from mistakes."}),"\n",(0,s.jsx)(t.h3,{id:"layer-3-episodic-graph",children:"Layer 3: Episodic Graph"}),"\n",(0,s.jsx)(t.p,{children:'Episodes are connected through typed, weighted links: CAUSED_BY, LED_TO, RETRY_OF, LEARNED_FROM, CONTINUATION, CONTRADICTED. Over time, this forms a directed graph that enables traversal by meaning and causality. You can follow the chain: "this incident caused that investigation, which led to a failed fix, which was corrected by this approach."'}),"\n",(0,s.jsx)(t.h3,{id:"layer-4-generalized-facts",children:"Layer 4: Generalized Facts"}),"\n",(0,s.jsx)(t.p,{children:'When multiple episodes exhibit consistent patterns, the system extracts reasoning heuristics: "When services fail immediately after deployment with no traffic change, investigate configuration errors before connection pool problems." Facts are versioned, never overwritten, and maintain links back to supporting and contradicting episodes. When contradicting evidence accumulates, confidence decreases. When confidence drops below a threshold, the fact is revised \u2014 but the old version is preserved.'}),"\n",(0,s.jsx)(t.p,{children:"The LLM sits above all four layers. At query time, the system assembles structured context \u2014 relevant episodes with outcomes, applicable facts with confidence scores, causal narratives \u2014 and passes it to the LLM for reasoning. The model reasons over structured memory. It doesn't store or manage memory."}),"\n",(0,s.jsx)(t.h3,{id:"the-reinforcement-loop",children:"The Reinforcement Loop"}),"\n",(0,s.jsx)(t.p,{children:"This is where it comes together:"}),"\n",(0,s.jsxs)(t.ol,{children:["\n",(0,s.jsx)(t.li,{children:"Agent reasons using retrieved episodes and facts"}),"\n",(0,s.jsx)(t.li,{children:"Outcome is detected (CI pass/fail, user correction, test result)"}),"\n",(0,s.jsx)(t.li,{children:"New episode is created with outcome tracking"}),"\n",(0,s.jsx)(t.li,{children:"Links are created between the retrieved episodes and the new episode"}),"\n",(0,s.jsx)(t.li,{children:"Facts are reinforced (if outcome aligned) or contradicted (if outcome conflicted)"}),"\n",(0,s.jsx)(t.li,{children:"If the decision was wrong and corrected, a LEARNED_FROM link is created"}),"\n"]}),"\n",(0,s.jsx)(t.p,{children:"The model weights never change. The memory structure evolves continuously. A frozen LLM produces better decisions over time because it receives better context from richer memory."}),"\n",(0,s.jsx)(t.h2,{id:"the-experiment",children:"The Experiment"}),"\n",(0,s.jsx)(t.p,{children:"We built the full system in Python (~1,000 lines) and tested it head-to-head against a baseline flat-vector RAG agent across a 9-round synthetic debugging scenario. Both agents used the identical LLM (Claude Sonnet 4) for reasoning. The only variable was the memory system."}),"\n",(0,s.jsx)(t.p,{children:"The scenario was designed to test five capabilities:"}),"\n",(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Round Type"}),(0,s.jsx)(t.th,{children:"What It Tests"}),(0,s.jsx)(t.th,{children:"Rounds"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"LEARN"}),(0,s.jsx)(t.td,{children:"Can the agent build experience from failures?"}),(0,s.jsx)(t.td,{children:"1, 2, 4"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"RED HERRING"}),(0,s.jsx)(t.td,{children:"Can the agent resist applying a pattern when it doesn't fit?"}),(0,s.jsx)(t.td,{children:"3"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TEST"}),(0,s.jsx)(t.td,{children:"Can the agent apply learned patterns to new services?"}),(0,s.jsx)(t.td,{children:"5, 6"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"SUBTLE"}),(0,s.jsx)(t.td,{children:"Can the agent generalize to different symptoms, same root cause?"}),(0,s.jsx)(t.td,{children:"7"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"CORRECTION"}),(0,s.jsx)(t.td,{children:"After being corrected, does the agent adapt?"}),(0,s.jsx)(t.td,{children:"8, 9"})]})]})]}),"\n",(0,s.jsxs)(t.p,{children:["Rounds 1-4 build experience: three connection pool failures across different services, plus one red herring (a deployment config error that ",(0,s.jsx)(t.em,{children:"looks"})," like a connection pool issue). Rounds 5-7 test whether the agent applies the learned pattern to unfamiliar services and subtle symptom variations. Rounds 8-9 are the critical test: the agent is corrected after misdiagnosing a deployment-correlated error, then tested on a near-identical scenario to see if it adapts."]}),"\n",(0,s.jsx)(t.h2,{id:"results",children:"Results"}),"\n",(0,s.jsx)(t.h3,{id:"decision-accuracy",children:"Decision Accuracy"}),"\n",(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Round"}),(0,s.jsx)(t.th,{children:"Type"}),(0,s.jsx)(t.th,{children:"Episodic Agent"}),(0,s.jsx)(t.th,{children:"Baseline Agent"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"1"}),(0,s.jsx)(t.td,{children:"LEARN"}),(0,s.jsx)(t.td,{children:"\u2717"}),(0,s.jsx)(t.td,{children:"\u2713"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"2"}),(0,s.jsx)(t.td,{children:"LEARN"}),(0,s.jsx)(t.td,{children:"\u2713"}),(0,s.jsx)(t.td,{children:"\u2713"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"3"}),(0,s.jsx)(t.td,{children:"RED HERRING"}),(0,s.jsx)(t.td,{children:"\u2717"}),(0,s.jsx)(t.td,{children:"\u2717"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"4"}),(0,s.jsx)(t.td,{children:"LEARN"}),(0,s.jsx)(t.td,{children:"\u2713"}),(0,s.jsx)(t.td,{children:"\u2713"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"5"}),(0,s.jsx)(t.td,{children:"TEST"}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.strong,{children:"\u2713"})}),(0,s.jsx)(t.td,{children:"\u2717"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"6"}),(0,s.jsx)(t.td,{children:"TEST"}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.strong,{children:"\u2713"})}),(0,s.jsx)(t.td,{children:"\u2717"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"7"}),(0,s.jsx)(t.td,{children:"SUBTLE"}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.strong,{children:"\u2713"})}),(0,s.jsx)(t.td,{children:"\u2717"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"8"}),(0,s.jsx)(t.td,{children:"CORRECTION"}),(0,s.jsx)(t.td,{children:"\u2713"}),(0,s.jsx)(t.td,{children:"\u2713"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"9"}),(0,s.jsx)(t.td,{children:"CORRECTION"}),(0,s.jsx)(t.td,{children:"\u2713"}),(0,s.jsx)(t.td,{children:"\u2713"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.strong,{children:"Total"})}),(0,s.jsx)(t.td,{}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.strong,{children:"7/9 (78%)"})}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.strong,{children:"5/9 (56%)"})})]})]})]}),"\n",(0,s.jsx)(t.p,{children:"The episodic agent won 7-5. A 40% relative improvement in decision accuracy using the exact same LLM."}),"\n",(0,s.jsx)(t.h3,{id:"where-the-gap-opened",children:"Where the Gap Opened"}),"\n",(0,s.jsx)(t.p,{children:"The episodic agent's advantage concentrated in exactly the rounds designed to test memory quality:"}),"\n",(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.strong,{children:"Rounds 5-6 (pattern application):"})," The episodic agent cited 4 past failure episodes with connection pool exhaustion as root cause, complete with correction annotations. It correctly identified pool exhaustion in new services. The baseline retrieved disconnected chunks and suggested checking timeout configurations \u2014 a pattern it picked up from the Round 3 red herring."]}),"\n",(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.strong,{children:"Round 7 (subtle symptoms \u2014 latency increase, no errors):"}),' Both agents had the same evidence available. The episodic agent\'s retrieval surfaced a diverse set of episodes (thanks to MMR diversity filtering) including the Redis pool exhaustion from Round 6, which primed it to recognize that latency without errors can still be pool contention. The baseline defaulted to "check recent config changes."']}),"\n",(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.strong,{children:"Round 9 (adaptation after correction):"})," This is the result we're most proud of. Look at the episodic agent's reasoning:"]}),"\n",(0,s.jsxs)(t.blockquote,{children:["\n",(0,s.jsx)(t.p,{children:(0,s.jsx)(t.em,{children:'"Episode 1 directly parallels this situation \u2014 errors spiking immediately after a deployment (v2.4.1 then, v3.1.0 now) with no traffic change. In that case, the root cause was a database migration that dropped an index. The generalized fact confirms that deployment-related issues with immediate onset after version changes are more likely caused by configuration errors or missing dependencies than by connection pool problems."'})}),"\n"]}),"\n",(0,s.jsxs)(t.p,{children:["It cited a specific past episode by analogy, quoted a generalized fact, and explained ",(0,s.jsx)(t.em,{children:"why"})," this situation matches the deployment pattern rather than the connection pool pattern. The baseline gave a vaguer assessment."]}),"\n",(0,s.jsx)(t.h3,{id:"retrieval-quality",children:"Retrieval Quality"}),"\n",(0,s.jsx)(t.p,{children:"This is where the structural difference is most visible:"}),"\n",(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Metric"}),(0,s.jsx)(t.th,{children:"Episodic Agent"}),(0,s.jsx)(t.th,{children:"Baseline Agent"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Retrieved items with explicit outcome labels"}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.strong,{children:"100%"})}),(0,s.jsx)(t.td,{children:"25%"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Correct pattern applications (Rounds 4-7)"}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.strong,{children:"4/4"})}),(0,s.jsx)(t.td,{children:"1/4"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"False positives (Rounds 8-9)"}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.strong,{children:"0"})}),(0,s.jsx)(t.td,{children:"0"})]})]})]}),"\n",(0,s.jsx)(t.p,{children:"Every item the episodic agent retrieved carried a structured outcome label (SUCCESS or FAILURE) with correction details. Only 25% of the baseline's chunks contained any outcome information \u2014 and those were incidental text mentions, not structured labels."}),"\n",(0,s.jsx)(t.p,{children:"The episodic agent correctly applied the connection pool pattern in all four rounds where it was the root cause, and correctly avoided it in both rounds where it wasn't. The baseline applied it correctly once."}),"\n",(0,s.jsx)(t.h2,{id:"what-didnt-work",children:"What Didn't Work"}),"\n",(0,s.jsx)(t.p,{children:"Two things didn't work as anticipated:"}),"\n",(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.strong,{children:"Round 3 (red herring):"})," Both agents failed. The symptoms looked like connection pool issues, but the root cause was a deployment config change. At this point, the episodic agent had only seen connection pool episodes \u2014 it had no counter-evidence for deployment-correlated errors. You can't distinguish patterns you've only seen one side of. After Round 8 introduced a correction, the agent successfully avoided this mistake in Round 9."]}),"\n",(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.strong,{children:"Fact quality variance."}),' Some extracted facts were specific and actionable ("Deployment-related issues with immediate onset are more likely configuration errors"). Others were vague ("Initial symptom-based diagnosis often leads to misidentifying the root cause"). A production system needs a usefulness filter, not just a confidence score.']}),"\n",(0,s.jsx)(t.h2,{id:"what-this-means",children:"What This Means"}),"\n",(0,s.jsx)(t.p,{children:"The most important finding isn't the accuracy improvement. It's that the reinforcement loop closes without retraining."}),"\n",(0,s.jsx)(t.p,{children:"In the POC, we observed:"}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsx)(t.li,{children:"Rounds 1-4: Agent encounters failures, episodes recorded with outcomes and corrections"}),"\n",(0,s.jsx)(t.li,{children:'After Round 4: Fact extracted \u2014 "Connection pool exhaustion is a common root cause under load"'}),"\n",(0,s.jsx)(t.li,{children:"Rounds 5-7: Agent applies the pattern with increasing confidence (fact support count grows)"}),"\n",(0,s.jsx)(t.li,{children:"Round 8: Agent encounters a deployment error, correctly identifies it as config, gets corrected"}),"\n",(0,s.jsx)(t.li,{children:'After Round 8: New fact \u2014 "Deployment-related issues with immediate onset are more likely configuration errors"'}),"\n",(0,s.jsx)(t.li,{children:"Round 9: Agent receives near-identical scenario, correctly avoids connection pool pattern, cites the Round 8 correction"}),"\n"]}),"\n",(0,s.jsx)(t.p,{children:"The model didn't change. The memory evolved. That's the whole point."}),"\n",(0,s.jsx)(t.h2,{id:"how-it-compares-to-existing-solutions",children:"How It Compares to Existing Solutions"}),"\n",(0,s.jsx)(t.p,{children:"Agent memory is a fast-moving space with several strong systems, each solving a different slice of the problem:"}),"\n",(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.strong,{children:"Mem0"})," excels at persistent personalization \u2014 extracting user preferences, managing session context, and reducing token costs through intelligent compression. It's the most production-ready memory layer available and integrates with nearly every agent framework. Its focus is on remembering about users and conversations rather than learning from task-level outcomes, which is a different problem than the one we're exploring here."]}),"\n",(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.strong,{children:"Zep/Graphiti"})," is doing some of the most interesting work in temporal knowledge graphs. Their bi-temporal model \u2014 tracking both when an event occurred and when it was ingested \u2014 addresses a real structural gap in how agent memory handles changing facts over time. Their episode and entity subgraphs share some philosophical DNA with our approach. Where our work diverges is in outcome tracking and reinforcement: we're specifically focused on whether a decision worked, and using that signal to update memory structure."]}),"\n",(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.strong,{children:"Letta (formerly MemGPT)"}),' pioneered self-editing memory \u2014 giving the LLM tools to manage its own memory blocks. This is a powerful paradigm, and their recent work on "Context Repositories" and sleep-time compute suggests they\'re actively pushing toward agents that learn over time. Their team has been transparent that experiential learning is an unsolved problem, which is part of what motivated our exploration.']}),"\n",(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.strong,{children:"MemRL (Jan 2026 paper)"})," is the closest to our work academically. It shares the core insight of decoupling stable LLM reasoning from plastic, evolving memory. Their approach uses reinforcement learning to assign utility Q-values to memories, which is elegant but requires training a value function. Our approach is purely structural \u2014 no training step, no Q-values, just graph evolution and LLM-based reasoning over outcomes."]}),"\n",(0,s.jsx)(t.p,{children:"The common thread: most existing systems focus on knowledge persistence \u2014 remembering facts, preferences, and conversation history across sessions. The problem we're exploring is experiential learning \u2014 tracking whether past decisions worked, forming causal chains between episodes, and extracting reasoning heuristics that improve over time. These are complementary capabilities that would be needed by an ideal production system."}),"\n",(0,s.jsx)(t.h2,{id:"try-it-yourself",children:"Try It Yourself"}),"\n",(0,s.jsx)(t.p,{children:"The prototype is available in our experiments directory:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{children:"experiments/episodic-memory-prototype/\n\u251c\u2500\u2500 memory/ # Timeline, encoder, episodes, graph, facts, retriever, reinforcer\n\u251c\u2500\u2500 agent/ # Episodic memory agent\n\u251c\u2500\u2500 baseline/ # Flat vector RAG agent (comparison)\n\u251c\u2500\u2500 simulator/ # 9-round debugging scenario\n\u251c\u2500\u2500 eval/ # Head-to-head comparison + scoring\n\u2514\u2500\u2500 tests/\n"})}),"\n",(0,s.jsx)(t.p,{children:"To run the comparison:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-bash",children:"cd experiments/episodic-memory-prototype\npython -m venv .venv && source .venv/bin/activate\npip install -r requirements.txt\nexport ANTHROPIC_API_KEY=sk-ant-...\npython -m eval.compare\n"})}),"\n",(0,s.jsx)(t.p,{children:"Without an API key, it runs in heuristic mode (keyword-based decisions). With a key, both agents use Claude Sonnet for reasoning \u2014 that's where the quality gap becomes visible."}),"\n",(0,s.jsx)(t.h2,{id:"conclusion",children:"Conclusion"}),"\n",(0,s.jsx)(t.p,{children:"This is a 9-round synthetic scenario we designed. It demonstrates the poc architecture works end-to-end and shows where episodic memory provides qualitatively different reasoning. It is not a peer-reviewed benchmark and should not be interpreted as a statistically rigorous claim. We're publishing the prototype so others can reproduce and extend the evaluation.\nIf this sparks interest do trigger github discussion."}),"\n",(0,s.jsx)(t.hr,{}),"\n",(0,s.jsx)(t.p,{children:(0,s.jsxs)(t.em,{children:["The episodic memory prototype is available in ",(0,s.jsx)(t.code,{children:"BharatMLStack"})," repo at ",(0,s.jsx)(t.code,{children:"/experiments/episodic-memory-prototype"})]})})]})}function h(e={}){const{wrapper:t}={...(0,r.R)(),...e.components};return t?(0,s.jsx)(t,{...e,children:(0,s.jsx)(l,{...e})}):l(e)}},3306:e=>{e.exports=JSON.parse('{"permalink":"/BharatMLStack/blog/episodic-memory-for-agents","editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/blog/bharatmlstack-history/episodic-memory-for-agents/index.md","source":"@site/blog/bharatmlstack-history/episodic-memory-for-agents/index.md","title":"Beyond Vector RAG: Building Agent Memory That Learns From Experience.","description":"Current agent memory is just search. We built an episodic memory system that tracks outcomes, forms causal links, extracts reasoning heuristics, and actually learns from failure \u2014 without retraining the model.","date":"2026-02-19T00:00:00.000Z","tags":[{"inline":true,"label":"ai-agents","permalink":"/BharatMLStack/blog/tags/ai-agents"},{"inline":true,"label":"memory","permalink":"/BharatMLStack/blog/tags/memory"},{"inline":true,"label":"architecture","permalink":"/BharatMLStack/blog/tags/architecture"},{"inline":true,"label":"llm","permalink":"/BharatMLStack/blog/tags/llm"},{"inline":true,"label":"episodic-memory","permalink":"/BharatMLStack/blog/tags/episodic-memory"}],"readingTime":11.67,"hasTruncateMarker":true,"authors":[{"name":"Adarsha Das","title":"Senior Architect @ Meesho","url":"https://github.com/a0d00kc","imageURL":"https://github.com/a0d00kc.png","key":"adarsha","page":null}],"frontMatter":{"title":"Beyond Vector RAG: Building Agent Memory That Learns From Experience.","description":"Current agent memory is just search. We built an episodic memory system that tracks outcomes, forms causal links, extracts reasoning heuristics, and actually learns from failure \u2014 without retraining the model.","slug":"episodic-memory-for-agents","authors":["adarsha"],"date":"2026-02-19T00:00:00.000Z","tags":["ai-agents","memory","architecture","llm","episodic-memory"]},"unlisted":false,"nextItem":{"title":"LLM Inference Optimization Techniques: Engineering Sub-Second Latency at Scale","permalink":"/BharatMLStack/blog/llm-inference-optimization-sub-sec-latency"}}')},8453:(e,t,n)=>{n.d(t,{R:()=>a,x:()=>o});var i=n(6540);const s={},r=i.createContext(s);function a(e){const t=i.useContext(r);return i.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function o(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:a(e.components),i.createElement(r.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/4caa95bf.2e5bda05.js b/docs/assets/js/4caa95bf.2e5bda05.js deleted file mode 100644 index 910749a2..00000000 --- a/docs/assets/js/4caa95bf.2e5bda05.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[2344],{551:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/v1.0.0-csdb-skip-read-e3926080f7341aa7d3c6ec6d8274ea14.png"},1807:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/v1.0.0-psdb-fixed-length-encodding-dd252110b084e01cf38f21de16b3a1a5.png"},2863:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/v1.0.0-psdb-string-encoding-b1d69e9452269124d1b545020fa27d63.png"},5019:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/v1.0.0-psdb-anatomy-c1735559f93dce6d0bb3894d16047059.png"},8453:(e,n,i)=>{i.d(n,{R:()=>d,x:()=>l});var t=i(6540);const s={},r=t.createContext(s);function d(e){const n=t.useContext(r);return t.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:d(e.components),t.createElement(r.Provider,{value:n},e.children)}},8726:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/v1.0.0-psdb-bool-encoding-4b154fdf5e6d79a67c91b6fb21c7209e.png"},9584:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>c,contentTitle:()=>l,default:()=>h,frontMatter:()=>d,metadata:()=>t,toc:()=>a});const t=JSON.parse('{"id":"online-feature-store/v1.0.0/data-formats","title":"Data Formats","description":"In this section we will go through the data-formats which is at the hear of online-feature-store, it\'s inspired form other storage efficient formats like parquet & arrow, but custom made to deliver in constraint environment. The two key data-formats are:","source":"@site/docs/online-feature-store/v1.0.0/data-formats.md","sourceDirName":"online-feature-store/v1.0.0","slug":"/online-feature-store/v1.0.0/data-formats","permalink":"/BharatMLStack/online-feature-store/v1.0.0/data-formats","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/online-feature-store/v1.0.0/data-formats.md","tags":[],"version":"current","sidebarPosition":2,"frontMatter":{"title":"Data Formats","sidebar_position":2},"sidebar":"tutorialSidebar","previous":{"title":"Architecture","permalink":"/BharatMLStack/online-feature-store/v1.0.0/architecture"},"next":{"title":"Benchmarks","permalink":"/BharatMLStack/online-feature-store/v1.0.0/benchmarks"}}');var s=i(4848),r=i(8453);const d={title:"Data Formats",sidebar_position:2},l="Data Format for Permanent & Cache Storage",c={},a=[{value:"PSDB (Permanent Storage Data Block) Format",id:"psdb-permanent-storage-data-block-format",level:2},{value:"\ud83e\uddf1 Structure Overview",id:"-structure-overview",level:3},{value:"Supported Data Types",id:"supported-data-types",level:3},{value:"Scalar Types",id:"scalar-types",level:4},{value:"Vector Types",id:"vector-types",level:4},{value:"\ud83d\udce6 Encoding for Scalar Feature Type",id:"-encoding-for-scalar-feature-type",level:3},{value:"1. \ud83d\udd21 String Feature Group (Variable Length Encoding using Pascal)",id:"1--string-feature-group-variable-length-encoding-using-pascal",level:4},{value:"2. \ud83d\udfe9 Boolean Feature Group (Bit-Packed)",id:"2--boolean-feature-group-bit-packed",level:4},{value:"3. \ud83d\udccf Fixed-Length Feature Group",id:"3--fixed-length-feature-group",level:4},{value:"4. Compression",id:"4-compression",level:4},{value:"\ud83e\uddec Encoding for Vector Types",id:"-encoding-for-vector-types",level:3},{value:"Conceptual Overview",id:"conceptual-overview",level:4},{value:"Vector Length Metadata",id:"vector-length-metadata",level:4},{value:"Encoding Process",id:"encoding-process",level:4},{value:"Input Structure",id:"input-structure",level:5},{value:"Length Validation",id:"length-validation",level:5},{value:"Flattening Strategy",id:"flattening-strategy",level:5},{value:"Contiguous Layout",id:"contiguous-layout",level:5},{value:"\ud83d\udd04 Deserialization/Decoding Flow",id:"-deserializationdecoding-flow",level:3},{value:"Memory Efficiency Benefits",id:"memory-efficiency-benefits",level:3},{value:"Cache Storage Data Block (CSDB) Design",id:"cache-storage-data-block-csdb-design",level:2},{value:"Overview",id:"overview",level:3},{value:"Structure and Purpose",id:"structure-and-purpose",level:3},{value:"Core Fields and Memory Layout",id:"core-fields-and-memory-layout",level:4},{value:"Cache Types",id:"cache-types",level:4},{value:"Format & Encoding",id:"format--encoding",level:3},{value:"Differences Between In-Memory and Distributed Caching",id:"differences-between-in-memory-and-distributed-caching",level:3},{value:"Optimizations & Features",id:"optimizations--features",level:3}];function o(e){const n={code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",h5:"h5",header:"header",hr:"hr",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,r.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.header,{children:(0,s.jsx)(n.h1,{id:"data-format-for-permanent--cache-storage",children:"Data Format for Permanent & Cache Storage"})}),"\n",(0,s.jsx)(n.p,{children:"In this section we will go through the data-formats which is at the hear of online-feature-store, it's inspired form other storage efficient formats like parquet & arrow, but custom made to deliver in constraint environment. The two key data-formats are:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"PSDB"})," - Permanent Storage Data Block used wile storing data in ScyllaDB"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"CSDB"})," - Cache Storage Data Block used while storing data in DragonflyDB or Redis, optimal for KV"]}),"\n",(0,s.jsx)(n.li,{}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"psdb-permanent-storage-data-block-format",children:"PSDB (Permanent Storage Data Block) Format"}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.strong,{children:"PSDB"})," format is a compact, versioned, and schema-aware binary layout used to store feature groups efficiently for ML inference. It supports multiple datatypes (strings, booleans, fixed-size vectors), versioning, TTL, and metadata encoding in a compact header."]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h3,{id:"-structure-overview",children:"\ud83e\uddf1 Structure Overview"}),"\n",(0,s.jsx)(n.p,{children:"Each PSDB block is composed of multiple byte sections:"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Permanent Storage Data Block Anatomy",src:i(5019).A+"",width:"1854",height:"1102"})}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Byte"}),(0,s.jsx)(n.th,{children:"Bits"}),(0,s.jsx)(n.th,{children:"Field"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"0-1"}),(0,s.jsx)(n.td,{children:"0-15"}),(0,s.jsx)(n.td,{children:"Feature Schema Version"}),(0,s.jsx)(n.td,{children:"Version for tracking schema changes (additions/deletions) in feature group"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"2-6"}),(0,s.jsx)(n.td,{children:"16-55"}),(0,s.jsx)(n.td,{children:"Expiry Timestamp"}),(0,s.jsx)(n.td,{children:"Encoded as a compact representation, ~513 days max"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"7"}),(0,s.jsx)(n.td,{children:"56-59"}),(0,s.jsx)(n.td,{children:"Layout Version"}),(0,s.jsx)(n.td,{children:"Used to ensure backward compatibility with layout format changes"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"7"}),(0,s.jsx)(n.td,{children:"60-62"}),(0,s.jsx)(n.td,{children:"Compression Type"}),(0,s.jsx)(n.td,{children:"3-bit field specifying compression algorithm"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"7-8"}),(0,s.jsx)(n.td,{children:"63-67"}),(0,s.jsx)(n.td,{children:"Data Type"}),(0,s.jsx)(n.td,{children:"5-bit field split across bytes 7 and 8"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"8"}),(0,s.jsx)(n.td,{children:"68-71"}),(0,s.jsx)(n.td,{children:"Bool Last Valid Bit"}),(0,s.jsx)(n.td,{children:"4-bit field for last valid boolean bit"})]})]})]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h3,{id:"supported-data-types",children:"Supported Data Types"}),"\n",(0,s.jsx)(n.h4,{id:"scalar-types",children:"Scalar Types"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Container"}),(0,s.jsx)(n.th,{children:"Size"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsxs)(n.td,{children:[(0,s.jsx)(n.code,{children:"FP32"}),", ",(0,s.jsx)(n.code,{children:"FP16"}),", ",(0,s.jsx)(n.code,{children:"FP8E4M3"}),", ",(0,s.jsx)(n.code,{children:"FP8E5M2"})]}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"[]float32"})}),(0,s.jsx)(n.td,{children:"4/2/1/1 bytes"}),(0,s.jsx)(n.td,{children:"Floating point numbers"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsxs)(n.td,{children:[(0,s.jsx)(n.code,{children:"Int32"}),", ",(0,s.jsx)(n.code,{children:"Int16"}),", ",(0,s.jsx)(n.code,{children:"Int8"})]}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"[]int32"})}),(0,s.jsx)(n.td,{children:"4/2/1 bytes"}),(0,s.jsx)(n.td,{children:"Signed integers"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsxs)(n.td,{children:[(0,s.jsx)(n.code,{children:"Uint32"}),", ",(0,s.jsx)(n.code,{children:"Uint16"}),", ",(0,s.jsx)(n.code,{children:"Uint8"})]}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"[]uint32"})}),(0,s.jsx)(n.td,{children:"4/2/1 bytes"}),(0,s.jsx)(n.td,{children:"Unsigned integers"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"FP64"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"[]float64"})}),(0,s.jsx)(n.td,{children:"8 bytes"}),(0,s.jsx)(n.td,{children:"Double precision float"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"Int64"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"[]int64"})}),(0,s.jsx)(n.td,{children:"8 bytes"}),(0,s.jsx)(n.td,{children:"64-bit signed integer"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"Uint64"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"[]uint64"})}),(0,s.jsx)(n.td,{children:"8 bytes"}),(0,s.jsx)(n.td,{children:"64-bit unsigned integer"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"String"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"[]string"})}),(0,s.jsx)(n.td,{children:"Variable"}),(0,s.jsx)(n.td,{children:"Pascal-style strings"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"Bool"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"[]uint8"})}),(0,s.jsx)(n.td,{children:"Bit-packed"}),(0,s.jsx)(n.td,{children:"Boolean values"})]})]})]}),"\n",(0,s.jsx)(n.h4,{id:"vector-types",children:"Vector Types"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Container"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsxs)(n.td,{children:[(0,s.jsx)(n.code,{children:"FP32Vector"}),", ",(0,s.jsx)(n.code,{children:"FP16Vector"}),", etc."]}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"[][]float32"})}),(0,s.jsx)(n.td,{children:"2D slices of floating point"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsxs)(n.td,{children:[(0,s.jsx)(n.code,{children:"Int32Vector"}),", ",(0,s.jsx)(n.code,{children:"Int16Vector"}),", etc."]}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"[][]int32"})}),(0,s.jsx)(n.td,{children:"2D slices of signed integers"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsxs)(n.td,{children:[(0,s.jsx)(n.code,{children:"Uint32Vector"}),", ",(0,s.jsx)(n.code,{children:"Uint16Vector"}),", etc."]}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"[][]uint32"})}),(0,s.jsx)(n.td,{children:"2D slices of unsigned integers"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"FP64Vector"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"[][]float64"})}),(0,s.jsx)(n.td,{children:"2D slices of doubles"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"Int64Vector"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"[][]int64"})}),(0,s.jsx)(n.td,{children:"2D slices of 64-bit signed"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"Uint64Vector"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"[][]uint64"})}),(0,s.jsx)(n.td,{children:"2D slices of 64-bit unsigned"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"StringVector"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"[][]string"})}),(0,s.jsx)(n.td,{children:"2D slices of strings"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"BoolVector"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"[][]uint8"})}),(0,s.jsx)(n.td,{children:"2D slices of bit-packed bools"})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"-encoding-for-scalar-feature-type",children:"\ud83d\udce6 Encoding for Scalar Feature Type"}),"\n",(0,s.jsx)(n.h4,{id:"1--string-feature-group-variable-length-encoding-using-pascal",children:"1. \ud83d\udd21 String Feature Group (Variable Length Encoding using Pascal)"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Max string length: ",(0,s.jsx)(n.strong,{children:"65536"})]}),"\n",(0,s.jsxs)(n.li,{children:["Format:\n",(0,s.jsx)(n.img,{alt:"PSDB String encoding",src:i(2863).A+"",width:"1488",height:"204"})]}),"\n",(0,s.jsxs)(n.li,{children:["Deserialization:","\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Read length prefixes"}),"\n",(0,s.jsxs)(n.li,{children:["Extract string bytes using ",(0,s.jsx)(n.code,{children:"StrLenX"})]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"2--boolean-feature-group-bit-packed",children:"2. \ud83d\udfe9 Boolean Feature Group (Bit-Packed)"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Saves space using bit-level packing."}),"\n",(0,s.jsxs)(n.li,{children:["Encoding:","\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Raw: 1 byte per feature"}),"\n",(0,s.jsx)(n.li,{children:"Bit-packed: 1 bit per boolean"}),"\n",(0,s.jsxs)(n.li,{children:["Additional index (",(0,s.jsx)(n.code,{children:"bool last idx"}),") stores where the last bit resides"]}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["Format:\n",(0,s.jsx)(n.img,{alt:"PSDB Bool encoding",src:i(8726).A+"",width:"1120",height:"712"})]}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"3--fixed-length-feature-group",children:"3. \ud83d\udccf Fixed-Length Feature Group"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["For fixed-size vectors (",(0,s.jsx)(n.code,{children:"n"})," bytes each)"]}),"\n",(0,s.jsxs)(n.li,{children:["Format:\n",(0,s.jsx)(n.img,{alt:"PSDB Fixed Length Datatype encoding",src:i(1807).A+"",width:"1122",height:"202"})]}),"\n",(0,s.jsx)(n.li,{children:"Efficient for dense numeric features like float32, int64, etc."}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"4-compression",children:"4. Compression"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"TypeNone (0)"}),": Raw storage"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"TypeZSTD (1)"}),": Compressed using Zstandard"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Compression is opportunistic. During serialization, if compressed size is not smaller, PSDB falls back to uncompressed format. It keeps the read/high througput path use less CPU cycles. Also only data part of PSDB is compressed allowing decompression only if block has a valid TTL"}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h3,{id:"-encoding-for-vector-types",children:"\ud83e\uddec Encoding for Vector Types"}),"\n",(0,s.jsx)(n.h4,{id:"conceptual-overview",children:"Conceptual Overview"}),"\n",(0,s.jsx)(n.p,{children:"PSDB encodes vector data by flattening multi-dimensional arrays into a single contiguous byte buffer while preserving the ability to reconstruct the original vector boundaries."}),"\n",(0,s.jsx)(n.h4,{id:"vector-length-metadata",children:"Vector Length Metadata"}),"\n",(0,s.jsx)(n.p,{children:"Each feature group maintains metadata about vector dimensions in the Feature Registry. For example, if a feature group has:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-yaml",children:"fg1:\n version-2:\n features:\n f1: { vector_len: 6, default: [bytes] }\n f2: { vector_len: 3, default: [bytes] }\n version-1:\n features:\n f1: { vector_len: 6, default: [bytes] }\n"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Feature f1 with vector_len: 6"}),"\n",(0,s.jsx)(n.li,{children:"Feature f2 with vector_len: 3"}),"\n",(0,s.jsx)(n.li,{}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"This means:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"f1"})," contains vectors of exactly 6 elements each"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"f2"})," contains vectors of exactly 3 elements each"]}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"encoding-process",children:"Encoding Process"}),"\n",(0,s.jsx)(n.h5,{id:"input-structure",children:(0,s.jsx)(n.strong,{children:"Input Structure"})}),"\n",(0,s.jsx)(n.p,{children:"The serializer receives vector data as 2D slices where:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Outer dimension represents different feature instances/entities"}),"\n",(0,s.jsx)(n.li,{children:"Inner dimension represents the vector elements for each instance"}),"\n"]}),"\n",(0,s.jsx)(n.h5,{id:"length-validation",children:(0,s.jsx)(n.strong,{children:"Length Validation"})}),"\n",(0,s.jsx)(n.p,{children:"Before encoding, PSDB validates that each vector's actual length matches the declared vector_len from the feature metadata. This ensures data integrity and enables efficient decoding."}),"\n",(0,s.jsx)(n.h5,{id:"flattening-strategy",children:(0,s.jsx)(n.strong,{children:"Flattening Strategy"})}),"\n",(0,s.jsx)(n.p,{children:"Vectors are serialized in row-major order (also called C-style order):"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"All elements of the first vector are written consecutively"}),"\n",(0,s.jsx)(n.li,{children:"Followed by all elements of the second vector"}),"\n",(0,s.jsx)(n.li,{children:"And so on..."}),"\n"]}),"\n",(0,s.jsx)(n.h5,{id:"contiguous-layout",children:(0,s.jsx)(n.strong,{children:"Contiguous Layout"})}),"\n",(0,s.jsx)(n.p,{children:"The resulting byte buffer contains all vector elements placed end-to-end without gaps or separators. The decoder can reconstruct vector boundaries because it knows:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"The data type size (e.g., 4 bytes for float32), from feature registry"}),"\n",(0,s.jsx)(n.li,{children:"The vector length for each position, from feature registry"}),"\n",(0,s.jsx)(n.li,{children:"The total number of vectors, from feature registry"}),"\n",(0,s.jsxs)(n.li,{children:["In case of ",(0,s.jsx)(n.code,{children:"variable length"})," length is encoded into the data, like for ",(0,s.jsx)(n.code,{children:"String"})," data-type"]}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h3,{id:"-deserializationdecoding-flow",children:"\ud83d\udd04 Deserialization/Decoding Flow"}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Extract version"})," from first 2 bytes."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Look up schema"})," from etcd using the version."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Determine feature shapes"})," (e.g., vector lengths)."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Slice and decode"})," data from byte buffer accordingly."]}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h3,{id:"memory-efficiency-benefits",children:"Memory Efficiency Benefits"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"No Padding"}),": Elements are packed tightly without alignment padding"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"No Delimiters"}),": Vector boundaries are implicit, not stored explicitly"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Cache Friendly"}),": Sequential memory access patterns during encoding/decoding"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Minimal Metadata"}),": Only vector lengths are stored separately, not per-element"]}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"cache-storage-data-block-csdb-design",children:"Cache Storage Data Block (CSDB) Design"}),"\n",(0,s.jsx)(n.h3,{id:"overview",children:"Overview"}),"\n",(0,s.jsx)(n.p,{children:"The Cache Storage Data Block (CSDB) is a compact binary data format that encapsulates serialized data blocks for multiple feature groups. It is designed to support both in-memory and distributed caching of deserialized PSDB (Permanent Storage Data Block) content, optimizing for speed, deduplication, and minimal memory overhead."}),"\n",(0,s.jsx)(n.h3,{id:"structure-and-purpose",children:"Structure and Purpose"}),"\n",(0,s.jsx)(n.p,{children:"Each CSDB contains a mapping of feature group IDs (FG IDs) to deserialized PSDBs. For distributed systems, this structure is flattened into a serialized byte slice. The CSDB supports layout versioning for backward compatibility and negative caching for feature groups with no associated data."}),"\n",(0,s.jsx)(n.h4,{id:"core-fields-and-memory-layout",children:"Core Fields and Memory Layout"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-go",children:"type CacheStorageDataBlock struct {\n // 8-byte aligned map pointer\n FGIdToDDB map[int]*DeserializedPSDB // offset: 0\n\n // 24-byte slice (ptr, len, cap)\n serializedCSDB []byte // offset: 8\n\n // 4-byte fields\n TTL uint32 // offset: 32\n\n // 1-byte fields\n layoutVersion uint8 // offset: 36\n cacheType CacheType // offset: 37\n // 2 bytes padding to maintain 4-byte alignment\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:"The structure is memory-aligned for optimal performance:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Pointers and slices are 8-byte aligned"}),"\n",(0,s.jsxs)(n.li,{children:["Smaller fields (like ",(0,s.jsx)(n.code,{children:"uint8"}),") are grouped and padded to avoid false sharing"]}),"\n",(0,s.jsx)(n.li,{children:"This layout ensures efficient use of CPU caches during access"}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"cache-types",children:"Cache Types"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"In-Memory Cache"}),": Uses the ",(0,s.jsx)(n.code,{children:"FGIdToDDB"})," map directly and avoids serialization unless explicitly requested."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Distributed Cache"}),": Stores a serialized binary format in ",(0,s.jsx)(n.code,{children:"serializedCSDB"}),", which is deserialized lazily when required."]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"format--encoding",children:"Format & Encoding"}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"CSDB Binary Layout"}),": Serialized CSDBs follow this compact format:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"[LayoutVersion (1 byte)][FGID (2 bytes)][DataLen (2 bytes)][Data ...] \u2192 repeated per feature group\n"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["FGID and DataLen are encoded as ",(0,s.jsx)(n.code,{children:"uint16"})]}),"\n",(0,s.jsxs)(n.li,{children:["If ",(0,s.jsx)(n.code,{children:"DataLen == 0"}),", it denotes a negative cache (no data available for that FG)"]}),"\n",(0,s.jsx)(n.li,{children:"The data section contains the PSDB header and either compressed or uncompressed data"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"This layout allows fast scanning and partial deserialization for selected FG IDs, making it optimal for large-scale caching systems."}),"\n",(0,s.jsx)(n.h3,{id:"differences-between-in-memory-and-distributed-caching",children:"Differences Between In-Memory and Distributed Caching"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Aspect"}),(0,s.jsx)(n.th,{children:"In-Memory CSDB"}),(0,s.jsx)(n.th,{children:"Distributed CSDB"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Storage Format"}),(0,s.jsx)(n.td,{children:"Live Go objects (map[int]*DeserializedPSDB)"}),(0,s.jsxs)(n.td,{children:["Serialized byte buffer (",(0,s.jsx)(n.code,{children:"[]byte"}),")"]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Deserialization"}),(0,s.jsx)(n.td,{children:"Performed on-demand using offset map"}),(0,s.jsx)(n.td,{children:"Performed on-demand using offset map"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Compression"}),(0,s.jsx)(n.td,{children:"Optional during serialization"}),(0,s.jsx)(n.td,{children:"Typically enabled to reduce payload size"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Usage Pattern"}),(0,s.jsx)(n.td,{children:"Fast lookup in active process memory"}),(0,s.jsx)(n.td,{children:"Cross-node cache sharing and persistence"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Memory Overhead"}),(0,s.jsx)(n.td,{children:"Higher (due to live objects)"}),(0,s.jsx)(n.td,{children:"Lower (compact representation)"})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"optimizations--features",children:"Optimizations & Features"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Partial FG ID Fetch"}),": When only a subset of FG IDs is needed, CSDB avoids unnecessary deserialization of other IDs."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Negative Caching"}),": FG IDs with no data are encoded with ",(0,s.jsx)(n.code,{children:"DataLen=0"}),", saving space and avoiding repeated lookups."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Offset-Length Map"}),": During deserialization, FGID to offset+length pairs are cached internally for efficient random access."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Versioning Support"}),": Layout version is stored as the first byte to enable format upgrades while maintaining backward compatibility."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Diagram below explains how compute cycles are saved by partial de-compression."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"CSDB Partial Decompression",src:i(551).A+"",width:"2292",height:"828"})})]})}function h(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(o,{...e})}):o(e)}}}]); \ No newline at end of file diff --git a/docs/assets/js/4caa95bf.ca3bb1d0.js b/docs/assets/js/4caa95bf.ca3bb1d0.js new file mode 100644 index 00000000..109ee1e0 --- /dev/null +++ b/docs/assets/js/4caa95bf.ca3bb1d0.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[2344],{3560:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/v1.0.0-psdb-anatomy-c1735559f93dce6d0bb3894d16047059.png"},6230:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/v1.0.0-psdb-fixed-length-encodding-dd252110b084e01cf38f21de16b3a1a5.png"},7676:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/v1.0.0-psdb-string-encoding-b1d69e9452269124d1b545020fa27d63.png"},7780:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/v1.0.0-csdb-skip-read-e3926080f7341aa7d3c6ec6d8274ea14.png"},8453:(e,n,i)=>{i.d(n,{R:()=>d,x:()=>l});var t=i(6540);const s={},r=t.createContext(s);function d(e){const n=t.useContext(r);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:d(e.components),t.createElement(r.Provider,{value:n},e.children)}},8645:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/v1.0.0-psdb-bool-encoding-4b154fdf5e6d79a67c91b6fb21c7209e.png"},9584:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>c,contentTitle:()=>l,default:()=>h,frontMatter:()=>d,metadata:()=>t,toc:()=>a});const t=JSON.parse('{"id":"online-feature-store/v1.0.0/data-formats","title":"Data Formats","description":"In this section we will go through the data-formats which is at the hear of online-feature-store, it\'s inspired form other storage efficient formats like parquet & arrow, but custom made to deliver in constraint environment. The two key data-formats are:","source":"@site/docs/online-feature-store/v1.0.0/data-formats.md","sourceDirName":"online-feature-store/v1.0.0","slug":"/online-feature-store/v1.0.0/data-formats","permalink":"/BharatMLStack/online-feature-store/v1.0.0/data-formats","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/online-feature-store/v1.0.0/data-formats.md","tags":[],"version":"current","sidebarPosition":2,"frontMatter":{"title":"Data Formats","sidebar_position":2},"sidebar":"tutorialSidebar","previous":{"title":"Architecture","permalink":"/BharatMLStack/online-feature-store/v1.0.0/architecture"},"next":{"title":"Benchmarks","permalink":"/BharatMLStack/online-feature-store/v1.0.0/benchmarks"}}');var s=i(4848),r=i(8453);const d={title:"Data Formats",sidebar_position:2},l="Data Format for Permanent & Cache Storage",c={},a=[{value:"PSDB (Permanent Storage Data Block) Format",id:"psdb-permanent-storage-data-block-format",level:2},{value:"\ud83e\uddf1 Structure Overview",id:"-structure-overview",level:3},{value:"Supported Data Types",id:"supported-data-types",level:3},{value:"Scalar Types",id:"scalar-types",level:4},{value:"Vector Types",id:"vector-types",level:4},{value:"\ud83d\udce6 Encoding for Scalar Feature Type",id:"-encoding-for-scalar-feature-type",level:3},{value:"1. \ud83d\udd21 String Feature Group (Variable Length Encoding using Pascal)",id:"1--string-feature-group-variable-length-encoding-using-pascal",level:4},{value:"2. \ud83d\udfe9 Boolean Feature Group (Bit-Packed)",id:"2--boolean-feature-group-bit-packed",level:4},{value:"3. \ud83d\udccf Fixed-Length Feature Group",id:"3--fixed-length-feature-group",level:4},{value:"4. Compression",id:"4-compression",level:4},{value:"\ud83e\uddec Encoding for Vector Types",id:"-encoding-for-vector-types",level:3},{value:"Conceptual Overview",id:"conceptual-overview",level:4},{value:"Vector Length Metadata",id:"vector-length-metadata",level:4},{value:"Encoding Process",id:"encoding-process",level:4},{value:"Input Structure",id:"input-structure",level:5},{value:"Length Validation",id:"length-validation",level:5},{value:"Flattening Strategy",id:"flattening-strategy",level:5},{value:"Contiguous Layout",id:"contiguous-layout",level:5},{value:"\ud83d\udd04 Deserialization/Decoding Flow",id:"-deserializationdecoding-flow",level:3},{value:"Memory Efficiency Benefits",id:"memory-efficiency-benefits",level:3},{value:"Cache Storage Data Block (CSDB) Design",id:"cache-storage-data-block-csdb-design",level:2},{value:"Overview",id:"overview",level:3},{value:"Structure and Purpose",id:"structure-and-purpose",level:3},{value:"Core Fields and Memory Layout",id:"core-fields-and-memory-layout",level:4},{value:"Cache Types",id:"cache-types",level:4},{value:"Format & Encoding",id:"format--encoding",level:3},{value:"Differences Between In-Memory and Distributed Caching",id:"differences-between-in-memory-and-distributed-caching",level:3},{value:"Optimizations & Features",id:"optimizations--features",level:3}];function o(e){const n={code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",h5:"h5",header:"header",hr:"hr",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,r.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.header,{children:(0,s.jsx)(n.h1,{id:"data-format-for-permanent--cache-storage",children:"Data Format for Permanent & Cache Storage"})}),"\n",(0,s.jsx)(n.p,{children:"In this section we will go through the data-formats which is at the hear of online-feature-store, it's inspired form other storage efficient formats like parquet & arrow, but custom made to deliver in constraint environment. The two key data-formats are:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"PSDB"})," - Permanent Storage Data Block used wile storing data in ScyllaDB"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"CSDB"})," - Cache Storage Data Block used while storing data in DragonflyDB or Redis, optimal for KV"]}),"\n",(0,s.jsx)(n.li,{}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"psdb-permanent-storage-data-block-format",children:"PSDB (Permanent Storage Data Block) Format"}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.strong,{children:"PSDB"})," format is a compact, versioned, and schema-aware binary layout used to store feature groups efficiently for ML inference. It supports multiple datatypes (strings, booleans, fixed-size vectors), versioning, TTL, and metadata encoding in a compact header."]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h3,{id:"-structure-overview",children:"\ud83e\uddf1 Structure Overview"}),"\n",(0,s.jsx)(n.p,{children:"Each PSDB block is composed of multiple byte sections:"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Permanent Storage Data Block Anatomy",src:i(3560).A+"",width:"1854",height:"1102"})}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Byte"}),(0,s.jsx)(n.th,{children:"Bits"}),(0,s.jsx)(n.th,{children:"Field"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"0-1"}),(0,s.jsx)(n.td,{children:"0-15"}),(0,s.jsx)(n.td,{children:"Feature Schema Version"}),(0,s.jsx)(n.td,{children:"Version for tracking schema changes (additions/deletions) in feature group"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"2-6"}),(0,s.jsx)(n.td,{children:"16-55"}),(0,s.jsx)(n.td,{children:"Expiry Timestamp"}),(0,s.jsx)(n.td,{children:"Encoded as a compact representation, ~513 days max"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"7"}),(0,s.jsx)(n.td,{children:"56-59"}),(0,s.jsx)(n.td,{children:"Layout Version"}),(0,s.jsx)(n.td,{children:"Used to ensure backward compatibility with layout format changes"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"7"}),(0,s.jsx)(n.td,{children:"60-62"}),(0,s.jsx)(n.td,{children:"Compression Type"}),(0,s.jsx)(n.td,{children:"3-bit field specifying compression algorithm"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"7-8"}),(0,s.jsx)(n.td,{children:"63-67"}),(0,s.jsx)(n.td,{children:"Data Type"}),(0,s.jsx)(n.td,{children:"5-bit field split across bytes 7 and 8"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"8"}),(0,s.jsx)(n.td,{children:"68-71"}),(0,s.jsx)(n.td,{children:"Bool Last Valid Bit"}),(0,s.jsx)(n.td,{children:"4-bit field for last valid boolean bit"})]})]})]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h3,{id:"supported-data-types",children:"Supported Data Types"}),"\n",(0,s.jsx)(n.h4,{id:"scalar-types",children:"Scalar Types"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Container"}),(0,s.jsx)(n.th,{children:"Size"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsxs)(n.td,{children:[(0,s.jsx)(n.code,{children:"FP32"}),", ",(0,s.jsx)(n.code,{children:"FP16"}),", ",(0,s.jsx)(n.code,{children:"FP8E4M3"}),", ",(0,s.jsx)(n.code,{children:"FP8E5M2"})]}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"[]float32"})}),(0,s.jsx)(n.td,{children:"4/2/1/1 bytes"}),(0,s.jsx)(n.td,{children:"Floating point numbers"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsxs)(n.td,{children:[(0,s.jsx)(n.code,{children:"Int32"}),", ",(0,s.jsx)(n.code,{children:"Int16"}),", ",(0,s.jsx)(n.code,{children:"Int8"})]}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"[]int32"})}),(0,s.jsx)(n.td,{children:"4/2/1 bytes"}),(0,s.jsx)(n.td,{children:"Signed integers"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsxs)(n.td,{children:[(0,s.jsx)(n.code,{children:"Uint32"}),", ",(0,s.jsx)(n.code,{children:"Uint16"}),", ",(0,s.jsx)(n.code,{children:"Uint8"})]}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"[]uint32"})}),(0,s.jsx)(n.td,{children:"4/2/1 bytes"}),(0,s.jsx)(n.td,{children:"Unsigned integers"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"FP64"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"[]float64"})}),(0,s.jsx)(n.td,{children:"8 bytes"}),(0,s.jsx)(n.td,{children:"Double precision float"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"Int64"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"[]int64"})}),(0,s.jsx)(n.td,{children:"8 bytes"}),(0,s.jsx)(n.td,{children:"64-bit signed integer"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"Uint64"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"[]uint64"})}),(0,s.jsx)(n.td,{children:"8 bytes"}),(0,s.jsx)(n.td,{children:"64-bit unsigned integer"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"String"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"[]string"})}),(0,s.jsx)(n.td,{children:"Variable"}),(0,s.jsx)(n.td,{children:"Pascal-style strings"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"Bool"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"[]uint8"})}),(0,s.jsx)(n.td,{children:"Bit-packed"}),(0,s.jsx)(n.td,{children:"Boolean values"})]})]})]}),"\n",(0,s.jsx)(n.h4,{id:"vector-types",children:"Vector Types"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Container"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsxs)(n.td,{children:[(0,s.jsx)(n.code,{children:"FP32Vector"}),", ",(0,s.jsx)(n.code,{children:"FP16Vector"}),", etc."]}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"[][]float32"})}),(0,s.jsx)(n.td,{children:"2D slices of floating point"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsxs)(n.td,{children:[(0,s.jsx)(n.code,{children:"Int32Vector"}),", ",(0,s.jsx)(n.code,{children:"Int16Vector"}),", etc."]}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"[][]int32"})}),(0,s.jsx)(n.td,{children:"2D slices of signed integers"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsxs)(n.td,{children:[(0,s.jsx)(n.code,{children:"Uint32Vector"}),", ",(0,s.jsx)(n.code,{children:"Uint16Vector"}),", etc."]}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"[][]uint32"})}),(0,s.jsx)(n.td,{children:"2D slices of unsigned integers"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"FP64Vector"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"[][]float64"})}),(0,s.jsx)(n.td,{children:"2D slices of doubles"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"Int64Vector"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"[][]int64"})}),(0,s.jsx)(n.td,{children:"2D slices of 64-bit signed"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"Uint64Vector"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"[][]uint64"})}),(0,s.jsx)(n.td,{children:"2D slices of 64-bit unsigned"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"StringVector"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"[][]string"})}),(0,s.jsx)(n.td,{children:"2D slices of strings"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"BoolVector"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"[][]uint8"})}),(0,s.jsx)(n.td,{children:"2D slices of bit-packed bools"})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"-encoding-for-scalar-feature-type",children:"\ud83d\udce6 Encoding for Scalar Feature Type"}),"\n",(0,s.jsx)(n.h4,{id:"1--string-feature-group-variable-length-encoding-using-pascal",children:"1. \ud83d\udd21 String Feature Group (Variable Length Encoding using Pascal)"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Max string length: ",(0,s.jsx)(n.strong,{children:"65536"})]}),"\n",(0,s.jsxs)(n.li,{children:["Format:\n",(0,s.jsx)(n.img,{alt:"PSDB String encoding",src:i(7676).A+"",width:"1488",height:"204"})]}),"\n",(0,s.jsxs)(n.li,{children:["Deserialization:","\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Read length prefixes"}),"\n",(0,s.jsxs)(n.li,{children:["Extract string bytes using ",(0,s.jsx)(n.code,{children:"StrLenX"})]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"2--boolean-feature-group-bit-packed",children:"2. \ud83d\udfe9 Boolean Feature Group (Bit-Packed)"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Saves space using bit-level packing."}),"\n",(0,s.jsxs)(n.li,{children:["Encoding:","\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Raw: 1 byte per feature"}),"\n",(0,s.jsx)(n.li,{children:"Bit-packed: 1 bit per boolean"}),"\n",(0,s.jsxs)(n.li,{children:["Additional index (",(0,s.jsx)(n.code,{children:"bool last idx"}),") stores where the last bit resides"]}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["Format:\n",(0,s.jsx)(n.img,{alt:"PSDB Bool encoding",src:i(8645).A+"",width:"1120",height:"712"})]}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"3--fixed-length-feature-group",children:"3. \ud83d\udccf Fixed-Length Feature Group"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["For fixed-size vectors (",(0,s.jsx)(n.code,{children:"n"})," bytes each)"]}),"\n",(0,s.jsxs)(n.li,{children:["Format:\n",(0,s.jsx)(n.img,{alt:"PSDB Fixed Length Datatype encoding",src:i(6230).A+"",width:"1122",height:"202"})]}),"\n",(0,s.jsx)(n.li,{children:"Efficient for dense numeric features like float32, int64, etc."}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"4-compression",children:"4. Compression"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"TypeNone (0)"}),": Raw storage"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"TypeZSTD (1)"}),": Compressed using Zstandard"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Compression is opportunistic. During serialization, if compressed size is not smaller, PSDB falls back to uncompressed format. It keeps the read/high througput path use less CPU cycles. Also only data part of PSDB is compressed allowing decompression only if block has a valid TTL"}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h3,{id:"-encoding-for-vector-types",children:"\ud83e\uddec Encoding for Vector Types"}),"\n",(0,s.jsx)(n.h4,{id:"conceptual-overview",children:"Conceptual Overview"}),"\n",(0,s.jsx)(n.p,{children:"PSDB encodes vector data by flattening multi-dimensional arrays into a single contiguous byte buffer while preserving the ability to reconstruct the original vector boundaries."}),"\n",(0,s.jsx)(n.h4,{id:"vector-length-metadata",children:"Vector Length Metadata"}),"\n",(0,s.jsx)(n.p,{children:"Each feature group maintains metadata about vector dimensions in the Feature Registry. For example, if a feature group has:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-yaml",children:"fg1:\n version-2:\n features:\n f1: { vector_len: 6, default: [bytes] }\n f2: { vector_len: 3, default: [bytes] }\n version-1:\n features:\n f1: { vector_len: 6, default: [bytes] }\n"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Feature f1 with vector_len: 6"}),"\n",(0,s.jsx)(n.li,{children:"Feature f2 with vector_len: 3"}),"\n",(0,s.jsx)(n.li,{}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"This means:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"f1"})," contains vectors of exactly 6 elements each"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"f2"})," contains vectors of exactly 3 elements each"]}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"encoding-process",children:"Encoding Process"}),"\n",(0,s.jsx)(n.h5,{id:"input-structure",children:(0,s.jsx)(n.strong,{children:"Input Structure"})}),"\n",(0,s.jsx)(n.p,{children:"The serializer receives vector data as 2D slices where:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Outer dimension represents different feature instances/entities"}),"\n",(0,s.jsx)(n.li,{children:"Inner dimension represents the vector elements for each instance"}),"\n"]}),"\n",(0,s.jsx)(n.h5,{id:"length-validation",children:(0,s.jsx)(n.strong,{children:"Length Validation"})}),"\n",(0,s.jsx)(n.p,{children:"Before encoding, PSDB validates that each vector's actual length matches the declared vector_len from the feature metadata. This ensures data integrity and enables efficient decoding."}),"\n",(0,s.jsx)(n.h5,{id:"flattening-strategy",children:(0,s.jsx)(n.strong,{children:"Flattening Strategy"})}),"\n",(0,s.jsx)(n.p,{children:"Vectors are serialized in row-major order (also called C-style order):"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"All elements of the first vector are written consecutively"}),"\n",(0,s.jsx)(n.li,{children:"Followed by all elements of the second vector"}),"\n",(0,s.jsx)(n.li,{children:"And so on..."}),"\n"]}),"\n",(0,s.jsx)(n.h5,{id:"contiguous-layout",children:(0,s.jsx)(n.strong,{children:"Contiguous Layout"})}),"\n",(0,s.jsx)(n.p,{children:"The resulting byte buffer contains all vector elements placed end-to-end without gaps or separators. The decoder can reconstruct vector boundaries because it knows:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"The data type size (e.g., 4 bytes for float32), from feature registry"}),"\n",(0,s.jsx)(n.li,{children:"The vector length for each position, from feature registry"}),"\n",(0,s.jsx)(n.li,{children:"The total number of vectors, from feature registry"}),"\n",(0,s.jsxs)(n.li,{children:["In case of ",(0,s.jsx)(n.code,{children:"variable length"})," length is encoded into the data, like for ",(0,s.jsx)(n.code,{children:"String"})," data-type"]}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h3,{id:"-deserializationdecoding-flow",children:"\ud83d\udd04 Deserialization/Decoding Flow"}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Extract version"})," from first 2 bytes."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Look up schema"})," from etcd using the version."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Determine feature shapes"})," (e.g., vector lengths)."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Slice and decode"})," data from byte buffer accordingly."]}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h3,{id:"memory-efficiency-benefits",children:"Memory Efficiency Benefits"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"No Padding"}),": Elements are packed tightly without alignment padding"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"No Delimiters"}),": Vector boundaries are implicit, not stored explicitly"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Cache Friendly"}),": Sequential memory access patterns during encoding/decoding"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Minimal Metadata"}),": Only vector lengths are stored separately, not per-element"]}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"cache-storage-data-block-csdb-design",children:"Cache Storage Data Block (CSDB) Design"}),"\n",(0,s.jsx)(n.h3,{id:"overview",children:"Overview"}),"\n",(0,s.jsx)(n.p,{children:"The Cache Storage Data Block (CSDB) is a compact binary data format that encapsulates serialized data blocks for multiple feature groups. It is designed to support both in-memory and distributed caching of deserialized PSDB (Permanent Storage Data Block) content, optimizing for speed, deduplication, and minimal memory overhead."}),"\n",(0,s.jsx)(n.h3,{id:"structure-and-purpose",children:"Structure and Purpose"}),"\n",(0,s.jsx)(n.p,{children:"Each CSDB contains a mapping of feature group IDs (FG IDs) to deserialized PSDBs. For distributed systems, this structure is flattened into a serialized byte slice. The CSDB supports layout versioning for backward compatibility and negative caching for feature groups with no associated data."}),"\n",(0,s.jsx)(n.h4,{id:"core-fields-and-memory-layout",children:"Core Fields and Memory Layout"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-go",children:"type CacheStorageDataBlock struct {\n // 8-byte aligned map pointer\n FGIdToDDB map[int]*DeserializedPSDB // offset: 0\n\n // 24-byte slice (ptr, len, cap)\n serializedCSDB []byte // offset: 8\n\n // 4-byte fields\n TTL uint32 // offset: 32\n\n // 1-byte fields\n layoutVersion uint8 // offset: 36\n cacheType CacheType // offset: 37\n // 2 bytes padding to maintain 4-byte alignment\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:"The structure is memory-aligned for optimal performance:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Pointers and slices are 8-byte aligned"}),"\n",(0,s.jsxs)(n.li,{children:["Smaller fields (like ",(0,s.jsx)(n.code,{children:"uint8"}),") are grouped and padded to avoid false sharing"]}),"\n",(0,s.jsx)(n.li,{children:"This layout ensures efficient use of CPU caches during access"}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"cache-types",children:"Cache Types"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"In-Memory Cache"}),": Uses the ",(0,s.jsx)(n.code,{children:"FGIdToDDB"})," map directly and avoids serialization unless explicitly requested."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Distributed Cache"}),": Stores a serialized binary format in ",(0,s.jsx)(n.code,{children:"serializedCSDB"}),", which is deserialized lazily when required."]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"format--encoding",children:"Format & Encoding"}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"CSDB Binary Layout"}),": Serialized CSDBs follow this compact format:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"[LayoutVersion (1 byte)][FGID (2 bytes)][DataLen (2 bytes)][Data ...] \u2192 repeated per feature group\n"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["FGID and DataLen are encoded as ",(0,s.jsx)(n.code,{children:"uint16"})]}),"\n",(0,s.jsxs)(n.li,{children:["If ",(0,s.jsx)(n.code,{children:"DataLen == 0"}),", it denotes a negative cache (no data available for that FG)"]}),"\n",(0,s.jsx)(n.li,{children:"The data section contains the PSDB header and either compressed or uncompressed data"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"This layout allows fast scanning and partial deserialization for selected FG IDs, making it optimal for large-scale caching systems."}),"\n",(0,s.jsx)(n.h3,{id:"differences-between-in-memory-and-distributed-caching",children:"Differences Between In-Memory and Distributed Caching"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Aspect"}),(0,s.jsx)(n.th,{children:"In-Memory CSDB"}),(0,s.jsx)(n.th,{children:"Distributed CSDB"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Storage Format"}),(0,s.jsx)(n.td,{children:"Live Go objects (map[int]*DeserializedPSDB)"}),(0,s.jsxs)(n.td,{children:["Serialized byte buffer (",(0,s.jsx)(n.code,{children:"[]byte"}),")"]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Deserialization"}),(0,s.jsx)(n.td,{children:"Performed on-demand using offset map"}),(0,s.jsx)(n.td,{children:"Performed on-demand using offset map"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Compression"}),(0,s.jsx)(n.td,{children:"Optional during serialization"}),(0,s.jsx)(n.td,{children:"Typically enabled to reduce payload size"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Usage Pattern"}),(0,s.jsx)(n.td,{children:"Fast lookup in active process memory"}),(0,s.jsx)(n.td,{children:"Cross-node cache sharing and persistence"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Memory Overhead"}),(0,s.jsx)(n.td,{children:"Higher (due to live objects)"}),(0,s.jsx)(n.td,{children:"Lower (compact representation)"})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"optimizations--features",children:"Optimizations & Features"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Partial FG ID Fetch"}),": When only a subset of FG IDs is needed, CSDB avoids unnecessary deserialization of other IDs."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Negative Caching"}),": FG IDs with no data are encoded with ",(0,s.jsx)(n.code,{children:"DataLen=0"}),", saving space and avoiding repeated lookups."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Offset-Length Map"}),": During deserialization, FGID to offset+length pairs are cached internally for efficient random access."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Versioning Support"}),": Layout version is stored as the first byte to enable format upgrades while maintaining backward compatibility."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Diagram below explains how compute cycles are saved by partial de-compression."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"CSDB Partial Decompression",src:i(7780).A+"",width:"2292",height:"828"})})]})}function h(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(o,{...e})}):o(e)}}}]); \ No newline at end of file diff --git a/docs/assets/js/4d1a2db0.1e2d0e16.js b/docs/assets/js/4d1a2db0.1e2d0e16.js new file mode 100644 index 00000000..d423bed6 --- /dev/null +++ b/docs/assets/js/4d1a2db0.1e2d0e16.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[8241],{2062:e=>{e.exports=JSON.parse('{"categoryGeneratedIndex":{"title":"Skye","description":"Skye is a high-performance vector similarity search platform that enables fast semantic retrieval by representing data as vectors and querying nearest matches in high-dimensional space. It supports pluggable vector databases, tenant-level index isolation, intelligent caching, and centralized cluster management.","slug":"/category/skye","permalink":"/BharatMLStack/category/skye","sidebar":"tutorialSidebar","navigation":{"previous":{"title":"Spark client","permalink":"/BharatMLStack/sdks/python/v1.0.0/spark_feature_push_client"},"next":{"title":"v1.0.0","permalink":"/BharatMLStack/skye/v1.0.0"}}}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/4dd73b28.06495428.js b/docs/assets/js/4dd73b28.06495428.js new file mode 100644 index 00000000..c53aabc3 --- /dev/null +++ b/docs/assets/js/4dd73b28.06495428.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[4797],{762:(e,t,a)=>{a.d(t,{A:()=>r});const r=a.p+"assets/images/bms-7399e8796d2cd24617c432518ce3f312.png"},3306:e=>{e.exports=JSON.parse('{"permalink":"/BharatMLStack/blog/episodic-memory-for-agents","editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/blog/bharatmlstack-history/episodic-memory-for-agents/index.md","source":"@site/blog/bharatmlstack-history/episodic-memory-for-agents/index.md","title":"Beyond Vector RAG: Building Agent Memory That Learns From Experience.","description":"Current agent memory is just search. We built an episodic memory system that tracks outcomes, forms causal links, extracts reasoning heuristics, and actually learns from failure \u2014 without retraining the model.","date":"2026-02-19T00:00:00.000Z","tags":[{"inline":true,"label":"ai-agents","permalink":"/BharatMLStack/blog/tags/ai-agents"},{"inline":true,"label":"memory","permalink":"/BharatMLStack/blog/tags/memory"},{"inline":true,"label":"architecture","permalink":"/BharatMLStack/blog/tags/architecture"},{"inline":true,"label":"llm","permalink":"/BharatMLStack/blog/tags/llm"},{"inline":true,"label":"episodic-memory","permalink":"/BharatMLStack/blog/tags/episodic-memory"}],"readingTime":11.67,"hasTruncateMarker":true,"authors":[{"name":"Adarsha Das","title":"Senior Architect @ Meesho","url":"https://github.com/a0d00kc","imageURL":"https://github.com/a0d00kc.png","key":"adarsha","page":null}],"frontMatter":{"title":"Beyond Vector RAG: Building Agent Memory That Learns From Experience.","description":"Current agent memory is just search. We built an episodic memory system that tracks outcomes, forms causal links, extracts reasoning heuristics, and actually learns from failure \u2014 without retraining the model.","slug":"episodic-memory-for-agents","authors":["adarsha"],"date":"2026-02-19T00:00:00.000Z","tags":["ai-agents","memory","architecture","llm","episodic-memory"]},"unlisted":false,"nextItem":{"title":"LLM Inference Optimization Techniques: Engineering Sub-Second Latency at Scale","permalink":"/BharatMLStack/blog/llm-inference-optimization-sub-sec-latency"}}')},4043:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>o,default:()=>u,frontMatter:()=>s,metadata:()=>r,toc:()=>l});var r=a(3306),n=a(4848),i=a(8453);const s={title:"Beyond Vector RAG: Building Agent Memory That Learns From Experience.",description:"Current agent memory is just search. We built an episodic memory system that tracks outcomes, forms causal links, extracts reasoning heuristics, and actually learns from failure \u2014 without retraining the model.",slug:"episodic-memory-for-agents",authors:["adarsha"],date:new Date("2026-02-19T00:00:00.000Z"),tags:["ai-agents","memory","architecture","llm","episodic-memory"]},o=void 0,c={authorsImageUrls:[void 0]},l=[];function m(e){const t={img:"img",p:"p",...(0,i.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)(t.p,{children:[(0,n.jsx)(t.img,{alt:"BharatMLStack",src:a(762).A+"",width:"1396",height:"460"}),"\nAgent memory has come a long way. Persistent context, vector retrieval, knowledge graphs \u2014 the building blocks are real and getting better fast."]}),"\n",(0,n.jsx)(t.p,{children:'But most of what we call "memory" today is still closer to search: chunk text, embed it, retrieve whatever looks similar at query time. That works well for recalling facts and preferences. It starts to break down when you need an agent to recall what happened last time, learn from a mistake, or avoid repeating a failed approach.'}),"\n",(0,n.jsx)(t.p,{children:"We are trying to experiment something different. An episodic memory system where a frozen LLM \u2014 same weights, no retraining \u2014 produces increasingly better decisions over time because the memory feeding it context is continuously evolving.\nThen we tested it. The results were interesting."})]})}function u(e={}){const{wrapper:t}={...(0,i.R)(),...e.components};return t?(0,n.jsx)(t,{...e,children:(0,n.jsx)(m,{...e})}):m(e)}},8453:(e,t,a)=>{a.d(t,{R:()=>s,x:()=>o});var r=a(6540);const n={},i=r.createContext(n);function s(e){const t=r.useContext(i);return r.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function o(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(n):e.components||n:s(e.components),r.createElement(i.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/4df0e30b.00b3a0ff.js b/docs/assets/js/4df0e30b.00b3a0ff.js new file mode 100644 index 00000000..ffd65ccd --- /dev/null +++ b/docs/assets/js/4df0e30b.00b3a0ff.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[2379],{8453:(e,n,i)=>{i.d(n,{R:()=>l,x:()=>o});var s=i(6540);const r={},t=s.createContext(r);function l(e){const n=s.useContext(t);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:l(e.components),s.createElement(t.Provider,{value:n},e.children)}},9680:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>a,contentTitle:()=>o,default:()=>h,frontMatter:()=>l,metadata:()=>s,toc:()=>c});const s=JSON.parse('{"id":"numerix/v1.0.0/architecture","title":"Architecture","description":"---","source":"@site/docs/numerix/v1.0.0/architecture.md","sourceDirName":"numerix/v1.0.0","slug":"/numerix/v1.0.0/architecture","permalink":"/BharatMLStack/numerix/v1.0.0/architecture","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/numerix/v1.0.0/architecture.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"Architecture","sidebar_position":1},"sidebar":"tutorialSidebar","previous":{"title":"v1.0.0","permalink":"/BharatMLStack/numerix/v1.0.0"},"next":{"title":"Benchmarks","permalink":"/BharatMLStack/numerix/v1.0.0/benchmarks"}}');var r=i(4848),t=i(8453);const l={title:"Architecture",sidebar_position:1},o="BharatMLStack - Numerix",a={},c=[{value:"High-Level Components",id:"high-level-components",level:2},{value:"What is SIMD?",id:"what-is-simd",level:2},{value:"Why SIMD Matters for Numerix",id:"why-simd-matters-for-numerix",level:2},{value:"Why ARM, Why LLVM",id:"why-arm-why-llvm",level:2},{value:"Request Model and Flow",id:"request-model-and-flow",level:2},{value:"Why Postfix Expressions",id:"why-postfix-expressions",level:2},{value:"gRPC Interface",id:"grpc-interface",level:2},{value:"Observability",id:"observability",level:2},{value:"Environments",id:"environments",level:2},{value:"Key Takeaways",id:"key-takeaways",level:2},{value:"Contributing",id:"contributing",level:2},{value:"Community & Support",id:"community--support",level:2},{value:"License",id:"license",level:2}];function d(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",header:"header",hr:"hr",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,t.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"bharatmlstack---numerix",children:"BharatMLStack - Numerix"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsxs)(n.p,{children:["Numerix is a Rust-based compute service in ",(0,r.jsx)(n.strong,{children:"BharatMLStack"})," designed for low-latency evaluation of mathematical expressions over feature matrices. Each request carries a compute_id and a matrix of features; Numerix fetches the corresponding postfix expression, maps variables to feature columns (treated as vectors), and evaluates the expression with a stack-based SIMD-optimized runtime."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"high-level-components",children:"High-Level Components"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Tonic gRPC server (Rust)"}),": exposes ",(0,r.jsx)(n.code,{children:"Numerix/Compute"})," for low-latency requests.","\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Accepts feature data as strings (for ease of use) or byte arrays (for efficient transmission)."}),"\n",(0,r.jsx)(n.li,{children:"All input data is converted internally to fp32 or fp64 vectors for evaluation."}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Compute Registry (etcd)"}),": stores ",(0,r.jsx)(n.code,{children:"compute_id (int) \u2192 postfix expression"})," mappings."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Stack-based Evaluator"}),": Runs postfix expressions in linear time using a stack based approach over aligned vectors."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Vectorized Math Runtime"}),": No handwritten SIMD intrinsics; relies on ",(0,r.jsx)(n.strong,{children:"LLVM autovectorization"}),".","\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Operations are intentionally simple and memory-aligned."}),"\n",(0,r.jsx)(n.li,{children:"Compiler emits SIMD instructions automatically."}),"\n",(0,r.jsx)(n.li,{children:"Portable across CPU architectures (ARM & AMD)."}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Metrics and Health"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Latency, RPS, and error rates via ",(0,r.jsx)(n.strong,{children:"Datadog/DogStatsD"})," UDP client."]}),"\n",(0,r.jsxs)(n.li,{children:["Minimal HTTP endpoints (",(0,r.jsx)(n.code,{children:"/health"}),", optional ",(0,r.jsx)(n.code,{children:"/metrics"}),") for diagnostics."]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"what-is-simd",children:"What is SIMD?"}),"\n",(0,r.jsx)(n.p,{children:"SIMD (Single Instruction, Multiple Data) is a CPU feature that allows a single instruction to operate on multiple data points at once. In Numerix, this means that operations on feature vectors can be executed in parallel, making evaluation of mathematical expressions faster and more predictable."}),"\n",(0,r.jsx)(n.h2,{id:"why-simd-matters-for-numerix",children:"Why SIMD Matters for Numerix"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Postfix expressions operate on vectors (columns of the input matrix)."}),"\n",(0,r.jsx)(n.li,{children:"SIMD allows multiple elements of these vectors to be processed in one CPU instruction, rather than element-by-element."}),"\n",(0,r.jsx)(n.li,{children:"This results in low-latency, high-throughput computation without the need for handwritten intrinsics \u2014 the compiler handles the vectorization automatically."}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"why-arm-why-llvm",children:"Why ARM, Why LLVM"}),"\n",(0,r.jsxs)(n.p,{children:["During design exploration, we tested SIMD on different architectures and found ",(0,r.jsx)(n.strong,{children:"ARM (AArch64)"})," with NEON/SVE/SVE2 provided excellent performance for our workloads."]}),"\n",(0,r.jsxs)(n.p,{children:["Instead of writing custom intrinsics, Numerix ",(0,r.jsx)(n.strong,{children:"compiles with SIMD flags"})," and lets LLVM handle vectorization:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:'RUSTFLAGS="-C target-feature=+neon,+sve,+sve2" \\\ncargo build --release --target aarch64-unknown-linux-gnu\n'})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"This approach works well because operations are straightforward, data is aligned, and compiler auto-vectorization is reliable."}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"AMD/x86 builds are equally supported \u2014 enabling their SIMD extensions is just a matter of changing build flags."}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"request-model-and-flow",children:"Request Model and Flow"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["Client calls gRPC ",(0,r.jsx)(n.code,{children:"numerix.Numerix/Compute"})," with:","\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"schema"}),": ordered feature names"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"entity_scores"}),": per-entity vectors (string or bytes)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"compute_id"}),": integer identifier for the expression"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"data_type"})," (optional): e.g., ",(0,r.jsx)(n.code,{children:"fp32"})," or ",(0,r.jsx)(n.code,{children:"fp64"})]}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["Service fetches the postfix expression for ",(0,r.jsx)(n.code,{children:"compute_id"})," which was pre-fetched from ",(0,r.jsx)(n.code,{children:"etcd"}),"."]}),"\n",(0,r.jsx)(n.li,{children:"Request is validated for schema and data shape."}),"\n",(0,r.jsx)(n.li,{children:"The stack-based evaluator executes the expression in O(n) over tokens, with vectorized inner operations."}),"\n",(0,r.jsxs)(n.li,{children:["Response returns ",(0,r.jsx)(n.code,{children:"computation_score_data"})," or a structured ",(0,r.jsx)(n.code,{children:"error"}),"."]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"why-postfix-expressions",children:"Why Postfix Expressions"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Stored in etcd"})," as postfix (Reverse Polish) notation."]}),"\n",(0,r.jsx)(n.li,{children:"Postfix makes evaluation parser-free and linear time."}),"\n",(0,r.jsxs)(n.li,{children:["Execution uses a stack machine:","\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Push operands (feature vectors)."}),"\n",(0,r.jsx)(n.li,{children:"Pop, compute, and push results for each operator."}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.li,{children:"Benefits: predictable runtime, compiler-friendly loops, cache efficiency."}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"grpc-interface",children:"gRPC Interface"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Service:"})," ",(0,r.jsx)(n.code,{children:"numerix.Numerix"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"RPC:"})," ",(0,r.jsx)(n.code,{children:"Compute(NumerixRequestProto) \u2192 NumerixResponseProto"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Request fields:"})," ",(0,r.jsx)(n.code,{children:"schema"}),", ",(0,r.jsx)(n.code,{children:"entity_scores"}),", ",(0,r.jsx)(n.code,{children:"compute_id"}),", optional ",(0,r.jsx)(n.code,{children:"data_type"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Response fields:"})," ",(0,r.jsx)(n.code,{children:"computation_score_data"})," or ",(0,r.jsx)(n.code,{children:"error"})]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"Example (grpcurl):"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:'grpcurl -plaintext \\\n -import-path ./numerix/src/protos/proto \\\n -proto numerix.proto \\\n -d \'{\n "entityScoreData": {\n "schema": ["feature1", "feature2"],\n "entityScores": [ { "stringData": { "values": ["1.0", "2.0"] } } ],\n "computeId": "1001",\n "dataType": "fp32"\n }\n }\' \\\n localhost:8080 numerix.Numerix/Compute\n'})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"observability",children:"Observability"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Datadog (DogStatsD)"})," metrics publication via UDP client:","\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Latency (P50/P95/P99), error rate, RPS, internal failures"}),"\n",(0,r.jsx)(n.li,{children:"Configurable sampling rate via environment variables"}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["Optional ",(0,r.jsx)(n.code,{children:"/metrics"})," HTTP endpoint can be enabled for local debugging."]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"environments",children:"Environments"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Kubernetes (K8s), including GKE and EKS"}),"\n",(0,r.jsx)(n.li,{children:"Multi-arch builds: amd64, arm64."}),"\n",(0,r.jsx)(n.li,{children:"ARM builds ship with NEON/SVE/SVE2 enabled."}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"key-takeaways",children:"Key Takeaways"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Minimal service surface: ",(0,r.jsx)(n.strong,{children:"gRPC + etcd"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"No custom intrinsics"})," \u2014 portable across ",(0,r.jsx)(n.strong,{children:"ARM & AMD"})," via compiler flags."]}),"\n",(0,r.jsx)(n.li,{children:"Supports both string and byte input, internally converted to aligned fp32/fp64 vectors."}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Stack-based postfix evaluation"})," : linear time, cache-friendly."]}),"\n",(0,r.jsx)(n.li,{children:"Predictable, ultra-low-latency performance."}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"contributing",children:"Contributing"}),"\n",(0,r.jsxs)(n.p,{children:["We welcome contributions from the community! Please see our ",(0,r.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTING.md",children:"Contributing Guide"})," for details on how to get started."]}),"\n",(0,r.jsx)(n.h2,{id:"community--support",children:"Community & Support"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["\ud83d\udcac ",(0,r.jsx)(n.strong,{children:"Discord"}),": Join our ",(0,r.jsx)(n.a,{href:"https://discord.gg/XkT7XsV2AU",children:"community chat"})]}),"\n",(0,r.jsxs)(n.li,{children:["\ud83d\udc1b ",(0,r.jsx)(n.strong,{children:"Issues"}),": Report bugs and request features on ",(0,r.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/issues",children:"GitHub Issues"})]}),"\n",(0,r.jsxs)(n.li,{children:["\ud83d\udce7 ",(0,r.jsx)(n.strong,{children:"Email"}),": Contact us at ",(0,r.jsx)(n.a,{href:"mailto:ml-oss@meesho.com",children:"ml-oss@meesho.com"})]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"license",children:"License"}),"\n",(0,r.jsxs)(n.p,{children:["BharatMLStack is open-source software licensed under the ",(0,r.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md",children:"BharatMLStack Business Source License 1.1"}),"."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)("div",{align:"center",children:(0,r.jsx)("strong",{children:"Built with \u2764\ufe0f for the ML community from Meesho"})}),"\n",(0,r.jsx)("div",{align:"center",children:(0,r.jsx)("strong",{children:"If you find this useful, \u2b50\ufe0f the repo \u2014 your support means the world to us!"})})]})}function h(e={}){const{wrapper:n}={...(0,t.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(d,{...e})}):d(e)}}}]); \ No newline at end of file diff --git a/docs/assets/js/50899a24.53577092.js b/docs/assets/js/50899a24.53577092.js new file mode 100644 index 00000000..7f1787bc --- /dev/null +++ b/docs/assets/js/50899a24.53577092.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[1009],{1008:e=>{e.exports=JSON.parse('{"categoryGeneratedIndex":{"title":"Numerix","description":"Numerix is a mathematical compute engine for BharatML Stack. It is used to perform mathematical operations on matrices and vectors.","slug":"/category/numerix","permalink":"/BharatMLStack/category/numerix","sidebar":"tutorialSidebar","navigation":{"previous":{"title":"Release Notes","permalink":"/BharatMLStack/skye/v1.0.0/release-notes"},"next":{"title":"v1.0.0","permalink":"/BharatMLStack/numerix/v1.0.0"}}}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/56eef1be.ad4e065e.js b/docs/assets/js/56eef1be.ad4e065e.js new file mode 100644 index 00000000..bf294bd1 --- /dev/null +++ b/docs/assets/js/56eef1be.ad4e065e.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[1508],{475:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/skye-system-overview-24940f4c319f41fb3b7583a525b0a534.png"},584:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/skye-rt-consumer-flow-7f064a31c41151ff4516900b3170dbc8.png"},8453:(e,n,i)=>{i.d(n,{R:()=>a,x:()=>d});var r=i(6540);const s={},t=r.createContext(s);function a(e){const n=r.useContext(t);return r.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function d(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:a(e.components),r.createElement(t.Provider,{value:n},e.children)}},9390:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>l,contentTitle:()=>d,default:()=>h,frontMatter:()=>a,metadata:()=>r,toc:()=>c});const r=JSON.parse('{"id":"skye/v1.0.0/architecture","title":"Architecture","description":"Skye is BharatMLStack\'s vector similarity search platform that enables fast semantic retrieval by representing data as vectors and querying nearest matches in high-dimensional space. It is composed of three runnable components: skye-admin, skye-consumers, and skye-serving.","source":"@site/docs/skye/v1.0.0/architecture.md","sourceDirName":"skye/v1.0.0","slug":"/skye/v1.0.0/architecture","permalink":"/BharatMLStack/skye/v1.0.0/architecture","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/skye/v1.0.0/architecture.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"Architecture","sidebar_position":1},"sidebar":"tutorialSidebar","previous":{"title":"v1.0.0","permalink":"/BharatMLStack/skye/v1.0.0"},"next":{"title":"Functionalities","permalink":"/BharatMLStack/skye/v1.0.0/functionalities"}}');var s=i(4848),t=i(8453);const a={title:"Architecture",sidebar_position:1},d="Skye - Vector Similarity Search Platform",l={},c=[{value:"System Overview",id:"system-overview",level:2},{value:"Component Architecture",id:"component-architecture",level:3},{value:"Data Model",id:"data-model",level:2},{value:"Model and Variant Hierarchy",id:"model-and-variant-hierarchy",level:3},{value:"Entity-Based Data Split",id:"entity-based-data-split",level:3},{value:"Serving Flow",id:"serving-flow",level:2},{value:"Configuration Bootstrap",id:"configuration-bootstrap",level:3},{value:"Admin Flows",id:"admin-flows",level:2},{value:"API Contracts",id:"api-contracts",level:3},{value:"Register Model",id:"register-model",level:4},{value:"Register Variant",id:"register-variant",level:4},{value:"Reset Model",id:"reset-model",level:4},{value:"Trigger Model Machine",id:"trigger-model-machine",level:4},{value:"Promote Model / Variant to Scale-Up Cluster",id:"promote-model--variant-to-scale-up-cluster",level:4},{value:"Consumer Flows",id:"consumer-flows",level:2},{value:"Reset/Delta Ingestion",id:"resetdelta-ingestion",level:3},{value:"Real-Time Consumers",id:"real-time-consumers",level:3},{value:"Retry Topic",id:"retry-topic",level:3},{value:"Key Design Decisions",id:"key-design-decisions",level:2},{value:"Pluggable Vector Database Support",id:"pluggable-vector-database-support",level:3},{value:"Variant-Based Model Sharing",id:"variant-based-model-sharing",level:3},{value:"ScyllaDB for Real-Time Aggregation",id:"scylladb-for-real-time-aggregation",level:3},{value:"Event-Driven State Management",id:"event-driven-state-management",level:3},{value:"Resiliency",id:"resiliency",level:2},{value:"Scalability",id:"scalability",level:2},{value:"Observability",id:"observability",level:2},{value:"Metrics (per model + variant)",id:"metrics-per-model--variant",level:3},{value:"Alerts",id:"alerts",level:3},{value:"Technology Stack",id:"technology-stack",level:2}];function o(e){const n={code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",hr:"hr",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,t.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.header,{children:(0,s.jsx)(n.h1,{id:"skye---vector-similarity-search-platform",children:"Skye - Vector Similarity Search Platform"})}),"\n",(0,s.jsxs)(n.p,{children:["Skye is BharatMLStack's vector similarity search platform that enables fast semantic retrieval by representing data as vectors and querying nearest matches in high-dimensional space. It is composed of three runnable components: ",(0,s.jsx)(n.strong,{children:"skye-admin"}),", ",(0,s.jsx)(n.strong,{children:"skye-consumers"}),", and ",(0,s.jsx)(n.strong,{children:"skye-serving"}),"."]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"system-overview",children:"System Overview"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Skye System Architecture",src:i(475).A+"",width:"1276",height:"870"})}),"\n",(0,s.jsx)(n.p,{children:"Skye provides a critical platform for managing data aggregation, model onboarding, and embedding support at production scale. The architecture is designed around three core pillars:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Pluggable Vector Databases"}),": Support for multiple vector database backends (Qdrant and extensible to others) via a generic abstraction layer."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Tenant-Level Index Isolation with Shared Embeddings"}),": Models are stored once but can serve multiple tenants (variants), reducing data redundancy."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Event-Driven Administration"}),": Model lifecycle management is handled through Kafka-based event flows for resilience and fault tolerance."]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"component-architecture",children:"Component Architecture"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Component"}),(0,s.jsx)(n.th,{children:"Role"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"skye-serving"})}),(0,s.jsx)(n.td,{children:"Handles real-time similarity search queries with in-memory caching and vector DB lookups"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"skye-consumers"})}),(0,s.jsx)(n.td,{children:"Processes embedding ingestion (reset/delta jobs) and real-time aggregation events from Kafka"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"skye-admin"})}),(0,s.jsx)(n.td,{children:"Manages model lifecycle, onboarding, variant registration, and coordinates Databricks jobs"})]})]})]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"data-model",children:"Data Model"}),"\n",(0,s.jsx)(n.h3,{id:"model-and-variant-hierarchy",children:"Model and Variant Hierarchy"}),"\n",(0,s.jsxs)(n.p,{children:["Skye uses a ",(0,s.jsx)(n.strong,{children:"model-first"})," hierarchy rather than a tenant-first approach. Models sit at the base level with variants (formerly tenants) nested within each model. This eliminates embedding duplication across tenants."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"model (e.g., intent_model)\n \u251c\u2500\u2500 model_config (distance_function, vector_dimension, etc.)\n \u251c\u2500\u2500 embedding_store (shared embeddings for all variants)\n \u251c\u2500\u2500 variant_1 (e.g., organic)\n \u2502 \u251c\u2500\u2500 vss_filter (criteria for index inclusion)\n \u2502 \u251c\u2500\u2500 vectordb_type (QDRANT, etc.)\n \u2502 \u251c\u2500\u2500 vectordb_config (host, port, replication, sharding)\n \u2502 \u251c\u2500\u2500 read_version / write_version\n \u2502 \u2514\u2500\u2500 job_frequency (FREQ_1D, FREQ_3H, etc.)\n \u2514\u2500\u2500 variant_2 (e.g., ad)\n \u251c\u2500\u2500 vss_filter\n \u251c\u2500\u2500 vectordb_type\n \u2514\u2500\u2500 ...\n"})}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Key benefit"}),": If a model consumes 30M embeddings and is used by two variants, the embeddings are stored once (30M) instead of duplicated (60M)."]}),"\n",(0,s.jsx)(n.h3,{id:"entity-based-data-split",children:"Entity-Based Data Split"}),"\n",(0,s.jsx)(n.p,{children:"Data is split at the entity level (catalog, product, user) into separate tables for both embeddings and aggregator data:"}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Embedding Tables"})," (per entity):"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-sql",children:"CREATE TABLE catalog_embeddings (\n model_name text,\n version int,\n id text,\n embedding frozen>,\n search_embedding frozen>,\n to_be_indexed_variant_1 boolean,\n to_be_indexed_variant_2 boolean,\n PRIMARY KEY ((model_name, version), id)\n);\n"})}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Aggregator Tables"})," (per entity):"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-sql",children:"CREATE TABLE catalog_aggregator (\n id text,\n is_live_ad text,\n out_of_stock text,\n PRIMARY KEY (id)\n);\n"})}),"\n",(0,s.jsx)(n.p,{children:"Each entity is mapped via a store configuration:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n "db_conf_id": "1",\n "embeddings_table": "catalog_embeddings",\n "aggregator_table": "catalog_aggregator"\n}\n'})}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"serving-flow",children:"Serving Flow"}),"\n",(0,s.jsx)(n.p,{children:"The serving path is optimized for low latency with multiple caching layers:"}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Request arrives"})," at skye-serving via gRPC"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"ConfigRepo"})," resolves the model configuration, variant filters, and vector DB connection"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"In-memory cache"})," is checked first to reduce load on distributed cache"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Distributed cache (Redis)"})," is checked next for cached similarity results"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Vector DB query"})," executes if cache misses, using ",(0,s.jsx)(n.code,{children:"search_indexed_only"})," flag for optimal searches within indexed space"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Aggregator data"})," is fetched from ScyllaDB to apply variant-level filters"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Response"})," returns ranked similar candidates with scores"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"configuration-bootstrap",children:"Configuration Bootstrap"}),"\n",(0,s.jsx)(n.p,{children:"On startup, ConfigRepo creates:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"A map of each model with its configurations (embedding table, vector DB channel)"}),"\n",(0,s.jsx)(n.li,{children:"A map of each entity to its aggregator table"}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n "intent_model": {\n "db_conf_id": "1",\n "index_embedding_table": "catalog_embeddings",\n "vector_db_grpc_channel": ""\n }\n}\n'})}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"admin-flows",children:"Admin Flows"}),"\n",(0,s.jsxs)(n.p,{children:["Skye uses an ",(0,s.jsx)(n.strong,{children:"event-driven approach"})," for model lifecycle management:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"All admin operations are processed through Kafka consumers asynchronously"}),"\n",(0,s.jsx)(n.li,{children:"A SQL database behind the admin stores all model states"}),"\n",(0,s.jsx)(n.li,{children:"Pod termination does not affect in-progress operations (events are re-consumed on failure)"}),"\n",(0,s.jsx)(n.li,{children:"Databricks jobs are triggered and monitored via the admin API"}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"api-contracts",children:"API Contracts"}),"\n",(0,s.jsx)(n.h4,{id:"register-model",children:"Register Model"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"POST /register-model\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n "entity": "catalog",\n "ingestion_column_mapping": "{\\"id_column\\":\\"id\\",\\"embedding_column\\":\\"features\\",\\"to_be_indexed_column\\":\\"to_be_indexed\\"}",\n "embedding_store_enabled": true,\n "embedding_store_ttl": 604800,\n "mq_id": 804,\n "model_config": "{\\"distance_function\\":\\"DOT\\",\\"vector_dimension\\":32}",\n "store_id": 1,\n "training_data_path": "gcs_path"\n}\n'})}),"\n",(0,s.jsx)(n.h4,{id:"register-variant",children:"Register Variant"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"POST /register-variant\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n "entity": "catalog",\n "model_name": "intent_model",\n "vss_filter": "{...filter criteria...}",\n "vectordb_type": "QDRANT",\n "vectordb_config": "{...connection config...}",\n "job_frequency": "FREQ_1D"\n}\n'})}),"\n",(0,s.jsx)(n.h4,{id:"reset-model",children:"Reset Model"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"POST /reset-model\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n "entity": "catalog",\n "model_name": "intent_model",\n "frequency": "FREQ_1D"\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:"Response includes variant version mappings, MQ ID, and training data path for the Databricks job."}),"\n",(0,s.jsx)(n.h4,{id:"trigger-model-machine",children:"Trigger Model Machine"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"POST /trigger-model-machine\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n "entity": "catalog",\n "model_name": "intent_model",\n "variant": "organic"\n}\n'})}),"\n",(0,s.jsx)(n.h4,{id:"promote-model--variant-to-scale-up-cluster",children:"Promote Model / Variant to Scale-Up Cluster"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"POST /promote-model\nPOST /promote-variant\n"})}),"\n",(0,s.jsx)(n.p,{children:"Used to transition successful experiments from experiment clusters to production clusters."}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"consumer-flows",children:"Consumer Flows"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Skye Real-Time Consumer Flow",src:i(584).A+"",width:"1192",height:"960"})}),"\n",(0,s.jsx)(n.h3,{id:"resetdelta-ingestion",children:"Reset/Delta Ingestion"}),"\n",(0,s.jsx)(n.p,{children:"Embedding ingestion occurs once per model and executes in parallel for each variant. The Kafka event contract supports:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Multiple variants per event"}),": A single embedding event specifies which variants should index the data"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Separate search and index embeddings"}),": Models can have different embeddings for search space vs index space"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"EOF handling"}),": EOF is sent to all partitions to ensure all data is consumed before completion"]}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n "entity": "catalog",\n "model_name": "intent_model",\n "candidate_id": "48869419",\n "version": "1",\n "index_space": {\n "variants_version_map": "{\'organic\':1,\'ad\':2}",\n "embedding": [0.036, -0.048, ...],\n "variants_index_map": "{\'organic\':true,\'ad\':false}",\n "operation": "A",\n "payload": "{\'sscat_id\':700}"\n },\n "search_space": {\n "embedding": [0.036, -0.048, ...]\n }\n}\n'})}),"\n",(0,s.jsx)(n.h3,{id:"real-time-consumers",children:"Real-Time Consumers"}),"\n",(0,s.jsx)(n.p,{children:"A generic Kafka schema is used for all real-time consumers, simplifying new integrations:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n "timestamp": 1719308350,\n "entity_label": "catalog",\n "data": [\n {\n "id": "125138466",\n "label": "is_live_ad",\n "value": "true"\n }\n ]\n}\n'})}),"\n",(0,s.jsx)(n.h3,{id:"retry-topic",children:"Retry Topic"}),"\n",(0,s.jsx)(n.p,{children:"Failed ingestion events are published to a retry topic for reprocessing, ensuring no data loss:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n "timestamp": 1719308350,\n "entity_label": "catalog",\n "model_name": "intent_model",\n "variant": "organic",\n "data": [\n {\n "id": "125138466",\n "label": "is_live_ad",\n "value": "true"\n }\n ]\n}\n'})}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"key-design-decisions",children:"Key Design Decisions"}),"\n",(0,s.jsx)(n.h3,{id:"pluggable-vector-database-support",children:"Pluggable Vector Database Support"}),"\n",(0,s.jsxs)(n.p,{children:["Skye introduces a generic ",(0,s.jsx)(n.code,{children:"vector_db_type"})," configuration and converts vendor-specific configs to a generic ",(0,s.jsx)(n.code,{children:"vector_config"}),", enabling support for multiple vector database backends beyond Qdrant."]}),"\n",(0,s.jsx)(n.h3,{id:"variant-based-model-sharing",children:"Variant-Based Model Sharing"}),"\n",(0,s.jsx)(n.p,{children:"By eliminating the tenant-based construct and introducing variants, Skye allows:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Models to be shared across tenants without duplication"}),"\n",(0,s.jsx)(n.li,{children:"Each variant to have its own filter criteria, vector DB config, and job frequency"}),"\n",(0,s.jsx)(n.li,{children:"Independent read/write version tracking per variant"}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"scylladb-for-real-time-aggregation",children:"ScyllaDB for Real-Time Aggregation"}),"\n",(0,s.jsx)(n.p,{children:"Replaced Delta Lake with self-hosted ScyllaDB for cost efficiency. The aggregator is entity-generic (not model/version-specific) since all real-time data is consistent across models."}),"\n",(0,s.jsx)(n.h3,{id:"event-driven-state-management",children:"Event-Driven State Management"}),"\n",(0,s.jsx)(n.p,{children:"Model state transitions are handled via Kafka events with a SQL database backing store. This eliminates:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Single points of failure in admin/ingestion flows"}),"\n",(0,s.jsx)(n.li,{children:"Models getting stuck during pod restarts"}),"\n",(0,s.jsx)(n.li,{children:"Manual intervention for consumer pause/resume"}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"resiliency",children:"Resiliency"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Mechanism"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"Retry Topics"})}),(0,s.jsx)(n.td,{children:"Failed ingestion messages are captured in a failure topic for reprocessing"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"Circuit Breakers"})}),(0,s.jsx)(n.td,{children:"Applied to similarity search API calls to throttle RPS during failures"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"Snapshot Backups"})}),(0,s.jsx)(n.td,{children:"Periodic collection snapshots enable quick restore during downtime"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"Automated Cluster Setup"})}),(0,s.jsx)(n.td,{children:"Scripted provisioning eliminates configuration inconsistencies"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"Databricks Job Retries"})}),(0,s.jsx)(n.td,{children:"Lambda functions with retry mechanisms for failed ingestion jobs"})]})]})]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"scalability",children:"Scalability"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Vector DB Scaling"}),": Generic scripts for adding nodes to existing clusters, enabling horizontal scaling based on load and RPS"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Service Scaling"}),": Hosted on EKS with CPU-based autoscaling"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Experiment Isolation"}),": Experiments run on separate EKS and vector DB clusters, reducing production cluster complexity"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Indexed-Only Search"}),": The ",(0,s.jsx)(n.code,{children:"search_indexed_only"})," flag ensures queries only search indexed space, avoiding latency from brute-force searches on partially built indexes"]}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"observability",children:"Observability"}),"\n",(0,s.jsx)(n.h3,{id:"metrics-per-model--variant",children:"Metrics (per model + variant)"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Metric"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"avg_similar_candidates"})}),(0,s.jsx)(n.td,{children:"Average number of similarity candidates returned"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"avg_recall"})}),(0,s.jsx)(n.td,{children:"Score of the first similar catalog returned"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Service Latency"}),(0,s.jsx)(n.td,{children:"P99.9 / P99 / P95 / P50"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Service 5xx Count"}),(0,s.jsx)(n.td,{children:"Error rate monitoring"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Vector DB Latency"}),(0,s.jsx)(n.td,{children:"P99.9 / P99 / P95 / P50"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Vector DB QPS"}),(0,s.jsx)(n.td,{children:"Throughput monitoring"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"ScyllaDB Latency"}),(0,s.jsx)(n.td,{children:"P99.9 / P99 / P95 / P90"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Redis Latency"}),(0,s.jsx)(n.td,{children:"P99.9 / P99 / P95 / P90"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Redis Hit %"}),(0,s.jsx)(n.td,{children:"Cache effectiveness"})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"alerts",children:"Alerts"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Alert"}),(0,s.jsx)(n.th,{children:"Threshold"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Indexed Vector Count"}),(0,s.jsx)(n.td,{children:"< 95%"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Events to Failure Topic"}),(0,s.jsx)(n.td,{children:"Rate > 0"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Service 5xx"}),(0,s.jsx)(n.td,{children:"< 10"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Service Latency"}),(0,s.jsx)(n.td,{children:"Model-dependent SLA"})]})]})]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"technology-stack",children:"Technology Stack"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Component"}),(0,s.jsx)(n.th,{children:"Technology"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Language"}),(0,s.jsx)(n.td,{children:"Go"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Vector Database"}),(0,s.jsx)(n.td,{children:"Qdrant (pluggable)"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Embedding Storage"}),(0,s.jsx)(n.td,{children:"ScyllaDB"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Real-Time Aggregation"}),(0,s.jsx)(n.td,{children:"ScyllaDB"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Caching"}),(0,s.jsx)(n.td,{children:"Redis + In-Memory"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Message Queue"}),(0,s.jsx)(n.td,{children:"Kafka"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Configuration"}),(0,s.jsx)(n.td,{children:"ZooKeeper / etcd"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Container Orchestration"}),(0,s.jsx)(n.td,{children:"Kubernetes (EKS)"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Job Orchestration"}),(0,s.jsx)(n.td,{children:"Databricks"})]})]})]})]})}function h(e={}){const{wrapper:n}={...(0,t.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(o,{...e})}):o(e)}}}]); \ No newline at end of file diff --git a/docs/assets/js/5e95c892.6dda521c.js b/docs/assets/js/5e95c892.6dda521c.js new file mode 100644 index 00000000..4dc7ee60 --- /dev/null +++ b/docs/assets/js/5e95c892.6dda521c.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[9647],{9502:(e,s,r)=>{r.r(s),r.d(s,{default:()=>l});r(6540);var c=r(4164),u=r(7559),a=r(5500),d=r(2831),n=r(1656),t=r(4848);function l(e){return(0,t.jsx)(a.e3,{className:(0,c.A)(u.G.wrapper.docsPages),children:(0,t.jsx)(n.A,{children:(0,d.v)(e.route.routes)})})}}}]); \ No newline at end of file diff --git a/docs/assets/js/5e95c892.a6c239e7.js b/docs/assets/js/5e95c892.a6c239e7.js deleted file mode 100644 index 80ddfbad..00000000 --- a/docs/assets/js/5e95c892.a6c239e7.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[9647],{7121:(e,s,r)=>{r.r(s),r.d(s,{default:()=>l});r(6540);var c=r(4164),u=r(5500),a=r(7559),d=r(2831),n=r(1656),t=r(4848);function l(e){return(0,t.jsx)(u.e3,{className:(0,c.A)(a.G.wrapper.docsPages),children:(0,t.jsx)(n.A,{children:(0,d.v)(e.route.routes)})})}}}]); \ No newline at end of file diff --git a/docs/assets/js/621db11d.1a835b77.js b/docs/assets/js/621db11d.1a835b77.js new file mode 100644 index 00000000..f2d6d885 --- /dev/null +++ b/docs/assets/js/621db11d.1a835b77.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[4212],{3250:(t,s,e)=>{e.r(s),e.d(s,{default:()=>m});e(6540);var a=e(4164),o=e(5500),r=e(7559),u=e(6461),l=e(8027),n=e(1463),i=e(1107),c=e(6382);const h={authorListItem:"authorListItem_n3yI"};var g=e(4848);function p({author:t}){return(0,g.jsx)("li",{className:h.authorListItem,children:(0,g.jsx)(c.A,{as:"h2",author:t,count:t.count})})}function d({authors:t}){return(0,g.jsx)("section",{className:(0,a.A)("margin-vert--lg",h.authorsListSection),children:(0,g.jsx)("ul",{children:t.map(t=>(0,g.jsx)(p,{author:t},t.key))})})}function m({authors:t,sidebar:s}){const e=(0,u.uz)();return(0,g.jsxs)(o.e3,{className:(0,a.A)(r.G.wrapper.blogPages,r.G.page.blogAuthorsListPage),children:[(0,g.jsx)(o.be,{title:e}),(0,g.jsx)(n.A,{tag:"blog_authors_list"}),(0,g.jsxs)(l.A,{sidebar:s,children:[(0,g.jsx)(i.A,{as:"h1",children:e}),(0,g.jsx)(d,{authors:t})]})]})}},6461:(t,s,e)=>{e.d(s,{ZD:()=>u,uz:()=>l});e(6540);var a=e(1312),o=e(5846);e(4848);function r(){const{selectMessage:t}=(0,o.W)();return s=>t(s,(0,a.T)({id:"theme.blog.post.plurals",description:'Pluralized label for "{count} posts". Use as much plural forms (separated by "|") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)',message:"One post|{count} posts"},{count:s}))}function u(t){const s=r();return(0,a.T)({id:"theme.blog.tagTitle",description:"The title of the page for a blog tag",message:'{nPosts} tagged with "{tagName}"'},{nPosts:s(t.count),tagName:t.label})}const l=()=>(0,a.T)({id:"theme.blog.authorsList.pageTitle",message:"Authors",description:"The title of the authors page"})}}]); \ No newline at end of file diff --git a/docs/assets/js/621db11d.515621df.js b/docs/assets/js/621db11d.515621df.js deleted file mode 100644 index de839fa8..00000000 --- a/docs/assets/js/621db11d.515621df.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[4212],{3250:(t,s,e)=>{e.r(s),e.d(s,{default:()=>m});e(6540);var a=e(4164),o=e(5500),r=e(7559),u=e(6461),l=e(8027),n=e(1463),i=e(1107),c=e(6382);const h={authorListItem:"authorListItem_n3yI"};var g=e(4848);function p({author:t}){return(0,g.jsx)("li",{className:h.authorListItem,children:(0,g.jsx)(c.A,{as:"h2",author:t,count:t.count})})}function d({authors:t}){return(0,g.jsx)("section",{className:(0,a.A)("margin-vert--lg",h.authorsListSection),children:(0,g.jsx)("ul",{children:t.map((t=>(0,g.jsx)(p,{author:t},t.key)))})})}function m({authors:t,sidebar:s}){const e=(0,u.uz)();return(0,g.jsxs)(o.e3,{className:(0,a.A)(r.G.wrapper.blogPages,r.G.page.blogAuthorsListPage),children:[(0,g.jsx)(o.be,{title:e}),(0,g.jsx)(n.A,{tag:"blog_authors_list"}),(0,g.jsxs)(l.A,{sidebar:s,children:[(0,g.jsx)(i.A,{as:"h1",children:e}),(0,g.jsx)(d,{authors:t})]})]})}},6461:(t,s,e)=>{e.d(s,{ZD:()=>u,uz:()=>l});e(6540);var a=e(1312),o=e(5846);e(4848);function r(){const{selectMessage:t}=(0,o.W)();return s=>t(s,(0,a.T)({id:"theme.blog.post.plurals",description:'Pluralized label for "{count} posts". Use as much plural forms (separated by "|") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)',message:"One post|{count} posts"},{count:s}))}function u(t){const s=r();return(0,a.T)({id:"theme.blog.tagTitle",description:"The title of the page for a blog tag",message:'{nPosts} tagged with "{tagName}"'},{nPosts:s(t.count),tagName:t.label})}const l=()=>(0,a.T)({id:"theme.blog.authorsList.pageTitle",message:"Authors",description:"The title of the authors page"})}}]); \ No newline at end of file diff --git a/docs/assets/js/6479fb86.431b9ea8.js b/docs/assets/js/6479fb86.431b9ea8.js new file mode 100644 index 00000000..4a69b80c --- /dev/null +++ b/docs/assets/js/6479fb86.431b9ea8.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[5579],{3751:e=>{e.exports=JSON.parse('{"archive":{"blogPosts":[{"id":"episodic-memory-for-agents","metadata":{"permalink":"/BharatMLStack/blog/episodic-memory-for-agents","editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/blog/bharatmlstack-history/episodic-memory-for-agents/index.md","source":"@site/blog/bharatmlstack-history/episodic-memory-for-agents/index.md","title":"Beyond Vector RAG: Building Agent Memory That Learns From Experience.","description":"Current agent memory is just search. We built an episodic memory system that tracks outcomes, forms causal links, extracts reasoning heuristics, and actually learns from failure \u2014 without retraining the model.","date":"2026-02-19T00:00:00.000Z","tags":[{"inline":true,"label":"ai-agents","permalink":"/BharatMLStack/blog/tags/ai-agents"},{"inline":true,"label":"memory","permalink":"/BharatMLStack/blog/tags/memory"},{"inline":true,"label":"architecture","permalink":"/BharatMLStack/blog/tags/architecture"},{"inline":true,"label":"llm","permalink":"/BharatMLStack/blog/tags/llm"},{"inline":true,"label":"episodic-memory","permalink":"/BharatMLStack/blog/tags/episodic-memory"}],"readingTime":11.67,"hasTruncateMarker":true,"authors":[{"name":"Adarsha Das","title":"Senior Architect @ Meesho","url":"https://github.com/a0d00kc","imageURL":"https://github.com/a0d00kc.png","key":"adarsha","page":null}],"frontMatter":{"title":"Beyond Vector RAG: Building Agent Memory That Learns From Experience.","description":"Current agent memory is just search. We built an episodic memory system that tracks outcomes, forms causal links, extracts reasoning heuristics, and actually learns from failure \u2014 without retraining the model.","slug":"episodic-memory-for-agents","authors":["adarsha"],"date":"2026-02-19T00:00:00.000Z","tags":["ai-agents","memory","architecture","llm","episodic-memory"]},"unlisted":false,"nextItem":{"title":"LLM Inference Optimization Techniques: Engineering Sub-Second Latency at Scale","permalink":"/BharatMLStack/blog/llm-inference-optimization-sub-sec-latency"}},"content":"![BharatMLStack](./bms.png)\\nAgent memory has come a long way. Persistent context, vector retrieval, knowledge graphs \u2014 the building blocks are real and getting better fast.\\n\\nBut most of what we call \\"memory\\" today is still closer to search: chunk text, embed it, retrieve whatever looks similar at query time. That works well for recalling facts and preferences. It starts to break down when you need an agent to recall what happened last time, learn from a mistake, or avoid repeating a failed approach.\\n\\nWe are trying to experiment something different. An episodic memory system where a frozen LLM \u2014 same weights, no retraining \u2014 produces increasingly better decisions over time because the memory feeding it context is continuously evolving.\\nThen we tested it. The results were interesting.\\n\\n\\n\x3c!-- truncate --\x3e\\n\\n## The Gap Nobody Talks About\\n\\nHere\'s a scenario every engineering team has encountered: AI agent hits a Redis connection pool exhaustion issue. It misdiagnoses it as a database problem. You correct it. Next week, a different service has the exact same failure pattern. The agent makes the exact same mistake.\\n\\nWhy? Because LLMs don\'t learn at inference time. Corrections adjust behavior within a conversation. Once the session ends, the lesson is gone. The model weights haven\'t changed. The next conversation starts from zero.\\n\\nCurrent \\"memory\\" systems don\'t fully address this. They store facts \u2014 user preferences, document chunks, conversation summaries. But facts aren\'t experience. Knowing that \\"Redis connection pools can exhaust under load\\" is different from remembering \\"last time I saw 500 errors under load, I assumed it was the database, I was wrong, it was actually the connection pool, and here\'s the correction I received.\\"\\n\\nThe first is a fact. The second is an episode. The difference matters.\\n\\n## What\'s Wrong With Vector RAG as Memory\\n\\nWe identified five structural gaps in how current agent frameworks handle memory:\\n\\n**No concept of time.** Two events are either semantically similar or they\'re not. The system can\'t represent \\"this happened after that\\" without distorting similarity scores. An agent can\'t reason about sequence or causality.\\n\\n**No concept of situation.** A production incident and a design review might use the same technical vocabulary. Flat vector search can\'t distinguish them. Your agent retrieves planning notes when it should be retrieving incident postmortems.\\n\\n**No outcome tracking.** The system stores *what happened* but not *whether it worked*. A failed approach and a successful one are equally retrievable. The agent has no way to prefer strategies that worked over strategies that didn\'t.\\n\\n**Summaries destroy evidence.** Summarization-based memory compresses experience but discards the reasoning chain. The agent loses the ability to explain *how* it arrived at a conclusion. The audit trail is gone.\\n\\n**No causal links.** Each memory chunk is independent. There\'s no way to express that incident A caused decision B, which led to outcome C, which was corrected by approach D. Without this structure, the agent can\'t traverse chains of reasoning.\\n\\nThese gaps compound. As an agent accumulates more experience, flat vector memory gets noisier, more contradictory, and less useful. The system degrades precisely when it should be improving.\\n\\n## The Architecture: Episodic Memory\\n\\nWe are building a memory system modeled on how human episodic memory works \u2014 not as a metaphor, but as an engineering specification.\\n\\nThe system has four layers:\\n\\n### Layer 1: Immutable Timeline\\n\\nEvery piece of agent experience is recorded as an append-only timeline entry. Each entry carries a semantic embedding (what it means), a timestamp (when it happened), and a state label (what situation the agent was in \u2014 debugging, planning, code review, incident response). Entries are never modified, never deleted, never summarized. This is the source of truth.\\n\\n### Layer 2: Episode Segmentation\\n\\nThe system watches the timeline and detects when one coherent unit of experience ends and another begins \u2014 via state transitions, semantic shifts, temporal gaps, or explicit signals. Each episode is a reference into the timeline (not a copy) with a generated summary, an outcome (SUCCESS, FAILURE, PARTIAL, UNKNOWN), decisions made, assumptions held, and corrections received.\\n\\nThe outcome field is the most important thing that doesn\'t exist in any current memory system. Without it, you can\'t learn from mistakes.\\n\\n### Layer 3: Episodic Graph\\n\\nEpisodes are connected through typed, weighted links: CAUSED_BY, LED_TO, RETRY_OF, LEARNED_FROM, CONTINUATION, CONTRADICTED. Over time, this forms a directed graph that enables traversal by meaning and causality. You can follow the chain: \\"this incident caused that investigation, which led to a failed fix, which was corrected by this approach.\\"\\n\\n### Layer 4: Generalized Facts\\n\\nWhen multiple episodes exhibit consistent patterns, the system extracts reasoning heuristics: \\"When services fail immediately after deployment with no traffic change, investigate configuration errors before connection pool problems.\\" Facts are versioned, never overwritten, and maintain links back to supporting and contradicting episodes. When contradicting evidence accumulates, confidence decreases. When confidence drops below a threshold, the fact is revised \u2014 but the old version is preserved.\\n\\nThe LLM sits above all four layers. At query time, the system assembles structured context \u2014 relevant episodes with outcomes, applicable facts with confidence scores, causal narratives \u2014 and passes it to the LLM for reasoning. The model reasons over structured memory. It doesn\'t store or manage memory.\\n\\n### The Reinforcement Loop\\n\\nThis is where it comes together:\\n\\n1. Agent reasons using retrieved episodes and facts\\n2. Outcome is detected (CI pass/fail, user correction, test result)\\n3. New episode is created with outcome tracking\\n4. Links are created between the retrieved episodes and the new episode\\n5. Facts are reinforced (if outcome aligned) or contradicted (if outcome conflicted)\\n6. If the decision was wrong and corrected, a LEARNED_FROM link is created\\n\\nThe model weights never change. The memory structure evolves continuously. A frozen LLM produces better decisions over time because it receives better context from richer memory.\\n\\n## The Experiment\\n\\nWe built the full system in Python (~1,000 lines) and tested it head-to-head against a baseline flat-vector RAG agent across a 9-round synthetic debugging scenario. Both agents used the identical LLM (Claude Sonnet 4) for reasoning. The only variable was the memory system.\\n\\nThe scenario was designed to test five capabilities:\\n\\n| Round Type | What It Tests | Rounds |\\n|---|---|---|\\n| LEARN | Can the agent build experience from failures? | 1, 2, 4 |\\n| RED HERRING | Can the agent resist applying a pattern when it doesn\'t fit? | 3 |\\n| TEST | Can the agent apply learned patterns to new services? | 5, 6 |\\n| SUBTLE | Can the agent generalize to different symptoms, same root cause? | 7 |\\n| CORRECTION | After being corrected, does the agent adapt? | 8, 9 |\\n\\nRounds 1-4 build experience: three connection pool failures across different services, plus one red herring (a deployment config error that *looks* like a connection pool issue). Rounds 5-7 test whether the agent applies the learned pattern to unfamiliar services and subtle symptom variations. Rounds 8-9 are the critical test: the agent is corrected after misdiagnosing a deployment-correlated error, then tested on a near-identical scenario to see if it adapts.\\n\\n## Results\\n\\n### Decision Accuracy\\n\\n| Round | Type | Episodic Agent | Baseline Agent |\\n|---|---|---|---|\\n| 1 | LEARN | \u2717 | \u2713 |\\n| 2 | LEARN | \u2713 | \u2713 |\\n| 3 | RED HERRING | \u2717 | \u2717 |\\n| 4 | LEARN | \u2713 | \u2713 |\\n| 5 | TEST | **\u2713** | \u2717 |\\n| 6 | TEST | **\u2713** | \u2717 |\\n| 7 | SUBTLE | **\u2713** | \u2717 |\\n| 8 | CORRECTION | \u2713 | \u2713 |\\n| 9 | CORRECTION | \u2713 | \u2713 |\\n| **Total** | | **7/9 (78%)** | **5/9 (56%)** |\\n\\nThe episodic agent won 7-5. A 40% relative improvement in decision accuracy using the exact same LLM.\\n\\n### Where the Gap Opened\\n\\nThe episodic agent\'s advantage concentrated in exactly the rounds designed to test memory quality:\\n\\n**Rounds 5-6 (pattern application):** The episodic agent cited 4 past failure episodes with connection pool exhaustion as root cause, complete with correction annotations. It correctly identified pool exhaustion in new services. The baseline retrieved disconnected chunks and suggested checking timeout configurations \u2014 a pattern it picked up from the Round 3 red herring.\\n\\n**Round 7 (subtle symptoms \u2014 latency increase, no errors):** Both agents had the same evidence available. The episodic agent\'s retrieval surfaced a diverse set of episodes (thanks to MMR diversity filtering) including the Redis pool exhaustion from Round 6, which primed it to recognize that latency without errors can still be pool contention. The baseline defaulted to \\"check recent config changes.\\"\\n\\n**Round 9 (adaptation after correction):** This is the result we\'re most proud of. Look at the episodic agent\'s reasoning:\\n\\n> *\\"Episode 1 directly parallels this situation \u2014 errors spiking immediately after a deployment (v2.4.1 then, v3.1.0 now) with no traffic change. In that case, the root cause was a database migration that dropped an index. The generalized fact confirms that deployment-related issues with immediate onset after version changes are more likely caused by configuration errors or missing dependencies than by connection pool problems.\\"*\\n\\nIt cited a specific past episode by analogy, quoted a generalized fact, and explained *why* this situation matches the deployment pattern rather than the connection pool pattern. The baseline gave a vaguer assessment.\\n\\n### Retrieval Quality\\n\\nThis is where the structural difference is most visible:\\n\\n| Metric | Episodic Agent | Baseline Agent |\\n|---|---|---|\\n| Retrieved items with explicit outcome labels | **100%** | 25% |\\n| Correct pattern applications (Rounds 4-7) | **4/4** | 1/4 |\\n| False positives (Rounds 8-9) | **0** | 0 |\\n\\nEvery item the episodic agent retrieved carried a structured outcome label (SUCCESS or FAILURE) with correction details. Only 25% of the baseline\'s chunks contained any outcome information \u2014 and those were incidental text mentions, not structured labels.\\n\\nThe episodic agent correctly applied the connection pool pattern in all four rounds where it was the root cause, and correctly avoided it in both rounds where it wasn\'t. The baseline applied it correctly once.\\n\\n## What Didn\'t Work\\n\\nTwo things didn\'t work as anticipated:\\n\\n**Round 3 (red herring):** Both agents failed. The symptoms looked like connection pool issues, but the root cause was a deployment config change. At this point, the episodic agent had only seen connection pool episodes \u2014 it had no counter-evidence for deployment-correlated errors. You can\'t distinguish patterns you\'ve only seen one side of. After Round 8 introduced a correction, the agent successfully avoided this mistake in Round 9.\\n\\n**Fact quality variance.** Some extracted facts were specific and actionable (\\"Deployment-related issues with immediate onset are more likely configuration errors\\"). Others were vague (\\"Initial symptom-based diagnosis often leads to misidentifying the root cause\\"). A production system needs a usefulness filter, not just a confidence score.\\n\\n## What This Means\\n\\nThe most important finding isn\'t the accuracy improvement. It\'s that the reinforcement loop closes without retraining.\\n\\nIn the POC, we observed:\\n\\n- Rounds 1-4: Agent encounters failures, episodes recorded with outcomes and corrections\\n- After Round 4: Fact extracted \u2014 \\"Connection pool exhaustion is a common root cause under load\\"\\n- Rounds 5-7: Agent applies the pattern with increasing confidence (fact support count grows)\\n- Round 8: Agent encounters a deployment error, correctly identifies it as config, gets corrected\\n- After Round 8: New fact \u2014 \\"Deployment-related issues with immediate onset are more likely configuration errors\\"\\n- Round 9: Agent receives near-identical scenario, correctly avoids connection pool pattern, cites the Round 8 correction\\n\\nThe model didn\'t change. The memory evolved. That\'s the whole point.\\n\\n## How It Compares to Existing Solutions\\n\\nAgent memory is a fast-moving space with several strong systems, each solving a different slice of the problem:\\n\\n**Mem0** excels at persistent personalization \u2014 extracting user preferences, managing session context, and reducing token costs through intelligent compression. It\'s the most production-ready memory layer available and integrates with nearly every agent framework. Its focus is on remembering about users and conversations rather than learning from task-level outcomes, which is a different problem than the one we\'re exploring here.\\n\\n**Zep/Graphiti** is doing some of the most interesting work in temporal knowledge graphs. Their bi-temporal model \u2014 tracking both when an event occurred and when it was ingested \u2014 addresses a real structural gap in how agent memory handles changing facts over time. Their episode and entity subgraphs share some philosophical DNA with our approach. Where our work diverges is in outcome tracking and reinforcement: we\'re specifically focused on whether a decision worked, and using that signal to update memory structure.\\n\\n**Letta (formerly MemGPT)** pioneered self-editing memory \u2014 giving the LLM tools to manage its own memory blocks. This is a powerful paradigm, and their recent work on \\"Context Repositories\\" and sleep-time compute suggests they\'re actively pushing toward agents that learn over time. Their team has been transparent that experiential learning is an unsolved problem, which is part of what motivated our exploration.\\n\\n**MemRL (Jan 2026 paper)** is the closest to our work academically. It shares the core insight of decoupling stable LLM reasoning from plastic, evolving memory. Their approach uses reinforcement learning to assign utility Q-values to memories, which is elegant but requires training a value function. Our approach is purely structural \u2014 no training step, no Q-values, just graph evolution and LLM-based reasoning over outcomes.\\n\\n\\nThe common thread: most existing systems focus on knowledge persistence \u2014 remembering facts, preferences, and conversation history across sessions. The problem we\'re exploring is experiential learning \u2014 tracking whether past decisions worked, forming causal chains between episodes, and extracting reasoning heuristics that improve over time. These are complementary capabilities that would be needed by an ideal production system.\\n\\n## Try It Yourself\\n\\nThe prototype is available in our experiments directory:\\n\\n```\\nexperiments/episodic-memory-prototype/\\n\u251c\u2500\u2500 memory/ # Timeline, encoder, episodes, graph, facts, retriever, reinforcer\\n\u251c\u2500\u2500 agent/ # Episodic memory agent\\n\u251c\u2500\u2500 baseline/ # Flat vector RAG agent (comparison)\\n\u251c\u2500\u2500 simulator/ # 9-round debugging scenario\\n\u251c\u2500\u2500 eval/ # Head-to-head comparison + scoring\\n\u2514\u2500\u2500 tests/\\n```\\n\\nTo run the comparison:\\n\\n```bash\\ncd experiments/episodic-memory-prototype\\npython -m venv .venv && source .venv/bin/activate\\npip install -r requirements.txt\\nexport ANTHROPIC_API_KEY=sk-ant-...\\npython -m eval.compare\\n```\\n\\nWithout an API key, it runs in heuristic mode (keyword-based decisions). With a key, both agents use Claude Sonnet for reasoning \u2014 that\'s where the quality gap becomes visible.\\n\\n\\n## Conclusion\\nThis is a 9-round synthetic scenario we designed. It demonstrates the poc architecture works end-to-end and shows where episodic memory provides qualitatively different reasoning. It is not a peer-reviewed benchmark and should not be interpreted as a statistically rigorous claim. We\'re publishing the prototype so others can reproduce and extend the evaluation.\\nIf this sparks interest do trigger github discussion.\\n\\n---\\n\\n*The episodic memory prototype is available in `BharatMLStack` repo at `/experiments/episodic-memory-prototype`*"},{"id":"llm-inference-optimization-sub-sec-latency","metadata":{"permalink":"/BharatMLStack/blog/llm-inference-optimization-sub-sec-latency","editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/blog/bharatmlstack-history/llm-inference-optimization/index.md","source":"@site/blog/bharatmlstack-history/llm-inference-optimization/index.md","title":"LLM Inference Optimization Techniques: Engineering Sub-Second Latency at Scale","description":"A practical guide to the optimization techniques behind sub-second LLM inference\u2014covering paged KV caching, INT4 AWQ and FP8 quantization, kernel fusion, inflight batching, parallelism strategies, and speculative decoding, with production benchmarks on L4 and A100 GPUs.","date":"2025-06-02T00:00:00.000Z","tags":[{"inline":true,"label":"llm","permalink":"/BharatMLStack/blog/tags/llm"},{"inline":true,"label":"vllm","permalink":"/BharatMLStack/blog/tags/vllm"},{"inline":true,"label":"tensorrt-llm","permalink":"/BharatMLStack/blog/tags/tensorrt-llm"},{"inline":true,"label":"mlplatform","permalink":"/BharatMLStack/blog/tags/mlplatform"},{"inline":true,"label":"meesho","permalink":"/BharatMLStack/blog/tags/meesho"},{"inline":true,"label":"bharatmlstack","permalink":"/BharatMLStack/blog/tags/bharatmlstack"}],"readingTime":4.88,"hasTruncateMarker":false,"authors":[{"name":"Jaya Kumar","title":"Lead ML Engineer @ Meesho","url":"https://github.com/jayakommuru","imageURL":"https://github.com/jayakommuru.png","key":"jaya","page":null}],"frontMatter":{"title":"LLM Inference Optimization Techniques: Engineering Sub-Second Latency at Scale","description":"A practical guide to the optimization techniques behind sub-second LLM inference\u2014covering paged KV caching, INT4 AWQ and FP8 quantization, kernel fusion, inflight batching, parallelism strategies, and speculative decoding, with production benchmarks on L4 and A100 GPUs.","authors":["jaya"],"slug":"llm-inference-optimization-sub-sec-latency","date":"2025-6-2","tags":["llm","vllm","tensorrt-llm","mlplatform","meesho","bharatmlstack"]},"unlisted":false,"prevItem":{"title":"Beyond Vector RAG: Building Agent Memory That Learns From Experience.","permalink":"/BharatMLStack/blog/episodic-memory-for-agents"},"nextItem":{"title":"Designing a Production-Grade LLM Inference Platform: From Model Weights to Scalable GPU Serving","permalink":"/BharatMLStack/blog/multi-engine-llm-inferencing-platform"}},"content":"![BharatMLStack](./bms.png)\\nRaw execution of Large Language Models is inherently expensive and memory-intensive. To achieve sub-second latency and high throughput, we implement a multi-layered optimization strategy that targets the entire inference stack\u2014from memory management to kernel execution.\\n\\n## 1. Advanced Memory Management: Paged & Prefix KV Caching\\n\\nThe most significant bottleneck in LLM inference is not always compute, but memory bandwidth\u2014specifically managing the Key-Value (KV) cache.\\n\\n### Paged KV caching\\n\\nStandard caching suffers from fragmentation. We use **Paged KV caching**, which operates similarly to an operating system\'s virtual memory: the KV cache is divided into non-contiguous blocks. This lets us serve larger batch sizes without running out of memory.\\n\\n### KV cache quantization\\n\\nTo further maximize available memory, we implement **KV cache quantization** (e.g., FP8). By compressing stored attention keys and values from 16-bit to 8-bit, we nearly double the effective context window capacity of the GPU, allowing longer conversations or larger batches without materially degrading quality.\\n\\n### Prefix caching (the \\"voice bot\\" optimizer)\\n\\nFor use cases like GenAI voice bots where the system prompt (e.g., \\"You are a helpful assistant...\\") is static across thousands of requests, we enable **prefix caching**.\\n\\n- **Impact**: By reusing pre-computed KV states for common prefixes, we achieve a cache hit rate of ~90%. This reduces **Time To First Token (TTFT)** by skipping redundant computation of the system prompt.\\n\\n## 2. Aggressive Quantization (INT4 AWQ & FP8)\\n\\nRunning models in their native 16-bit precision (BF16) restricts maximum batch size and throughput. We use quantization to shrink model weights without sacrificing accuracy.\\n\\n### INT4 AWQ (Activation-aware Weight Quantization)\\n\\nFor the Llama 3 family, we use **AWQ** to compress weights to 4 bits. This reduces model size by ~75%, allowing larger models to fit into L4 GPU memory and significantly improving token generation speed.\\n\\n### FP8 precision\\n\\nFor NVIDIA Hopper (H100) architectures, we are exploring **FP8 quantization**, leveraging native FP8 tensor cores to accelerate matrix multiplications while maintaining a higher dynamic range than integer quantization.\\n\\n- **Verification**: We validate quantized models by comparing dot-product similarity of embeddings against the FP16 baseline, consistently achieving **>99% similarity**.\\n\\n## 3. Kernel Fusion & Custom Plugins\\n\\nTo minimize overhead from launching thousands of small GPU operations, we fuse them into monolithic kernels using NVIDIA TensorRT plugins.\\n\\n- **Flash attention & FMHA**: We enable **Fused Multi-Head Attention (FMHA)** combined with flash attention to reduce memory reads/writes.\\n- **GEMM plugins**: We use specialized **GEMM** plugins to accelerate transformer linear layers.\\n- **Removing input padding**: Instead of padding short sequences to match the longest, we remove input padding so the GPU processes only valid tokens.\\n\\n## 4. Inflight (Continuous) Batching\\n\\nTraditional static batching waits for all requests in a batch to finish before returning results\u2014so one long response delays everyone else.\\n\\nWe implement **inflight batching**: as soon as one request completes, its slot is freed and filled by a new request from the queue. This keeps GPUs saturated and decouples latency of short queries from long ones.\\n\\n## 5. Parallelism Strategies: Scaling Beyond One GPU\\n\\nFor large models (e.g., 70B+ parameters) that cannot fit into the VRAM of a single GPU, we use parallelism strategies.\\n\\n- **Tensor parallelism (TP)**: Split weight matrices across multiple GPUs (e.g., 4\xd7 L4 or 8\xd7 A100). Each GPU computes a shard and outputs are reduced at every layer.\\n- **Pipeline parallelism (PP)**: Split model layers across GPUs to pipeline compute (e.g., while one GPU computes later layers for Request A, another starts early layers for Request B).\\n\\n## 6. Speculative Decoding\\n\\nTo reduce inter-token latency (ITL), we explore **speculative decoding**.\\n\\n- **Mechanism**: A smaller, faster \\"draft\\" model speculatively generates a short token sequence (e.g., 5 tokens).\\n- **Verification**: The larger target model verifies those tokens in one parallel forward pass. If correct, we effectively generate multiple tokens per large-model step; if not, we discard and regenerate. This is effective for predictable text, improving perceived generation speed.\\n\\n## Few Benchmarks\\n\\nBelow are a couple of representative use cases and performance numbers.\\n\\n### Search query rewriting\\n\\n- **LLM**: Fine-tuned llama-3.2-1B\\n- **Input & output token length**: ~10\u201320\\n- **Response type**: Non-streaming\\n\\n| Inference runtime | Hardware | Max requests/sec | Max p99 latency |\\n| --- | --- | ---: | ---: |\\n| TensorRT-LLM | 4 \xd7 L4 GPUs (multi-GPU) | 1000 | 95 ms |\\n| TensorRT-LLM | 1 \xd7 A100 40 GB GPU | 1000 | 69 ms |\\n\\n### Voice bot query\\n\\n- **LLM**: Llama-3.1-8B\\n- **Input token length**: ~1900\u20132000\\n- **Output token length**: ~200\\n- **Response type**: Streaming\\n\\n| Inference runtime | Concurrency | p99 TTFT (ms) | p99 ITL (ms) | Token throughput (tokens/sec) | Request throughput (req/sec) | Hardware |\\n| --- | ---: | ---: | ---: | ---: | ---: | --- |\\n| TensorRT-LLM | 1 | 36.27 | 22.78 | 45.66 | 0.23 | L4 |\\n| TensorRT-LLM | 2 | 49.81 | 23.21 | 89.37 | 0.45 | L4 |\\n| TensorRT-LLM | 4 | 55.33 | 36.62 | 153.39 | 0.78 | L4 |\\n| TensorRT-LLM | 8 | 66.5 | 39.11 | 279.88 | 1.47 | L4 |\\n| TensorRT-LLM | 16 | 131.8 | 30.39 | 547.8 | 2.77 | L4 |\\n| TensorRT-LLM | 32 | 277.22 | 48.02 | 925.7 | 4.78 | L4 |\\n| TensorRT-LLM | 64 | 498.52 | 71.62 | 1,164.40 | 6.2 | L4 |\\n| TensorRT-LLM | 128 | 677.31 | 120.37 | 1,445.18 | 7.69 | L4 |\\n| TensorRT-LLM | 256 | 1,926.31 | 216.88 | 1,600.81 | 8.52 | L4 |\\n| TensorRT-LLM | 1 | 21.17 | 9.24 | 130.05 | 0.68 | A100 |\\n| TensorRT-LLM | 2 | 25.78 | 9.21 | 264.5 | 1.35 | A100 |\\n| TensorRT-LLM | 4 | 28.52 | 10.99 | 437.69 | 2.27 | A100 |\\n| TensorRT-LLM | 8 | 34.4 | 12.61 | 760.49 | 3.96 | A100 |\\n| TensorRT-LLM | 16 | 68.03 | 14.32 | 1,343.80 | 7.01 | A100 |\\n| TensorRT-LLM | 32 | 185.96 | 16.82 | 2,287.30 | 11.92 | A100 |\\n| TensorRT-LLM | 64 | 136.87 | 21.17 | 3,625.22 | 18.89 | A100 |\\n| TensorRT-LLM | 128 | 463.78 | 34.15 | 4,456.51 | 23.24 | A100 |\\n| TensorRT-LLM | 256 | 890.12 | 59.18 | 5,188.24 | 27.05 | A100 |\\n\\n## Conclusion\\n\\nHigh-performance LLM inference is fundamentally a systems engineering problem: memory efficiency, kernel execution, batching strategy, and parallelism determine real-world latency and throughput. Techniques such as paged KV caching, aggressive quantization, kernel fusion, and inflight batching improve GPU utilization while reducing latency and memory pressure.\\n\\nThese optimizations enable the platform to deliver sub-second responses, sustain high concurrency, and efficiently serve both lightweight and long-context workloads. By continuously optimizing across the full inference stack, we keep LLM serving scalable, cost-efficient, and production-ready for real-time AI applications."},{"id":"multi-engine-llm-inferencing-platform","metadata":{"permalink":"/BharatMLStack/blog/multi-engine-llm-inferencing-platform","editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/blog/bharatmlstack-history/llm-inferencing-platform/index.md","source":"@site/blog/bharatmlstack-history/llm-inferencing-platform/index.md","title":"Designing a Production-Grade LLM Inference Platform: From Model Weights to Scalable GPU Serving","description":"A deep dive into building a production-grade LLM inference platform\u2014covering the full LLMOps lifecycle from model onboarding and automated compilation to multi-engine serving with TensorRT-LLM, vLLM, and Dynamo, along with cold-start mitigation and LLM-specific observability.","date":"2025-03-29T00:00:00.000Z","tags":[{"inline":true,"label":"llm","permalink":"/BharatMLStack/blog/tags/llm"},{"inline":true,"label":"vllm","permalink":"/BharatMLStack/blog/tags/vllm"},{"inline":true,"label":"tensorrt-llm","permalink":"/BharatMLStack/blog/tags/tensorrt-llm"},{"inline":true,"label":"mlplatform","permalink":"/BharatMLStack/blog/tags/mlplatform"},{"inline":true,"label":"meesho","permalink":"/BharatMLStack/blog/tags/meesho"},{"inline":true,"label":"bharatmlstack","permalink":"/BharatMLStack/blog/tags/bharatmlstack"}],"readingTime":13.31,"hasTruncateMarker":false,"authors":[{"name":"Jaya Kumar","title":"Lead ML Engineer @ Meesho","url":"https://github.com/jayakommuru","imageURL":"https://github.com/jayakommuru.png","key":"jaya","page":null}],"frontMatter":{"title":"Designing a Production-Grade LLM Inference Platform: From Model Weights to Scalable GPU Serving","description":"A deep dive into building a production-grade LLM inference platform\u2014covering the full LLMOps lifecycle from model onboarding and automated compilation to multi-engine serving with TensorRT-LLM, vLLM, and Dynamo, along with cold-start mitigation and LLM-specific observability.","authors":["jaya"],"slug":"multi-engine-llm-inferencing-platform","date":"2025-3-29","tags":["llm","vllm","tensorrt-llm","mlplatform","meesho","bharatmlstack"]},"unlisted":false,"prevItem":{"title":"LLM Inference Optimization Techniques: Engineering Sub-Second Latency at Scale","permalink":"/BharatMLStack/blog/llm-inference-optimization-sub-sec-latency"},"nextItem":{"title":"Cracking the Code: Scaling Model Inference & Real-Time Embedding Search","permalink":"/BharatMLStack/blog/scaling-model-inference-and-embedding-search"}},"content":"![BharatMLStack](./bms.png)\\nServing large language models in production introduces new challenges across infrastructure, performance optimization, and operational lifecycle management. The LLM Inference Platform addresses these challenges by providing a unified system for deploying and managing open-source and fine-tuned LLMs at scale.\\n\\nThe platform implements a complete LLMOps lifecycle \u2014 from model registration and automated compilation to deployment, runtime optimization, and monitoring. Designed as a self-service environment, users can onboard models directly from open repositories such as Hugging Face or upload custom fine-tuned models, and deploy them using a single-click workflow with no manual infrastructure or configuration steps required.\\n\\nIn addition to fully automated deployment, the platform allows users to select and apply custom inference optimization techniques \u2014 such as quantization strategies, batching configurations, and runtime-specific performance enhancements \u2014 enabling teams to balance latency, throughput, and cost based on their use case. The goal is to reduce operational friction while enabling high-performance, production-grade LLM inference.\\n\\n## Why LLM Inference Is not just bigger ML model serving\\n\\nLarge language model (LLM) inference introduces a fundamentally different set of challenges compared to traditional machine learning inference. While classical ML models typically perform a single forward pass to produce a fixed prediction, LLMs operate as autoregressive systems, generating outputs token by token based on previously generated context. This difference dramatically changes how inference systems must be designed, optimized, and scaled.\\n\\n### Autoregressive Generation and Sequential Computation:\\n\\nUnlike traditional models such as classifiers or recommenders \u2014 where inference cost is relatively constant \u2014 LLMs generate responses incrementally. Each new token depends on all previously generated tokens, making inference inherently sequential and dynamic. This means latency and compute requirements vary significantly depending on prompt length and output size, introducing complexity in scheduling and resource allocation.\\nBecause tokens cannot be generated fully in parallel during decoding, GPUs may become underutilized without specialized batching and scheduling strategies. This has led to the development of dedicated LLM inference engines optimized for token-level execution.\\n\\n### Prefill and Decode Phases:\\n\\nLLM inference typically consists of two distinct stages:\\n\\n- Prefill phase \u2014 the model processes the input prompt and builds internal representations. This stage is compute-heavy and highly parallelizable.\\n- Decode phase \u2014 the model generates tokens sequentially, predicting one token at a time using previously generated context.\\n\\nThe decode stage often becomes memory-bound rather than compute-bound, which creates new performance bottlenecks compared to traditional ML workloads.\\n\\n### Context Management and KV Caching:\\n\\nAnother fundamental difference lies in how LLMs maintain context. Transformer-based models rely on attention mechanisms that require access to past token representations. To avoid recomputing these representations repeatedly, inference engines use key-value (KV) caching, which stores intermediate activations from previous tokens.\\nKV caching significantly improves performance by eliminating redundant computation, but it introduces new challenges:\\n\\n- Memory consumption grows with sequence length and batch size\\n- GPU memory becomes a critical bottleneck\\n- Efficient memory management becomes essential for scaling concurrent requests\\n\\nThis tradeoff between compute efficiency and memory usage is unique to LLM inference workloads.\\n\\n### Dynamic and Irregular Workloads:\\n\\nTraditional ML inference typically operates on fixed-size inputs with predictable latency. In contrast, LLM requests vary widely in prompt length, output length, and runtime behavior. As a result:\\n\\n- Batch sizes must be dynamic rather than static\\n- Requests may enter and leave batches asynchronously\\n- Scheduling systems must continuously rebalance workloads to maximize GPU utilization\\n\\nThese characteristics require specialized serving architectures that differ significantly from standard ML serving pipelines.\\n\\n### Streaming and User Experience Constraints:\\n\\nAnother distinguishing factor is the expectation of real-time streaming responses. Instead of returning a single output, LLM systems often stream tokens to users as they are generated. \\nBecause of these differences \u2014 sequential generation, growing memory requirements, dynamic workloads, and streaming constraints \u2014 LLM inference cannot be treated as a simple extension of existing ML serving systems. Production platforms must incorporate specialized runtime engines, advanced optimization techniques, and observability tailored specifically to LLM workloads.\\n\\n## LLMOps: High-Level Architecture \\n\\n![LLM Architecture](./llm-plat.png)\\n\\nThe LLM Inference Framework is designed as a fully automated, end-to-end system for deploying and operating open-source and fine-tuned large language models at scale. The architecture abstracts the complexity of model optimization, hardware selection, deployment, and runtime management into a unified workflow that enables users to move from raw model weights to production-ready inference endpoints with minimal manual intervention.\\n\\nOur LLM Inference Framework is architected not just as a serving engine, but as a complete lifecycle management system. As illustrated in the high-level design below, the platform automates the journey of a model through seven distinct stages, ensuring reproducibility, performance, and scalability.\\n\\n1. Onboarding & Registration (The Source of Truth)\\n\\n The lifecycle begins with the Data Scientist or engineer.\\n\\n - Model Ingestion: Users onboard models\u2014whether open-source (Hugging Face, NeMo) or internally fine-tuned\u2014via the Truffle Box SDK/UI.\\n - LLM + Prompt Registry: Unlike traditional systems that only track model weights, our registry is a unified control plane. It stores both the Model Artifacts and the Prompt Templates. This allows Data Scientists to register and version-control prompts (e.g., \\"customer_support_v2\\") independently of the application code.\\n\\n2. The \\"Black Box\\" Build Engine\\n\\n Once a model is registered, the Automated LLM Compiler + Quantizer Module kicks off a background job on ephemeral GPU resources.\\n\\n - Transformation: The raw model is converted into a TRT-LLM Checkpoint.\\n - Quantization: The system automatically applies quantization algorithms (like INT4 AWQ or FP8) to reduce memory footprint.\\n - Engine Building: Finally, it compiles a highly optimized TRT Engine specifically tuned for the target hardware.\\n\\n3. Intelligent Profiling & Validation\\n\\n Before deployment, the new engine passes through the Hardware & Inference Runtime Profiler.\\n\\n - Benchmarking: This module empirically tests the engine against various hardware configurations (L4 vs. A100) and runtimes (TRT-LLM vs. vLLM).\\n - Optimization: It recommends the optimal configuration that meets latency SLAs (Time-To-First-Token) while minimizing cost.\\n\\n4. Smart Artifact Generation & Distribution\\n\\n To solve the Kubernetes \\"Cold Start\\" problem, the LLM Serving Artifacts Generation module packages the model using a bifurcated strategy:\\n\\n - Standard Models: Artifacts are uploaded to Cloud Storage (GCS) and downloaded by pods at startup.\\n - Very Large Models: For massive models (>8GB) where network downloads are too slow, the system pre-caches the model onto Secondary Boot Disks. These disks are attached directly to new GPU nodes during autoscaling, eliminating download wait times.\\n\\n5. Image Streaming & Deployment\\n\\n Simultaneously, the inference runtime container images are pulled from the Artifact Registry.\\n\\n - Image Streaming: We utilize container image streaming to allow pods to start initializing while the massive Triton/Dynamo container layers are still downloading, further shaving seconds off the startup time. link\\n\\n6. The Inference Runtime (Kubernetes)\\n\\n The workload lands on Kubernetes with Autoscaling.\\n\\n - Dynamic Backends: Depending on the profile generated in Stage 3, the pod initializes either TensorRT-LLM (for throughput) or vLLM (for flexibility), or spins up a Dynamo worker for distributed inference.\\n - Data Loading: The pod either downloads the model from Cloud Storage or mounts the pre-warmed Secondary Boot Disk (\\"Pull from Disk\\").\\n\\n7. Client Interaction & Observability\\n\\n Finally, the LLM Inference Client executes the request.\\n\\n - Prompt Injection: The client pulls the specific prompt template ID from the Registry, ensuring the exact versioned instructions are used.\\n - Streaming Response: The request is sent via gRPC, and tokens are streamed back to the user in real-time.\\n\\n8. Observability: Monitoring the Pulse of GenAI\\n\\n In traditional microservices, success is measured by CPU utilization and request latency (p99). For Large Language Models, these metrics are insufficient. A user doesn\'t care if the GPU is at 80% utilization; they care about how fast the first word appears and how smoothly the rest of the sentence follows.\\n\\n To capture the true user experience, our platform instrumentation focuses on three critical LLM-specific metrics:\\n\\n 1. Time to First Token (TTFT)\\n - Definition: TTFT measures the time elapsed from the moment a request is received until the very first token is generated and streamed back to the user.\\n - Why it matters: This represents the \\"Prefill Phase\\" latency\u2014the time the model takes to process the input prompt and load weights. A high TTFT makes the application feel unresponsive or \\"hung.\\"\\n - Optimization: We closely monitor TTFT to ensure our Prefix Caching is effective (aiming for high cache hitrates), which drastically lowers this metric by skipping redundant prompt processing.\\n\\n 2. Inter-Token Latency (ITL)\\n - Definition: ITL measures the average time interval between the generation of consecutive tokens during the \\"Decode Phase\\".\\n - Why it matters: This defines the \\"perceived speed\\" of reading. Even if the first token is fast (low TTFT), high ITL makes the text generation look \\"jerky\\" or slow to the user.\\n - Benchmarks: In our testing with Llama 3.1, we track p99 ITL to ensure it stays below human reading speeds to maintain a natural conversational flow.\\n\\n 3. Token Throughput vs. Request Throughput\\n - We distinguish between two types of throughput to balance system efficiency with user load:\\n - Token Throughput (tokens/sec): The total number of tokens generated across all concurrent requests. This measures the raw compute efficiency of the GPU and the effectiveness of batching.\\n - Request Throughput (req/sec): The number of distinct user queries served per second. We use this to determine autoscaling thresholds, ensuring we scale out before the queue depth impacts ITL.\\n\\n 4. The Monitoring Stack\\n - Real-time Dashboards: We utilize Grafana to visualize these streaming metrics in real-time, allowing on-call engineers to spot \\"slow generation\\" incidents that generic \\"500 error\\" alerts would miss.\\n - Request Tracing: Since Triton Inference Server does not log request payloads by default, we integrate a Helix Client to asynchronously publish request logs to Log Tables. This allows us to trace a specific \\"slow\\" request back to its prompt to understand if a complex input caused the latency spike.\\n\\n## Supported Inference backends (TensorRT LLM, Dynamo & vLLM)\\n\\nTailored for the Use Case: We do not believe in a \\"one-size-fits-all\\" approach to inference. Different use cases\u2014whether a real-time voice bot requiring ultra-lowsub-second latency or a massive reasoning task requiring huge context windows\u2014demand different runtime characteristics. Our platform is designed to be runtime-agnostic, allowing us to automatically select and tailor the best engine based on the specific requirements of the application:\\n\\n1. TensorRT-LLM: The High-Performance Standard\\n\\n Suitable for: High-throughput production workloads where latency is critical (e.g., customer support chat, real-time voice bots).\\n\\n TensorRT-LLM serves as our default backend for these scenarios. Our internal benchmarks on Llama 3.1 and 3.2 models demonstrated that a tuned TensorRT-LLM engine significantly outperforms standard runtimes, especially when utilizing INT4 AWQ and FP8 quantization .\\n\\n Key optimizations we tailor for these high-load cases include:\\n\\n - Optimized execution via TensorRT engine compilation\\n - Quantization-aware execution for reduced memory usage and improved throughput\\n - Inflight Batching: Allowing requests to be processed continuously without waiting for the entire batch to finish, drastically improving GPU utilization .\\n - Custom Plugins: Enabling specific NVIDIA plugins like the GEMM plugin and GPT Attention plugin to accelerate matrix multiplications and attention mechanisms .\\n\\n2. Dynamo: Distributed Inference for Reasoning Models\\n\\n Suitable for: Very large \\"reasoning\\" models (70B+) or scenarios requiring massive context windows where a single GPU\'s memory is insufficient.\\n\\n For these memory-bound tasks, we utilize Dynamo, a low-latency distributed inference framework . Unlike monolithic servers, Dynamo disaggregates the inference process to scale resources horizontally:\\n\\n - KV Aware Routing: A specialized router directs requests to workers that already hold the relevant Key-Value (KV) cache, minimizing redundant computation .\\n - Prefill vs. Decode Split: The workload is divided into Prefill Workers (processing the prompt) and Decode Workers (generating tokens), allowing us to scale the compute-heavy \\"reading\\" phase independently from the memory-heavy \\"writing\\" phase .\\n - Distributed execution across multiple GPU resources\\n\\n3. vLLM: The Flexible Baseline\\n\\n Suitable for: Rapid prototyping, testing new model architectures, or low-traffic internal tools where ease of deployment outweighs raw throughput.\\n\\n While TensorRT-LLM is optimized for maximum speed, vLLM provides a robust and flexible baseline .\\n\\n - High throughput through dynamic batching and efficient memory utilization\\n - Paged KV cache management for handling long contexts and concurrent requests\\n - Strong support for open-source model ecosystems\\n - Rapid Adoption: It allows us to onboard new model architectures immediately without waiting for a custom TensorRT build.\\n - Benchmarking Insight: In our internal tests, vLLM provided a strong baseline but often lacked the specific max-token optimizations present in our custom TRT engines . We use it strategically for initial testing before committing to a full TensorRT optimization pipeline.\\n\\n## Conclusion\\n\\nLarge language model inference introduces a fundamentally new class of infrastructure challenges\u2014where performance is governed not just by raw compute, but by memory efficiency, intelligent scheduling, runtime specialization, and lifecycle automation. Unlike traditional ML serving, LLM inference requires systems that understand token-level execution, manage rapidly growing context state, and continuously balance latency, throughput, and cost under highly dynamic workloads.\\n\\nThe LLM Inference Framework addresses these challenges by transforming inference into a fully automated, reproducible lifecycle\u2014from model onboarding and compilation to deployment, optimization, and observability. By integrating automated quantization and engine compilation, intelligent runtime selection, cold-start mitigation strategies, and LLM-specific observability metrics such as Time-to-First-Token and Inter-Token Latency, the platform ensures both high performance and operational simplicity.\\n\\nEqually important, the framework is designed with flexibility and future evolution in mind. Its runtime-agnostic architecture enables seamless adoption of emerging inference engines, hardware accelerators, and optimization techniques without requiring platform redesign. This ensures that teams can continuously leverage advancements in the rapidly evolving LLM ecosystem while maintaining consistent operational workflows.\\n\\nUltimately, the goal of the platform is to make production-scale LLM deployment as seamless and reliable as traditional software deployment\u2014allowing teams to focus on building intelligent applications rather than managing infrastructure complexity. By combining lifecycle automation, runtime optimization, and deep observability, the LLM Inference Framework provides a scalable foundation for delivering fast, cost-efficient, and production-ready LLM experiences.\\n\\n## Future Explorations\\n\\nWhile we have achieved significant milestones in latency and throughput, the landscape of GenAI is evolving rapidly. Our roadmap focuses on increasing flexibility, reducing costs, and enhancing reliability for enterprise-grade workloads. Here is what we are building next:\\n\\n- TPU Support: To diversify our hardware supply chain and further optimize cost-per-token, we are evaluating Google Cloud TPUs to bake it into our platform. By leveraging the JAX and PyTorch/XLA ecosystems, we aim to unlock the massive throughput potential of TPU v5e chips, particularly for our open-source Llama models. This will allow the hardware profiler to dynamically choose between NVIDIA GPUs and Google TPUs based on real-time availability and price-performance metrics.\\n- Multi-LoRA Serving (Serverless Experience): Currently, deploying a fine-tuned model requires a dedicated GPU. We are building support for Multi-LoRA serving, which will allow us to serve hundreds of unique, fine-tuned adapters on top of a single frozen base model. This will drastically reduce costs for multi-tenant applications, enabling a \\"serverless\\" experience where specific fine-tunes are hot-swapped instantly per request.\\n- Spot Instance Orchestration: To further optimize cloud costs, we are developing fault-tolerant mechanisms to run inference workloads on Spot Instances. By implementing aggressive checkpointing and seamless request draining, we aim to leverage cheaper, preemptible compute capacity without interrupting the user\'s streaming experience.\\n- Semantic Caching Layer: We plan to move beyond standard Prefix Caching to implement Semantic Caching. By using a vector database to fetch responses for semantically similar queries (e.g., \\"How do I reset my password?\\" vs. \\"Password reset steps\\"), we can bypass the GPU entirely for repetitive queries, reducing latency to near-zero.\\n- Context-Aware Autoscaling: Standard CPU/GPU utilization metrics are often insufficient signals for scaling LLMs. We are working on KV-cache pressure metrics for autoscaling. This ensures that we scale out before the memory fills up, preventing eviction-based slowdowns during traffic spikes.\\n- Online Evaluation & Guardrails: We are integrating a lightweight \\"Trust Layer\\" into the proxy. This will allow for low-latency input/output filtering (Guardrails) and asynchronous \\"LLM-as-a-Judge\\" evaluation pipelines to monitor response quality in production, not just system health."},{"id":"scaling-model-inference-and-embedding-search","metadata":{"permalink":"/BharatMLStack/blog/scaling-model-inference-and-embedding-search","editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/blog/bharatmlstack-history/scaling-model-inference-and-embedding-search/index.md","source":"@site/blog/bharatmlstack-history/scaling-model-inference-and-embedding-search/index.md","title":"Cracking the Code: Scaling Model Inference & Real-Time Embedding Search","description":"How Meesho scaled model inference with self-hosted Triton on GKE\u2014slashing latency and costs by 65%\u2014and built a real-time embedding search system on Qdrant to power personalized recommendations at scale.","date":"2024-05-21T00:00:00.000Z","tags":[{"inline":true,"label":"model-inference","permalink":"/BharatMLStack/blog/tags/model-inference"},{"inline":true,"label":"embedding-search","permalink":"/BharatMLStack/blog/tags/embedding-search"},{"inline":true,"label":"mlplatform","permalink":"/BharatMLStack/blog/tags/mlplatform"},{"inline":true,"label":"meesho","permalink":"/BharatMLStack/blog/tags/meesho"},{"inline":true,"label":"bharatmlstack","permalink":"/BharatMLStack/blog/tags/bharatmlstack"}],"readingTime":3.55,"hasTruncateMarker":false,"authors":[{"name":"Aditya Kumar","title":"Lead Software Engineer @ Meesho","url":"https://github.com/Adit2607","imageURL":"https://github.com/Adit2607.png","key":"aditya","page":null},{"name":"Jaya Kumar","title":"Lead ML Engineer @ Meesho","url":"https://github.com/jayakommuru","imageURL":"https://github.com/jayakommuru.png","key":"jaya","page":null},{"name":"Adarsha Das","title":"Senior Architect @ Meesho","url":"https://github.com/a0d00kc","imageURL":"https://github.com/a0d00kc.png","key":"adarsha","page":null}],"frontMatter":{"title":"Cracking the Code: Scaling Model Inference & Real-Time Embedding Search","description":"How Meesho scaled model inference with self-hosted Triton on GKE\u2014slashing latency and costs by 65%\u2014and built a real-time embedding search system on Qdrant to power personalized recommendations at scale.","authors":["aditya","jaya","adarsha"],"slug":"scaling-model-inference-and-embedding-search","date":"2024-05-21T00:00:00.000Z","tags":["model-inference","embedding-search","mlplatform","meesho","bharatmlstack"]},"unlisted":false,"prevItem":{"title":"Designing a Production-Grade LLM Inference Platform: From Model Weights to Scalable GPU Serving","permalink":"/BharatMLStack/blog/multi-engine-llm-inferencing-platform"},"nextItem":{"title":"Building Meesho\u2019s ML Platform: Lessons from the First-Gen System (Part 2)","permalink":"/BharatMLStack/blog/building-meeshos-mlplatform-lessons-from-first-gen"}},"content":"![BharatMLStack](./bms.png)\\nBy mid-2023, we had transformed our ML stack\u2014building a real-time feature store, optimizing model retrieval, and fine-tuning ranking. But two critical gaps remained:\\n\\n- \ud83d\udd39 Scaling model inference without hitting infrastructure roadblocks\\n- \ud83d\udd39 Moving embedding search from batch to real-time for candidate generation\\n\\nHere\u2019s how we tackled these last-mile challenges, broke free from infrastructure constraints, and built a cost-efficient, high-performance system.\\n\\n## Breaking Free from the Scalability Ceiling\\n\\n### The Model Serving Bottleneck\u2014A Wake-Up Call\\n\\nJuly 2023. With just months left for the Mega Blockbuster Sale (MBS), we noticed a serious issue\u2014scaling our model-serving infrastructure was taking 10\u201315 minutes. In real-time ML, that\u2019s an eternity.\\nIn one of our war rooms, we ran a quick experiment:\\n\\n- \ud83d\ude80 We deployed an XGBoost model on a self-hosted Triton Inference Server running on a 16-core machine.\\n- \ud83d\ude80 Fired requests and compared the outputs with our existing cloud-hosted setup.\\n- \ud83d\ude80 The results matched\u2014perfectly.\\n\\nThat moment changed everything. We prepped a backup Triton setup on EKS, just in case our cloud provider couldn\'t allocate enough compute resources in time. Luckily, they did\u2014but the seed was planted.\\nThen in October, just two weeks before MBS, we got an alarming response from our infrastructure team:\\n \\"Node availability may be an issue.\\"\\nWith no time to waste, we moved 30% of real-time ML traffic to our self-hosted Triton cluster. The results?\\n\\n- \u2705 p99 latency dropped from 90\u2013100ms to 30\u201340ms\\n- \u2705 Triton handled significantly higher throughput on fewer resources\\n- \u2705 No model changes were needed\\n\\nMBS ran without a hitch, proving that self-hosted inference was the way forward.\\n\\n### Scaling Triton on GKE\\n\\nThis left us with two choices:\\n\\n- 1\ufe0f\u20e3 Port models to a managed cloud inference service, investing time in learning a new deployment stack\\n- 2\ufe0f\u20e3 Scale our existing Triton setup on GKE, optimizing for cost and performance\\n\\nWe went with Option 2\u2014and it slashed inference costs to 35% of what we previously paid, while giving us full control over scaling and optimizations.\\n\\n### Fixing the Cold Start Problem\\n\\nAs we onboarded more deep learning (DL) models, we hit a new bottleneck, new inference pods took 7\u20139 minutes to spin up.\\n\\nAfter profiling, we found the culprits:\\n\\n- Triton\u2019s base image\u2014a massive 5GB\\n- Model binaries\u2014often 1GB+\\n- Startup delay\u2014mostly due to downloading and initializing these assets\\n\\nTo fix this, we built a lightweight Triton image, stripping unused components and shrinking the size to 900MB. This cut cold start times drastically, making auto-scaling faster and smoother.\\n\\n## Embedding Search: The Last Piece of the Puzzle\\n\\nBy mid-2023, most of our ML stack had gone real-time\u2014except for Candidate Generation (CG), which still ran in batch mode. To truly power real-time recommendations, we needed an online embedding search system.\\n\\n### Choosing the Right Vector Database\\n\\nWe benchmarked three production-ready vector DBs across key parameters:\\n\\n- Milvus\\n- Qdrant\\n- Weaviate\\n\\nAfter extensive POCs, Qdrant stood out for its:\\n\\n- \u2705 Blazing-fast search latency on high-dimensional vectors\\n- \u2705 Efficient memory usage, crucial for in-memory workloads\\n- \u2705 Support for upserts and soft deletes, vital for Ads use cases\\n- \u2705 gRPC + REST APIs, making integration seamless\\n- \u2705 Powerful filtering, allowing fine-tuned retrieval (e.g., filtering Ads by category, active status, etc.)\\n\\nAt its core, Qdrant uses HNSW indexing, delivering both high recall and low-latency nearest-neighbor search\u2014a perfect fit for our needs.\\n\\n### Embedding Freshness & Real-Time Updates\\n\\nTo ensure embeddings stayed up to date, we built a dual ingestion pipeline:\\n\\n- \ud83d\udccc Daily Refresh: A bulk pipeline updated embeddings overnight\\n- \ud83d\udccc Real-Time Updates: Ads events triggered immediate upserts/deletes\\n\\nThis setup powered real-time \\"Similar Products\\" recommendations on the product page and became the foundation for Ads Candidate Generation, ensuring the right ads surfaced in milliseconds.\\n\\n![Skye](./vss.png)\\n\\n## Final Takeaways: Scaling Smartly for Real-Time ML\\n\\n- \ud83d\ude80 Self-hosted inference on Triton gave us lower cost, faster scaling, and better performance than managed services\\n- \ud83d\ude80 Building a custom Triton image reduced cold starts, improving responsiveness\\n- \ud83d\ude80 Qdrant-based embedding search enabled real-time personalization at scale\\n- \ud83d\ude80 Real-time updates for embeddings unlocked dynamic, up-to-date recommendations\\n\\nBy early 2024, Meesho\u2019s ML stack had evolved into a fully real-time, scalable, and cost-efficient system, setting the foundation for even bigger leaps ahead."},{"id":"building-meeshos-mlplatform-lessons-from-first-gen","metadata":{"permalink":"/BharatMLStack/blog/building-meeshos-mlplatform-lessons-from-first-gen","editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/blog/bharatmlstack-history/building-meeshos-mlplatform-lessons-from-first-gen/index.md","source":"@site/blog/bharatmlstack-history/building-meeshos-mlplatform-lessons-from-first-gen/index.md","title":"Building Meesho\u2019s ML Platform: Lessons from the First-Gen System (Part 2)","description":"Lessons from scaling Meesho\'s first-gen ML platform\u2014building Inferflow for no-code feature retrieval, migrating from Cassandra to ScyllaDB, optimizing the Interaction Store with tiered storage, and cutting infra costs by 60% while hitting 1M QPS.","date":"2023-04-10T00:00:00.000Z","tags":[{"inline":true,"label":"inferflow","permalink":"/BharatMLStack/blog/tags/inferflow"},{"inline":true,"label":"interaction-store","permalink":"/BharatMLStack/blog/tags/interaction-store"},{"inline":true,"label":"mlplatform","permalink":"/BharatMLStack/blog/tags/mlplatform"},{"inline":true,"label":"meesho","permalink":"/BharatMLStack/blog/tags/meesho"},{"inline":true,"label":"bharatmlstack","permalink":"/BharatMLStack/blog/tags/bharatmlstack"}],"readingTime":6.25,"hasTruncateMarker":false,"authors":[{"name":"Bhawani Singh","title":"Architect @ Meesho","url":"https://github.com/singh-bhawani","imageURL":"https://github.com/singh-bhawani.png","key":"bhawani","page":null},{"name":"Jigar Dave","title":"Lead Software Engineer @ Meesho","url":"https://github.com/jigarpatel26","imageURL":"https://github.com/jigarpatel26.png","key":"jigar","page":null},{"name":"Adarsha Das","title":"Senior Architect @ Meesho","url":"https://github.com/a0d00kc","imageURL":"https://github.com/a0d00kc.png","key":"adarsha","page":null}],"frontMatter":{"title":"Building Meesho\u2019s ML Platform: Lessons from the First-Gen System (Part 2)","description":"Lessons from scaling Meesho\'s first-gen ML platform\u2014building Inferflow for no-code feature retrieval, migrating from Cassandra to ScyllaDB, optimizing the Interaction Store with tiered storage, and cutting infra costs by 60% while hitting 1M QPS.","authors":["bhawani","jigar","adarsha"],"slug":"building-meeshos-mlplatform-lessons-from-first-gen","date":"2023-4-10","tags":["inferflow","interaction-store","mlplatform","meesho","bharatmlstack"]},"unlisted":false,"prevItem":{"title":"Cracking the Code: Scaling Model Inference & Real-Time Embedding Search","permalink":"/BharatMLStack/blog/scaling-model-inference-and-embedding-search"},"nextItem":{"title":"Building Meesho\u2019s ML Platform: From Chaos to Cutting-Edge (Part 1)","permalink":"/BharatMLStack/blog/building-meeshos-mlplatform"}},"content":"![BharatMLStack](./bms.png)\\nBy late 2022, we had built something we were truly proud of\u2014a real-time ML serving system with a DAG-based executor, a feature store, and an interaction store powering key ranking and personalization models. It was a major milestone, the culmination of months of effort from data scientists, ML engineers, and backend teams. Our system was live, and we were ready to push the boundaries of experimentation.\\nAnd it worked. Mostly.\\nBut soon, cracks appeared. Every new model needed custom feature retrieval logic, DAGs became dense and unmanageable, and scaling turned into a constant firefight. Costs surged, and infra bottlenecks slowed experimentation. Our system worked, but it wasn\u2019t built for scale.\\nThis is the story of how we tackled these challenges\u2014building Inferflow for seamless feature retrieval, optimizing real-time infra, and cutting costs while scaling to millions of QPS.\\n\\n### The Cost of Success\\nEvery new Ranker model required its own feature set, often pulling from different entities. Each addition meant:\\n\\n- Adding new DAG nodes in IOP\\n- Writing custom logic to fetch features from multiple sources (e.g., user, product, user \xd7 category)\\n- Inferring intermediate features (e.g., extracting category from a product to fetch user \xd7 category data)\\n- Optimizing I/O and dealing with the inevitable bugs\\n\\nWhat began as clean DAGs soon turned into a tangled web of cross-dependent graphs. Every experimentation cycle meant new nodes, new dependencies, and slower iterations.\\n\\n### Scaling Pains (and Cassandra\u2019s Limits)\\nAt some point, we were hitting:\\n\\n- 250\u2013300K reads/sec\\n- 1M writes/sec (during lean hours)\\n\\nAll of this ran on Cassandra. While its distributed architecture had been proven in production, operating large-scale clusters came with considerable infrastructure overhead. Our proof-of-concept (POC) demonstrated throughput of around 100K ops/sec, but as we scaled further, the challenges grew. Ensuring node health, optimizing compaction, and maintaining storage balance became increasingly demanding. We also observed latency spikes under heavy load, alongside a sharp increase in total cost of ownership.\\n\\n### Interaction Store Woes\\nOur interaction store was another ticking time bomb:\\n\\n- \ud83d\udea8 Clusters kept growing in size and cost\\n- \ud83d\udea8 Latency spikes became increasingly frequent\\n- \ud83d\udea8 The DMC proxy occasionally lost locality of nodes against shards, causing cross-node communication and degraded performance\\n\\nEach time this happened, we had to manually rebalance shards just to restore stable latency, making operations unsustainable at scale.\\n\\n### Silver Linings\\nDespite the chaos, the system was live and delivering value:\\n\\n- Real-time infrastructure was in production\\n- Costs dropped by 60\u201370% compared to offline personalization\\n- New experiments rolled out faster and more successfully\\n- User engagement metrics improved\\n\\nIt wasn\u2019t perfect. It was far from easy. But it worked\u2014and that counted for a lot.\\n\\n### Round Two: Solving the Top 2 Bottlenecks\\nWith the first-gen system stretched to its limits, we stepped back. Conversations with data scientists and backend engineers revealed three recurring pain points:\\n\\n1. Coding feature retrieval logic for every new model was becoming unsustainable\\n2. ML scale was exploding\u2014bringing rising infra costs with it\\n3. Real-time embedding search was the next big unlock\\n\\nWe tackled them one by one\u2014starting with the biggest pain point.\\n\\n#### Problem 1: No-Code Feature Retrieval for Model Inference\\nWe noticed a pattern: for personalized ranking, models needed features from:\\n\\n- \u2705 Product\\n- \u2705 User\\n- \u2705 User \xd7 Category\\n- \u2705 Region, cohort, sub-category, etc.\\n\\nA key insight emerged: Entities that contribute features for a model always map back to the context entities.\\n\\n![MP Dag](./mp-dag.png)\\n\\nWith this, we designed Inferflow, a graph-driven feature retrieval and model orchestration system:\\n\\n- 1\ufe0f\u20e3 Inferflow takes a modelId and context IDs (e.g., userId, productIds)\\n- 2\ufe0f\u20e3 Loads a pre-defined feature retrieval graph from ZooKeeper\\n- 3\ufe0f\u20e3 Executes the graph to resolve entity relationships dynamically\\n- 4\ufe0f\u20e3 Outputs a 2D matrix of feature vectors\\n\\n\ud83d\udca1 The impact?\\n\\n- \ud83d\ude80 No more custom feature retrieval code\u2014just graph updates in config\\n- \ud83d\ude80 Feature consistency across experiments\\n- \ud83d\ude80 Faster iteration cycles for ranking, fraud detection, and beyond\\n\\nHere\u2019s a visual example that shows how this graph plays out during execution. We further extended the graph to call multiple models as needed:\\n![MP matrix](./mp-matrix.png)\\nWe built Inferflow in GoLang, using gRPC and Proto3 serialization for efficiency.\\n\\n#### Problem 2: Scaling Without Breaking the Bank\\nWith more ML use cases coming online, we needed to cut costs without compromising performance. We focused on:\\n\\n- \ud83d\udd39 Online Feature Store\\n- \ud83d\udd39 Interaction Store\\n\\n#### Optimizing the Online Feature Store\\nOur costs were concentrated in:\\n\\n- \ud83d\udccc Database (Cassandra)\\n- \ud83d\udccc Cache (Redis)\\n- \ud83d\udccc Running Pods (Java services)\\n\\n1\ufe0f\u20e3 Replacing Cassandra with ScyllaDB\\nAs we hit the operational limits of large Cassandra clusters, we transitioned to ScyllaDB, which offered a seamless drop-in replacement without major code changes. The switch brought significant benefits:\\n\\n- Throughput: Matched or exceeded Cassandra\'s performance under identical workloads, even under high concurrency.\\n- Latency: Achieved consistently lower P99 latencies due to ScyllaDB\'s shard-per-core architecture and better I/O utilization.\\n- Cost Efficiency: Reduced infra footprint by ~70% through better CPU and memory efficiency, eliminating the need for over-provisioned nodes.\\n\\n2\ufe0f\u20e3 Finding the Right Cache\\nTo reduce backend load and improve response times, we benchmarked multiple caching solutions\u2014Memcached, KeyDB, and Dragonfly\u2014under real production traffic patterns. Dragonfly stood out due to its robust architecture and operational simplicity:\\n\\n- Data Skew Handling: Efficiently managed extreme key hotness and uneven access patterns without performance degradation.\\n- Throughput: Delivered consistently high throughput, even with large object sizes and concurrent access.\\n- Ease of Adoption: Acted as a drop-in Redis replacement with full protocol compatibility\u2014no changes needed in application code or client libraries.\\n\\n3\ufe0f\u20e3 Moving to GoLang for Cost-Efficient Serving\\nJava services were memory-heavy\u2014so we rewrote core services in GoLang. The results?\\n\\n\u2705 Memory usage dropped by ~80%\\n\u2705 CPU utilization was significantly lower\\n\u2705 Faster, more efficient deployments\\n\\n#### Optimizing the Interaction Store\\nWe realized that we only need a user\u2019s interaction data in Redis when they open the app. So, we implemented a tiered storage approach:\\n\\n- \ud83d\udccc Cold Tier (ScyllaDB)\u2014Stores click, order, wishlist events\\n- \ud83d\udccc Hot Tier (Redis)\u2014Loads a user\u2019s past interactions only when they open the app\\n\\nSmart Offloading: We introduced an inactivity tracker to detect when a user session ends. At that point, Redis data was flushed back to Scylla, reducing unnecessary writes.\\n\\n![InteractionStore](./interaction-str.png)\\n#### Results\\n\\n- Online Feature Store hit 1M QPS for the first time during the 2023 Mega Blockbuster Sale\u2014without breaking a sweat\\n- Infra costs for Online Feature Store and Interaction Store dropped by ~60%\\n\\n#### The Catch: Our ML Hosting Hit a Hard Limit\\nWhile planning for 2023 MBS, we ran into a critical scalability bottleneck:\\n\\n- \u274c Insufficient compute availability in our region for ML instances\\n- \u274c Couldn\u2019t provision enough nodes to handle real-time inference at scale\\n\\nThis forced us to rethink where and how we hosted our models. The existing setup was great for prototyping\u2014but it wasn\u2019t built to handle the bursty, high-QPS demands of real-world production workloads.\\n\\n### Conclusion: From Firefighting to Future-Proofing\\nWhat started as an ambitious experiment turned into a real-time ML infrastructure that powered millions of requests per second. We battled scaling pains, rethought feature retrieval with Inferflow, and rebuilt our infra stack for efficiency\u2014driving down costs while improving experimentation velocity.\\nBut new challenges emerged. Our infrastructure could now handle scale, but our ML model hosting setup hit a hard limit. With compute availability bottlenecks threatening real-time inference, we faced a critical decision: how do we make model serving as scalable and cost-efficient as the rest of our stack? That\u2019s the next piece of the puzzle\u2014and the story of Part 3."},{"id":"building-meeshos-mlplatform","metadata":{"permalink":"/BharatMLStack/blog/building-meeshos-mlplatform","editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/blog/bharatmlstack-history/building-meeshos-mlplatform-from-chaos-to-cutting-edge/index.md","source":"@site/blog/bharatmlstack-history/building-meeshos-mlplatform-from-chaos-to-cutting-edge/index.md","title":"Building Meesho\u2019s ML Platform: From Chaos to Cutting-Edge (Part 1)","description":"How Meesho transitioned from batch-based recommendations to a real-time ML platform\u2014building an Online Feature Store, Interaction Store, and DAG execution framework that became BharatMLStack.","date":"2022-11-15T00:00:00.000Z","tags":[{"inline":true,"label":"online-feature-store","permalink":"/BharatMLStack/blog/tags/online-feature-store"},{"inline":true,"label":"interaction-store","permalink":"/BharatMLStack/blog/tags/interaction-store"},{"inline":true,"label":"mlplatform","permalink":"/BharatMLStack/blog/tags/mlplatform"},{"inline":true,"label":"meesho","permalink":"/BharatMLStack/blog/tags/meesho"}],"readingTime":10.19,"hasTruncateMarker":false,"authors":[{"name":"Adarsha Das","title":"Senior Architect @ Meesho","url":"https://github.com/a0d00kc","imageURL":"https://github.com/a0d00kc.png","key":"adarsha","page":null},{"name":"Aditya Kumar","title":"Lead Software Engineer @ Meesho","url":"https://github.com/Adit2607","imageURL":"https://github.com/Adit2607.png","key":"aditya","page":null},{"name":"Bhawani Singh","title":"Architect @ Meesho","url":"https://github.com/singh-bhawani","imageURL":"https://github.com/singh-bhawani.png","key":"bhawani","page":null},{"name":"Jigar Dave","title":"Lead Software Engineer @ Meesho","url":"https://github.com/jigarpatel26","imageURL":"https://github.com/jigarpatel26.png","key":"jigar","page":null}],"frontMatter":{"title":"Building Meesho\u2019s ML Platform: From Chaos to Cutting-Edge (Part 1)","description":"How Meesho transitioned from batch-based recommendations to a real-time ML platform\u2014building an Online Feature Store, Interaction Store, and DAG execution framework that became BharatMLStack.","slug":"building-meeshos-mlplatform","authors":["adarsha","aditya","bhawani","jigar"],"date":"2022-11-15T00:00:00.000Z","tags":["online-feature-store","interaction-store","mlplatform","meesho"]},"unlisted":false,"prevItem":{"title":"Building Meesho\u2019s ML Platform: Lessons from the First-Gen System (Part 2)","permalink":"/BharatMLStack/blog/building-meeshos-mlplatform-lessons-from-first-gen"}},"content":"![BharatMLStack](./bms.png)\\nIt all started in early 2022, over a casual Friday evening catch-up. Like many great origin stories, this one began with friendly banter between a group of backend engineers and data scientists. As the conversations unfolded, so did the roasting\u2014until one remark hit a little too close to home:\\n\\n*\\"Why are we still crunching data for Monthly Active Users (MAU) when the next day it\u2019s all about Daily Active Users (DAU)?\\"*\\n\\nThe laughter died down, and the question lingered. When we regrouped on Monday\u2014clear-headed and slightly reflective\u2014we decided to dig into the numbers. What they discovered was quite revealing: a large portion of compute resources wasn\u2019t being put to good use.\\nMuch of the system\u2019s effort was spent supporting users who weren\u2019t actively engaging, and even for new users, the experience wasn\u2019t optimized to make a meaningful impact.\\n\\nAt the same time, Meesho had just launched a company-wide initiative to reduce costs\u2014and every team had to contribute. This realization sparked the journey that would eventually lead to the **Meesho ML Platform**, known today as **BharatMLStack**.\\n\\n![Alt Text](./old-batch-arch.png)\\n\\nBefore the ML Platform, our recommendation and ranking pipelines followed a batch processing approach:\\n- **Data Ingestion**: The Data Platform team executed ETL jobs to ingest raw user data\u2014including user profiles, interaction logs, and product impressions\u2014into designated S3 buckets.\\n- **Layer 1**: Embedding Generation: On the Data Science side, Spark jobs pulled data from multiple S3 sources, cleaned and preprocessed it, and applied matrix factorization to generate user and item embeddings. The processed data and embeddings were then stored back in S3 in a structured format.\\n- **Layer 2**: Candidate Generation (CG): In this stage, Spark jobs leveraged embeddings and historical interaction data to generate candidate recommendations for users. These candidate lists were subsequently written to S3.\\n- **Layer 3**: Ranking and Merging \u2013 A final round of processing ranked the generated candidates using ML models, combined different candidate lists, and stored the final ranked recommendations in a caching system.\\n- **Serving**: A microservice retrieved ranked recommendations from an in-memory data store via exposed APIs, delivering personalized listings across key surfaces such as \\"For You\\" and Category Landing Pages (CLP).\\n\\nThis approach held up well\u2014until Meesho started seeing a significant surge in traffic.\\n\\n## The Turning Point: From Batch to Real-Time\\n\\nAt this time, the team was iterating on new **Ranker models**, and real-time inference seemed like the next logical step. But Rankers needed **real-time feature retrieval**, which meant an **online feature store** had to be built first.\\n\\nExploring open-source options led to **cost vs. performance trade-offs**, but Meesho\u2019s surging traffic meant that **latency and stability were non-negotiable**. After multiple debates and stakeholder discussions, a bold decision was made:\\n\\n*We would build our own feature store.*\\n\\nMeanwhile, efforts began to bring **Candidate Generators (CGs)** to real-time. The challenge? **Storing and retrieving user interactions quickly enough** to power real-time recommendations.\\n\\nAs the team dove deeper, a new roadblock emerged: \\nOur ML jobs were orchestrated using **Airflow DAGs**, giving data scientists flexibility in experimentation. But transitioning to real-time execution threatened this agility. Every change would now require backend engineering support, **slowing down iteration cycles**.\\n\\nThat\u2019s when the idea struck: \\nWe needed a **framework for real-time DAG execution**\u2014one that preserved the same flexibility as Airflow but worked for **streaming data**.\\n\\nThis moment shaped the **next phase of our journey**.\\n\\n## First Generation Design\\n\\n![Alt Text](./first-gen-arch.png)\\n\\n# Laying the Groundwork: The First-Gen ML Platform\\n\\nTo solve these challenges, the team built three foundational components:\\n\\n\\n### 1. IOP Framework: A Real-Time DAG Executor\\n\\n- **Reusable Nodes**: Each DAG node (e.g., an invocation to a CG service, a ranker, or a filter) had to be implemented only once. After that, it could be reused across any workflow by referencing it in config.\\n- **Config-driven Dynamic Graphs**: Execution graphs were defined as adjacency lists stored in **ZooKeeper**, allowing teams to modify the sequence or structure of operations without touching application code.\\n- **Plug-and-play CGs**: The Candidate Generator interface was preserved, so a single CG node could call any CG service by passing `cg_name` in the request. This drastically reduced the code surface area and improved maintainability.\\n- **Production-Grade DAGs**: DAGs were designed to execute in **low-latency real-time environments**, with support for **parallel execution, retries, and branching**.\\n\\n[More about IOP DAG](https://www.meesho.io/blog/rebuilding-meeshos-ranking-platform)\\n\\n\\n### 2. Online Feature Store - 0th Version\\n\\n- Used **Cassandra** and **Redis** for low-latency feature serving.\\n- Maintained feature consistency using **Feature Groups** with TTL-based expiry.\\n- A hybrid schema was used: feature keys stored in **ZooKeeper**, data stored in **compact arrays**.\\n\\n\\n### 3. Interaction Store - 0th Version\\n\\n- Captured real-time user interactions like clicks, orders, and add-to-cart events.\\n- Stored event data in **Redis ZSETs (sorted sets)** to enable fast lookups for recommendation engines.\\n- Provided an API to fetch a user\'s **last _k_ interactions** or **interactions within a time window**.\\n\\n\\nWith these components in place, **real-time ML at Meesho became a reality**.\\n\\nThis was just the beginning.\\n\\n## Building the Online Feature Store - 0th Version\\n\\n![Alt text](./online-feature-store-v0.png)\\n\\n### Choosing the Right Tech Stack\\n\\nWe spent considerable time evaluating various databases, caches, and communication protocols for our **online feature store**. After carefully weighing **cost, latency, throughput**, and **operational stability**, we settled on a combination of:\\n\\n- **Cassandra** and **Redis** for storage\\n- **gRPC + Proto3** as our communication layer\\n\\n\\n### Streamlining the Data Flow\\n\\nTo keep things simple in the initial version:\\n\\n- **Feature engineering jobs** wrote raw outputs to an **S3 bucket**\\n- A **daily feature push job**:\\n - Read from S3\\n - Grouped related features into **Feature Groups** (ensuring consistency)\\n - Pushed them to **Kafka**\\n\\nFor features requiring frequent updates:\\n\\n- **Ad-hoc jobs** computed features in higher frequency\\n- These jobs pushed to both **Kafka** and **S3** (S3 preserved historical data for future model training)\\n\\n\\n## The Challenges: Data Format and Storage\\n\\nOne of the most critical design challenges was how to store feature data **efficiently and consistently**, especially in databases like **Cassandra** and **Redis**, which come with unique storage constraints.\\n\\nWe had to solve for three key requirements:\\n\\n- ### Feature Consistency\\n When a feature group contains features like `order_count_1h` and `click_count_1h`, both must reflect the **same time window**. Inconsistent updates would lead to **unreliable model predictions**.\\n\\n- ### TTL Granularity\\n Each feature group required an **expiry timestamp**, so that **all features within it expired together**\u2014preserving consistency during reads.\\n\\n- ### Extensibility Across Databases\\n We anticipated that infra needs would evolve. To future-proof our system, the data format was designed to be **decoupled from DB-specific layouts**, enabling portability to systems like **ScyllaDB**, **DynamoDB**, **HBase**, or **BigTable**.\\n\\n\\n---\\n\\n## Overcoming Technical Constraints\\nAt the time, we were using Cassandra, which not only imposed a soft limit of 75 columns per row, but also exhibited significant performance degradation as the number of columns increased further, particularly in memory constrained machines. Wide rows caused high memory usage during reads, unpredictable latencies due to heavy deserialization overhead, and inefficiencies during compactions and repairs. This ruled out the naive \\"one column per feature\\" approach. We needed a format that was compact, minimized the number of columns, and remained efficient and portable across different storage systems.\\n\\n## The Solution: Schema Separation\\n\\nWe introduced the concept of Feature Groups\u2014logical groupings of features that must remain consistent with one another.\\nTo represent these groups efficiently, we adopted a layered storage approach:\\n\\n- **Feature Labels (Keys)** were stored in ZooKeeper, serving as the schema.\\n- **Feature Values** were stored as a comma-separated string array in Cassandra or Redis.\\n- **Expiry Timestamp and Schema Version** were appended using a semi-colon delimiter at the end of the string.\\n\\nExample:\\n\\n```bash\\nfeature_1_value,feature_2_value,feature_3_value;expiry_ts\\n```\\n\\nThis format allowed:\\n- Consistent writes and reads at the group level\\n- Easy parsing of feature values using the schema lookup from ZooKeeper\\n- Efficient storage with minimal DB column usage\\n- Support for per-group TTLs and schema evolution\\n\\n## Tracking Changes in Feature Groups\\nFeature groups don\u2019t stay static. As models evolve, features get added, renamed, or removed. But schema changes often go live before the data is ready\u2014and stopping ingestion just to wait for everything to align isn\'t feasible.\\n\\n### Common Real-World Scenarios:\\n- A new feature is added to the schema, but ingestion jobs still use the older schema version.\\n- Ongoing writes don\u2019t include the newly added feature, and stopping ingestion would break freshness for existing features.\\n- During serving, models request a mix of old and new features, depending on rollout stages.\\n\\n## The Solution: Schema Versioning\\nWe solved this with versioned feature group schemas, which unlocked several capabilities:\\n- ### Backward Compatibility\\n Older ingestion jobs can continue writing using older schema versions. During reads, the system uses the schema version embedded in the value to interpret the data correctly.\\n- ### Partial Availability Handling \\n During inference, if some features in the request aren\u2019t available (due to rollout delays or missing data), the system serves default values, ensuring the inference call doesn\u2019t fail.\\n- ### Safe Writes Without Pipeline Pauses\\n With schema versioning, we no longer had to stop ingestion pipelines for schema updates. Writes using previous versions can continue safely, and downstream consumers evolve independently.\\nThis design gave us the flexibility to move fast without breaking things\u2014preserving data quality, enabling experimentation, and ensuring reliability at scale.\\n\\n![Alt Text](./schema.png)\\n\\n## Interaction Store - 0th Version\\n\\n![Alt Text](./interaction-store-v0.png)\\n\\nTo power real-time Candidate Generators (CGs), we needed fast access to user behavior signals\u2014like what a user recently clicked, ordered, or added to their cart. These interactions form the basis for many real-time recommendations, such as **Similar Products**, **People Also Viewed**, or **Recently Ordered Again**.\\nFor the **0th version** of the Interaction Store, we focused on a design that was **simple, fast, and reliable** \u2014 optimized for high-throughput ingestion and low-latency lookups.\\n\\n## Event Ingestion\\nWe instrumented our backend services to emit key user interaction events to Kafka in real time. These included:\\n- Click\\n- Order\\n- Add to Cart\\n- Wishlist\\n- Share\\n\\nEach event carried essential metadata:\\n- userId \u2014 uniquely identifies the user\\n- productId \u2014 the item being interacted with\\n- timestamp \u2014 the moment the interaction occurred\\n\\nThis decoupled the interaction logging from storage, allowing ingestion and consumption to scale independently.\\n\\n## Storage Design\\nTo store these events, we built Kafka consumers that processed the incoming streams and wrote the data into Redis, using sorted sets (ZSETs) as the primary data structure.\\n\\n### Why Redis?\\nRedis gave us:\\n- **Low-latency** reads and writes\\n- **Time-ordered data** using ZSETs (via score = timestamp)\\n- **Native TTL support**, if needed in later versions\\n- **In-memory performance** \u2014ideal for real-time CGs\\n\\n### Storage Structure\\nEach user\u2019s interactions were stored using a composite key format, uniquely identifying the user and interaction type. This structure allowed efficient organization and quick retrieval of recent activity for recommendation generation:\\n\\n```bash\\nuserId_eventType \u2192 ZSET[...(pid, ts)...]\\n```\\n\\nWithin each ZSET:\\n\\n- The **timestamp** served as the score, maintaining temporal order\\n- The **productId** (optionally with metadata) was the **value**\\n\\nThis allowed us to efficiently retrieve the interactions with HTTP-based API server with two query modes:\\n- Fetch the **last k interactions** of a specific type for a given user with `ZREVRANGE(userId_eventType, count)`\\n- Retrieve **all interactions within a time range** (e.g., last 24 hours) with `ZREVRANGEBYSCORE(userId_eventType, timeRange)`\\n\\n### Built-in Guardrails\\nSince Redis was the sole store, we implemented High Availability (HA) to prevent data loss. To optimize memory usage, we also enforced size limits per event type\u2014only storing the last k interactions per user, with older entries getting truncated.\\n\\n## Conclusion: Laying the Foundation for Real-Time ML\\n\\nIn this first phase, we tackled the **fundamentals**\u2014shifting from batch-based recommendations to a **real-time Recommendation** using ML platform that could keep up with Meesho\u2019s growth.\\n\\nWith the **IOP Framework**, **Online Feature Store**, and **Interaction Store**, we built the core infrastructure to support real-time personalization at scale. These wins have already unlocked: \\n- \u2705 Faster, more dynamic recommendations for millions of users. \\n- \u2705 Better infrastructure efficiency, reducing wasted compute power. \\n- \u2705 A flexible, modular system that allows for further experimentation.\\n\\nBut this is just the beginning. While we\'ve solved key challenges, **certain roadblocks remain** \u2014from optimizing **cost-performance trade-offs** to **seamlessly evolving schemas**.\\n\\n\\nThis foundational work laid the path for a reliable and scalable **real-time feature serving layer**."}]}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/6479fb86.d4723af3.js b/docs/assets/js/6479fb86.d4723af3.js deleted file mode 100644 index f3817767..00000000 --- a/docs/assets/js/6479fb86.d4723af3.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[5579],{3751:e=>{e.exports=JSON.parse('{"archive":{"blogPosts":[{"id":"post-one","metadata":{"permalink":"/BharatMLStack/blog/post-one","editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/blog/bharatmlstack-history/post-one/index.md","source":"@site/blog/bharatmlstack-history/post-one/index.md","title":"Building Meesho\u2019s ML Platform: From Chaos to Cutting-Edge (Part 1)","description":"BharatMLStack","date":"2022-11-15T00:00:00.000Z","tags":[{"inline":true,"label":"online-feature-store","permalink":"/BharatMLStack/blog/tags/online-feature-store"},{"inline":true,"label":"interaction-store","permalink":"/BharatMLStack/blog/tags/interaction-store"},{"inline":true,"label":"mlplatform","permalink":"/BharatMLStack/blog/tags/mlplatform"},{"inline":true,"label":"meesho","permalink":"/BharatMLStack/blog/tags/meesho"}],"readingTime":10.25,"hasTruncateMarker":false,"authors":[{"name":"Adarsha Das","title":"Senior Architect @ Meesho","url":"https://github.com/a0d00kc","imageURL":"https://github.com/a0d00kc.png","key":"adarsha","page":null},{"name":"Aditya Kumar","title":"SDE-III @ Meesho","url":"https://github.com/Adit2607","imageURL":"https://github.com/Adit2607.png","key":"aditya","page":null},{"name":"Bhawani Singh","title":"SDE-IV @ Meesho","url":"https://github.com/singh-bhawani","imageURL":"https://github.com/singh-bhawani.png","key":"bhawani","page":null},{"name":"Jigar Dave","title":"SDE-IV @ Meesho","url":"https://github.com/jigarpatel26","imageURL":"https://github.com/jigarpatel26.png","key":"jigar","page":null}],"frontMatter":{"slug":"post-one","title":"Building Meesho\u2019s ML Platform: From Chaos to Cutting-Edge (Part 1)","authors":["adarsha","aditya","bhawani","jigar"],"date":"2022-11-15T00:00:00.000Z","tags":["online-feature-store","interaction-store","mlplatform","meesho"]},"unlisted":false},"content":"![BharatMLStack](./bharatmlstack.png)\\n## The Genesis: How a Friday Night Roast Sparked Meesho\u2019s ML Platform\\n\\nIt all started in early 2022, over a casual Friday evening catch-up. Like many great origin stories, this one began with friendly banter between a group of backend engineers and data scientists. As the conversations unfolded, so did the roasting\u2014until one remark hit a little too close to home:\\n\\n*\\"Why are we still crunching data for Monthly Active Users (MAU) when the next day it\u2019s all about Daily Active Users (DAU)?\\"*\\n\\nThe laughter died down, and the question lingered. When we regrouped on Monday\u2014clear-headed and slightly reflective\u2014we decided to dig into the numbers. What they discovered was quite revealing: a large portion of compute resources wasn\u2019t being put to good use.\\nMuch of the system\u2019s effort was spent supporting users who weren\u2019t actively engaging, and even for new users, the experience wasn\u2019t optimized to make a meaningful impact.\\n\\nAt the same time, Meesho had just launched a company-wide initiative to reduce costs\u2014and every team had to contribute. This realization sparked the journey that would eventually lead to the **Meesho ML Platform**, known today as **BharatMLStack**.\\n\\n![Alt Text](./old-batch-arch.png)\\n\\nBefore the ML Platform, our recommendation and ranking pipelines followed a batch processing approach:\\n- **Data Ingestion**: The Data Platform team executed ETL jobs to ingest raw user data\u2014including user profiles, interaction logs, and product impressions\u2014into designated S3 buckets.\\n- **Layer 1**: Embedding Generation: On the Data Science side, Spark jobs pulled data from multiple S3 sources, cleaned and preprocessed it, and applied matrix factorization to generate user and item embeddings. The processed data and embeddings were then stored back in S3 in a structured format.\\n- **Layer 2**: Candidate Generation (CG): In this stage, Spark jobs leveraged embeddings and historical interaction data to generate candidate recommendations for users. These candidate lists were subsequently written to S3.\\n- **Layer 3**: Ranking and Merging \u2013 A final round of processing ranked the generated candidates using ML models, combined different candidate lists, and stored the final ranked recommendations in a caching system.\\n- **Serving**: A microservice retrieved ranked recommendations from an in-memory data store via exposed APIs, delivering personalized listings across key surfaces such as \\"For You\\" and Category Landing Pages (CLP).\\n\\nThis approach held up well\u2014until Meesho started seeing a significant surge in traffic.\\n\\n## The Turning Point: From Batch to Real-Time\\n\\nAt this time, the team was iterating on new **Ranker models**, and real-time inference seemed like the next logical step. But Rankers needed **real-time feature retrieval**, which meant an **online feature store** had to be built first.\\n\\nExploring open-source options led to **cost vs. performance trade-offs**, but Meesho\u2019s surging traffic meant that **latency and stability were non-negotiable**. After multiple debates and stakeholder discussions, a bold decision was made:\\n\\n*We would build our own feature store.*\\n\\nMeanwhile, efforts began to bring **Candidate Generators (CGs)** to real-time. The challenge? **Storing and retrieving user interactions quickly enough** to power real-time recommendations.\\n\\nAs the team dove deeper, a new roadblock emerged: \\nOur ML jobs were orchestrated using **Airflow DAGs**, giving data scientists flexibility in experimentation. But transitioning to real-time execution threatened this agility. Every change would now require backend engineering support, **slowing down iteration cycles**.\\n\\nThat\u2019s when the idea struck: \\nWe needed a **framework for real-time DAG execution**\u2014one that preserved the same flexibility as Airflow but worked for **streaming data**.\\n\\nThis moment shaped the **next phase of our journey**.\\n\\n## First Generation Design\\n\\n![Alt Text](./first-gen-arch.png)\\n\\n# Laying the Groundwork: The First-Gen ML Platform\\n\\nTo solve these challenges, the team built three foundational components:\\n\\n\\n### 1. IOP Framework: A Real-Time DAG Executor\\n\\n- **Reusable Nodes**: Each DAG node (e.g., an invocation to a CG service, a ranker, or a filter) had to be implemented only once. After that, it could be reused across any workflow by referencing it in config.\\n- **Config-driven Dynamic Graphs**: Execution graphs were defined as adjacency lists stored in **ZooKeeper**, allowing teams to modify the sequence or structure of operations without touching application code.\\n- **Plug-and-play CGs**: The Candidate Generator interface was preserved, so a single CG node could call any CG service by passing `cg_name` in the request. This drastically reduced the code surface area and improved maintainability.\\n- **Production-Grade DAGs**: DAGs were designed to execute in **low-latency real-time environments**, with support for **parallel execution, retries, and branching**.\\n\\n[More about IOP DAG](https://www.meesho.io/blog/rebuilding-meeshos-ranking-platform)\\n\\n\\n### 2. Online Feature Store - 0th Version\\n\\n- Used **Cassandra** and **Redis** for low-latency feature serving.\\n- Maintained feature consistency using **Feature Groups** with TTL-based expiry.\\n- A hybrid schema was used: feature keys stored in **ZooKeeper**, data stored in **compact arrays**.\\n\\n\\n### 3. Interaction Store - 0th Version\\n\\n- Captured real-time user interactions like clicks, orders, and add-to-cart events.\\n- Stored event data in **Redis ZSETs (sorted sets)** to enable fast lookups for recommendation engines.\\n- Provided an API to fetch a user\'s **last _k_ interactions** or **interactions within a time window**.\\n\\n\\nWith these components in place, **real-time ML at Meesho became a reality**.\\n\\nThis was just the beginning.\\n\\n## Building the Online Feature Store - 0th Version\\n\\n![Alt text](./online-feature-store-v0.png)\\n\\n### Choosing the Right Tech Stack\\n\\nWe spent considerable time evaluating various databases, caches, and communication protocols for our **online feature store**. After carefully weighing **cost, latency, throughput**, and **operational stability**, we settled on a combination of:\\n\\n- **Cassandra** and **Redis** for storage\\n- **gRPC + Proto3** as our communication layer\\n\\n\\n### Streamlining the Data Flow\\n\\nTo keep things simple in the initial version:\\n\\n- **Feature engineering jobs** wrote raw outputs to an **S3 bucket**\\n- A **daily feature push job**:\\n - Read from S3\\n - Grouped related features into **Feature Groups** (ensuring consistency)\\n - Pushed them to **Kafka**\\n\\nFor features requiring frequent updates:\\n\\n- **Ad-hoc jobs** computed features in higher frequency\\n- These jobs pushed to both **Kafka** and **S3** (S3 preserved historical data for future model training)\\n\\n\\n## The Challenges: Data Format and Storage\\n\\nOne of the most critical design challenges was how to store feature data **efficiently and consistently**, especially in databases like **Cassandra** and **Redis**, which come with unique storage constraints.\\n\\nWe had to solve for three key requirements:\\n\\n- ### Feature Consistency\\n When a feature group contains features like `order_count_1h` and `click_count_1h`, both must reflect the **same time window**. Inconsistent updates would lead to **unreliable model predictions**.\\n\\n- ### TTL Granularity\\n Each feature group required an **expiry timestamp**, so that **all features within it expired together**\u2014preserving consistency during reads.\\n\\n- ### Extensibility Across Databases\\n We anticipated that infra needs would evolve. To future-proof our system, the data format was designed to be **decoupled from DB-specific layouts**, enabling portability to systems like **ScyllaDB**, **DynamoDB**, **HBase**, or **BigTable**.\\n\\n\\n---\\n\\n## Overcoming Technical Constraints\\nAt the time, we were using Cassandra, which not only imposed a soft limit of 75 columns per row, but also exhibited significant performance degradation as the number of columns increased further, particularly in memory constrained machines. Wide rows caused high memory usage during reads, unpredictable latencies due to heavy deserialization overhead, and inefficiencies during compactions and repairs. This ruled out the naive \\"one column per feature\\" approach. We needed a format that was compact, minimized the number of columns, and remained efficient and portable across different storage systems.\\n\\n## The Solution: Schema Separation\\n\\nWe introduced the concept of Feature Groups\u2014logical groupings of features that must remain consistent with one another.\\nTo represent these groups efficiently, we adopted a layered storage approach:\\n\\n- **Feature Labels (Keys)** were stored in ZooKeeper, serving as the schema.\\n- **Feature Values** were stored as a comma-separated string array in Cassandra or Redis.\\n- **Expiry Timestamp and Schema Version** were appended using a semi-colon delimiter at the end of the string.\\n\\nExample:\\n\\n```bash\\nfeature_1_value,feature_2_value,feature_3_value;expiry_ts\\n```\\n\\nThis format allowed:\\n- Consistent writes and reads at the group level\\n- Easy parsing of feature values using the schema lookup from ZooKeeper\\n- Efficient storage with minimal DB column usage\\n- Support for per-group TTLs and schema evolution\\n\\n## Tracking Changes in Feature Groups\\nFeature groups don\u2019t stay static. As models evolve, features get added, renamed, or removed. But schema changes often go live before the data is ready\u2014and stopping ingestion just to wait for everything to align isn\'t feasible.\\n\\n### Common Real-World Scenarios:\\n- A new feature is added to the schema, but ingestion jobs still use the older schema version.\\n- Ongoing writes don\u2019t include the newly added feature, and stopping ingestion would break freshness for existing features.\\n- During serving, models request a mix of old and new features, depending on rollout stages.\\n\\n## The Solution: Schema Versioning\\nWe solved this with versioned feature group schemas, which unlocked several capabilities:\\n- ### Backward Compatibility\\n Older ingestion jobs can continue writing using older schema versions. During reads, the system uses the schema version embedded in the value to interpret the data correctly.\\n- ### Partial Availability Handling \\n During inference, if some features in the request aren\u2019t available (due to rollout delays or missing data), the system serves default values, ensuring the inference call doesn\u2019t fail.\\n- ### Safe Writes Without Pipeline Pauses\\n With schema versioning, we no longer had to stop ingestion pipelines for schema updates. Writes using previous versions can continue safely, and downstream consumers evolve independently.\\nThis design gave us the flexibility to move fast without breaking things\u2014preserving data quality, enabling experimentation, and ensuring reliability at scale.\\n\\n![Alt Text](./schema.png)\\n\\n## Interaction Store - 0th Version\\n\\n![Alt Text](./interaction-store-v0.png)\\n\\nTo power real-time Candidate Generators (CGs), we needed fast access to user behavior signals\u2014like what a user recently clicked, ordered, or added to their cart. These interactions form the basis for many real-time recommendations, such as **Similar Products**, **People Also Viewed**, or **Recently Ordered Again**.\\nFor the **0th version** of the Interaction Store, we focused on a design that was **simple, fast, and reliable** \u2014 optimized for high-throughput ingestion and low-latency lookups.\\n\\n## Event Ingestion\\nWe instrumented our backend services to emit key user interaction events to Kafka in real time. These included:\\n- Click\\n- Order\\n- Add to Cart\\n- Wishlist\\n- Share\\n\\nEach event carried essential metadata:\\n- userId \u2014 uniquely identifies the user\\n- productId \u2014 the item being interacted with\\n- timestamp \u2014 the moment the interaction occurred\\n\\nThis decoupled the interaction logging from storage, allowing ingestion and consumption to scale independently.\\n\\n## Storage Design\\nTo store these events, we built Kafka consumers that processed the incoming streams and wrote the data into Redis, using sorted sets (ZSETs) as the primary data structure.\\n\\n### Why Redis?\\nRedis gave us:\\n- **Low-latency** reads and writes\\n- **Time-ordered data** using ZSETs (via score = timestamp)\\n- **Native TTL support**, if needed in later versions\\n- **In-memory performance** \u2014ideal for real-time CGs\\n\\n### Storage Structure\\nEach user\u2019s interactions were stored using a composite key format, uniquely identifying the user and interaction type. This structure allowed efficient organization and quick retrieval of recent activity for recommendation generation:\\n\\n```bash\\nuserId_eventType \u2192 ZSET[...(pid, ts)...]\\n```\\n\\nWithin each ZSET:\\n\\n- The **timestamp** served as the score, maintaining temporal order\\n- The **productId** (optionally with metadata) was the **value**\\n\\nThis allowed us to efficiently retrieve the interactions with HTTP-based API server with two query modes:\\n- Fetch the **last k interactions** of a specific type for a given user with `ZREVRANGE(userId_eventType, count)`\\n- Retrieve **all interactions within a time range** (e.g., last 24 hours) with `ZREVRANGEBYSCORE(userId_eventType, timeRange)`\\n\\n### Built-in Guardrails\\nSince Redis was the sole store, we implemented High Availability (HA) to prevent data loss. To optimize memory usage, we also enforced size limits per event type\u2014only storing the last k interactions per user, with older entries getting truncated.\\n\\n## Conclusion: Laying the Foundation for Real-Time ML\\n\\nIn this first phase, we tackled the **fundamentals**\u2014shifting from batch-based recommendations to a **real-time Recommendation** using ML platform that could keep up with Meesho\u2019s growth.\\n\\nWith the **IOP Framework**, **Online Feature Store**, and **Interaction Store**, we built the core infrastructure to support real-time personalization at scale. These wins have already unlocked: \\n- \u2705 Faster, more dynamic recommendations for millions of users. \\n- \u2705 Better infrastructure efficiency, reducing wasted compute power. \\n- \u2705 A flexible, modular system that allows for further experimentation.\\n\\nBut this is just the beginning. While we\'ve solved key challenges, **certain roadblocks remain** \u2014from optimizing **cost-performance trade-offs** to **seamlessly evolving schemas**.\\n\\n\\nThis foundational work laid the path for a reliable and scalable **real-time feature serving layer**."}]}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/67d4782a.09ac845b.js b/docs/assets/js/67d4782a.09ac845b.js deleted file mode 100644 index 6a09823e..00000000 --- a/docs/assets/js/67d4782a.09ac845b.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[8588],{8453:(e,r,n)=>{n.d(r,{R:()=>t,x:()=>l});var s=n(6540);const o={},i=s.createContext(o);function t(e){const r=s.useContext(i);return s.useMemo((function(){return"function"==typeof e?e(r):{...r,...e}}),[r,e])}function l(e){let r;return r=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:t(e.components),s.createElement(i.Provider,{value:r},e.children)}},8769:(e,r,n)=>{n.r(r),n.d(r,{assets:()=>a,contentTitle:()=>l,default:()=>h,frontMatter:()=>t,metadata:()=>s,toc:()=>c});const s=JSON.parse('{"id":"online-feature-store/v1.0.0/benchmarks","title":"Benchmarks","description":"Summary","source":"@site/docs/online-feature-store/v1.0.0/benchmarks.md","sourceDirName":"online-feature-store/v1.0.0","slug":"/online-feature-store/v1.0.0/benchmarks","permalink":"/BharatMLStack/online-feature-store/v1.0.0/benchmarks","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/online-feature-store/v1.0.0/benchmarks.md","tags":[],"version":"current","sidebarPosition":3,"frontMatter":{"title":"Benchmarks","sidebar_position":3},"sidebar":"tutorialSidebar","previous":{"title":"Data Formats","permalink":"/BharatMLStack/online-feature-store/v1.0.0/data-formats"},"next":{"title":"Key Functionalities","permalink":"/BharatMLStack/online-feature-store/v1.0.0/functionalities"}}');var o=n(4848),i=n(8453);const t={title:"Benchmarks",sidebar_position:3},l="Serialization Performance Benchmarks",a={},c=[{value:"Summary",id:"summary",level:2},{value:"Test Methodology",id:"test-methodology",level:2},{value:"Environment",id:"environment",level:3},{value:"Test Data",id:"test-data",level:3},{value:"Performance Results",id:"performance-results",level:2},{value:"Serialization Speed (Lower is Better)",id:"serialization-speed-lower-is-better",level:3},{value:"Serialized Size (Lower is Better)",id:"serialized-size-lower-is-better",level:3},{value:"Memory Efficiency (Lower is Better)",id:"memory-efficiency-lower-is-better",level:3},{value:"Throughput (Higher is Better)",id:"throughput-higher-is-better",level:3},{value:"Detailed Analysis",id:"detailed-analysis",level:2},{value:"PSDB Advantages",id:"psdb-advantages",level:3},{value:"Protocol Buffers Analysis",id:"protocol-buffers-analysis",level:3},{value:"Apache Arrow Analysis",id:"apache-arrow-analysis",level:3},{value:"Scaling Characteristics",id:"scaling-characteristics",level:2},{value:"Small Datasets (100-1,000 features)",id:"small-datasets-100-1000-features",level:3},{value:"Large Datasets (10,000+ features)",id:"large-datasets-10000-features",level:3},{value:"Technical Implementation Notes",id:"technical-implementation-notes",level:2},{value:"PSDB Optimizations",id:"psdb-optimizations",level:3},{value:"Memory Layout Comparison",id:"memory-layout-comparison",level:3},{value:"Conclusion",id:"conclusion",level:2},{value:"PSDB: Best for Small-Medium Scale (\u22641,000 features)",id:"psdb-best-for-small-medium-scale-1000-features",level:3},{value:"Apache Arrow: Best for Large Scale (\u226510,000 features)",id:"apache-arrow-best-for-large-scale-10000-features",level:3},{value:"Protocol Buffers: Balanced Middle Ground",id:"protocol-buffers-balanced-middle-ground",level:3},{value:"Raw Benchmark Output [Uncompressed Data]",id:"raw-benchmark-output-uncompressed-data",level:2}];function d(e){const r={code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,i.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(r.header,{children:(0,o.jsx)(r.h1,{id:"serialization-performance-benchmarks",children:"Serialization Performance Benchmarks"})}),"\n",(0,o.jsx)(r.h2,{id:"summary",children:"Summary"}),"\n",(0,o.jsx)(r.p,{children:"This report presents comprehensive benchmark results comparing three serialization formats for the BharatML Online Feature Store:"}),"\n",(0,o.jsxs)(r.ul,{children:["\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"PSDB (Permanent Storage Data Block)"})," - Our custom format"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Protocol Buffers v3"})," - Google's binary serialization"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Apache Arrow"})," - Columnar in-memory analytics format"]}),"\n"]}),"\n",(0,o.jsx)(r.p,{children:(0,o.jsx)(r.strong,{children:"Key Findings:"})}),"\n",(0,o.jsxs)(r.ul,{children:["\n",(0,o.jsxs)(r.li,{children:["\ud83c\udfc6 ",(0,o.jsx)(r.strong,{children:"PSDB excels at small-to-medium scales"})," (100-1,000 features)"]}),"\n",(0,o.jsxs)(r.li,{children:["\u26a1 ",(0,o.jsx)(r.strong,{children:"35% faster"})," than Proto3, but ",(0,o.jsx)(r.strong,{children:"67% slower"})," than Arrow (for 100k features)"]}),"\n",(0,o.jsxs)(r.li,{children:["\ud83d\udce6 ",(0,o.jsx)(r.strong,{children:"18% smaller"})," than Proto3, comparable to Arrow"]}),"\n",(0,o.jsxs)(r.li,{children:["\ud83e\udde0 ",(0,o.jsx)(r.strong,{children:"93% fewer allocations"})," than Arrow (4 vs 66 allocs/op)"]}),"\n"]}),"\n",(0,o.jsx)(r.h2,{id:"test-methodology",children:"Test Methodology"}),"\n",(0,o.jsx)(r.h3,{id:"environment",children:"Environment"}),"\n",(0,o.jsxs)(r.ul,{children:["\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Platform"}),": macOS ARM64 (Apple Silicon)"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Go Version"}),": 1.22.12"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Test Date"}),": January 2025"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Compression"}),": Disabled for fair comparison (",(0,o.jsx)(r.code,{children:"compression.TypeNone"}),")"]}),"\n"]}),"\n",(0,o.jsx)(r.h3,{id:"test-data",children:"Test Data"}),"\n",(0,o.jsxs)(r.ul,{children:["\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Data Type"}),": Int32 arrays"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Feature Group Sizes"}),": 100, 1,000, 10,000, 100,000 features"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Test Iterations"}),": Variable (Go benchmark auto-scaling)"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Pool Optimization"}),": PSDB uses object pooling for memory efficiency"]}),"\n"]}),"\n",(0,o.jsx)(r.h2,{id:"performance-results",children:"Performance Results"}),"\n",(0,o.jsx)(r.h3,{id:"serialization-speed-lower-is-better",children:"Serialization Speed (Lower is Better)"}),"\n",(0,o.jsxs)(r.table,{children:[(0,o.jsx)(r.thead,{children:(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.th,{children:"Feature Count"}),(0,o.jsx)(r.th,{children:"PSDB (ns/op)"}),(0,o.jsx)(r.th,{children:"Proto3 (ns/op)"}),(0,o.jsx)(r.th,{children:"Arrow (ns/op)"}),(0,o.jsx)(r.th,{children:"PSDB vs Proto3"}),(0,o.jsx)(r.th,{children:"PSDB vs Arrow"})]})}),(0,o.jsxs)(r.tbody,{children:[(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.td,{children:"100"}),(0,o.jsx)(r.td,{children:"625"}),(0,o.jsx)(r.td,{children:"696"}),(0,o.jsx)(r.td,{children:"3,831"}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"10% faster"})}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"84% faster"})})]}),(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.td,{children:"1,000"}),(0,o.jsx)(r.td,{children:"4,056"}),(0,o.jsx)(r.td,{children:"6,004"}),(0,o.jsx)(r.td,{children:"5,191"}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"32% faster"})}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"22% faster"})})]}),(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.td,{children:"10,000"}),(0,o.jsx)(r.td,{children:"37,357"}),(0,o.jsx)(r.td,{children:"57,674"}),(0,o.jsx)(r.td,{children:"23,173"}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"35% faster"})}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"38% slower"})})]}),(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.td,{children:"100,000"}),(0,o.jsx)(r.td,{children:"359,932"}),(0,o.jsx)(r.td,{children:"556,541"}),(0,o.jsx)(r.td,{children:"118,489"}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"35% faster"})}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"67% slower"})})]})]})]}),"\n",(0,o.jsx)(r.h3,{id:"serialized-size-lower-is-better",children:"Serialized Size (Lower is Better)"}),"\n",(0,o.jsxs)(r.table,{children:[(0,o.jsx)(r.thead,{children:(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.th,{children:"Feature Count"}),(0,o.jsx)(r.th,{children:"Raw Size (bytes)"}),(0,o.jsx)(r.th,{children:"PSDB (bytes)"}),(0,o.jsx)(r.th,{children:"Proto3 (bytes)"}),(0,o.jsx)(r.th,{children:"Arrow (bytes)"}),(0,o.jsx)(r.th,{children:"PSDB Ratio"}),(0,o.jsx)(r.th,{children:"Proto3 Ratio"}),(0,o.jsx)(r.th,{children:"Arrow Ratio"})]})}),(0,o.jsxs)(r.tbody,{children:[(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.td,{children:"100"}),(0,o.jsx)(r.td,{children:"400"}),(0,o.jsx)(r.td,{children:"409"}),(0,o.jsx)(r.td,{children:"490"}),(0,o.jsx)(r.td,{children:"680"}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"102.2%"})}),(0,o.jsx)(r.td,{children:"122.5%"}),(0,o.jsx)(r.td,{children:"170.0%"})]}),(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.td,{children:"1,000"}),(0,o.jsx)(r.td,{children:"4,000"}),(0,o.jsx)(r.td,{children:"4,009"}),(0,o.jsx)(r.td,{children:"4,881"}),(0,o.jsx)(r.td,{children:"4,280"}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"100.2%"})}),(0,o.jsx)(r.td,{children:"122.0%"}),(0,o.jsx)(r.td,{children:"107.0%"})]}),(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.td,{children:"10,000"}),(0,o.jsx)(r.td,{children:"40,000"}),(0,o.jsx)(r.td,{children:"40,009"}),(0,o.jsx)(r.td,{children:"48,717"}),(0,o.jsx)(r.td,{children:"40,280"}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"100.0%"})}),(0,o.jsx)(r.td,{children:"121.8%"}),(0,o.jsx)(r.td,{children:"100.7%"})]}),(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.td,{children:"100,000"}),(0,o.jsx)(r.td,{children:"400,000"}),(0,o.jsx)(r.td,{children:"400,009"}),(0,o.jsx)(r.td,{children:"487,225"}),(0,o.jsx)(r.td,{children:"400,280"}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"100.0%"})}),(0,o.jsx)(r.td,{children:"121.8%"}),(0,o.jsx)(r.td,{children:"100.1%"})]})]})]}),"\n",(0,o.jsx)(r.h3,{id:"memory-efficiency-lower-is-better",children:"Memory Efficiency (Lower is Better)"}),"\n",(0,o.jsxs)(r.table,{children:[(0,o.jsx)(r.thead,{children:(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.th,{children:"Feature Count"}),(0,o.jsx)(r.th,{children:"PSDB (B/op)"}),(0,o.jsx)(r.th,{children:"Proto3 (B/op)"}),(0,o.jsx)(r.th,{children:"Arrow (B/op)"}),(0,o.jsx)(r.th,{children:"PSDB (allocs/op)"}),(0,o.jsx)(r.th,{children:"Proto3 (allocs/op)"}),(0,o.jsx)(r.th,{children:"Arrow (allocs/op)"})]})}),(0,o.jsxs)(r.tbody,{children:[(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.td,{children:"100"}),(0,o.jsx)(r.td,{children:"461"}),(0,o.jsx)(r.td,{children:"768"}),(0,o.jsx)(r.td,{children:"7,032"}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"4"})}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"2"})}),(0,o.jsx)(r.td,{children:"66"})]}),(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.td,{children:"1,000"}),(0,o.jsx)(r.td,{children:"4,143"}),(0,o.jsx)(r.td,{children:"5,632"}),(0,o.jsx)(r.td,{children:"15,544"}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"4"})}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"2"})}),(0,o.jsx)(r.td,{children:"66"})]}),(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.td,{children:"10,000"}),(0,o.jsx)(r.td,{children:"41,029"}),(0,o.jsx)(r.td,{children:"49,408"}),(0,o.jsx)(r.td,{children:"122,617"}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"4"})}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"2"})}),(0,o.jsx)(r.td,{children:"66"})]}),(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.td,{children:"100,000"}),(0,o.jsx)(r.td,{children:"401,814"}),(0,o.jsx)(r.td,{children:"491,776"}),(0,o.jsx)(r.td,{children:"957,948"}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"4"})}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"2"})}),(0,o.jsx)(r.td,{children:"66"})]})]})]}),"\n",(0,o.jsx)(r.h3,{id:"throughput-higher-is-better",children:"Throughput (Higher is Better)"}),"\n",(0,o.jsxs)(r.table,{children:[(0,o.jsx)(r.thead,{children:(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.th,{children:"Format"}),(0,o.jsx)(r.th,{children:"Throughput (MB/s)"}),(0,o.jsx)(r.th,{children:"Relative Performance"})]})}),(0,o.jsxs)(r.tbody,{children:[(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"PSDB"})}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"975.31"})}),(0,o.jsx)(r.td,{children:"Baseline (100%)"})]}),(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.td,{children:"Proto3"}),(0,o.jsx)(r.td,{children:"666.12"}),(0,o.jsx)(r.td,{children:"68% of PSDB"})]}),(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.td,{children:"Arrow"}),(0,o.jsx)(r.td,{children:"768.25"}),(0,o.jsx)(r.td,{children:"79% of PSDB"})]})]})]}),"\n",(0,o.jsx)(r.h2,{id:"detailed-analysis",children:"Detailed Analysis"}),"\n",(0,o.jsx)(r.h3,{id:"psdb-advantages",children:"PSDB Advantages"}),"\n",(0,o.jsxs)(r.ol,{children:["\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Minimal Overhead"}),": Only 9-byte header + raw data"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Optimal Packing"}),": No padding or metadata bloat"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Memory Pooling"}),": Reuses objects to minimize allocations"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Native Optimization"}),": Designed specifically for feature store use cases"]}),"\n"]}),"\n",(0,o.jsx)(r.h3,{id:"protocol-buffers-analysis",children:"Protocol Buffers Analysis"}),"\n",(0,o.jsxs)(r.ul,{children:["\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Consistent Overhead"}),": ~22% size penalty across all scales"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Moderate Speed"}),": Reasonable serialization performance"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Low Allocations"}),": Only 2 allocations per operation"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Varint Encoding"}),": Efficient for smaller integers"]}),"\n"]}),"\n",(0,o.jsx)(r.h3,{id:"apache-arrow-analysis",children:"Apache Arrow Analysis"}),"\n",(0,o.jsxs)(r.ul,{children:["\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"High Setup Cost"}),": Complex object creation (66 allocations)"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Good Large-Scale"}),": Better relative performance with more data"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Size Efficient"}),": Approaches raw data size for large datasets"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Memory Intensive"}),": Significant memory overhead per operation"]}),"\n"]}),"\n",(0,o.jsx)(r.h2,{id:"scaling-characteristics",children:"Scaling Characteristics"}),"\n",(0,o.jsx)(r.h3,{id:"small-datasets-100-1000-features",children:"Small Datasets (100-1,000 features)"}),"\n",(0,o.jsxs)(r.ul,{children:["\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"PSDB"}),": Consistent low overhead"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Proto3"}),": Moderate overhead, stable performance"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Arrow"}),": High setup cost dominates"]}),"\n"]}),"\n",(0,o.jsx)(r.h3,{id:"large-datasets-10000-features",children:"Large Datasets (10,000+ features)"}),"\n",(0,o.jsxs)(r.ul,{children:["\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"PSDB"}),": Linear scaling, maintains efficiency"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Proto3"}),": Good scaling but with consistent 22% size penalty"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Arrow"}),": Better amortization of setup costs"]}),"\n"]}),"\n",(0,o.jsx)(r.h2,{id:"technical-implementation-notes",children:"Technical Implementation Notes"}),"\n",(0,o.jsx)(r.h3,{id:"psdb-optimizations",children:"PSDB Optimizations"}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{className:"language-go",children:"// Object pooling for zero allocations\nvar psdbPool = GetPSDBPool()\n\n// Direct buffer allocation\nheaderSize := PSDBLayout1LengthBytes // 9 bytes\ndataSize := len(data) * 4 // 4 bytes per int32\n\n// No compression for maximum speed\ncompressionType = compression.TypeNone\n"})}),"\n",(0,o.jsx)(r.h3,{id:"memory-layout-comparison",children:"Memory Layout Comparison"}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{children:"PSDB Layout: [9-byte header][raw data]\nProto3 Layout: [varint lengths][encoded data][padding]\nArrow Layout: [schema][metadata][buffers][padding]\n"})}),"\n",(0,o.jsx)(r.h2,{id:"conclusion",children:"Conclusion"}),"\n",(0,o.jsxs)(r.p,{children:[(0,o.jsx)(r.strong,{children:"The optimal format depends on your use case and scale"}),":"]}),"\n",(0,o.jsx)(r.h3,{id:"psdb-best-for-small-medium-scale-1000-features",children:(0,o.jsx)(r.strong,{children:"PSDB: Best for Small-Medium Scale (\u22641,000 features)"})}),"\n",(0,o.jsxs)(r.ul,{children:["\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Excellent speed"}),": Up to 83% faster than Arrow for small datasets"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Optimal size efficiency"}),": Closest to raw data size (100.0-102.2%)"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Memory efficiency"}),": Only 4 allocations per operation"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Low overhead"}),": Minimal 9-byte header"]}),"\n"]}),"\n",(0,o.jsx)(r.h3,{id:"apache-arrow-best-for-large-scale-10000-features",children:(0,o.jsx)(r.strong,{children:"Apache Arrow: Best for Large Scale (\u226510,000 features)"})}),"\n",(0,o.jsxs)(r.ul,{children:["\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Superior large-scale performance"}),": 67% faster than PSDB at 100k features"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Efficient scaling"}),": Better amortization of setup costs"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Size competitive"}),": Approaches raw data size for large datasets"]}),"\n"]}),"\n",(0,o.jsx)(r.h3,{id:"protocol-buffers-balanced-middle-ground",children:(0,o.jsx)(r.strong,{children:"Protocol Buffers: Balanced Middle Ground"})}),"\n",(0,o.jsxs)(r.ul,{children:["\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Consistent performance"}),": Moderate speed across all scales"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Standard tooling"}),": Wide ecosystem support"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Predictable overhead"}),": ~22% size penalty but stable"]}),"\n"]}),"\n",(0,o.jsxs)(r.p,{children:[(0,o.jsx)(r.strong,{children:"Recommendation"}),": For the Online Feature Store's typical use patterns with ",(0,o.jsx)(r.strong,{children:"sub-1,000 feature requests"}),", ",(0,o.jsx)(r.strong,{children:"PSDB is the optimal choice"})," for production deployments."]}),"\n",(0,o.jsx)(r.h2,{id:"raw-benchmark-output-uncompressed-data",children:"Raw Benchmark Output [Uncompressed Data]"}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{children:"goos: darwin\ngoarch: arm64\npkg: github.com/Meesho/BharatMLStack/online-feature-store/internal/data/blocks\nBenchmarkInt32SerializationPSDB/PSDB/Size-100-10 1940238 625.3 ns/op 409.0 bytes 461 B/op 4 allocs/op\nBenchmarkInt32SerializationPSDB/PSDB/Size-1000-10 288300 4056 ns/op 4009 bytes 4143 B/op 4 allocs/op\nBenchmarkInt32SerializationPSDB/PSDB/Size-10000-10 32144 37357 ns/op 40009 bytes 41032 B/op 4 allocs/op\nBenchmarkInt32SerializationPSDB/PSDB/Size-100000-10 3244 359932 ns/op 400009 bytes 401572 B/op 4 allocs/op\nBenchmarkInt32SerializationProto3/Proto3/Size-100-10 1703066 695.9 ns/op 486.0 bytes 768 B/op 2 allocs/op\nBenchmarkInt32SerializationProto3/Proto3/Size-1000-10 194142 6004 ns/op 4885 bytes 5632 B/op 2 allocs/op\nBenchmarkInt32SerializationProto3/Proto3/Size-10000-10 20937 57674 ns/op 48734 bytes 49408 B/op 2 allocs/op\nBenchmarkInt32SerializationProto3/Proto3/Size-100000-10 2085 556541 ns/op 487263 bytes 491776 B/op 2 allocs/op\nBenchmarkInt32SerializationArrow/Arrow/Size-100-10 302257 3831 ns/op 680.0 bytes 7032 B/op 66 allocs/op\nBenchmarkInt32SerializationArrow/Arrow/Size-1000-10 228718 5191 ns/op 4280 bytes 15544 B/op 66 allocs/op\nBenchmarkInt32SerializationArrow/Arrow/Size-10000-10 52482 23173 ns/op 40280 bytes 122617 B/op 66 allocs/op\nBenchmarkInt32SerializationArrow/Arrow/Size-100000-10 9765 120081 ns/op 400280 bytes 957948 B/op 66 allocs/op\nBenchmarkInt32SerializationComparison/Comparison/Size-100/PSDB-10 1919401 670.2 ns/op 409.0 bytes 461 B/op 4 allocs/op\nBenchmarkInt32SerializationComparison/Comparison/Size-100/Proto3-10 1733599 693.2 ns/op 490.0 bytes 768 B/op 2 allocs/op\nBenchmarkInt32SerializationComparison/Comparison/Size-100/Arrow-10 304066 3896 ns/op 680.0 bytes 7032 B/op 66 allocs/op\nBenchmarkInt32SerializationComparison/Comparison/Size-1000/PSDB-10 290784 4074 ns/op 4009 bytes 4143 B/op 4 allocs/op\nBenchmarkInt32SerializationComparison/Comparison/Size-1000/Proto3-10 196962 6034 ns/op 4882 bytes 5632 B/op 2 allocs/op\nBenchmarkInt32SerializationComparison/Comparison/Size-1000/Arrow-10 227908 5240 ns/op 4280 bytes 15544 B/op 66 allocs/op\nBenchmarkInt32SerializationComparison/Comparison/Size-10000/PSDB-10 31732 38064 ns/op 40009 bytes 41024 B/op 4 allocs/op\nBenchmarkInt32SerializationComparison/Comparison/Size-10000/Proto3-10 20827 57670 ns/op 48745 bytes 49408 B/op 2 allocs/op\nBenchmarkInt32SerializationComparison/Comparison/Size-10000/Arrow-10 52000 23557 ns/op 40280 bytes 122617 B/op 66 allocs/op\nBenchmarkInt32SerializationComparison/Comparison/Size-100000/PSDB-10 3268 363817 ns/op 400009 bytes 401575 B/op 4 allocs/op\nBenchmarkInt32SerializationComparison/Comparison/Size-100000/Proto3-10 2097 559621 ns/op 487247 bytes 491776 B/op 2 allocs/op\nBenchmarkInt32SerializationComparison/Comparison/Size-100000/Arrow-10 10000 118489 ns/op 400280 bytes 957947 B/op 66 allocs/op\nBenchmarkInt32SizeComparison/SizeOnly/Size-100-10 1000000000 0.0000223 ns/op 680.0 arrow_bytes 170.0 arrow_ratio_pct 490.0 proto3_bytes 122.5 proto3_ratio_pct 409.0 psdb_bytes 102.2 psdb_ratio_pct 400.0 raw_bytes\nBenchmarkInt32SizeComparison/SizeOnly/Size-1000-10 1000000000 0.0000379 ns/op 4280 arrow_bytes 107.0 arrow_ratio_pct 4881 proto3_bytes 122.0 proto3_ratio_pct 4009 psdb_bytes 100.2 psdb_ratio_pct 4000 raw_bytes\nBenchmarkInt32SizeComparison/SizeOnly/Size-10000-10 1000000000 0.0001182 ns/op 40280 arrow_bytes 100.7 arrow_ratio_pct 48717 proto3_bytes 121.8 proto3_ratio_pct 40009 psdb_bytes 100.0 psdb_ratio_pct 40000 raw_bytes\nBenchmarkInt32SizeComparison/SizeOnly/Size-100000-10 1000000000 0.001034 ns/op 400280 arrow_bytes 100.1 arrow_ratio_pct 487225 proto3_bytes 121.8 proto3_ratio_pct 400009 psdb_bytes 100.0 psdb_ratio_pct 400000 raw_bytes\nBenchmarkInt32MemoryEfficiency/Memory/Size-100/PSDB_Pooled-10 1926676 622.4 ns/op 461 B/op 4 allocs/op\nBenchmarkInt32MemoryEfficiency/Memory/Size-100/Proto3-10 1713428 685.0 ns/op 768 B/op 2 allocs/op\nBenchmarkInt32MemoryEfficiency/Memory/Size-100/Arrow-10 312584 4029 ns/op 7032 B/op 66 allocs/op\nBenchmarkInt32MemoryEfficiency/Memory/Size-1000/PSDB_Pooled-10 290197 4189 ns/op 4143 B/op 4 allocs/op\nBenchmarkInt32MemoryEfficiency/Memory/Size-1000/Proto3-10 195694 6078 ns/op 5632 B/op 2 allocs/op\nBenchmarkInt32MemoryEfficiency/Memory/Size-1000/Arrow-10 224722 5190 ns/op 15544 B/op 66 allocs/op\nBenchmarkInt32MemoryEfficiency/Memory/Size-10000/PSDB_Pooled-10 31898 37684 ns/op 41029 B/op 4 allocs/op\nBenchmarkInt32MemoryEfficiency/Memory/Size-10000/Proto3-10 20840 58032 ns/op 49408 B/op 2 allocs/op\nBenchmarkInt32MemoryEfficiency/Memory/Size-10000/Arrow-10 51440 24049 ns/op 122617 B/op 66 allocs/op\nBenchmarkInt32MemoryEfficiency/Memory/Size-100000/PSDB_Pooled-10 3325 357690 ns/op 401814 B/op 4 allocs/op\nBenchmarkInt32MemoryEfficiency/Memory/Size-100000/Proto3-10 2158 559694 ns/op 491776 B/op 2 allocs/op\nBenchmarkInt32MemoryEfficiency/Memory/Size-100000/Arrow-10 9622 117515 ns/op 957948 B/op 66 allocs/op\nBenchmarkInt32Throughput/Throughput/PSDB-10 290912 4101 ns/op 975.31 MB/s 4143 B/op 4 allocs/op\nBenchmarkInt32Throughput/Throughput/Proto3-10 199087 6005 ns/op 666.12 MB/s 5632 B/op 2 allocs/op\nBenchmarkInt32Throughput/Throughput/Arrow-10 229594 5207 ns/op 768.25 MB/s 15544 B/op 66 allocs/op\nBenchmarkGetPSDBPoolWithoutPool-10 23836599 50.64 ns/op 192 B/op 1 allocs/op\nBenchmarkGetPSDBPoolWithPool-10 100000000 10.76 ns/op 0 B/op 0 allocs/op\nPASS\nok github.com/Meesho/BharatMLStack/online-feature-store/internal/data/blocks 58.891s\n"})}),"\n",(0,o.jsx)(r.hr,{}),"\n",(0,o.jsx)(r.p,{children:(0,o.jsx)(r.em,{children:"Benchmarks run on Apple Silicon (ARM64) with Go 1.22.12. Results may vary on different architectures and Go versions."})})]})}function h(e={}){const{wrapper:r}={...(0,i.R)(),...e.components};return r?(0,o.jsx)(r,{...e,children:(0,o.jsx)(d,{...e})}):d(e)}}}]); \ No newline at end of file diff --git a/docs/assets/js/67d4782a.96733ef0.js b/docs/assets/js/67d4782a.96733ef0.js new file mode 100644 index 00000000..dfdae632 --- /dev/null +++ b/docs/assets/js/67d4782a.96733ef0.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[8588],{8453:(e,r,n)=>{n.d(r,{R:()=>t,x:()=>l});var s=n(6540);const o={},i=s.createContext(o);function t(e){const r=s.useContext(i);return s.useMemo(function(){return"function"==typeof e?e(r):{...r,...e}},[r,e])}function l(e){let r;return r=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:t(e.components),s.createElement(i.Provider,{value:r},e.children)}},8769:(e,r,n)=>{n.r(r),n.d(r,{assets:()=>a,contentTitle:()=>l,default:()=>h,frontMatter:()=>t,metadata:()=>s,toc:()=>c});const s=JSON.parse('{"id":"online-feature-store/v1.0.0/benchmarks","title":"Benchmarks","description":"Summary","source":"@site/docs/online-feature-store/v1.0.0/benchmarks.md","sourceDirName":"online-feature-store/v1.0.0","slug":"/online-feature-store/v1.0.0/benchmarks","permalink":"/BharatMLStack/online-feature-store/v1.0.0/benchmarks","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/online-feature-store/v1.0.0/benchmarks.md","tags":[],"version":"current","sidebarPosition":3,"frontMatter":{"title":"Benchmarks","sidebar_position":3},"sidebar":"tutorialSidebar","previous":{"title":"Data Formats","permalink":"/BharatMLStack/online-feature-store/v1.0.0/data-formats"},"next":{"title":"Key Functionalities","permalink":"/BharatMLStack/online-feature-store/v1.0.0/functionalities"}}');var o=n(4848),i=n(8453);const t={title:"Benchmarks",sidebar_position:3},l="Serialization Performance Benchmarks",a={},c=[{value:"Summary",id:"summary",level:2},{value:"Test Methodology",id:"test-methodology",level:2},{value:"Environment",id:"environment",level:3},{value:"Test Data",id:"test-data",level:3},{value:"Performance Results",id:"performance-results",level:2},{value:"Serialization Speed (Lower is Better)",id:"serialization-speed-lower-is-better",level:3},{value:"Serialized Size (Lower is Better)",id:"serialized-size-lower-is-better",level:3},{value:"Memory Efficiency (Lower is Better)",id:"memory-efficiency-lower-is-better",level:3},{value:"Throughput (Higher is Better)",id:"throughput-higher-is-better",level:3},{value:"Detailed Analysis",id:"detailed-analysis",level:2},{value:"PSDB Advantages",id:"psdb-advantages",level:3},{value:"Protocol Buffers Analysis",id:"protocol-buffers-analysis",level:3},{value:"Apache Arrow Analysis",id:"apache-arrow-analysis",level:3},{value:"Scaling Characteristics",id:"scaling-characteristics",level:2},{value:"Small Datasets (100-1,000 features)",id:"small-datasets-100-1000-features",level:3},{value:"Large Datasets (10,000+ features)",id:"large-datasets-10000-features",level:3},{value:"Technical Implementation Notes",id:"technical-implementation-notes",level:2},{value:"PSDB Optimizations",id:"psdb-optimizations",level:3},{value:"Memory Layout Comparison",id:"memory-layout-comparison",level:3},{value:"Conclusion",id:"conclusion",level:2},{value:"PSDB: Best for Small-Medium Scale (\u22641,000 features)",id:"psdb-best-for-small-medium-scale-1000-features",level:3},{value:"Apache Arrow: Best for Large Scale (\u226510,000 features)",id:"apache-arrow-best-for-large-scale-10000-features",level:3},{value:"Protocol Buffers: Balanced Middle Ground",id:"protocol-buffers-balanced-middle-ground",level:3},{value:"Raw Benchmark Output [Uncompressed Data]",id:"raw-benchmark-output-uncompressed-data",level:2}];function d(e){const r={code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,i.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(r.header,{children:(0,o.jsx)(r.h1,{id:"serialization-performance-benchmarks",children:"Serialization Performance Benchmarks"})}),"\n",(0,o.jsx)(r.h2,{id:"summary",children:"Summary"}),"\n",(0,o.jsx)(r.p,{children:"This report presents comprehensive benchmark results comparing three serialization formats for the BharatML Online Feature Store:"}),"\n",(0,o.jsxs)(r.ul,{children:["\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"PSDB (Permanent Storage Data Block)"})," - Our custom format"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Protocol Buffers v3"})," - Google's binary serialization"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Apache Arrow"})," - Columnar in-memory analytics format"]}),"\n"]}),"\n",(0,o.jsx)(r.p,{children:(0,o.jsx)(r.strong,{children:"Key Findings:"})}),"\n",(0,o.jsxs)(r.ul,{children:["\n",(0,o.jsxs)(r.li,{children:["\ud83c\udfc6 ",(0,o.jsx)(r.strong,{children:"PSDB excels at small-to-medium scales"})," (100-1,000 features)"]}),"\n",(0,o.jsxs)(r.li,{children:["\u26a1 ",(0,o.jsx)(r.strong,{children:"35% faster"})," than Proto3, but ",(0,o.jsx)(r.strong,{children:"67% slower"})," than Arrow (for 100k features)"]}),"\n",(0,o.jsxs)(r.li,{children:["\ud83d\udce6 ",(0,o.jsx)(r.strong,{children:"18% smaller"})," than Proto3, comparable to Arrow"]}),"\n",(0,o.jsxs)(r.li,{children:["\ud83e\udde0 ",(0,o.jsx)(r.strong,{children:"93% fewer allocations"})," than Arrow (4 vs 66 allocs/op)"]}),"\n"]}),"\n",(0,o.jsx)(r.h2,{id:"test-methodology",children:"Test Methodology"}),"\n",(0,o.jsx)(r.h3,{id:"environment",children:"Environment"}),"\n",(0,o.jsxs)(r.ul,{children:["\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Platform"}),": macOS ARM64 (Apple Silicon)"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Go Version"}),": 1.22.12"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Test Date"}),": January 2025"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Compression"}),": Disabled for fair comparison (",(0,o.jsx)(r.code,{children:"compression.TypeNone"}),")"]}),"\n"]}),"\n",(0,o.jsx)(r.h3,{id:"test-data",children:"Test Data"}),"\n",(0,o.jsxs)(r.ul,{children:["\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Data Type"}),": Int32 arrays"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Feature Group Sizes"}),": 100, 1,000, 10,000, 100,000 features"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Test Iterations"}),": Variable (Go benchmark auto-scaling)"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Pool Optimization"}),": PSDB uses object pooling for memory efficiency"]}),"\n"]}),"\n",(0,o.jsx)(r.h2,{id:"performance-results",children:"Performance Results"}),"\n",(0,o.jsx)(r.h3,{id:"serialization-speed-lower-is-better",children:"Serialization Speed (Lower is Better)"}),"\n",(0,o.jsxs)(r.table,{children:[(0,o.jsx)(r.thead,{children:(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.th,{children:"Feature Count"}),(0,o.jsx)(r.th,{children:"PSDB (ns/op)"}),(0,o.jsx)(r.th,{children:"Proto3 (ns/op)"}),(0,o.jsx)(r.th,{children:"Arrow (ns/op)"}),(0,o.jsx)(r.th,{children:"PSDB vs Proto3"}),(0,o.jsx)(r.th,{children:"PSDB vs Arrow"})]})}),(0,o.jsxs)(r.tbody,{children:[(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.td,{children:"100"}),(0,o.jsx)(r.td,{children:"625"}),(0,o.jsx)(r.td,{children:"696"}),(0,o.jsx)(r.td,{children:"3,831"}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"10% faster"})}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"84% faster"})})]}),(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.td,{children:"1,000"}),(0,o.jsx)(r.td,{children:"4,056"}),(0,o.jsx)(r.td,{children:"6,004"}),(0,o.jsx)(r.td,{children:"5,191"}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"32% faster"})}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"22% faster"})})]}),(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.td,{children:"10,000"}),(0,o.jsx)(r.td,{children:"37,357"}),(0,o.jsx)(r.td,{children:"57,674"}),(0,o.jsx)(r.td,{children:"23,173"}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"35% faster"})}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"38% slower"})})]}),(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.td,{children:"100,000"}),(0,o.jsx)(r.td,{children:"359,932"}),(0,o.jsx)(r.td,{children:"556,541"}),(0,o.jsx)(r.td,{children:"118,489"}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"35% faster"})}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"67% slower"})})]})]})]}),"\n",(0,o.jsx)(r.h3,{id:"serialized-size-lower-is-better",children:"Serialized Size (Lower is Better)"}),"\n",(0,o.jsxs)(r.table,{children:[(0,o.jsx)(r.thead,{children:(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.th,{children:"Feature Count"}),(0,o.jsx)(r.th,{children:"Raw Size (bytes)"}),(0,o.jsx)(r.th,{children:"PSDB (bytes)"}),(0,o.jsx)(r.th,{children:"Proto3 (bytes)"}),(0,o.jsx)(r.th,{children:"Arrow (bytes)"}),(0,o.jsx)(r.th,{children:"PSDB Ratio"}),(0,o.jsx)(r.th,{children:"Proto3 Ratio"}),(0,o.jsx)(r.th,{children:"Arrow Ratio"})]})}),(0,o.jsxs)(r.tbody,{children:[(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.td,{children:"100"}),(0,o.jsx)(r.td,{children:"400"}),(0,o.jsx)(r.td,{children:"409"}),(0,o.jsx)(r.td,{children:"490"}),(0,o.jsx)(r.td,{children:"680"}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"102.2%"})}),(0,o.jsx)(r.td,{children:"122.5%"}),(0,o.jsx)(r.td,{children:"170.0%"})]}),(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.td,{children:"1,000"}),(0,o.jsx)(r.td,{children:"4,000"}),(0,o.jsx)(r.td,{children:"4,009"}),(0,o.jsx)(r.td,{children:"4,881"}),(0,o.jsx)(r.td,{children:"4,280"}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"100.2%"})}),(0,o.jsx)(r.td,{children:"122.0%"}),(0,o.jsx)(r.td,{children:"107.0%"})]}),(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.td,{children:"10,000"}),(0,o.jsx)(r.td,{children:"40,000"}),(0,o.jsx)(r.td,{children:"40,009"}),(0,o.jsx)(r.td,{children:"48,717"}),(0,o.jsx)(r.td,{children:"40,280"}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"100.0%"})}),(0,o.jsx)(r.td,{children:"121.8%"}),(0,o.jsx)(r.td,{children:"100.7%"})]}),(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.td,{children:"100,000"}),(0,o.jsx)(r.td,{children:"400,000"}),(0,o.jsx)(r.td,{children:"400,009"}),(0,o.jsx)(r.td,{children:"487,225"}),(0,o.jsx)(r.td,{children:"400,280"}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"100.0%"})}),(0,o.jsx)(r.td,{children:"121.8%"}),(0,o.jsx)(r.td,{children:"100.1%"})]})]})]}),"\n",(0,o.jsx)(r.h3,{id:"memory-efficiency-lower-is-better",children:"Memory Efficiency (Lower is Better)"}),"\n",(0,o.jsxs)(r.table,{children:[(0,o.jsx)(r.thead,{children:(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.th,{children:"Feature Count"}),(0,o.jsx)(r.th,{children:"PSDB (B/op)"}),(0,o.jsx)(r.th,{children:"Proto3 (B/op)"}),(0,o.jsx)(r.th,{children:"Arrow (B/op)"}),(0,o.jsx)(r.th,{children:"PSDB (allocs/op)"}),(0,o.jsx)(r.th,{children:"Proto3 (allocs/op)"}),(0,o.jsx)(r.th,{children:"Arrow (allocs/op)"})]})}),(0,o.jsxs)(r.tbody,{children:[(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.td,{children:"100"}),(0,o.jsx)(r.td,{children:"461"}),(0,o.jsx)(r.td,{children:"768"}),(0,o.jsx)(r.td,{children:"7,032"}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"4"})}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"2"})}),(0,o.jsx)(r.td,{children:"66"})]}),(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.td,{children:"1,000"}),(0,o.jsx)(r.td,{children:"4,143"}),(0,o.jsx)(r.td,{children:"5,632"}),(0,o.jsx)(r.td,{children:"15,544"}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"4"})}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"2"})}),(0,o.jsx)(r.td,{children:"66"})]}),(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.td,{children:"10,000"}),(0,o.jsx)(r.td,{children:"41,029"}),(0,o.jsx)(r.td,{children:"49,408"}),(0,o.jsx)(r.td,{children:"122,617"}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"4"})}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"2"})}),(0,o.jsx)(r.td,{children:"66"})]}),(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.td,{children:"100,000"}),(0,o.jsx)(r.td,{children:"401,814"}),(0,o.jsx)(r.td,{children:"491,776"}),(0,o.jsx)(r.td,{children:"957,948"}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"4"})}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"2"})}),(0,o.jsx)(r.td,{children:"66"})]})]})]}),"\n",(0,o.jsx)(r.h3,{id:"throughput-higher-is-better",children:"Throughput (Higher is Better)"}),"\n",(0,o.jsxs)(r.table,{children:[(0,o.jsx)(r.thead,{children:(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.th,{children:"Format"}),(0,o.jsx)(r.th,{children:"Throughput (MB/s)"}),(0,o.jsx)(r.th,{children:"Relative Performance"})]})}),(0,o.jsxs)(r.tbody,{children:[(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"PSDB"})}),(0,o.jsx)(r.td,{children:(0,o.jsx)(r.strong,{children:"975.31"})}),(0,o.jsx)(r.td,{children:"Baseline (100%)"})]}),(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.td,{children:"Proto3"}),(0,o.jsx)(r.td,{children:"666.12"}),(0,o.jsx)(r.td,{children:"68% of PSDB"})]}),(0,o.jsxs)(r.tr,{children:[(0,o.jsx)(r.td,{children:"Arrow"}),(0,o.jsx)(r.td,{children:"768.25"}),(0,o.jsx)(r.td,{children:"79% of PSDB"})]})]})]}),"\n",(0,o.jsx)(r.h2,{id:"detailed-analysis",children:"Detailed Analysis"}),"\n",(0,o.jsx)(r.h3,{id:"psdb-advantages",children:"PSDB Advantages"}),"\n",(0,o.jsxs)(r.ol,{children:["\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Minimal Overhead"}),": Only 9-byte header + raw data"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Optimal Packing"}),": No padding or metadata bloat"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Memory Pooling"}),": Reuses objects to minimize allocations"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Native Optimization"}),": Designed specifically for feature store use cases"]}),"\n"]}),"\n",(0,o.jsx)(r.h3,{id:"protocol-buffers-analysis",children:"Protocol Buffers Analysis"}),"\n",(0,o.jsxs)(r.ul,{children:["\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Consistent Overhead"}),": ~22% size penalty across all scales"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Moderate Speed"}),": Reasonable serialization performance"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Low Allocations"}),": Only 2 allocations per operation"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Varint Encoding"}),": Efficient for smaller integers"]}),"\n"]}),"\n",(0,o.jsx)(r.h3,{id:"apache-arrow-analysis",children:"Apache Arrow Analysis"}),"\n",(0,o.jsxs)(r.ul,{children:["\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"High Setup Cost"}),": Complex object creation (66 allocations)"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Good Large-Scale"}),": Better relative performance with more data"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Size Efficient"}),": Approaches raw data size for large datasets"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Memory Intensive"}),": Significant memory overhead per operation"]}),"\n"]}),"\n",(0,o.jsx)(r.h2,{id:"scaling-characteristics",children:"Scaling Characteristics"}),"\n",(0,o.jsx)(r.h3,{id:"small-datasets-100-1000-features",children:"Small Datasets (100-1,000 features)"}),"\n",(0,o.jsxs)(r.ul,{children:["\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"PSDB"}),": Consistent low overhead"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Proto3"}),": Moderate overhead, stable performance"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Arrow"}),": High setup cost dominates"]}),"\n"]}),"\n",(0,o.jsx)(r.h3,{id:"large-datasets-10000-features",children:"Large Datasets (10,000+ features)"}),"\n",(0,o.jsxs)(r.ul,{children:["\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"PSDB"}),": Linear scaling, maintains efficiency"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Proto3"}),": Good scaling but with consistent 22% size penalty"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Arrow"}),": Better amortization of setup costs"]}),"\n"]}),"\n",(0,o.jsx)(r.h2,{id:"technical-implementation-notes",children:"Technical Implementation Notes"}),"\n",(0,o.jsx)(r.h3,{id:"psdb-optimizations",children:"PSDB Optimizations"}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{className:"language-go",children:"// Object pooling for zero allocations\nvar psdbPool = GetPSDBPool()\n\n// Direct buffer allocation\nheaderSize := PSDBLayout1LengthBytes // 9 bytes\ndataSize := len(data) * 4 // 4 bytes per int32\n\n// No compression for maximum speed\ncompressionType = compression.TypeNone\n"})}),"\n",(0,o.jsx)(r.h3,{id:"memory-layout-comparison",children:"Memory Layout Comparison"}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{children:"PSDB Layout: [9-byte header][raw data]\nProto3 Layout: [varint lengths][encoded data][padding]\nArrow Layout: [schema][metadata][buffers][padding]\n"})}),"\n",(0,o.jsx)(r.h2,{id:"conclusion",children:"Conclusion"}),"\n",(0,o.jsxs)(r.p,{children:[(0,o.jsx)(r.strong,{children:"The optimal format depends on your use case and scale"}),":"]}),"\n",(0,o.jsx)(r.h3,{id:"psdb-best-for-small-medium-scale-1000-features",children:(0,o.jsx)(r.strong,{children:"PSDB: Best for Small-Medium Scale (\u22641,000 features)"})}),"\n",(0,o.jsxs)(r.ul,{children:["\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Excellent speed"}),": Up to 83% faster than Arrow for small datasets"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Optimal size efficiency"}),": Closest to raw data size (100.0-102.2%)"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Memory efficiency"}),": Only 4 allocations per operation"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Low overhead"}),": Minimal 9-byte header"]}),"\n"]}),"\n",(0,o.jsx)(r.h3,{id:"apache-arrow-best-for-large-scale-10000-features",children:(0,o.jsx)(r.strong,{children:"Apache Arrow: Best for Large Scale (\u226510,000 features)"})}),"\n",(0,o.jsxs)(r.ul,{children:["\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Superior large-scale performance"}),": 67% faster than PSDB at 100k features"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Efficient scaling"}),": Better amortization of setup costs"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Size competitive"}),": Approaches raw data size for large datasets"]}),"\n"]}),"\n",(0,o.jsx)(r.h3,{id:"protocol-buffers-balanced-middle-ground",children:(0,o.jsx)(r.strong,{children:"Protocol Buffers: Balanced Middle Ground"})}),"\n",(0,o.jsxs)(r.ul,{children:["\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Consistent performance"}),": Moderate speed across all scales"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Standard tooling"}),": Wide ecosystem support"]}),"\n",(0,o.jsxs)(r.li,{children:[(0,o.jsx)(r.strong,{children:"Predictable overhead"}),": ~22% size penalty but stable"]}),"\n"]}),"\n",(0,o.jsxs)(r.p,{children:[(0,o.jsx)(r.strong,{children:"Recommendation"}),": For the Online Feature Store's typical use patterns with ",(0,o.jsx)(r.strong,{children:"sub-1,000 feature requests"}),", ",(0,o.jsx)(r.strong,{children:"PSDB is the optimal choice"})," for production deployments."]}),"\n",(0,o.jsx)(r.h2,{id:"raw-benchmark-output-uncompressed-data",children:"Raw Benchmark Output [Uncompressed Data]"}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{children:"goos: darwin\ngoarch: arm64\npkg: github.com/Meesho/BharatMLStack/online-feature-store/internal/data/blocks\nBenchmarkInt32SerializationPSDB/PSDB/Size-100-10 1940238 625.3 ns/op 409.0 bytes 461 B/op 4 allocs/op\nBenchmarkInt32SerializationPSDB/PSDB/Size-1000-10 288300 4056 ns/op 4009 bytes 4143 B/op 4 allocs/op\nBenchmarkInt32SerializationPSDB/PSDB/Size-10000-10 32144 37357 ns/op 40009 bytes 41032 B/op 4 allocs/op\nBenchmarkInt32SerializationPSDB/PSDB/Size-100000-10 3244 359932 ns/op 400009 bytes 401572 B/op 4 allocs/op\nBenchmarkInt32SerializationProto3/Proto3/Size-100-10 1703066 695.9 ns/op 486.0 bytes 768 B/op 2 allocs/op\nBenchmarkInt32SerializationProto3/Proto3/Size-1000-10 194142 6004 ns/op 4885 bytes 5632 B/op 2 allocs/op\nBenchmarkInt32SerializationProto3/Proto3/Size-10000-10 20937 57674 ns/op 48734 bytes 49408 B/op 2 allocs/op\nBenchmarkInt32SerializationProto3/Proto3/Size-100000-10 2085 556541 ns/op 487263 bytes 491776 B/op 2 allocs/op\nBenchmarkInt32SerializationArrow/Arrow/Size-100-10 302257 3831 ns/op 680.0 bytes 7032 B/op 66 allocs/op\nBenchmarkInt32SerializationArrow/Arrow/Size-1000-10 228718 5191 ns/op 4280 bytes 15544 B/op 66 allocs/op\nBenchmarkInt32SerializationArrow/Arrow/Size-10000-10 52482 23173 ns/op 40280 bytes 122617 B/op 66 allocs/op\nBenchmarkInt32SerializationArrow/Arrow/Size-100000-10 9765 120081 ns/op 400280 bytes 957948 B/op 66 allocs/op\nBenchmarkInt32SerializationComparison/Comparison/Size-100/PSDB-10 1919401 670.2 ns/op 409.0 bytes 461 B/op 4 allocs/op\nBenchmarkInt32SerializationComparison/Comparison/Size-100/Proto3-10 1733599 693.2 ns/op 490.0 bytes 768 B/op 2 allocs/op\nBenchmarkInt32SerializationComparison/Comparison/Size-100/Arrow-10 304066 3896 ns/op 680.0 bytes 7032 B/op 66 allocs/op\nBenchmarkInt32SerializationComparison/Comparison/Size-1000/PSDB-10 290784 4074 ns/op 4009 bytes 4143 B/op 4 allocs/op\nBenchmarkInt32SerializationComparison/Comparison/Size-1000/Proto3-10 196962 6034 ns/op 4882 bytes 5632 B/op 2 allocs/op\nBenchmarkInt32SerializationComparison/Comparison/Size-1000/Arrow-10 227908 5240 ns/op 4280 bytes 15544 B/op 66 allocs/op\nBenchmarkInt32SerializationComparison/Comparison/Size-10000/PSDB-10 31732 38064 ns/op 40009 bytes 41024 B/op 4 allocs/op\nBenchmarkInt32SerializationComparison/Comparison/Size-10000/Proto3-10 20827 57670 ns/op 48745 bytes 49408 B/op 2 allocs/op\nBenchmarkInt32SerializationComparison/Comparison/Size-10000/Arrow-10 52000 23557 ns/op 40280 bytes 122617 B/op 66 allocs/op\nBenchmarkInt32SerializationComparison/Comparison/Size-100000/PSDB-10 3268 363817 ns/op 400009 bytes 401575 B/op 4 allocs/op\nBenchmarkInt32SerializationComparison/Comparison/Size-100000/Proto3-10 2097 559621 ns/op 487247 bytes 491776 B/op 2 allocs/op\nBenchmarkInt32SerializationComparison/Comparison/Size-100000/Arrow-10 10000 118489 ns/op 400280 bytes 957947 B/op 66 allocs/op\nBenchmarkInt32SizeComparison/SizeOnly/Size-100-10 1000000000 0.0000223 ns/op 680.0 arrow_bytes 170.0 arrow_ratio_pct 490.0 proto3_bytes 122.5 proto3_ratio_pct 409.0 psdb_bytes 102.2 psdb_ratio_pct 400.0 raw_bytes\nBenchmarkInt32SizeComparison/SizeOnly/Size-1000-10 1000000000 0.0000379 ns/op 4280 arrow_bytes 107.0 arrow_ratio_pct 4881 proto3_bytes 122.0 proto3_ratio_pct 4009 psdb_bytes 100.2 psdb_ratio_pct 4000 raw_bytes\nBenchmarkInt32SizeComparison/SizeOnly/Size-10000-10 1000000000 0.0001182 ns/op 40280 arrow_bytes 100.7 arrow_ratio_pct 48717 proto3_bytes 121.8 proto3_ratio_pct 40009 psdb_bytes 100.0 psdb_ratio_pct 40000 raw_bytes\nBenchmarkInt32SizeComparison/SizeOnly/Size-100000-10 1000000000 0.001034 ns/op 400280 arrow_bytes 100.1 arrow_ratio_pct 487225 proto3_bytes 121.8 proto3_ratio_pct 400009 psdb_bytes 100.0 psdb_ratio_pct 400000 raw_bytes\nBenchmarkInt32MemoryEfficiency/Memory/Size-100/PSDB_Pooled-10 1926676 622.4 ns/op 461 B/op 4 allocs/op\nBenchmarkInt32MemoryEfficiency/Memory/Size-100/Proto3-10 1713428 685.0 ns/op 768 B/op 2 allocs/op\nBenchmarkInt32MemoryEfficiency/Memory/Size-100/Arrow-10 312584 4029 ns/op 7032 B/op 66 allocs/op\nBenchmarkInt32MemoryEfficiency/Memory/Size-1000/PSDB_Pooled-10 290197 4189 ns/op 4143 B/op 4 allocs/op\nBenchmarkInt32MemoryEfficiency/Memory/Size-1000/Proto3-10 195694 6078 ns/op 5632 B/op 2 allocs/op\nBenchmarkInt32MemoryEfficiency/Memory/Size-1000/Arrow-10 224722 5190 ns/op 15544 B/op 66 allocs/op\nBenchmarkInt32MemoryEfficiency/Memory/Size-10000/PSDB_Pooled-10 31898 37684 ns/op 41029 B/op 4 allocs/op\nBenchmarkInt32MemoryEfficiency/Memory/Size-10000/Proto3-10 20840 58032 ns/op 49408 B/op 2 allocs/op\nBenchmarkInt32MemoryEfficiency/Memory/Size-10000/Arrow-10 51440 24049 ns/op 122617 B/op 66 allocs/op\nBenchmarkInt32MemoryEfficiency/Memory/Size-100000/PSDB_Pooled-10 3325 357690 ns/op 401814 B/op 4 allocs/op\nBenchmarkInt32MemoryEfficiency/Memory/Size-100000/Proto3-10 2158 559694 ns/op 491776 B/op 2 allocs/op\nBenchmarkInt32MemoryEfficiency/Memory/Size-100000/Arrow-10 9622 117515 ns/op 957948 B/op 66 allocs/op\nBenchmarkInt32Throughput/Throughput/PSDB-10 290912 4101 ns/op 975.31 MB/s 4143 B/op 4 allocs/op\nBenchmarkInt32Throughput/Throughput/Proto3-10 199087 6005 ns/op 666.12 MB/s 5632 B/op 2 allocs/op\nBenchmarkInt32Throughput/Throughput/Arrow-10 229594 5207 ns/op 768.25 MB/s 15544 B/op 66 allocs/op\nBenchmarkGetPSDBPoolWithoutPool-10 23836599 50.64 ns/op 192 B/op 1 allocs/op\nBenchmarkGetPSDBPoolWithPool-10 100000000 10.76 ns/op 0 B/op 0 allocs/op\nPASS\nok github.com/Meesho/BharatMLStack/online-feature-store/internal/data/blocks 58.891s\n"})}),"\n",(0,o.jsx)(r.hr,{}),"\n",(0,o.jsx)(r.p,{children:(0,o.jsx)(r.em,{children:"Benchmarks run on Apple Silicon (ARM64) with Go 1.22.12. Results may vary on different architectures and Go versions."})})]})}function h(e={}){const{wrapper:r}={...(0,i.R)(),...e.components};return r?(0,o.jsx)(r,{...e,children:(0,o.jsx)(d,{...e})}):d(e)}}}]); \ No newline at end of file diff --git a/docs/assets/js/6870.25f53758.js b/docs/assets/js/6870.25f53758.js new file mode 100644 index 00000000..e0e7d9dc --- /dev/null +++ b/docs/assets/js/6870.25f53758.js @@ -0,0 +1 @@ +(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[6870],{3253:(e,t,n)=>{"use strict";n.d(t,{A:()=>ze});var s=n(6540),a=n(8453),r=n(5260),o=n(2303),c=n(4164),i=n(5293),l=n(6342);function d(){const{prism:e}=(0,l.p)(),{colorMode:t}=(0,i.G)(),n=e.theme,s=e.darkTheme||n;return"dark"===t?s:n}var u=n(7559),m=n(8426),h=n.n(m),f=n(9532),p=n(4848);const g=/title=(?["'])(?.*?)\1/,x=/\{(?<range>[\d,-]+)\}/,j={js:{start:"\\/\\/",end:""},jsBlock:{start:"\\/\\*",end:"\\*\\/"},jsx:{start:"\\{\\s*\\/\\*",end:"\\*\\/\\s*\\}"},bash:{start:"#",end:""},html:{start:"\x3c!--",end:"--\x3e"}},b={...j,lua:{start:"--",end:""},wasm:{start:"\\;\\;",end:""},tex:{start:"%",end:""},vb:{start:"['\u2018\u2019]",end:""},vbnet:{start:"(?:_\\s*)?['\u2018\u2019]",end:""},rem:{start:"[Rr][Ee][Mm]\\b",end:""},f90:{start:"!",end:""},ml:{start:"\\(\\*",end:"\\*\\)"},cobol:{start:"\\*>",end:""}},v=Object.keys(j);function N(e,t){const n=e.map(e=>{const{start:n,end:s}=b[e];return`(?:${n}\\s*(${t.flatMap(e=>[e.line,e.block?.start,e.block?.end].filter(Boolean)).join("|")})\\s*${s})`}).join("|");return new RegExp(`^\\s*(?:${n})\\s*$`)}function y({showLineNumbers:e,metastring:t}){return"boolean"==typeof e?e?1:void 0:"number"==typeof e?e:function(e){const t=e?.split(" ").find(e=>e.startsWith("showLineNumbers"));if(t){if(t.startsWith("showLineNumbers=")){const e=t.replace("showLineNumbers=","");return parseInt(e,10)}return 1}}(t)}function A(e,t){const{language:n,magicComments:s}=t;if(void 0===n)return{lineClassNames:{},code:e};const a=function(e,t){switch(e){case"js":case"javascript":case"ts":case"typescript":return N(["js","jsBlock"],t);case"jsx":case"tsx":return N(["js","jsBlock","jsx"],t);case"html":return N(["js","jsBlock","html"],t);case"python":case"py":case"bash":return N(["bash"],t);case"markdown":case"md":return N(["html","jsx","bash"],t);case"tex":case"latex":case"matlab":return N(["tex"],t);case"lua":case"haskell":return N(["lua"],t);case"sql":return N(["lua","jsBlock"],t);case"wasm":return N(["wasm"],t);case"vb":case"vba":case"visual-basic":return N(["vb","rem"],t);case"vbnet":return N(["vbnet","rem"],t);case"batch":return N(["rem"],t);case"basic":return N(["rem","f90"],t);case"fsharp":return N(["js","ml"],t);case"ocaml":case"sml":return N(["ml"],t);case"fortran":return N(["f90"],t);case"cobol":return N(["cobol"],t);default:return N(v,t)}}(n,s),r=e.split(/\r?\n/),o=Object.fromEntries(s.map(e=>[e.className,{start:0,range:""}])),c=Object.fromEntries(s.filter(e=>e.line).map(({className:e,line:t})=>[t,e])),i=Object.fromEntries(s.filter(e=>e.block).map(({className:e,block:t})=>[t.start,e])),l=Object.fromEntries(s.filter(e=>e.block).map(({className:e,block:t})=>[t.end,e]));for(let u=0;u<r.length;){const e=r[u].match(a);if(!e){u+=1;continue}const t=e.slice(1).find(e=>void 0!==e);c[t]?o[c[t]].range+=`${u},`:i[t]?o[i[t]].start=u:l[t]&&(o[l[t]].range+=`${o[l[t]].start}-${u-1},`),r.splice(u,1)}const d={};return Object.entries(o).forEach(([e,{range:t}])=>{h()(t).forEach(t=>{d[t]??=[],d[t].push(e)})}),{code:r.join("\n"),lineClassNames:d}}function w(e,t){const n=e.replace(/\r?\n$/,"");return function(e,{metastring:t,magicComments:n}){if(t&&x.test(t)){const s=t.match(x).groups.range;if(0===n.length)throw new Error(`A highlight range has been given in code block's metastring (\`\`\` ${t}), but no magic comment config is available. Docusaurus applies the first magic comment entry's className for metastring ranges.`);const a=n[0].className,r=h()(s).filter(e=>e>0).map(e=>[e-1,[a]]);return{lineClassNames:Object.fromEntries(r),code:e}}return null}(n,{...t})??A(n,{...t})}function C(e){const t=function(e){return t=e.language??function(e){if(!e)return;const t=e.split(" ").find(e=>e.startsWith("language-"));return t?.replace(/language-/,"")}(e.className)??e.defaultLanguage,t?.toLowerCase()??"text";var t}({language:e.language,defaultLanguage:e.defaultLanguage,className:e.className}),{lineClassNames:n,code:s}=w(e.code,{metastring:e.metastring,magicComments:e.magicComments,language:t}),a=function({className:e,language:t}){return(0,c.A)(e,t&&!e?.includes(`language-${t}`)&&`language-${t}`)}({className:e.className,language:t}),r=(o=e.metastring,(o?.match(g)?.groups.title??"")||e.title);var o;const i=y({showLineNumbers:e.showLineNumbers,metastring:e.metastring});return{codeInput:e.code,code:s,className:a,language:t,title:r,lineNumbersStart:i,lineClassNames:n}}const k=(0,s.createContext)(null);function B({metadata:e,wordWrap:t,children:n}){const a=(0,s.useMemo)(()=>({metadata:e,wordWrap:t}),[e,t]);return(0,p.jsx)(k.Provider,{value:a,children:n})}function E(){const e=(0,s.useContext)(k);if(null===e)throw new f.dV("CodeBlockContextProvider");return e}const L="codeBlockContainer_Ckt0";function T({as:e,...t}){const n=function(e){const t={color:"--prism-color",backgroundColor:"--prism-background-color"},n={};return Object.entries(e.plain).forEach(([e,s])=>{const a=t[e];a&&"string"==typeof s&&(n[a]=s)}),n}(d());return(0,p.jsx)(e,{...t,style:n,className:(0,c.A)(t.className,L,u.G.common.codeBlock)})}const _="codeBlock_bY9V",M="codeBlockStandalone_MEMb",S="codeBlockLines_e6Vv",U="codeBlockLinesWithNumbering_o6Pm";function z({children:e,className:t}){return(0,p.jsx)(T,{as:"pre",tabIndex:0,className:(0,c.A)(M,"thin-scrollbar",t),children:(0,p.jsx)("code",{className:S,children:e})})}const I={attributes:!0,characterData:!0,childList:!0,subtree:!0};function H(e,t){const[n,a]=(0,s.useState)(),r=(0,s.useCallback)(()=>{a(e.current?.closest("[role=tabpanel][hidden]"))},[e,a]);(0,s.useEffect)(()=>{r()},[r]),function(e,t,n=I){const a=(0,f._q)(t),r=(0,f.Be)(n);(0,s.useEffect)(()=>{const t=new MutationObserver(a);return e&&t.observe(e,r),()=>t.disconnect()},[e,a,r])}(n,e=>{e.forEach(e=>{"attributes"===e.type&&"hidden"===e.attributeName&&(t(),r())})},{attributes:!0,characterData:!1,childList:!1,subtree:!1})}function R({children:e}){return e}var V=n(1765);function P({line:e,token:t,...n}){return(0,p.jsx)("span",{...n})}const W="codeLine_lJS_",$="codeLineNumber_Tfdd",D="codeLineContent_feaV";function q({line:e,classNames:t,showLineNumbers:n,getLineProps:s,getTokenProps:a}){const r=function(e){const t=1===e.length&&"\n"===e[0].content?e[0]:void 0;return t?[{...t,content:""}]:e}(e),o=s({line:r,className:(0,c.A)(t,n&&W)}),i=r.map((e,t)=>{const n=a({token:e});return(0,p.jsx)(P,{...n,line:r,token:e,children:n.children},t)});return(0,p.jsxs)("span",{...o,children:[n?(0,p.jsxs)(p.Fragment,{children:[(0,p.jsx)("span",{className:$}),(0,p.jsx)("span",{className:D,children:i})]}):i,(0,p.jsx)("br",{})]})}const O=s.forwardRef((e,t)=>(0,p.jsx)("pre",{ref:t,tabIndex:0,...e,className:(0,c.A)(e.className,_,"thin-scrollbar")}));function F(e){const{metadata:t}=E();return(0,p.jsx)("code",{...e,className:(0,c.A)(e.className,S,void 0!==t.lineNumbersStart&&U),style:{...e.style,counterReset:void 0===t.lineNumbersStart?void 0:"line-count "+(t.lineNumbersStart-1)}})}function G({className:e}){const{metadata:t,wordWrap:n}=E(),s=d(),{code:a,language:r,lineNumbersStart:o,lineClassNames:i}=t;return(0,p.jsx)(V.f4,{theme:s,code:a,language:r,children:({className:t,style:s,tokens:a,getLineProps:r,getTokenProps:l})=>(0,p.jsx)(O,{ref:n.codeBlockRef,className:(0,c.A)(e,t),style:s,children:(0,p.jsx)(F,{children:a.map((e,t)=>(0,p.jsx)(q,{line:e,getLineProps:r,getTokenProps:l,classNames:i[t],showLineNumbers:void 0!==o},t))})})})}function J({children:e,fallback:t}){return(0,o.A)()?(0,p.jsx)(p.Fragment,{children:e?.()}):t??null}var Z=n(1312);function Y({className:e,...t}){return(0,p.jsx)("button",{type:"button",...t,className:(0,c.A)("clean-btn",e)})}function Q(e){return(0,p.jsx)("svg",{viewBox:"0 0 24 24",...e,children:(0,p.jsx)("path",{fill:"currentColor",d:"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"})})}function X(e){return(0,p.jsx)("svg",{viewBox:"0 0 24 24",...e,children:(0,p.jsx)("path",{fill:"currentColor",d:"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"})})}const K={copyButtonCopied:"copyButtonCopied_Vdqa",copyButtonIcons:"copyButtonIcons_IEyt",copyButtonIcon:"copyButtonIcon_TrPX",copyButtonSuccessIcon:"copyButtonSuccessIcon_cVMy"};function ee(e){return e?(0,Z.T)({id:"theme.CodeBlock.copied",message:"Copied",description:"The copied button label on code blocks"}):(0,Z.T)({id:"theme.CodeBlock.copyButtonAriaLabel",message:"Copy code to clipboard",description:"The ARIA label for copy code blocks button"})}function te(){const{metadata:{code:e}}=E(),[t,n]=(0,s.useState)(!1),a=(0,s.useRef)(void 0),r=(0,s.useCallback)(()=>{!function(e,{target:t=document.body}={}){if("string"!=typeof e)throw new TypeError(`Expected parameter \`text\` to be a \`string\`, got \`${typeof e}\`.`);const n=document.createElement("textarea"),s=document.activeElement;n.value=e,n.setAttribute("readonly",""),n.style.all="unset",n.style.contain="strict",n.style.position="absolute",n.style.left="-9999px",n.style.width="2em",n.style.height="2em",n.style.padding="0",n.style.border="none",n.style.outline="none",n.style.boxShadow="none",n.style.background="transparent",n.style.fontSize="12pt";const a=document.getSelection(),r=a.rangeCount>0&&a.getRangeAt(0);t.append(n),n.select(),n.selectionStart=0,n.selectionEnd=e.length;let o=!1;try{o=document.execCommand("copy")}catch{}n.remove(),r&&(a.removeAllRanges(),a.addRange(r)),s&&s.focus()}(e),n(!0),a.current=window.setTimeout(()=>{n(!1)},1e3)},[e]);return(0,s.useEffect)(()=>()=>window.clearTimeout(a.current),[]),{copyCode:r,isCopied:t}}function ne({className:e}){const{copyCode:t,isCopied:n}=te();return(0,p.jsx)(Y,{"aria-label":ee(n),title:(0,Z.T)({id:"theme.CodeBlock.copy",message:"Copy",description:"The copy button label on code blocks"}),className:(0,c.A)(e,K.copyButton,n&&K.copyButtonCopied),onClick:t,children:(0,p.jsxs)("span",{className:K.copyButtonIcons,"aria-hidden":"true",children:[(0,p.jsx)(Q,{className:K.copyButtonIcon}),(0,p.jsx)(X,{className:K.copyButtonSuccessIcon})]})})}function se(e){return(0,p.jsx)("svg",{viewBox:"0 0 24 24",...e,children:(0,p.jsx)("path",{fill:"currentColor",d:"M4 19h6v-2H4v2zM20 5H4v2h16V5zm-3 6H4v2h13.25c1.1 0 2 .9 2 2s-.9 2-2 2H15v-2l-3 3l3 3v-2h2c2.21 0 4-1.79 4-4s-1.79-4-4-4z"})})}const ae="wordWrapButtonIcon_b1P5",re="wordWrapButtonEnabled_uzNF";function oe({className:e}){const{wordWrap:t}=E();if(!(t.isEnabled||t.isCodeScrollable))return!1;const n=(0,Z.T)({id:"theme.CodeBlock.wordWrapToggle",message:"Toggle word wrap",description:"The title attribute for toggle word wrapping button of code block lines"});return(0,p.jsx)(Y,{onClick:()=>t.toggle(),className:(0,c.A)(e,t.isEnabled&&re),"aria-label":n,title:n,children:(0,p.jsx)(se,{className:ae,"aria-hidden":"true"})})}const ce="buttonGroup_M5ko";function ie({className:e}){return(0,p.jsx)(J,{children:()=>(0,p.jsxs)("div",{className:(0,c.A)(e,ce),children:[(0,p.jsx)(oe,{}),(0,p.jsx)(ne,{})]})})}const le="codeBlockContent_QJqH",de="codeBlockTitle_OeMC";function ue({className:e}){const{metadata:t}=E();return(0,p.jsxs)(T,{as:"div",className:(0,c.A)(e,t.className),children:[t.title&&(0,p.jsx)("div",{className:de,children:(0,p.jsx)(R,{children:t.title})}),(0,p.jsxs)("div",{className:le,children:[(0,p.jsx)(G,{}),(0,p.jsx)(ie,{})]})]})}function me(e){const t=function(e){const{prism:t}=(0,l.p)();return C({code:e.children,className:e.className,metastring:e.metastring,magicComments:t.magicComments,defaultLanguage:t.defaultLanguage,language:e.language,title:e.title,showLineNumbers:e.showLineNumbers})}(e),n=function(){const[e,t]=(0,s.useState)(!1),[n,a]=(0,s.useState)(!1),r=(0,s.useRef)(null),o=(0,s.useCallback)(()=>{const n=r.current.querySelector("code");e?n.removeAttribute("style"):(n.style.whiteSpace="pre-wrap",n.style.overflowWrap="anywhere"),t(e=>!e)},[r,e]),c=(0,s.useCallback)(()=>{const{scrollWidth:e,clientWidth:t}=r.current,n=e>t||r.current.querySelector("code").hasAttribute("style");a(n)},[r]);return H(r,c),(0,s.useEffect)(()=>{c()},[e,c]),(0,s.useEffect)(()=>(window.addEventListener("resize",c,{passive:!0}),()=>{window.removeEventListener("resize",c)}),[c]),{codeBlockRef:r,isEnabled:e,isCodeScrollable:n,toggle:o}}();return(0,p.jsx)(B,{metadata:t,wordWrap:n,children:(0,p.jsx)(ue,{})})}function he({children:e,...t}){const n=(0,o.A)(),a=function(e){return s.Children.toArray(e).some(e=>(0,s.isValidElement)(e))?e:Array.isArray(e)?e.join(""):e}(e),r="string"==typeof a?me:z;return(0,p.jsx)(r,{...t,children:a},String(n))}function fe(e){return(0,p.jsx)("code",{...e})}var pe=n(8774);var ge=n(3427),xe=n(1422);const je="details_lb9f",be="isBrowser_bmU9",ve="collapsibleContent_i85q";function Ne(e){return!!e&&("SUMMARY"===e.tagName||Ne(e.parentElement))}function ye(e,t){return!!e&&(e===t||ye(e.parentElement,t))}function Ae({summary:e,children:t,...n}){(0,ge.A)().collectAnchor(n.id);const a=(0,o.A)(),r=(0,s.useRef)(null),{collapsed:i,setCollapsed:l}=(0,xe.u)({initialState:!n.open}),[d,u]=(0,s.useState)(n.open),m=s.isValidElement(e)?e:(0,p.jsx)("summary",{children:e??"Details"});return(0,p.jsxs)("details",{...n,ref:r,open:d,"data-collapsed":i,className:(0,c.A)(je,a&&be,n.className),onMouseDown:e=>{Ne(e.target)&&e.detail>1&&e.preventDefault()},onClick:e=>{e.stopPropagation();const t=e.target;Ne(t)&&ye(t,r.current)&&(e.preventDefault(),i?(l(!1),u(!0)):l(!0))},children:[m,(0,p.jsx)(xe.N,{lazy:!1,collapsed:i,onCollapseTransitionEnd:e=>{l(e),u(!e)},children:(0,p.jsx)("div",{className:ve,children:t})})]})}const we="details_b_Ee";function Ce({...e}){return(0,p.jsx)(Ae,{...e,className:(0,c.A)("alert alert--info",we,e.className)})}function ke(e){const t=s.Children.toArray(e.children),n=t.find(e=>s.isValidElement(e)&&"summary"===e.type),a=(0,p.jsx)(p.Fragment,{children:t.filter(e=>e!==n)});return(0,p.jsx)(Ce,{...e,summary:n,children:a})}var Be=n(1107);function Ee(e){return(0,p.jsx)(Be.A,{...e})}const Le="containsTaskList_mC6p";function Te(e){if(void 0!==e)return(0,c.A)(e,e?.includes("contains-task-list")&&Le)}const _e="img_ev3q";var Me=n(7293),Se=n(418);const Ue={Head:r.A,details:ke,Details:ke,code:function(e){return function(e){return void 0!==e.children&&s.Children.toArray(e.children).every(e=>"string"==typeof e&&!e.includes("\n"))}(e)?(0,p.jsx)(fe,{...e}):(0,p.jsx)(he,{...e})},a:function(e){return(0,p.jsx)(pe.A,{...e})},pre:function(e){return(0,p.jsx)(p.Fragment,{children:e.children})},ul:function(e){return(0,p.jsx)("ul",{...e,className:Te(e.className)})},li:function(e){return(0,ge.A)().collectAnchor(e.id),(0,p.jsx)("li",{...e})},img:function(e){return(0,p.jsx)("img",{decoding:"async",loading:"lazy",...e,className:(t=e.className,(0,c.A)(t,_e))});var t},h1:e=>(0,p.jsx)(Ee,{as:"h1",...e}),h2:e=>(0,p.jsx)(Ee,{as:"h2",...e}),h3:e=>(0,p.jsx)(Ee,{as:"h3",...e}),h4:e=>(0,p.jsx)(Ee,{as:"h4",...e}),h5:e=>(0,p.jsx)(Ee,{as:"h5",...e}),h6:e=>(0,p.jsx)(Ee,{as:"h6",...e}),admonition:Me.A,mermaid:Se.A};function ze({children:e}){return(0,p.jsx)(a.x,{components:Ue,children:e})}},4336:(e,t,n)=>{"use strict";n.d(t,{A:()=>g});n(6540);var s=n(4164),a=n(1312),r=n(7559),o=n(8774);const c={iconEdit:"iconEdit_Z9Sw"};var i=n(4848);function l({className:e,...t}){return(0,i.jsx)("svg",{fill:"currentColor",height:"20",width:"20",viewBox:"0 0 40 40",className:(0,s.A)(c.iconEdit,e),"aria-hidden":"true",...t,children:(0,i.jsx)("g",{children:(0,i.jsx)("path",{d:"m34.5 11.7l-3 3.1-6.3-6.3 3.1-3q0.5-0.5 1.2-0.5t1.1 0.5l3.9 3.9q0.5 0.4 0.5 1.1t-0.5 1.2z m-29.5 17.1l18.4-18.5 6.3 6.3-18.4 18.4h-6.3v-6.2z"})})})}function d({editUrl:e}){return(0,i.jsxs)(o.A,{to:e,className:r.G.common.editThisPage,children:[(0,i.jsx)(l,{}),(0,i.jsx)(a.A,{id:"theme.common.editThisPage",description:"The link label to edit the current page",children:"Edit this page"})]})}var u=n(6266);function m({lastUpdatedAt:e}){const t=new Date(e),n=(0,u.i)({day:"numeric",month:"short",year:"numeric",timeZone:"UTC"}).format(t);return(0,i.jsx)(a.A,{id:"theme.lastUpdated.atDate",description:"The words used to describe on which date a page has been last updated",values:{date:(0,i.jsx)("b",{children:(0,i.jsx)("time",{dateTime:t.toISOString(),itemProp:"dateModified",children:n})})},children:" on {date}"})}function h({lastUpdatedBy:e}){return(0,i.jsx)(a.A,{id:"theme.lastUpdated.byUser",description:"The words used to describe by who the page has been last updated",values:{user:(0,i.jsx)("b",{children:e})},children:" by {user}"})}function f({lastUpdatedAt:e,lastUpdatedBy:t}){return(0,i.jsxs)("span",{className:r.G.common.lastUpdated,children:[(0,i.jsx)(a.A,{id:"theme.lastUpdated.lastUpdatedAtBy",description:"The sentence used to display when a page has been last updated, and by who",values:{atDate:e?(0,i.jsx)(m,{lastUpdatedAt:e}):"",byUser:t?(0,i.jsx)(h,{lastUpdatedBy:t}):""},children:"Last updated{atDate}{byUser}"}),!1]})}const p={lastUpdated:"lastUpdated_JAkA"};function g({className:e,editUrl:t,lastUpdatedAt:n,lastUpdatedBy:a}){return(0,i.jsxs)("div",{className:(0,s.A)("row",e),children:[(0,i.jsx)("div",{className:"col",children:t&&(0,i.jsx)(d,{editUrl:t})}),(0,i.jsx)("div",{className:(0,s.A)("col",p.lastUpdated),children:(n||a)&&(0,i.jsx)(f,{lastUpdatedAt:n,lastUpdatedBy:a})})]})}},6266:(e,t,n)=>{"use strict";n.d(t,{i:()=>a});var s=n(4586);function a(e={}){const{i18n:{currentLocale:t}}=(0,s.A)(),n=function(){const{i18n:{currentLocale:e,localeConfigs:t}}=(0,s.A)();return t[e].calendar}();return new Intl.DateTimeFormat(t,{calendar:n,...e})}},7293:(e,t,n)=>{"use strict";n.d(t,{A:()=>M});var s=n(6540),a=n(4848);function r(e){const{mdxAdmonitionTitle:t,rest:n}=function(e){const t=s.Children.toArray(e),n=t.find(e=>s.isValidElement(e)&&"mdxAdmonitionTitle"===e.type),r=t.filter(e=>e!==n),o=n?.props.children;return{mdxAdmonitionTitle:o,rest:r.length>0?(0,a.jsx)(a.Fragment,{children:r}):null}}(e.children),r=e.title??t;return{...e,...r&&{title:r},children:n}}var o=n(4164),c=n(1312),i=n(7559);const l="admonition_xJq3",d="admonitionHeading_Gvgb",u="admonitionIcon_Rf37",m="admonitionContent_BuS1";function h({type:e,className:t,children:n}){return(0,a.jsx)("div",{className:(0,o.A)(i.G.common.admonition,i.G.common.admonitionType(e),l,t),children:n})}function f({icon:e,title:t}){return(0,a.jsxs)("div",{className:d,children:[(0,a.jsx)("span",{className:u,children:e}),t]})}function p({children:e}){return e?(0,a.jsx)("div",{className:m,children:e}):null}function g(e){const{type:t,icon:n,title:s,children:r,className:o}=e;return(0,a.jsxs)(h,{type:t,className:o,children:[s||n?(0,a.jsx)(f,{title:s,icon:n}):null,(0,a.jsx)(p,{children:r})]})}function x(e){return(0,a.jsx)("svg",{viewBox:"0 0 14 16",...e,children:(0,a.jsx)("path",{fillRule:"evenodd",d:"M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"})})}const j={icon:(0,a.jsx)(x,{}),title:(0,a.jsx)(c.A,{id:"theme.admonition.note",description:"The default label used for the Note admonition (:::note)",children:"note"})};function b(e){return(0,a.jsx)(g,{...j,...e,className:(0,o.A)("alert alert--secondary",e.className),children:e.children})}function v(e){return(0,a.jsx)("svg",{viewBox:"0 0 12 16",...e,children:(0,a.jsx)("path",{fillRule:"evenodd",d:"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"})})}const N={icon:(0,a.jsx)(v,{}),title:(0,a.jsx)(c.A,{id:"theme.admonition.tip",description:"The default label used for the Tip admonition (:::tip)",children:"tip"})};function y(e){return(0,a.jsx)(g,{...N,...e,className:(0,o.A)("alert alert--success",e.className),children:e.children})}function A(e){return(0,a.jsx)("svg",{viewBox:"0 0 14 16",...e,children:(0,a.jsx)("path",{fillRule:"evenodd",d:"M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"})})}const w={icon:(0,a.jsx)(A,{}),title:(0,a.jsx)(c.A,{id:"theme.admonition.info",description:"The default label used for the Info admonition (:::info)",children:"info"})};function C(e){return(0,a.jsx)(g,{...w,...e,className:(0,o.A)("alert alert--info",e.className),children:e.children})}function k(e){return(0,a.jsx)("svg",{viewBox:"0 0 16 16",...e,children:(0,a.jsx)("path",{fillRule:"evenodd",d:"M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"})})}const B={icon:(0,a.jsx)(k,{}),title:(0,a.jsx)(c.A,{id:"theme.admonition.warning",description:"The default label used for the Warning admonition (:::warning)",children:"warning"})};function E(e){return(0,a.jsx)("svg",{viewBox:"0 0 12 16",...e,children:(0,a.jsx)("path",{fillRule:"evenodd",d:"M5.05.31c.81 2.17.41 3.38-.52 4.31C3.55 5.67 1.98 6.45.9 7.98c-1.45 2.05-1.7 6.53 3.53 7.7-2.2-1.16-2.67-4.52-.3-6.61-.61 2.03.53 3.33 1.94 2.86 1.39-.47 2.3.53 2.27 1.67-.02.78-.31 1.44-1.13 1.81 3.42-.59 4.78-3.42 4.78-5.56 0-2.84-2.53-3.22-1.25-5.61-1.52.13-2.03 1.13-1.89 2.75.09 1.08-1.02 1.8-1.86 1.33-.67-.41-.66-1.19-.06-1.78C8.18 5.31 8.68 2.45 5.05.32L5.03.3l.02.01z"})})}const L={icon:(0,a.jsx)(E,{}),title:(0,a.jsx)(c.A,{id:"theme.admonition.danger",description:"The default label used for the Danger admonition (:::danger)",children:"danger"})};const T={icon:(0,a.jsx)(k,{}),title:(0,a.jsx)(c.A,{id:"theme.admonition.caution",description:"The default label used for the Caution admonition (:::caution)",children:"caution"})};const _={...{note:b,tip:y,info:C,warning:function(e){return(0,a.jsx)(g,{...B,...e,className:(0,o.A)("alert alert--warning",e.className),children:e.children})},danger:function(e){return(0,a.jsx)(g,{...L,...e,className:(0,o.A)("alert alert--danger",e.className),children:e.children})}},...{secondary:e=>(0,a.jsx)(b,{title:"secondary",...e}),important:e=>(0,a.jsx)(C,{title:"important",...e}),success:e=>(0,a.jsx)(y,{title:"success",...e}),caution:function(e){return(0,a.jsx)(g,{...T,...e,className:(0,o.A)("alert alert--warning",e.className),children:e.children})}}};function M(e){const t=r(e),n=(s=t.type,_[s]||(console.warn(`No admonition component found for admonition type "${s}". Using Info as fallback.`),_.info));var s;return(0,a.jsx)(n,{...t})}},8426:(e,t)=>{function n(e){let t,n=[];for(let s of e.split(",").map(e=>e.trim()))if(/^-?\d+$/.test(s))n.push(parseInt(s,10));else if(t=s.match(/^(-?\d+)(-|\.\.\.?|\u2025|\u2026|\u22EF)(-?\d+)$/)){let[e,s,a,r]=t;if(s&&r){s=parseInt(s),r=parseInt(r);const e=s<r?1:-1;"-"!==a&&".."!==a&&"\u2025"!==a||(r+=e);for(let t=s;t!==r;t+=e)n.push(t)}}return n}t.default=n,e.exports=n},8453:(e,t,n)=>{"use strict";n.d(t,{R:()=>o,x:()=>c});var s=n(6540);const a={},r=s.createContext(a);function o(e){const t=s.useContext(r);return s.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function c(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(a):e.components||a:o(e.components),s.createElement(r.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/6870.6d10e1d8.js b/docs/assets/js/6870.6d10e1d8.js deleted file mode 100644 index 092c2d9d..00000000 --- a/docs/assets/js/6870.6d10e1d8.js +++ /dev/null @@ -1 +0,0 @@ -(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[6870],{3253:(e,t,n)=>{"use strict";n.d(t,{A:()=>ze});var s=n(6540),a=n(8453),r=n(5260),c=n(2303),o=n(4164),i=n(5293),l=n(6342);function d(){const{prism:e}=(0,l.p)(),{colorMode:t}=(0,i.G)(),n=e.theme,s=e.darkTheme||n;return"dark"===t?s:n}var u=n(7559),m=n(8426),h=n.n(m),f=n(9532),p=n(4848);const g=/title=(?<quote>["'])(?<title>.*?)\1/,x=/\{(?<range>[\d,-]+)\}/,j={js:{start:"\\/\\/",end:""},jsBlock:{start:"\\/\\*",end:"\\*\\/"},jsx:{start:"\\{\\s*\\/\\*",end:"\\*\\/\\s*\\}"},bash:{start:"#",end:""},html:{start:"\x3c!--",end:"--\x3e"}},b={...j,lua:{start:"--",end:""},wasm:{start:"\\;\\;",end:""},tex:{start:"%",end:""},vb:{start:"['\u2018\u2019]",end:""},vbnet:{start:"(?:_\\s*)?['\u2018\u2019]",end:""},rem:{start:"[Rr][Ee][Mm]\\b",end:""},f90:{start:"!",end:""},ml:{start:"\\(\\*",end:"\\*\\)"},cobol:{start:"\\*>",end:""}},v=Object.keys(j);function N(e,t){const n=e.map((e=>{const{start:n,end:s}=b[e];return`(?:${n}\\s*(${t.flatMap((e=>[e.line,e.block?.start,e.block?.end].filter(Boolean))).join("|")})\\s*${s})`})).join("|");return new RegExp(`^\\s*(?:${n})\\s*$`)}function y({showLineNumbers:e,metastring:t}){return"boolean"==typeof e?e?1:void 0:"number"==typeof e?e:function(e){const t=e?.split(" ").find((e=>e.startsWith("showLineNumbers")));if(t){if(t.startsWith("showLineNumbers=")){const e=t.replace("showLineNumbers=","");return parseInt(e,10)}return 1}}(t)}function A(e,t){const{language:n,magicComments:s}=t;if(void 0===n)return{lineClassNames:{},code:e};const a=function(e,t){switch(e){case"js":case"javascript":case"ts":case"typescript":return N(["js","jsBlock"],t);case"jsx":case"tsx":return N(["js","jsBlock","jsx"],t);case"html":return N(["js","jsBlock","html"],t);case"python":case"py":case"bash":return N(["bash"],t);case"markdown":case"md":return N(["html","jsx","bash"],t);case"tex":case"latex":case"matlab":return N(["tex"],t);case"lua":case"haskell":return N(["lua"],t);case"sql":return N(["lua","jsBlock"],t);case"wasm":return N(["wasm"],t);case"vb":case"vba":case"visual-basic":return N(["vb","rem"],t);case"vbnet":return N(["vbnet","rem"],t);case"batch":return N(["rem"],t);case"basic":return N(["rem","f90"],t);case"fsharp":return N(["js","ml"],t);case"ocaml":case"sml":return N(["ml"],t);case"fortran":return N(["f90"],t);case"cobol":return N(["cobol"],t);default:return N(v,t)}}(n,s),r=e.split(/\r?\n/),c=Object.fromEntries(s.map((e=>[e.className,{start:0,range:""}]))),o=Object.fromEntries(s.filter((e=>e.line)).map((({className:e,line:t})=>[t,e]))),i=Object.fromEntries(s.filter((e=>e.block)).map((({className:e,block:t})=>[t.start,e]))),l=Object.fromEntries(s.filter((e=>e.block)).map((({className:e,block:t})=>[t.end,e])));for(let u=0;u<r.length;){const e=r[u].match(a);if(!e){u+=1;continue}const t=e.slice(1).find((e=>void 0!==e));o[t]?c[o[t]].range+=`${u},`:i[t]?c[i[t]].start=u:l[t]&&(c[l[t]].range+=`${c[l[t]].start}-${u-1},`),r.splice(u,1)}const d={};return Object.entries(c).forEach((([e,{range:t}])=>{h()(t).forEach((t=>{d[t]??=[],d[t].push(e)}))})),{code:r.join("\n"),lineClassNames:d}}function w(e,t){const n=e.replace(/\r?\n$/,"");return function(e,{metastring:t,magicComments:n}){if(t&&x.test(t)){const s=t.match(x).groups.range;if(0===n.length)throw new Error(`A highlight range has been given in code block's metastring (\`\`\` ${t}), but no magic comment config is available. Docusaurus applies the first magic comment entry's className for metastring ranges.`);const a=n[0].className,r=h()(s).filter((e=>e>0)).map((e=>[e-1,[a]]));return{lineClassNames:Object.fromEntries(r),code:e}}return null}(n,{...t})??A(n,{...t})}function C(e){const t=function(e){return t=e.language??function(e){if(!e)return;const t=e.split(" ").find((e=>e.startsWith("language-")));return t?.replace(/language-/,"")}(e.className)??e.defaultLanguage,t?.toLowerCase()??"text";var t}({language:e.language,defaultLanguage:e.defaultLanguage,className:e.className}),{lineClassNames:n,code:s}=w(e.code,{metastring:e.metastring,magicComments:e.magicComments,language:t}),a=function({className:e,language:t}){return(0,o.A)(e,t&&!e?.includes(`language-${t}`)&&`language-${t}`)}({className:e.className,language:t}),r=(c=e.metastring,(c?.match(g)?.groups.title??"")||e.title);var c;const i=y({showLineNumbers:e.showLineNumbers,metastring:e.metastring});return{codeInput:e.code,code:s,className:a,language:t,title:r,lineNumbersStart:i,lineClassNames:n}}const k=(0,s.createContext)(null);function B({metadata:e,wordWrap:t,children:n}){const a=(0,s.useMemo)((()=>({metadata:e,wordWrap:t})),[e,t]);return(0,p.jsx)(k.Provider,{value:a,children:n})}function E(){const e=(0,s.useContext)(k);if(null===e)throw new f.dV("CodeBlockContextProvider");return e}const L="codeBlockContainer_Ckt0";function T({as:e,...t}){const n=function(e){const t={color:"--prism-color",backgroundColor:"--prism-background-color"},n={};return Object.entries(e.plain).forEach((([e,s])=>{const a=t[e];a&&"string"==typeof s&&(n[a]=s)})),n}(d());return(0,p.jsx)(e,{...t,style:n,className:(0,o.A)(t.className,L,u.G.common.codeBlock)})}const _="codeBlock_bY9V",M="codeBlockStandalone_MEMb",S="codeBlockLines_e6Vv",U="codeBlockLinesWithNumbering_o6Pm";function z({children:e,className:t}){return(0,p.jsx)(T,{as:"pre",tabIndex:0,className:(0,o.A)(M,"thin-scrollbar",t),children:(0,p.jsx)("code",{className:S,children:e})})}const I={attributes:!0,characterData:!0,childList:!0,subtree:!0};function H(e,t){const[n,a]=(0,s.useState)(),r=(0,s.useCallback)((()=>{a(e.current?.closest("[role=tabpanel][hidden]"))}),[e,a]);(0,s.useEffect)((()=>{r()}),[r]),function(e,t,n=I){const a=(0,f._q)(t),r=(0,f.Be)(n);(0,s.useEffect)((()=>{const t=new MutationObserver(a);return e&&t.observe(e,r),()=>t.disconnect()}),[e,a,r])}(n,(e=>{e.forEach((e=>{"attributes"===e.type&&"hidden"===e.attributeName&&(t(),r())}))}),{attributes:!0,characterData:!1,childList:!1,subtree:!1})}function R({children:e}){return e}var V=n(1765);function P({line:e,token:t,...n}){return(0,p.jsx)("span",{...n})}const W="codeLine_lJS_",$="codeLineNumber_Tfdd",D="codeLineContent_feaV";function q({line:e,classNames:t,showLineNumbers:n,getLineProps:s,getTokenProps:a}){const r=function(e){const t=1===e.length&&"\n"===e[0].content?e[0]:void 0;return t?[{...t,content:""}]:e}(e),c=s({line:r,className:(0,o.A)(t,n&&W)}),i=r.map(((e,t)=>{const n=a({token:e});return(0,p.jsx)(P,{...n,line:r,token:e,children:n.children},t)}));return(0,p.jsxs)("span",{...c,children:[n?(0,p.jsxs)(p.Fragment,{children:[(0,p.jsx)("span",{className:$}),(0,p.jsx)("span",{className:D,children:i})]}):i,(0,p.jsx)("br",{})]})}const O=s.forwardRef(((e,t)=>(0,p.jsx)("pre",{ref:t,tabIndex:0,...e,className:(0,o.A)(e.className,_,"thin-scrollbar")})));function F(e){const{metadata:t}=E();return(0,p.jsx)("code",{...e,className:(0,o.A)(e.className,S,void 0!==t.lineNumbersStart&&U),style:{...e.style,counterReset:void 0===t.lineNumbersStart?void 0:"line-count "+(t.lineNumbersStart-1)}})}function G({className:e}){const{metadata:t,wordWrap:n}=E(),s=d(),{code:a,language:r,lineNumbersStart:c,lineClassNames:i}=t;return(0,p.jsx)(V.f4,{theme:s,code:a,language:r,children:({className:t,style:s,tokens:a,getLineProps:r,getTokenProps:l})=>(0,p.jsx)(O,{ref:n.codeBlockRef,className:(0,o.A)(e,t),style:s,children:(0,p.jsx)(F,{children:a.map(((e,t)=>(0,p.jsx)(q,{line:e,getLineProps:r,getTokenProps:l,classNames:i[t],showLineNumbers:void 0!==c},t)))})})})}function J({children:e,fallback:t}){return(0,c.A)()?(0,p.jsx)(p.Fragment,{children:e?.()}):t??null}var Z=n(1312);function Y({className:e,...t}){return(0,p.jsx)("button",{type:"button",...t,className:(0,o.A)("clean-btn",e)})}function Q(e){return(0,p.jsx)("svg",{viewBox:"0 0 24 24",...e,children:(0,p.jsx)("path",{fill:"currentColor",d:"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"})})}function X(e){return(0,p.jsx)("svg",{viewBox:"0 0 24 24",...e,children:(0,p.jsx)("path",{fill:"currentColor",d:"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"})})}const K={copyButtonCopied:"copyButtonCopied_Vdqa",copyButtonIcons:"copyButtonIcons_IEyt",copyButtonIcon:"copyButtonIcon_TrPX",copyButtonSuccessIcon:"copyButtonSuccessIcon_cVMy"};function ee(e){return e?(0,Z.T)({id:"theme.CodeBlock.copied",message:"Copied",description:"The copied button label on code blocks"}):(0,Z.T)({id:"theme.CodeBlock.copyButtonAriaLabel",message:"Copy code to clipboard",description:"The ARIA label for copy code blocks button"})}function te(){const{metadata:{code:e}}=E(),[t,n]=(0,s.useState)(!1),a=(0,s.useRef)(void 0),r=(0,s.useCallback)((()=>{!function(e,{target:t=document.body}={}){if("string"!=typeof e)throw new TypeError(`Expected parameter \`text\` to be a \`string\`, got \`${typeof e}\`.`);const n=document.createElement("textarea"),s=document.activeElement;n.value=e,n.setAttribute("readonly",""),n.style.contain="strict",n.style.position="absolute",n.style.left="-9999px",n.style.fontSize="12pt";const a=document.getSelection(),r=a.rangeCount>0&&a.getRangeAt(0);t.append(n),n.select(),n.selectionStart=0,n.selectionEnd=e.length;let c=!1;try{c=document.execCommand("copy")}catch{}n.remove(),r&&(a.removeAllRanges(),a.addRange(r)),s&&s.focus()}(e),n(!0),a.current=window.setTimeout((()=>{n(!1)}),1e3)}),[e]);return(0,s.useEffect)((()=>()=>window.clearTimeout(a.current)),[]),{copyCode:r,isCopied:t}}function ne({className:e}){const{copyCode:t,isCopied:n}=te();return(0,p.jsx)(Y,{"aria-label":ee(n),title:(0,Z.T)({id:"theme.CodeBlock.copy",message:"Copy",description:"The copy button label on code blocks"}),className:(0,o.A)(e,K.copyButton,n&&K.copyButtonCopied),onClick:t,children:(0,p.jsxs)("span",{className:K.copyButtonIcons,"aria-hidden":"true",children:[(0,p.jsx)(Q,{className:K.copyButtonIcon}),(0,p.jsx)(X,{className:K.copyButtonSuccessIcon})]})})}function se(e){return(0,p.jsx)("svg",{viewBox:"0 0 24 24",...e,children:(0,p.jsx)("path",{fill:"currentColor",d:"M4 19h6v-2H4v2zM20 5H4v2h16V5zm-3 6H4v2h13.25c1.1 0 2 .9 2 2s-.9 2-2 2H15v-2l-3 3l3 3v-2h2c2.21 0 4-1.79 4-4s-1.79-4-4-4z"})})}const ae="wordWrapButtonIcon_b1P5",re="wordWrapButtonEnabled_uzNF";function ce({className:e}){const{wordWrap:t}=E();if(!(t.isEnabled||t.isCodeScrollable))return!1;const n=(0,Z.T)({id:"theme.CodeBlock.wordWrapToggle",message:"Toggle word wrap",description:"The title attribute for toggle word wrapping button of code block lines"});return(0,p.jsx)(Y,{onClick:()=>t.toggle(),className:(0,o.A)(e,t.isEnabled&&re),"aria-label":n,title:n,children:(0,p.jsx)(se,{className:ae,"aria-hidden":"true"})})}const oe="buttonGroup_M5ko";function ie({className:e}){return(0,p.jsx)(J,{children:()=>(0,p.jsxs)("div",{className:(0,o.A)(e,oe),children:[(0,p.jsx)(ce,{}),(0,p.jsx)(ne,{})]})})}const le="codeBlockContent_QJqH",de="codeBlockTitle_OeMC";function ue({className:e}){const{metadata:t}=E();return(0,p.jsxs)(T,{as:"div",className:(0,o.A)(e,t.className),children:[t.title&&(0,p.jsx)("div",{className:de,children:(0,p.jsx)(R,{children:t.title})}),(0,p.jsxs)("div",{className:le,children:[(0,p.jsx)(G,{}),(0,p.jsx)(ie,{})]})]})}function me(e){const t=function(e){const{prism:t}=(0,l.p)();return C({code:e.children,className:e.className,metastring:e.metastring,magicComments:t.magicComments,defaultLanguage:t.defaultLanguage,language:e.language,title:e.title,showLineNumbers:e.showLineNumbers})}(e),n=function(){const[e,t]=(0,s.useState)(!1),[n,a]=(0,s.useState)(!1),r=(0,s.useRef)(null),c=(0,s.useCallback)((()=>{const n=r.current.querySelector("code");e?n.removeAttribute("style"):(n.style.whiteSpace="pre-wrap",n.style.overflowWrap="anywhere"),t((e=>!e))}),[r,e]),o=(0,s.useCallback)((()=>{const{scrollWidth:e,clientWidth:t}=r.current,n=e>t||r.current.querySelector("code").hasAttribute("style");a(n)}),[r]);return H(r,o),(0,s.useEffect)((()=>{o()}),[e,o]),(0,s.useEffect)((()=>(window.addEventListener("resize",o,{passive:!0}),()=>{window.removeEventListener("resize",o)})),[o]),{codeBlockRef:r,isEnabled:e,isCodeScrollable:n,toggle:c}}();return(0,p.jsx)(B,{metadata:t,wordWrap:n,children:(0,p.jsx)(ue,{})})}function he({children:e,...t}){const n=(0,c.A)(),a=function(e){return s.Children.toArray(e).some((e=>(0,s.isValidElement)(e)))?e:Array.isArray(e)?e.join(""):e}(e),r="string"==typeof a?me:z;return(0,p.jsx)(r,{...t,children:a},String(n))}function fe(e){return(0,p.jsx)("code",{...e})}var pe=n(8774);var ge=n(3427),xe=n(1422);const je="details_lb9f",be="isBrowser_bmU9",ve="collapsibleContent_i85q";function Ne(e){return!!e&&("SUMMARY"===e.tagName||Ne(e.parentElement))}function ye(e,t){return!!e&&(e===t||ye(e.parentElement,t))}function Ae({summary:e,children:t,...n}){(0,ge.A)().collectAnchor(n.id);const a=(0,c.A)(),r=(0,s.useRef)(null),{collapsed:i,setCollapsed:l}=(0,xe.u)({initialState:!n.open}),[d,u]=(0,s.useState)(n.open),m=s.isValidElement(e)?e:(0,p.jsx)("summary",{children:e??"Details"});return(0,p.jsxs)("details",{...n,ref:r,open:d,"data-collapsed":i,className:(0,o.A)(je,a&&be,n.className),onMouseDown:e=>{Ne(e.target)&&e.detail>1&&e.preventDefault()},onClick:e=>{e.stopPropagation();const t=e.target;Ne(t)&&ye(t,r.current)&&(e.preventDefault(),i?(l(!1),u(!0)):l(!0))},children:[m,(0,p.jsx)(xe.N,{lazy:!1,collapsed:i,onCollapseTransitionEnd:e=>{l(e),u(!e)},children:(0,p.jsx)("div",{className:ve,children:t})})]})}const we="details_b_Ee";function Ce({...e}){return(0,p.jsx)(Ae,{...e,className:(0,o.A)("alert alert--info",we,e.className)})}function ke(e){const t=s.Children.toArray(e.children),n=t.find((e=>s.isValidElement(e)&&"summary"===e.type)),a=(0,p.jsx)(p.Fragment,{children:t.filter((e=>e!==n))});return(0,p.jsx)(Ce,{...e,summary:n,children:a})}var Be=n(1107);function Ee(e){return(0,p.jsx)(Be.A,{...e})}const Le="containsTaskList_mC6p";function Te(e){if(void 0!==e)return(0,o.A)(e,e?.includes("contains-task-list")&&Le)}const _e="img_ev3q";var Me=n(7293),Se=n(418);const Ue={Head:r.A,details:ke,Details:ke,code:function(e){return function(e){return void 0!==e.children&&s.Children.toArray(e.children).every((e=>"string"==typeof e&&!e.includes("\n")))}(e)?(0,p.jsx)(fe,{...e}):(0,p.jsx)(he,{...e})},a:function(e){return(0,p.jsx)(pe.A,{...e})},pre:function(e){return(0,p.jsx)(p.Fragment,{children:e.children})},ul:function(e){return(0,p.jsx)("ul",{...e,className:Te(e.className)})},li:function(e){return(0,ge.A)().collectAnchor(e.id),(0,p.jsx)("li",{...e})},img:function(e){return(0,p.jsx)("img",{decoding:"async",loading:"lazy",...e,className:(t=e.className,(0,o.A)(t,_e))});var t},h1:e=>(0,p.jsx)(Ee,{as:"h1",...e}),h2:e=>(0,p.jsx)(Ee,{as:"h2",...e}),h3:e=>(0,p.jsx)(Ee,{as:"h3",...e}),h4:e=>(0,p.jsx)(Ee,{as:"h4",...e}),h5:e=>(0,p.jsx)(Ee,{as:"h5",...e}),h6:e=>(0,p.jsx)(Ee,{as:"h6",...e}),admonition:Me.A,mermaid:Se.A};function ze({children:e}){return(0,p.jsx)(a.x,{components:Ue,children:e})}},4336:(e,t,n)=>{"use strict";n.d(t,{A:()=>g});n(6540);var s=n(4164),a=n(1312),r=n(7559),c=n(8774);const o={iconEdit:"iconEdit_Z9Sw"};var i=n(4848);function l({className:e,...t}){return(0,i.jsx)("svg",{fill:"currentColor",height:"20",width:"20",viewBox:"0 0 40 40",className:(0,s.A)(o.iconEdit,e),"aria-hidden":"true",...t,children:(0,i.jsx)("g",{children:(0,i.jsx)("path",{d:"m34.5 11.7l-3 3.1-6.3-6.3 3.1-3q0.5-0.5 1.2-0.5t1.1 0.5l3.9 3.9q0.5 0.4 0.5 1.1t-0.5 1.2z m-29.5 17.1l18.4-18.5 6.3 6.3-18.4 18.4h-6.3v-6.2z"})})})}function d({editUrl:e}){return(0,i.jsxs)(c.A,{to:e,className:r.G.common.editThisPage,children:[(0,i.jsx)(l,{}),(0,i.jsx)(a.A,{id:"theme.common.editThisPage",description:"The link label to edit the current page",children:"Edit this page"})]})}var u=n(6266);function m({lastUpdatedAt:e}){const t=new Date(e),n=(0,u.i)({day:"numeric",month:"short",year:"numeric",timeZone:"UTC"}).format(t);return(0,i.jsx)(a.A,{id:"theme.lastUpdated.atDate",description:"The words used to describe on which date a page has been last updated",values:{date:(0,i.jsx)("b",{children:(0,i.jsx)("time",{dateTime:t.toISOString(),itemProp:"dateModified",children:n})})},children:" on {date}"})}function h({lastUpdatedBy:e}){return(0,i.jsx)(a.A,{id:"theme.lastUpdated.byUser",description:"The words used to describe by who the page has been last updated",values:{user:(0,i.jsx)("b",{children:e})},children:" by {user}"})}function f({lastUpdatedAt:e,lastUpdatedBy:t}){return(0,i.jsxs)("span",{className:r.G.common.lastUpdated,children:[(0,i.jsx)(a.A,{id:"theme.lastUpdated.lastUpdatedAtBy",description:"The sentence used to display when a page has been last updated, and by who",values:{atDate:e?(0,i.jsx)(m,{lastUpdatedAt:e}):"",byUser:t?(0,i.jsx)(h,{lastUpdatedBy:t}):""},children:"Last updated{atDate}{byUser}"}),!1]})}const p={lastUpdated:"lastUpdated_JAkA"};function g({className:e,editUrl:t,lastUpdatedAt:n,lastUpdatedBy:a}){return(0,i.jsxs)("div",{className:(0,s.A)("row",e),children:[(0,i.jsx)("div",{className:"col",children:t&&(0,i.jsx)(d,{editUrl:t})}),(0,i.jsx)("div",{className:(0,s.A)("col",p.lastUpdated),children:(n||a)&&(0,i.jsx)(f,{lastUpdatedAt:n,lastUpdatedBy:a})})]})}},6266:(e,t,n)=>{"use strict";n.d(t,{i:()=>a});var s=n(4586);function a(e={}){const{i18n:{currentLocale:t}}=(0,s.A)(),n=function(){const{i18n:{currentLocale:e,localeConfigs:t}}=(0,s.A)();return t[e].calendar}();return new Intl.DateTimeFormat(t,{calendar:n,...e})}},7293:(e,t,n)=>{"use strict";n.d(t,{A:()=>M});var s=n(6540),a=n(4848);function r(e){const{mdxAdmonitionTitle:t,rest:n}=function(e){const t=s.Children.toArray(e),n=t.find((e=>s.isValidElement(e)&&"mdxAdmonitionTitle"===e.type)),r=t.filter((e=>e!==n)),c=n?.props.children;return{mdxAdmonitionTitle:c,rest:r.length>0?(0,a.jsx)(a.Fragment,{children:r}):null}}(e.children),r=e.title??t;return{...e,...r&&{title:r},children:n}}var c=n(4164),o=n(1312),i=n(7559);const l="admonition_xJq3",d="admonitionHeading_Gvgb",u="admonitionIcon_Rf37",m="admonitionContent_BuS1";function h({type:e,className:t,children:n}){return(0,a.jsx)("div",{className:(0,c.A)(i.G.common.admonition,i.G.common.admonitionType(e),l,t),children:n})}function f({icon:e,title:t}){return(0,a.jsxs)("div",{className:d,children:[(0,a.jsx)("span",{className:u,children:e}),t]})}function p({children:e}){return e?(0,a.jsx)("div",{className:m,children:e}):null}function g(e){const{type:t,icon:n,title:s,children:r,className:c}=e;return(0,a.jsxs)(h,{type:t,className:c,children:[s||n?(0,a.jsx)(f,{title:s,icon:n}):null,(0,a.jsx)(p,{children:r})]})}function x(e){return(0,a.jsx)("svg",{viewBox:"0 0 14 16",...e,children:(0,a.jsx)("path",{fillRule:"evenodd",d:"M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"})})}const j={icon:(0,a.jsx)(x,{}),title:(0,a.jsx)(o.A,{id:"theme.admonition.note",description:"The default label used for the Note admonition (:::note)",children:"note"})};function b(e){return(0,a.jsx)(g,{...j,...e,className:(0,c.A)("alert alert--secondary",e.className),children:e.children})}function v(e){return(0,a.jsx)("svg",{viewBox:"0 0 12 16",...e,children:(0,a.jsx)("path",{fillRule:"evenodd",d:"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"})})}const N={icon:(0,a.jsx)(v,{}),title:(0,a.jsx)(o.A,{id:"theme.admonition.tip",description:"The default label used for the Tip admonition (:::tip)",children:"tip"})};function y(e){return(0,a.jsx)(g,{...N,...e,className:(0,c.A)("alert alert--success",e.className),children:e.children})}function A(e){return(0,a.jsx)("svg",{viewBox:"0 0 14 16",...e,children:(0,a.jsx)("path",{fillRule:"evenodd",d:"M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"})})}const w={icon:(0,a.jsx)(A,{}),title:(0,a.jsx)(o.A,{id:"theme.admonition.info",description:"The default label used for the Info admonition (:::info)",children:"info"})};function C(e){return(0,a.jsx)(g,{...w,...e,className:(0,c.A)("alert alert--info",e.className),children:e.children})}function k(e){return(0,a.jsx)("svg",{viewBox:"0 0 16 16",...e,children:(0,a.jsx)("path",{fillRule:"evenodd",d:"M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"})})}const B={icon:(0,a.jsx)(k,{}),title:(0,a.jsx)(o.A,{id:"theme.admonition.warning",description:"The default label used for the Warning admonition (:::warning)",children:"warning"})};function E(e){return(0,a.jsx)("svg",{viewBox:"0 0 12 16",...e,children:(0,a.jsx)("path",{fillRule:"evenodd",d:"M5.05.31c.81 2.17.41 3.38-.52 4.31C3.55 5.67 1.98 6.45.9 7.98c-1.45 2.05-1.7 6.53 3.53 7.7-2.2-1.16-2.67-4.52-.3-6.61-.61 2.03.53 3.33 1.94 2.86 1.39-.47 2.3.53 2.27 1.67-.02.78-.31 1.44-1.13 1.81 3.42-.59 4.78-3.42 4.78-5.56 0-2.84-2.53-3.22-1.25-5.61-1.52.13-2.03 1.13-1.89 2.75.09 1.08-1.02 1.8-1.86 1.33-.67-.41-.66-1.19-.06-1.78C8.18 5.31 8.68 2.45 5.05.32L5.03.3l.02.01z"})})}const L={icon:(0,a.jsx)(E,{}),title:(0,a.jsx)(o.A,{id:"theme.admonition.danger",description:"The default label used for the Danger admonition (:::danger)",children:"danger"})};const T={icon:(0,a.jsx)(k,{}),title:(0,a.jsx)(o.A,{id:"theme.admonition.caution",description:"The default label used for the Caution admonition (:::caution)",children:"caution"})};const _={...{note:b,tip:y,info:C,warning:function(e){return(0,a.jsx)(g,{...B,...e,className:(0,c.A)("alert alert--warning",e.className),children:e.children})},danger:function(e){return(0,a.jsx)(g,{...L,...e,className:(0,c.A)("alert alert--danger",e.className),children:e.children})}},...{secondary:e=>(0,a.jsx)(b,{title:"secondary",...e}),important:e=>(0,a.jsx)(C,{title:"important",...e}),success:e=>(0,a.jsx)(y,{title:"success",...e}),caution:function(e){return(0,a.jsx)(g,{...T,...e,className:(0,c.A)("alert alert--warning",e.className),children:e.children})}}};function M(e){const t=r(e),n=(s=t.type,_[s]||(console.warn(`No admonition component found for admonition type "${s}". Using Info as fallback.`),_.info));var s;return(0,a.jsx)(n,{...t})}},8426:(e,t)=>{function n(e){let t,n=[];for(let s of e.split(",").map((e=>e.trim())))if(/^-?\d+$/.test(s))n.push(parseInt(s,10));else if(t=s.match(/^(-?\d+)(-|\.\.\.?|\u2025|\u2026|\u22EF)(-?\d+)$/)){let[e,s,a,r]=t;if(s&&r){s=parseInt(s),r=parseInt(r);const e=s<r?1:-1;"-"!==a&&".."!==a&&"\u2025"!==a||(r+=e);for(let t=s;t!==r;t+=e)n.push(t)}}return n}t.default=n,e.exports=n},8453:(e,t,n)=>{"use strict";n.d(t,{R:()=>c,x:()=>o});var s=n(6540);const a={},r=s.createContext(a);function c(e){const t=s.useContext(r);return s.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function o(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(a):e.components||a:c(e.components),s.createElement(r.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/6875c492.1403aab6.js b/docs/assets/js/6875c492.1403aab6.js new file mode 100644 index 00000000..0f0f07e9 --- /dev/null +++ b/docs/assets/js/6875c492.1403aab6.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[4813],{2234:(e,t,a)=>{a.d(t,{A:()=>c});a(6540);var n=a(4164),s=a(7559),i=a(4084),r=a(7293),l=a(4848);function o({className:e}){return(0,l.jsx)(r.A,{type:"caution",title:(0,l.jsx)(i.Rc,{}),className:(0,n.A)(e,s.G.common.unlistedBanner),children:(0,l.jsx)(i.Uh,{})})}function c(e){return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(i.AE,{}),(0,l.jsx)(o,{...e})]})}},2907:(e,t,a)=>{a.d(t,{A:()=>B});a(6540);var n=a(4164),s=a(4096),i=a(4848);function r({children:e,className:t}){return(0,i.jsx)("article",{className:t,children:e})}var l=a(8774);const o={title:"title_f1Hy"};function c({className:e}){const{metadata:t,isBlogPostPage:a}=(0,s.e7)(),{permalink:r,title:c}=t,d=a?"h1":"h2";return(0,i.jsx)(d,{className:(0,n.A)(o.title,e),children:a?c:(0,i.jsx)(l.A,{to:r,children:c})})}var d=a(1312),g=a(5846),u=a(6266);const m={container:"container_mt6G"};function h({readingTime:e}){const t=function(){const{selectMessage:e}=(0,g.W)();return t=>{const a=Math.ceil(t);return e(a,(0,d.T)({id:"theme.blog.post.readingTime.plurals",description:'Pluralized label for "{readingTime} min read". Use as much plural forms (separated by "|") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)',message:"One min read|{readingTime} min read"},{readingTime:a}))}}();return(0,i.jsx)(i.Fragment,{children:t(e)})}function p({date:e,formattedDate:t}){return(0,i.jsx)("time",{dateTime:e,children:t})}function x(){return(0,i.jsx)(i.Fragment,{children:" \xb7 "})}function j({className:e}){const{metadata:t}=(0,s.e7)(),{date:a,readingTime:r}=t,l=(0,u.i)({day:"numeric",month:"long",year:"numeric",timeZone:"UTC"});return(0,i.jsxs)("div",{className:(0,n.A)(m.container,"margin-vert--md",e),children:[(0,i.jsx)(p,{date:a,formattedDate:(o=a,l.format(new Date(o)))}),void 0!==r&&(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(x,{}),(0,i.jsx)(h,{readingTime:r})]})]});var o}var b=a(6382);const A={authorCol:"authorCol_Hf19",imageOnlyAuthorRow:"imageOnlyAuthorRow_pa_O",imageOnlyAuthorCol:"imageOnlyAuthorCol_G86a"};function f({className:e}){const{metadata:{authors:t},assets:a}=(0,s.e7)();if(0===t.length)return null;const r=t.every(({name:e})=>!e),l=1===t.length;return(0,i.jsx)("div",{className:(0,n.A)("margin-top--md margin-bottom--sm",r?A.imageOnlyAuthorRow:"row",e),children:t.map((e,t)=>(0,i.jsx)("div",{className:(0,n.A)(!r&&(l?"col col--12":"col col--6"),r?A.imageOnlyAuthorCol:A.authorCol),children:(0,i.jsx)(b.A,{author:{...e,imageURL:a.authorsImageUrls[t]??e.imageURL}})},t))})}function v(){return(0,i.jsxs)("header",{children:[(0,i.jsx)(c,{}),(0,i.jsx)(j,{}),(0,i.jsx)(f,{})]})}var T=a(440),N=a(3253);function w({children:e,className:t}){const{isBlogPostPage:a}=(0,s.e7)();return(0,i.jsx)("div",{id:a?T.LU:void 0,className:(0,n.A)("markdown",t),children:(0,i.jsx)(N.A,{children:e})})}var _=a(7559),k=a(4336),y=a(4434);function P(){return(0,i.jsx)("b",{children:(0,i.jsx)(d.A,{id:"theme.blog.post.readMore",description:"The label used in blog post item excerpts to link to full blog posts",children:"Read more"})})}function R(e){const{blogPostTitle:t,...a}=e;return(0,i.jsx)(l.A,{"aria-label":(0,d.T)({message:"Read more about {title}",id:"theme.blog.post.readMoreLabel",description:"The ARIA label for the link to full blog posts from excerpts"},{title:t}),...a,children:(0,i.jsx)(P,{})})}function U(){const{metadata:e,isBlogPostPage:t}=(0,s.e7)(),{tags:a,title:r,editUrl:l,hasTruncateMarker:o,lastUpdatedBy:c,lastUpdatedAt:d}=e,g=!t&&o,u=a.length>0;if(!(u||g||l))return null;if(t){const e=!!(l||d||c);return(0,i.jsxs)("footer",{className:"docusaurus-mt-lg",children:[u&&(0,i.jsx)("div",{className:(0,n.A)("row","margin-top--sm",_.G.blog.blogFooterEditMetaRow),children:(0,i.jsx)("div",{className:"col",children:(0,i.jsx)(y.A,{tags:a})})}),e&&(0,i.jsx)(k.A,{className:(0,n.A)("margin-top--sm",_.G.blog.blogFooterEditMetaRow),editUrl:l,lastUpdatedAt:d,lastUpdatedBy:c})]})}return(0,i.jsxs)("footer",{className:"row docusaurus-mt-lg",children:[u&&(0,i.jsx)("div",{className:(0,n.A)("col",{"col--9":g}),children:(0,i.jsx)(y.A,{tags:a})}),g&&(0,i.jsx)("div",{className:(0,n.A)("col text--right",{"col--3":u}),children:(0,i.jsx)(R,{blogPostTitle:r,to:e.permalink})})]})}function B({children:e,className:t}){const a=function(){const{isBlogPostPage:e}=(0,s.e7)();return e?void 0:"margin-bottom--xl"}();return(0,i.jsxs)(r,{className:(0,n.A)(a,t),children:[(0,i.jsx)(v,{}),(0,i.jsx)(w,{children:e}),(0,i.jsx)(U,{})]})}},3069:(e,t,a)=>{a.r(t),a.d(t,{default:()=>b});a(6540);var n=a(4164),s=a(1312),i=a(7559),r=a(5500),l=a(6461),o=a(8774),c=a(8027),d=a(7713),g=a(1463),u=a(3892),m=a(2234),h=a(1107),p=a(4848);function x({tag:e}){const t=(0,l.ZD)(e);return(0,p.jsxs)(p.Fragment,{children:[(0,p.jsx)(r.be,{title:t,description:e.description}),(0,p.jsx)(g.A,{tag:"blog_tags_posts"})]})}function j({tag:e,items:t,sidebar:a,listMetadata:n}){const i=(0,l.ZD)(e);return(0,p.jsxs)(c.A,{sidebar:a,children:[e.unlisted&&(0,p.jsx)(m.A,{}),(0,p.jsxs)("header",{className:"margin-bottom--xl",children:[(0,p.jsx)(h.A,{as:"h1",children:i}),e.description&&(0,p.jsx)("p",{children:e.description}),(0,p.jsx)(o.A,{href:e.allTagsPath,children:(0,p.jsx)(s.A,{id:"theme.tags.tagsPageLink",description:"The label of the link targeting the tag list page",children:"View All Tags"})})]}),(0,p.jsx)(u.A,{items:t}),(0,p.jsx)(d.A,{metadata:n})]})}function b(e){return(0,p.jsxs)(r.e3,{className:(0,n.A)(i.G.wrapper.blogPages,i.G.page.blogTagPostListPage),children:[(0,p.jsx)(x,{...e}),(0,p.jsx)(j,{...e})]})}},3892:(e,t,a)=>{a.d(t,{A:()=>r});a(6540);var n=a(4096),s=a(2907),i=a(4848);function r({items:e,component:t=s.A}){return(0,i.jsx)(i.Fragment,{children:e.map(({content:e})=>(0,i.jsx)(n.in,{content:e,children:(0,i.jsx)(t,{children:(0,i.jsx)(e,{})})},e.metadata.permalink))})}},4084:(e,t,a)=>{a.d(t,{AE:()=>o,Rc:()=>r,TT:()=>d,Uh:()=>l,Yh:()=>c});a(6540);var n=a(1312),s=a(5260),i=a(4848);function r(){return(0,i.jsx)(n.A,{id:"theme.contentVisibility.unlistedBanner.title",description:"The unlisted content banner title",children:"Unlisted page"})}function l(){return(0,i.jsx)(n.A,{id:"theme.contentVisibility.unlistedBanner.message",description:"The unlisted content banner message",children:"This page is unlisted. Search engines will not index it, and only users having a direct link can access it."})}function o(){return(0,i.jsx)(s.A,{children:(0,i.jsx)("meta",{name:"robots",content:"noindex, nofollow"})})}function c(){return(0,i.jsx)(n.A,{id:"theme.contentVisibility.draftBanner.title",description:"The draft content banner title",children:"Draft page"})}function d(){return(0,i.jsx)(n.A,{id:"theme.contentVisibility.draftBanner.message",description:"The draft content banner message",children:"This page is a draft. It will only be visible in dev and be excluded from the production build."})}},4434:(e,t,a)=>{a.d(t,{A:()=>o});a(6540);var n=a(4164),s=a(1312),i=a(6133);const r={tags:"tags_jXut",tag:"tag_QGVx"};var l=a(4848);function o({tags:e}){return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)("b",{children:(0,l.jsx)(s.A,{id:"theme.tags.tagsListLabel",description:"The label alongside a tag list",children:"Tags:"})}),(0,l.jsx)("ul",{className:(0,n.A)(r.tags,"padding--none","margin-left--sm"),children:e.map(e=>(0,l.jsx)("li",{className:r.tag,children:(0,l.jsx)(i.A,{...e})},e.permalink))})]})}},6133:(e,t,a)=>{a.d(t,{A:()=>l});a(6540);var n=a(4164),s=a(8774);const i={tag:"tag_zVej",tagRegular:"tagRegular_sFm0",tagWithCount:"tagWithCount_h2kH"};var r=a(4848);function l({permalink:e,label:t,count:a,description:l}){return(0,r.jsxs)(s.A,{rel:"tag",href:e,title:l,className:(0,n.A)(i.tag,a?i.tagWithCount:i.tagRegular),children:[t,a&&(0,r.jsx)("span",{children:a})]})}},6461:(e,t,a)=>{a.d(t,{ZD:()=>r,uz:()=>l});a(6540);var n=a(1312),s=a(5846);a(4848);function i(){const{selectMessage:e}=(0,s.W)();return t=>e(t,(0,n.T)({id:"theme.blog.post.plurals",description:'Pluralized label for "{count} posts". Use as much plural forms (separated by "|") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)',message:"One post|{count} posts"},{count:t}))}function r(e){const t=i();return(0,n.T)({id:"theme.blog.tagTitle",description:"The title of the page for a blog tag",message:'{nPosts} tagged with "{tagName}"'},{nPosts:t(e.count),tagName:e.label})}const l=()=>(0,n.T)({id:"theme.blog.authorsList.pageTitle",message:"Authors",description:"The title of the authors page"})},7713:(e,t,a)=>{a.d(t,{A:()=>r});a(6540);var n=a(1312),s=a(9022),i=a(4848);function r(e){const{metadata:t}=e,{previousPage:a,nextPage:r}=t;return(0,i.jsxs)("nav",{className:"pagination-nav","aria-label":(0,n.T)({id:"theme.blog.paginator.navAriaLabel",message:"Blog list page navigation",description:"The ARIA label for the blog pagination"}),children:[a&&(0,i.jsx)(s.A,{permalink:a,title:(0,i.jsx)(n.A,{id:"theme.blog.paginator.newerEntries",description:"The label used to navigate to the newer blog posts page (previous page)",children:"Newer entries"})}),r&&(0,i.jsx)(s.A,{permalink:r,title:(0,i.jsx)(n.A,{id:"theme.blog.paginator.olderEntries",description:"The label used to navigate to the older blog posts page (next page)",children:"Older entries"}),isNext:!0})]})}},9022:(e,t,a)=>{a.d(t,{A:()=>r});a(6540);var n=a(4164),s=a(8774),i=a(4848);function r(e){const{permalink:t,title:a,subLabel:r,isNext:l}=e;return(0,i.jsxs)(s.A,{className:(0,n.A)("pagination-nav__link",l?"pagination-nav__link--next":"pagination-nav__link--prev"),to:t,children:[r&&(0,i.jsx)("div",{className:"pagination-nav__sublabel",children:r}),(0,i.jsx)("div",{className:"pagination-nav__label",children:a})]})}}}]); \ No newline at end of file diff --git a/docs/assets/js/6875c492.72d20027.js b/docs/assets/js/6875c492.72d20027.js deleted file mode 100644 index 9a0b33af..00000000 --- a/docs/assets/js/6875c492.72d20027.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[4813],{2053:(e,t,a)=>{a.d(t,{A:()=>o});a(6540);var n=a(4164),s=a(1312),i=a(6133);const r={tags:"tags_jXut",tag:"tag_QGVx"};var l=a(4848);function o({tags:e}){return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)("b",{children:(0,l.jsx)(s.A,{id:"theme.tags.tagsListLabel",description:"The label alongside a tag list",children:"Tags:"})}),(0,l.jsx)("ul",{className:(0,n.A)(r.tags,"padding--none","margin-left--sm"),children:e.map((e=>(0,l.jsx)("li",{className:r.tag,children:(0,l.jsx)(i.A,{...e})},e.permalink)))})]})}},2234:(e,t,a)=>{a.d(t,{A:()=>c});a(6540);var n=a(4164),s=a(4084),i=a(7559),r=a(7293),l=a(4848);function o({className:e}){return(0,l.jsx)(r.A,{type:"caution",title:(0,l.jsx)(s.Rc,{}),className:(0,n.A)(e,i.G.common.unlistedBanner),children:(0,l.jsx)(s.Uh,{})})}function c(e){return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(s.AE,{}),(0,l.jsx)(o,{...e})]})}},2907:(e,t,a)=>{a.d(t,{A:()=>B});a(6540);var n=a(4164),s=a(4096),i=a(4848);function r({children:e,className:t}){return(0,i.jsx)("article",{className:t,children:e})}var l=a(8774);const o={title:"title_f1Hy"};function c({className:e}){const{metadata:t,isBlogPostPage:a}=(0,s.e7)(),{permalink:r,title:c}=t,d=a?"h1":"h2";return(0,i.jsx)(d,{className:(0,n.A)(o.title,e),children:a?c:(0,i.jsx)(l.A,{to:r,children:c})})}var d=a(1312),g=a(5846),u=a(6266);const m={container:"container_mt6G"};function h({readingTime:e}){const t=function(){const{selectMessage:e}=(0,g.W)();return t=>{const a=Math.ceil(t);return e(a,(0,d.T)({id:"theme.blog.post.readingTime.plurals",description:'Pluralized label for "{readingTime} min read". Use as much plural forms (separated by "|") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)',message:"One min read|{readingTime} min read"},{readingTime:a}))}}();return(0,i.jsx)(i.Fragment,{children:t(e)})}function p({date:e,formattedDate:t}){return(0,i.jsx)("time",{dateTime:e,children:t})}function x(){return(0,i.jsx)(i.Fragment,{children:" \xb7 "})}function j({className:e}){const{metadata:t}=(0,s.e7)(),{date:a,readingTime:r}=t,l=(0,u.i)({day:"numeric",month:"long",year:"numeric",timeZone:"UTC"});return(0,i.jsxs)("div",{className:(0,n.A)(m.container,"margin-vert--md",e),children:[(0,i.jsx)(p,{date:a,formattedDate:(o=a,l.format(new Date(o)))}),void 0!==r&&(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(x,{}),(0,i.jsx)(h,{readingTime:r})]})]});var o}var b=a(6382);const A={authorCol:"authorCol_Hf19",imageOnlyAuthorRow:"imageOnlyAuthorRow_pa_O",imageOnlyAuthorCol:"imageOnlyAuthorCol_G86a"};function f({className:e}){const{metadata:{authors:t},assets:a}=(0,s.e7)();if(0===t.length)return null;const r=t.every((({name:e})=>!e)),l=1===t.length;return(0,i.jsx)("div",{className:(0,n.A)("margin-top--md margin-bottom--sm",r?A.imageOnlyAuthorRow:"row",e),children:t.map(((e,t)=>(0,i.jsx)("div",{className:(0,n.A)(!r&&(l?"col col--12":"col col--6"),r?A.imageOnlyAuthorCol:A.authorCol),children:(0,i.jsx)(b.A,{author:{...e,imageURL:a.authorsImageUrls[t]??e.imageURL}})},t)))})}function v(){return(0,i.jsxs)("header",{children:[(0,i.jsx)(c,{}),(0,i.jsx)(j,{}),(0,i.jsx)(f,{})]})}var T=a(440),N=a(3253);function w({children:e,className:t}){const{isBlogPostPage:a}=(0,s.e7)();return(0,i.jsx)("div",{id:a?T.LU:void 0,className:(0,n.A)("markdown",t),children:(0,i.jsx)(N.A,{children:e})})}var _=a(7559),k=a(4336),y=a(2053);function P(){return(0,i.jsx)("b",{children:(0,i.jsx)(d.A,{id:"theme.blog.post.readMore",description:"The label used in blog post item excerpts to link to full blog posts",children:"Read more"})})}function R(e){const{blogPostTitle:t,...a}=e;return(0,i.jsx)(l.A,{"aria-label":(0,d.T)({message:"Read more about {title}",id:"theme.blog.post.readMoreLabel",description:"The ARIA label for the link to full blog posts from excerpts"},{title:t}),...a,children:(0,i.jsx)(P,{})})}function U(){const{metadata:e,isBlogPostPage:t}=(0,s.e7)(),{tags:a,title:r,editUrl:l,hasTruncateMarker:o,lastUpdatedBy:c,lastUpdatedAt:d}=e,g=!t&&o,u=a.length>0;if(!(u||g||l))return null;if(t){const e=!!(l||d||c);return(0,i.jsxs)("footer",{className:"docusaurus-mt-lg",children:[u&&(0,i.jsx)("div",{className:(0,n.A)("row","margin-top--sm",_.G.blog.blogFooterEditMetaRow),children:(0,i.jsx)("div",{className:"col",children:(0,i.jsx)(y.A,{tags:a})})}),e&&(0,i.jsx)(k.A,{className:(0,n.A)("margin-top--sm",_.G.blog.blogFooterEditMetaRow),editUrl:l,lastUpdatedAt:d,lastUpdatedBy:c})]})}return(0,i.jsxs)("footer",{className:"row docusaurus-mt-lg",children:[u&&(0,i.jsx)("div",{className:(0,n.A)("col",{"col--9":g}),children:(0,i.jsx)(y.A,{tags:a})}),g&&(0,i.jsx)("div",{className:(0,n.A)("col text--right",{"col--3":u}),children:(0,i.jsx)(R,{blogPostTitle:r,to:e.permalink})})]})}function B({children:e,className:t}){const a=function(){const{isBlogPostPage:e}=(0,s.e7)();return e?void 0:"margin-bottom--xl"}();return(0,i.jsxs)(r,{className:(0,n.A)(a,t),children:[(0,i.jsx)(v,{}),(0,i.jsx)(w,{children:e}),(0,i.jsx)(U,{})]})}},3069:(e,t,a)=>{a.r(t),a.d(t,{default:()=>b});a(6540);var n=a(4164),s=a(1312),i=a(5500),r=a(7559),l=a(6461),o=a(8774),c=a(8027),d=a(7713),g=a(1463),u=a(3892),m=a(2234),h=a(1107),p=a(4848);function x({tag:e}){const t=(0,l.ZD)(e);return(0,p.jsxs)(p.Fragment,{children:[(0,p.jsx)(i.be,{title:t,description:e.description}),(0,p.jsx)(g.A,{tag:"blog_tags_posts"})]})}function j({tag:e,items:t,sidebar:a,listMetadata:n}){const i=(0,l.ZD)(e);return(0,p.jsxs)(c.A,{sidebar:a,children:[e.unlisted&&(0,p.jsx)(m.A,{}),(0,p.jsxs)("header",{className:"margin-bottom--xl",children:[(0,p.jsx)(h.A,{as:"h1",children:i}),e.description&&(0,p.jsx)("p",{children:e.description}),(0,p.jsx)(o.A,{href:e.allTagsPath,children:(0,p.jsx)(s.A,{id:"theme.tags.tagsPageLink",description:"The label of the link targeting the tag list page",children:"View All Tags"})})]}),(0,p.jsx)(u.A,{items:t}),(0,p.jsx)(d.A,{metadata:n})]})}function b(e){return(0,p.jsxs)(i.e3,{className:(0,n.A)(r.G.wrapper.blogPages,r.G.page.blogTagPostListPage),children:[(0,p.jsx)(x,{...e}),(0,p.jsx)(j,{...e})]})}},3892:(e,t,a)=>{a.d(t,{A:()=>r});a(6540);var n=a(4096),s=a(2907),i=a(4848);function r({items:e,component:t=s.A}){return(0,i.jsx)(i.Fragment,{children:e.map((({content:e})=>(0,i.jsx)(n.in,{content:e,children:(0,i.jsx)(t,{children:(0,i.jsx)(e,{})})},e.metadata.permalink)))})}},4084:(e,t,a)=>{a.d(t,{AE:()=>o,Rc:()=>r,TT:()=>d,Uh:()=>l,Yh:()=>c});a(6540);var n=a(1312),s=a(5260),i=a(4848);function r(){return(0,i.jsx)(n.A,{id:"theme.contentVisibility.unlistedBanner.title",description:"The unlisted content banner title",children:"Unlisted page"})}function l(){return(0,i.jsx)(n.A,{id:"theme.contentVisibility.unlistedBanner.message",description:"The unlisted content banner message",children:"This page is unlisted. Search engines will not index it, and only users having a direct link can access it."})}function o(){return(0,i.jsx)(s.A,{children:(0,i.jsx)("meta",{name:"robots",content:"noindex, nofollow"})})}function c(){return(0,i.jsx)(n.A,{id:"theme.contentVisibility.draftBanner.title",description:"The draft content banner title",children:"Draft page"})}function d(){return(0,i.jsx)(n.A,{id:"theme.contentVisibility.draftBanner.message",description:"The draft content banner message",children:"This page is a draft. It will only be visible in dev and be excluded from the production build."})}},6133:(e,t,a)=>{a.d(t,{A:()=>l});a(6540);var n=a(4164),s=a(8774);const i={tag:"tag_zVej",tagRegular:"tagRegular_sFm0",tagWithCount:"tagWithCount_h2kH"};var r=a(4848);function l({permalink:e,label:t,count:a,description:l}){return(0,r.jsxs)(s.A,{rel:"tag",href:e,title:l,className:(0,n.A)(i.tag,a?i.tagWithCount:i.tagRegular),children:[t,a&&(0,r.jsx)("span",{children:a})]})}},6461:(e,t,a)=>{a.d(t,{ZD:()=>r,uz:()=>l});a(6540);var n=a(1312),s=a(5846);a(4848);function i(){const{selectMessage:e}=(0,s.W)();return t=>e(t,(0,n.T)({id:"theme.blog.post.plurals",description:'Pluralized label for "{count} posts". Use as much plural forms (separated by "|") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)',message:"One post|{count} posts"},{count:t}))}function r(e){const t=i();return(0,n.T)({id:"theme.blog.tagTitle",description:"The title of the page for a blog tag",message:'{nPosts} tagged with "{tagName}"'},{nPosts:t(e.count),tagName:e.label})}const l=()=>(0,n.T)({id:"theme.blog.authorsList.pageTitle",message:"Authors",description:"The title of the authors page"})},7713:(e,t,a)=>{a.d(t,{A:()=>r});a(6540);var n=a(1312),s=a(9022),i=a(4848);function r(e){const{metadata:t}=e,{previousPage:a,nextPage:r}=t;return(0,i.jsxs)("nav",{className:"pagination-nav","aria-label":(0,n.T)({id:"theme.blog.paginator.navAriaLabel",message:"Blog list page navigation",description:"The ARIA label for the blog pagination"}),children:[a&&(0,i.jsx)(s.A,{permalink:a,title:(0,i.jsx)(n.A,{id:"theme.blog.paginator.newerEntries",description:"The label used to navigate to the newer blog posts page (previous page)",children:"Newer entries"})}),r&&(0,i.jsx)(s.A,{permalink:r,title:(0,i.jsx)(n.A,{id:"theme.blog.paginator.olderEntries",description:"The label used to navigate to the older blog posts page (next page)",children:"Older entries"}),isNext:!0})]})}},9022:(e,t,a)=>{a.d(t,{A:()=>r});a(6540);var n=a(4164),s=a(8774),i=a(4848);function r(e){const{permalink:t,title:a,subLabel:r,isNext:l}=e;return(0,i.jsxs)(s.A,{className:(0,n.A)("pagination-nav__link",l?"pagination-nav__link--next":"pagination-nav__link--prev"),to:t,children:[r&&(0,i.jsx)("div",{className:"pagination-nav__sublabel",children:r}),(0,i.jsx)("div",{className:"pagination-nav__label",children:a})]})}}}]); \ No newline at end of file diff --git a/docs/assets/js/6bb91276.d5be09e8.js b/docs/assets/js/6bb91276.d5be09e8.js new file mode 100644 index 00000000..ae68881c --- /dev/null +++ b/docs/assets/js/6bb91276.d5be09e8.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[9226],{8758:a=>{a.exports=JSON.parse('{"tag":{"label":"vllm","permalink":"/BharatMLStack/blog/tags/vllm","allTagsPath":"/BharatMLStack/blog/tags","count":2,"unlisted":false},"listMetadata":{"permalink":"/BharatMLStack/blog/tags/vllm","page":1,"postsPerPage":10,"totalPages":1,"totalCount":2,"blogDescription":"Blog","blogTitle":"Blog"}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/72dc5b25.e57720a7.js b/docs/assets/js/72dc5b25.e57720a7.js deleted file mode 100644 index 5c7514b9..00000000 --- a/docs/assets/js/72dc5b25.e57720a7.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[8261],{3613:e=>{e.exports=JSON.parse('{"categoryGeneratedIndex":{"title":"v1.0.0","description":"Python SDK v1.0.0 documentation for BharatML Stack. Contains API reference, usage guides, and examples for the Python client libraries including gRPC feature client, Spark feature push client, and common utilities.","slug":"/online-feature-store/v1.0.0","permalink":"/BharatMLStack/online-feature-store/v1.0.0","sidebar":"tutorialSidebar","navigation":{"previous":{"title":"Online Feature Store","permalink":"/BharatMLStack/category/online-feature-store"},"next":{"title":"Architecture","permalink":"/BharatMLStack/online-feature-store/v1.0.0/architecture"}}}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/74783256.0fa34723.js b/docs/assets/js/74783256.0fa34723.js new file mode 100644 index 00000000..089fd491 --- /dev/null +++ b/docs/assets/js/74783256.0fa34723.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[7813],{1566:e=>{e.exports=JSON.parse('{"categoryGeneratedIndex":{"title":"Predator","description":"Predator is a scalable, high-performance model inference service built as a wrapper around NVIDIA Triton Inference Server, designed to serve ML models with low latency in Kubernetes, with OnFS and Interflow integration.","slug":"/category/predator","permalink":"/BharatMLStack/category/predator","sidebar":"tutorialSidebar","navigation":{"previous":{"title":"Release Notes","permalink":"/BharatMLStack/numerix/v1.0.0/release-notes"},"next":{"title":"v1.0.0","permalink":"/BharatMLStack/predator/v1.0.0"}}}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/7518.6ac3b679.js b/docs/assets/js/7518.6ac3b679.js deleted file mode 100644 index c86683c0..00000000 --- a/docs/assets/js/7518.6ac3b679.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[7518],{4096:(e,t,s)=>{s.d(t,{in:()=>c,OU:()=>A,Ki:()=>k,kJ:()=>f,x:()=>l,e7:()=>h,J_:()=>p,Gx:()=>N});var a=s(6540),n=s(9532),i=s(6803),r=s(4848);function l(){const e=(0,i.A)(),t=e?.data?.blogMetadata;if(!t)throw new Error("useBlogMetadata() can't be called on the current route because the blog metadata could not be found in route context");return t}const o=a.createContext(null);function c({children:e,content:t,isBlogPostPage:s=!1}){const n=function({content:e,isBlogPostPage:t}){return(0,a.useMemo)((()=>({metadata:e.metadata,frontMatter:e.frontMatter,assets:e.assets,toc:e.toc,isBlogPostPage:t})),[e,t])}({content:t,isBlogPostPage:s});return(0,r.jsx)(o.Provider,{value:n,children:e})}function h(){const e=(0,a.useContext)(o);if(null===e)throw new n.dV("BlogPostProvider");return e}var m=s(6025),u=s(4586);const d=e=>new Date(e).toISOString();function g(e){const t=e.map(v);return{author:1===t.length?t[0]:t}}function x(e,t,s){return e?{image:w({imageUrl:t(e,{absolute:!0}),caption:`title image for the blog post: ${s}`})}:{}}function f(e){const{siteConfig:t}=(0,u.A)(),{withBaseUrl:s}=(0,m.hH)(),{metadata:{blogDescription:a,blogTitle:n,permalink:i}}=e,r=`${t.url}${i}`;return{"@context":"https://schema.org","@type":"Blog","@id":r,mainEntityOfPage:r,headline:n,description:a,blogPost:e.items.map((e=>function(e,t,s){const{assets:a,frontMatter:n,metadata:i}=e,{date:r,title:l,description:o,lastUpdatedAt:c}=i,h=a.image??n.image,m=n.keywords??[],u=`${t.url}${i.permalink}`,f=c?d(c):void 0;return{"@type":"BlogPosting","@id":u,mainEntityOfPage:u,url:u,headline:l,name:l,description:o,datePublished:r,...f?{dateModified:f}:{},...g(i.authors),...x(h,s,l),...m?{keywords:m}:{}}}(e.content,t,s)))}}function p(){const e=l(),{assets:t,metadata:s}=h(),{siteConfig:a}=(0,u.A)(),{withBaseUrl:n}=(0,m.hH)(),{date:i,title:r,description:o,frontMatter:c,lastUpdatedAt:f}=s,p=t.image??c.image,v=c.keywords??[],w=f?d(f):void 0,j=`${a.url}${s.permalink}`;return{"@context":"https://schema.org","@type":"BlogPosting","@id":j,mainEntityOfPage:j,url:j,headline:r,name:r,description:o,datePublished:i,...w?{dateModified:w}:{},...g(s.authors),...x(p,n,r),...v?{keywords:v}:{},isPartOf:{"@type":"Blog","@id":`${a.url}${e.blogBasePath}`,name:e.blogTitle}}}function v(e){return{"@type":"Person",...e.name?{name:e.name}:{},...e.title?{description:e.title}:{},...e.url?{url:e.url}:{},...e.email?{email:e.email}:{},...e.imageURL?{image:e.imageURL}:{}}}function w({imageUrl:e,caption:t}){return{"@type":"ImageObject","@id":e,url:e,contentUrl:e,caption:t}}var j=s(6347),b=s(8774),C=s(1682),M=s(9169);function N(e){const{pathname:t}=(0,j.zy)();return(0,a.useMemo)((()=>e.filter((e=>function(e,t){return!(e.unlisted&&!(0,M.ys)(e.permalink,t))}(e,t)))),[e,t])}function k(e){const t=(0,C.$z)(e,(e=>`${new Date(e.date).getFullYear()}`)),s=Object.entries(t);return s.reverse(),s}function A({items:e,ulClassName:t,liClassName:s,linkClassName:a,linkActiveClassName:n}){return(0,r.jsx)("ul",{className:t,children:e.map((e=>(0,r.jsx)("li",{className:s,children:(0,r.jsx)(b.A,{isNavLink:!0,to:e.permalink,className:a,activeClassName:n,children:e.title})},e.permalink)))})}},5846:(e,t,s)=>{s.d(t,{W:()=>c});var a=s(6540),n=s(4586);const i=["zero","one","two","few","many","other"];function r(e){return i.filter((t=>e.includes(t)))}const l={locale:"en",pluralForms:r(["one","other"]),select:e=>1===e?"one":"other"};function o(){const{i18n:{currentLocale:e}}=(0,n.A)();return(0,a.useMemo)((()=>{try{return function(e){const t=new Intl.PluralRules(e);return{locale:e,pluralForms:r(t.resolvedOptions().pluralCategories),select:e=>t.select(e)}}(e)}catch(t){return console.error(`Failed to use Intl.PluralRules for locale "${e}".\nDocusaurus will fallback to the default (English) implementation.\nError: ${t.message}\n`),l}}),[e])}function c(){const e=o();return{selectMessage:(t,s)=>function(e,t,s){const a=e.split("|");if(1===a.length)return a[0];a.length>s.pluralForms.length&&console.error(`For locale=${s.locale}, a maximum of ${s.pluralForms.length} plural forms are expected (${s.pluralForms.join(",")}), but the message contains ${a.length}: ${e}`);const n=s.select(t),i=s.pluralForms.indexOf(n);return a[Math.min(i,a.length-1)]}(s,t,e)}}},6382:(e,t,s)=>{s.d(t,{A:()=>C});var a=s(6540),n=s(4164),i=s(8774),r=s(4848);const l="githubSvg_Uu4N";const o="xSvg_y3PF";const c=function(e){return(0,r.jsxs)("svg",{xmlns:"http://www.w3.org/2000/svg",width:"1em",height:"1em",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",...e,children:[(0,r.jsx)("path",{stroke:"none",d:"M0 0h24v24H0z",fill:"none"}),(0,r.jsx)("path",{d:"M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0"}),(0,r.jsx)("path",{d:"M3.6 9h16.8"}),(0,r.jsx)("path",{d:"M3.6 15h16.8"}),(0,r.jsx)("path",{d:"M11.5 3a17 17 0 0 0 0 18"}),(0,r.jsx)("path",{d:"M12.5 3a17 17 0 0 1 0 18"})]})};const h="instagramSvg_YC40";const m="threadsSvg_PTXY";const u={authorSocials:"authorSocials_rSDt",authorSocialLink:"authorSocialLink_owbf",authorSocialIcon:"authorSocialIcon_XYv3"},d={twitter:{Icon:function(e){return(0,r.jsx)("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 256 209",width:"1em",height:"1em",preserveAspectRatio:"xMidYMid",...e,children:(0,r.jsx)("path",{d:"M256 25.45c-9.42 4.177-19.542 7-30.166 8.27 10.845-6.5 19.172-16.793 23.093-29.057a105.183 105.183 0 0 1-33.351 12.745C205.995 7.201 192.346.822 177.239.822c-29.006 0-52.523 23.516-52.523 52.52 0 4.117.465 8.125 1.36 11.97-43.65-2.191-82.35-23.1-108.255-54.876-4.52 7.757-7.11 16.78-7.11 26.404 0 18.222 9.273 34.297 23.365 43.716a52.312 52.312 0 0 1-23.79-6.57c-.003.22-.003.44-.003.661 0 25.447 18.104 46.675 42.13 51.5a52.592 52.592 0 0 1-23.718.9c6.683 20.866 26.08 36.05 49.062 36.475-17.975 14.086-40.622 22.483-65.228 22.483-4.24 0-8.42-.249-12.529-.734 23.243 14.902 50.85 23.597 80.51 23.597 96.607 0 149.434-80.031 149.434-149.435 0-2.278-.05-4.543-.152-6.795A106.748 106.748 0 0 0 256 25.45",fill:"#55acee"})})},label:"Twitter"},github:{Icon:function(e){return(0,r.jsx)("svg",{xmlns:"http://www.w3.org/2000/svg",width:"1em",height:"1em",viewBox:"0 0 256 250",preserveAspectRatio:"xMidYMid",style:{"--dark":"#000","--light":"#fff"},...e,className:(0,n.A)(e.className,l),children:(0,r.jsx)("path",{d:"M128.001 0C57.317 0 0 57.307 0 128.001c0 56.554 36.676 104.535 87.535 121.46 6.397 1.185 8.746-2.777 8.746-6.158 0-3.052-.12-13.135-.174-23.83-35.61 7.742-43.124-15.103-43.124-15.103-5.823-14.795-14.213-18.73-14.213-18.73-11.613-7.944.876-7.78.876-7.78 12.853.902 19.621 13.19 19.621 13.19 11.417 19.568 29.945 13.911 37.249 10.64 1.149-8.272 4.466-13.92 8.127-17.116-28.431-3.236-58.318-14.212-58.318-63.258 0-13.975 5-25.394 13.188-34.358-1.329-3.224-5.71-16.242 1.24-33.874 0 0 10.749-3.44 35.21 13.121 10.21-2.836 21.16-4.258 32.038-4.307 10.878.049 21.837 1.47 32.066 4.307 24.431-16.56 35.165-13.12 35.165-13.12 6.967 17.63 2.584 30.65 1.255 33.873 8.207 8.964 13.173 20.383 13.173 34.358 0 49.163-29.944 59.988-58.447 63.157 4.591 3.972 8.682 11.762 8.682 23.704 0 17.126-.148 30.91-.148 35.126 0 3.407 2.304 7.398 8.792 6.14C219.37 232.5 256 184.537 256 128.002 256 57.307 198.691 0 128.001 0Zm-80.06 182.34c-.282.636-1.283.827-2.194.39-.929-.417-1.45-1.284-1.15-1.922.276-.655 1.279-.838 2.205-.399.93.418 1.46 1.293 1.139 1.931Zm6.296 5.618c-.61.566-1.804.303-2.614-.591-.837-.892-.994-2.086-.375-2.66.63-.566 1.787-.301 2.626.591.838.903 1 2.088.363 2.66Zm4.32 7.188c-.785.545-2.067.034-2.86-1.104-.784-1.138-.784-2.503.017-3.05.795-.547 2.058-.055 2.861 1.075.782 1.157.782 2.522-.019 3.08Zm7.304 8.325c-.701.774-2.196.566-3.29-.49-1.119-1.032-1.43-2.496-.726-3.27.71-.776 2.213-.558 3.315.49 1.11 1.03 1.45 2.505.701 3.27Zm9.442 2.81c-.31 1.003-1.75 1.459-3.199 1.033-1.448-.439-2.395-1.613-2.103-2.626.301-1.01 1.747-1.484 3.207-1.028 1.446.436 2.396 1.602 2.095 2.622Zm10.744 1.193c.036 1.055-1.193 1.93-2.715 1.95-1.53.034-2.769-.82-2.786-1.86 0-1.065 1.202-1.932 2.733-1.958 1.522-.03 2.768.818 2.768 1.868Zm10.555-.405c.182 1.03-.875 2.088-2.387 2.37-1.485.271-2.861-.365-3.05-1.386-.184-1.056.893-2.114 2.376-2.387 1.514-.263 2.868.356 3.061 1.403Z"})})},label:"GitHub"},stackoverflow:{Icon:function(e){return(0,r.jsxs)("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 169.61 200",width:"1em",height:"1em",...e,children:[(0,r.jsx)("path",{d:"M140.44 178.38v-48.65h21.61V200H0v-70.27h21.61v48.65z",fill:"#bcbbbb"}),(0,r.jsx)("path",{d:"M124.24 140.54l4.32-16.22-86.97-17.83-3.78 17.83zM49.7 82.16L130.72 120l7.56-16.22-81.02-37.83zm22.68-40l68.06 57.3 11.35-13.51-68.6-57.3-11.35 13.51zM116.14 0l-14.59 10.81 53.48 71.89 14.58-10.81zM37.81 162.16h86.43v-16.21H37.81z",fill:"#f48024"})]})},label:"Stack Overflow"},linkedin:{Icon:function(e){return(0,r.jsx)("svg",{xmlns:"http://www.w3.org/2000/svg",width:"1em",height:"1em",preserveAspectRatio:"xMidYMid",viewBox:"0 0 256 256",...e,children:(0,r.jsx)("path",{d:"M218.123 218.127h-37.931v-59.403c0-14.165-.253-32.4-19.728-32.4-19.756 0-22.779 15.434-22.779 31.369v60.43h-37.93V95.967h36.413v16.694h.51a39.907 39.907 0 0 1 35.928-19.733c38.445 0 45.533 25.288 45.533 58.186l-.016 67.013ZM56.955 79.27c-12.157.002-22.014-9.852-22.016-22.009-.002-12.157 9.851-22.014 22.008-22.016 12.157-.003 22.014 9.851 22.016 22.008A22.013 22.013 0 0 1 56.955 79.27m18.966 138.858H37.95V95.967h37.97v122.16ZM237.033.018H18.89C8.58-.098.125 8.161-.001 18.471v219.053c.122 10.315 8.576 18.582 18.89 18.474h218.144c10.336.128 18.823-8.139 18.966-18.474V18.454c-.147-10.33-8.635-18.588-18.966-18.453",fill:"#0A66C2"})})},label:"LinkedIn"},x:{Icon:function(e){return(0,r.jsx)("svg",{xmlns:"http://www.w3.org/2000/svg",width:"1em",height:"1em",fill:"none",viewBox:"0 0 1200 1227",style:{"--dark":"#000","--light":"#fff"},...e,className:(0,n.A)(e.className,o),children:(0,r.jsx)("path",{d:"M714.163 519.284 1160.89 0h-105.86L667.137 450.887 357.328 0H0l468.492 681.821L0 1226.37h105.866l409.625-476.152 327.181 476.152H1200L714.137 519.284h.026ZM569.165 687.828l-47.468-67.894-377.686-540.24h162.604l304.797 435.991 47.468 67.894 396.2 566.721H892.476L569.165 687.854v-.026Z"})})},label:"X"},bluesky:{Icon:function(e){return(0,r.jsx)("svg",{xmlns:"http://www.w3.org/2000/svg",width:"1em",height:"1em",preserveAspectRatio:"xMidYMid",viewBox:"0 0 256 226",...e,children:(0,r.jsx)("path",{fill:"#1185FE",d:"M55.491 15.172c29.35 22.035 60.917 66.712 72.509 90.686 11.592-23.974 43.159-68.651 72.509-90.686C221.686-.727 256-13.028 256 26.116c0 7.818-4.482 65.674-7.111 75.068-9.138 32.654-42.436 40.983-72.057 35.942 51.775 8.812 64.946 38 36.501 67.187-54.021 55.433-77.644-13.908-83.696-31.676-1.11-3.257-1.63-4.78-1.637-3.485-.008-1.296-.527.228-1.637 3.485-6.052 17.768-29.675 87.11-83.696 31.676-28.445-29.187-15.274-58.375 36.5-67.187-29.62 5.041-62.918-3.288-72.056-35.942C4.482 91.79 0 33.934 0 26.116 0-13.028 34.314-.727 55.491 15.172Z"})})},label:"Bluesky"},instagram:{Icon:function(e){return(0,r.jsx)("svg",{xmlns:"http://www.w3.org/2000/svg",width:"1em",height:"1em",preserveAspectRatio:"xMidYMid",viewBox:"0 0 256 256",style:{"--dark":"#000","--light":"#fff"},...e,className:(0,n.A)(e.className,h),children:(0,r.jsx)("path",{d:"M128 23.064c34.177 0 38.225.13 51.722.745 12.48.57 19.258 2.655 23.769 4.408 5.974 2.322 10.238 5.096 14.717 9.575 4.48 4.479 7.253 8.743 9.575 14.717 1.753 4.511 3.838 11.289 4.408 23.768.615 13.498.745 17.546.745 51.723 0 34.178-.13 38.226-.745 51.723-.57 12.48-2.655 19.257-4.408 23.768-2.322 5.974-5.096 10.239-9.575 14.718-4.479 4.479-8.743 7.253-14.717 9.574-4.511 1.753-11.289 3.839-23.769 4.408-13.495.616-17.543.746-51.722.746-34.18 0-38.228-.13-51.723-.746-12.48-.57-19.257-2.655-23.768-4.408-5.974-2.321-10.239-5.095-14.718-9.574-4.479-4.48-7.253-8.744-9.574-14.718-1.753-4.51-3.839-11.288-4.408-23.768-.616-13.497-.746-17.545-.746-51.723 0-34.177.13-38.225.746-51.722.57-12.48 2.655-19.258 4.408-23.769 2.321-5.974 5.095-10.238 9.574-14.717 4.48-4.48 8.744-7.253 14.718-9.575 4.51-1.753 11.288-3.838 23.768-4.408 13.497-.615 17.545-.745 51.723-.745M128 0C93.237 0 88.878.147 75.226.77c-13.625.622-22.93 2.786-31.071 5.95-8.418 3.271-15.556 7.648-22.672 14.764C14.367 28.6 9.991 35.738 6.72 44.155 3.555 52.297 1.392 61.602.77 75.226.147 88.878 0 93.237 0 128c0 34.763.147 39.122.77 52.774.622 13.625 2.785 22.93 5.95 31.071 3.27 8.417 7.647 15.556 14.763 22.672 7.116 7.116 14.254 11.492 22.672 14.763 8.142 3.165 17.446 5.328 31.07 5.95 13.653.623 18.012.77 52.775.77s39.122-.147 52.774-.77c13.624-.622 22.929-2.785 31.07-5.95 8.418-3.27 15.556-7.647 22.672-14.763 7.116-7.116 11.493-14.254 14.764-22.672 3.164-8.142 5.328-17.446 5.95-31.07.623-13.653.77-18.012.77-52.775s-.147-39.122-.77-52.774c-.622-13.624-2.786-22.929-5.95-31.07-3.271-8.418-7.648-15.556-14.764-22.672C227.4 14.368 220.262 9.99 211.845 6.72c-8.142-3.164-17.447-5.328-31.071-5.95C167.122.147 162.763 0 128 0Zm0 62.27C91.698 62.27 62.27 91.7 62.27 128c0 36.302 29.428 65.73 65.73 65.73 36.301 0 65.73-29.428 65.73-65.73 0-36.301-29.429-65.73-65.73-65.73Zm0 108.397c-23.564 0-42.667-19.103-42.667-42.667S104.436 85.333 128 85.333s42.667 19.103 42.667 42.667-19.103 42.667-42.667 42.667Zm83.686-110.994c0 8.484-6.876 15.36-15.36 15.36-8.483 0-15.36-6.876-15.36-15.36 0-8.483 6.877-15.36 15.36-15.36 8.484 0 15.36 6.877 15.36 15.36Z"})})},label:"Instagram"},threads:{Icon:function(e){return(0,r.jsx)("svg",{xmlns:"http://www.w3.org/2000/svg","aria-label":"Threads",viewBox:"0 0 192 192",width:"1em",fill:"none",height:"1em",style:{"--dark":"#000","--light":"#fff"},...e,className:(0,n.A)(e.className,m),children:(0,r.jsx)("path",{d:"M141.537 88.988a66.667 66.667 0 0 0-2.518-1.143c-1.482-27.307-16.403-42.94-41.457-43.1h-.34c-14.986 0-27.449 6.396-35.12 18.036l13.779 9.452c5.73-8.695 14.724-10.548 21.348-10.548h.229c8.249.053 14.474 2.452 18.503 7.129 2.932 3.405 4.893 8.111 5.864 14.05-7.314-1.243-15.224-1.626-23.68-1.14-23.82 1.371-39.134 15.264-38.105 34.568.522 9.792 5.4 18.216 13.735 23.719 7.047 4.652 16.124 6.927 25.557 6.412 12.458-.683 22.231-5.436 29.049-14.127 5.178-6.6 8.453-15.153 9.899-25.93 5.937 3.583 10.337 8.298 12.767 13.966 4.132 9.635 4.373 25.468-8.546 38.376-11.319 11.308-24.925 16.2-45.488 16.351-22.809-.169-40.06-7.484-51.275-21.742C35.236 139.966 29.808 120.682 29.605 96c.203-24.682 5.63-43.966 16.133-57.317C56.954 24.425 74.204 17.11 97.013 16.94c22.975.17 40.526 7.52 52.171 21.847 5.71 7.026 10.015 15.86 12.853 26.162l16.147-4.308c-3.44-12.68-8.853-23.606-16.219-32.668C147.036 9.607 125.202.195 97.07 0h-.113C68.882.194 47.292 9.642 32.788 28.08 19.882 44.485 13.224 67.315 13.001 95.932L13 96v.067c.224 28.617 6.882 51.447 19.788 67.854C47.292 182.358 68.882 191.806 96.957 192h.113c24.96-.173 42.554-6.708 57.048-21.189 18.963-18.945 18.392-42.692 12.142-57.27-4.484-10.454-13.033-18.945-24.723-24.553ZM98.44 129.507c-10.44.588-21.286-4.098-21.82-14.135-.397-7.442 5.296-15.746 22.461-16.735 1.966-.114 3.895-.169 5.79-.169 6.235 0 12.068.606 17.371 1.765-1.978 24.702-13.58 28.713-23.802 29.274Z"})})},label:"Threads"},mastodon:{Icon:function(e){const t=(0,a.useId)();return(0,r.jsxs)("svg",{xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 61 65",width:"1em",height:"1em",...e,children:[(0,r.jsx)("path",{fill:`url(#${t})`,d:"M60.754 14.39C59.814 7.406 53.727 1.903 46.512.836 45.294.656 40.682 0 29.997 0h-.08C19.23 0 16.938.656 15.72.836 8.705 1.873 2.299 6.82.745 13.886c-.748 3.48-.828 7.338-.689 10.877.198 5.075.237 10.142.697 15.197a71.482 71.482 0 0 0 1.664 9.968c1.477 6.056 7.458 11.096 13.317 13.152a35.718 35.718 0 0 0 19.484 1.028 28.365 28.365 0 0 0 2.107-.576c1.572-.5 3.413-1.057 4.766-2.038a.154.154 0 0 0 .062-.118v-4.899a.146.146 0 0 0-.055-.111.145.145 0 0 0-.122-.028 54 54 0 0 1-12.644 1.478c-7.328 0-9.298-3.478-9.863-4.925a15.258 15.258 0 0 1-.857-3.882.142.142 0 0 1 .178-.145 52.976 52.976 0 0 0 12.437 1.477c1.007 0 2.012 0 3.02-.026 4.213-.119 8.654-.334 12.8-1.144.103-.02.206-.038.295-.065 6.539-1.255 12.762-5.196 13.394-15.176.024-.393.083-4.115.083-4.523.003-1.386.446-9.829-.065-15.017Z"}),(0,r.jsx)("path",{fill:"#fff",d:"M50.394 22.237v17.35H43.52V22.749c0-3.545-1.478-5.353-4.483-5.353-3.303 0-4.958 2.139-4.958 6.364v9.217h-6.835V23.76c0-4.225-1.657-6.364-4.96-6.364-2.988 0-4.48 1.808-4.48 5.353v16.84H10.93V22.237c0-3.545.905-6.362 2.715-8.45 1.868-2.082 4.317-3.152 7.358-3.152 3.519 0 6.178 1.354 7.951 4.057l1.711 2.871 1.714-2.871c1.773-2.704 4.432-4.056 7.945-4.056 3.038 0 5.487 1.069 7.36 3.152 1.81 2.085 2.712 4.902 2.71 8.449Z"}),(0,r.jsx)("defs",{children:(0,r.jsxs)("linearGradient",{id:t,x1:30.5,x2:30.5,y1:0,y2:65,gradientUnits:"userSpaceOnUse",children:[(0,r.jsx)("stop",{stopColor:"#6364FF"}),(0,r.jsx)("stop",{offset:1,stopColor:"#563ACC"})]})})]})},label:"Mastodon"},youtube:{Icon:function(e){return(0,r.jsxs)("svg",{viewBox:"0 0 256 180",width:"1em",height:"1em",xmlns:"http://www.w3.org/2000/svg",preserveAspectRatio:"xMidYMid",...e,children:[(0,r.jsx)("path",{d:"M250.346 28.075A32.18 32.18 0 0 0 227.69 5.418C207.824 0 127.87 0 127.87 0S47.912.164 28.046 5.582A32.18 32.18 0 0 0 5.39 28.24c-6.009 35.298-8.34 89.084.165 122.97a32.18 32.18 0 0 0 22.656 22.657c19.866 5.418 99.822 5.418 99.822 5.418s79.955 0 99.82-5.418a32.18 32.18 0 0 0 22.657-22.657c6.338-35.348 8.291-89.1-.164-123.134Z",fill:"red"}),(0,r.jsx)("path",{fill:"#FFF",d:"m102.421 128.06 66.328-38.418-66.328-38.418z"})]})},label:"YouTube"},twitch:{Icon:function(e){return(0,r.jsxs)("svg",{xmlns:"http://www.w3.org/2000/svg",x:0,y:0,viewBox:"0 0 2400 2800",width:"1em",height:"1em",...e,children:[(0,r.jsx)("path",{d:"m2200 1300-400 400h-400l-350 350v-350H600V200h1600z",fill:"#fff"}),(0,r.jsxs)("g",{children:[(0,r.jsx)("path",{d:"M500 0 0 500v1800h600v500l500-500h400l900-900V0H500zm1700 1300-400 400h-400l-350 350v-350H600V200h1600v1100z",fill:"#9146ff"}),(0,r.jsx)("path",{d:"M1700 550h200v600h-200zM1150 550h200v600h-200z",fill:"#9146ff"})]})]})},label:"Twitch"}};function g({platform:e,link:t}){const{Icon:s,label:a}=d[l=e]??{Icon:c,label:l};var l;return(0,r.jsx)(i.A,{className:u.authorSocialLink,href:t,title:a,children:(0,r.jsx)(s,{className:(0,n.A)(u.authorSocialLink)})})}function x({author:e}){const t=Object.entries(e.socials??{});return(0,r.jsx)("div",{className:u.authorSocials,children:t.map((([e,t])=>(0,r.jsx)(g,{platform:e,link:t},e)))})}var f=s(1107);const p={authorImage:"authorImage_XqGP","author-as-h1":"author-as-h1_n9oJ","author-as-h2":"author-as-h2_gXvM",authorDetails:"authorDetails_lV9A",authorName:"authorName_yefp",authorTitle:"authorTitle_nd0D",authorBlogPostCount:"authorBlogPostCount_iiJ5"};function v(e){return e.href?(0,r.jsx)(i.A,{...e}):(0,r.jsx)(r.Fragment,{children:e.children})}function w({title:e}){return(0,r.jsx)("small",{className:p.authorTitle,title:e,children:e})}function j({name:e,as:t}){return t?(0,r.jsx)(f.A,{as:t,className:p.authorName,children:e}):(0,r.jsx)("span",{className:p.authorName,children:e})}function b({count:e}){return(0,r.jsx)("span",{className:(0,n.A)(p.authorBlogPostCount),children:e})}function C({as:e,author:t,className:s,count:a}){const{name:i,title:l,url:o,imageURL:c,email:h,page:m}=t,u=m?.permalink||o||h&&`mailto:${h}`||void 0;return(0,r.jsxs)("div",{className:(0,n.A)("avatar margin-bottom--sm",s,p[`author-as-${e}`]),children:[c&&(0,r.jsx)(v,{href:u,className:"avatar__photo-link",children:(0,r.jsx)("img",{className:(0,n.A)("avatar__photo",p.authorImage),src:c,alt:i})}),(i||l)&&(0,r.jsxs)("div",{className:(0,n.A)("avatar__intro",p.authorDetails),children:[(0,r.jsxs)("div",{className:"avatar__name",children:[i&&(0,r.jsx)(v,{href:u,children:(0,r.jsx)(j,{name:i,as:e})}),void 0!==a&&(0,r.jsx)(b,{count:a})]}),!!l&&(0,r.jsx)(w,{title:l}),(0,r.jsx)(x,{author:t})]})]})}},8027:(e,t,s)=>{s.d(t,{A:()=>L});var a=s(6540),n=s(4164),i=s(1656),r=s(4581),l=s(1312),o=s(4096),c=s(6342),h=s(1107),m=s(4848);function u({year:e,yearGroupHeadingClassName:t,children:s}){return(0,m.jsxs)("div",{role:"group",children:[(0,m.jsx)(h.A,{as:"h3",className:t,children:e}),s]})}function d({items:e,yearGroupHeadingClassName:t,ListComponent:s}){if((0,c.p)().blog.sidebar.groupByYear){const a=(0,o.Ki)(e);return(0,m.jsx)(m.Fragment,{children:a.map((([e,a])=>(0,m.jsx)(u,{year:e,yearGroupHeadingClassName:t,children:(0,m.jsx)(s,{items:a})},e)))})}return(0,m.jsx)(s,{items:e})}const g=(0,a.memo)(d),x="sidebar_re4s",f="sidebarItemTitle_pO2u",p="sidebarItemList_Yudw",v="sidebarItem__DBe",w="sidebarItemLink_mo7H",j="sidebarItemLinkActive_I1ZP",b="yearGroupHeading_rMGB",C=({items:e})=>(0,m.jsx)(o.OU,{items:e,ulClassName:(0,n.A)(p,"clean-list"),liClassName:v,linkClassName:w,linkActiveClassName:j});function M({sidebar:e}){const t=(0,o.Gx)(e.items);return(0,m.jsx)("aside",{className:"col col--3",children:(0,m.jsxs)("nav",{className:(0,n.A)(x,"thin-scrollbar"),"aria-label":(0,l.T)({id:"theme.blog.sidebar.navAriaLabel",message:"Blog recent posts navigation",description:"The ARIA label for recent posts in the blog sidebar"}),children:[(0,m.jsx)("div",{className:(0,n.A)(f,"margin-bottom--md"),children:e.title}),(0,m.jsx)(g,{items:t,ListComponent:C,yearGroupHeadingClassName:b})]})})}const N=(0,a.memo)(M);var k=s(5600);const A="yearGroupHeading_QT03",_=({items:e})=>(0,m.jsx)(o.OU,{items:e,ulClassName:"menu__list",liClassName:"menu__list-item",linkClassName:"menu__link",linkActiveClassName:"menu__link--active"});function y({sidebar:e}){const t=(0,o.Gx)(e.items);return(0,m.jsx)(g,{items:t,ListComponent:_,yearGroupHeadingClassName:A})}function B(e){return(0,m.jsx)(k.GX,{component:y,props:e})}const I=(0,a.memo)(B);function P({sidebar:e}){const t=(0,r.l)();return e?.items.length?"mobile"===t?(0,m.jsx)(I,{sidebar:e}):(0,m.jsx)(N,{sidebar:e}):null}function L(e){const{sidebar:t,toc:s,children:a,...r}=e,l=t&&t.items.length>0;return(0,m.jsx)(i.A,{...r,children:(0,m.jsx)("div",{className:"container margin-vert--lg",children:(0,m.jsxs)("div",{className:"row",children:[(0,m.jsx)(P,{sidebar:t}),(0,m.jsx)("main",{className:(0,n.A)("col",{"col--7":l,"col--9 col--offset-1":!l}),children:a}),s&&(0,m.jsx)("div",{className:"col col--2",children:s})]})})})}}}]); \ No newline at end of file diff --git a/docs/assets/js/7518.9525ffbe.js b/docs/assets/js/7518.9525ffbe.js new file mode 100644 index 00000000..2702681c --- /dev/null +++ b/docs/assets/js/7518.9525ffbe.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[7518],{4096:(e,t,s)=>{s.d(t,{in:()=>c,OU:()=>A,Ki:()=>k,kJ:()=>f,x:()=>l,e7:()=>h,J_:()=>p,Gx:()=>N});var a=s(6540),n=s(9532),i=s(6803),r=s(4848);function l(){const e=(0,i.A)(),t=e?.data?.blogMetadata;if(!t)throw new Error("useBlogMetadata() can't be called on the current route because the blog metadata could not be found in route context");return t}const o=a.createContext(null);function c({children:e,content:t,isBlogPostPage:s=!1}){const n=function({content:e,isBlogPostPage:t}){return(0,a.useMemo)(()=>({metadata:e.metadata,frontMatter:e.frontMatter,assets:e.assets,toc:e.toc,isBlogPostPage:t}),[e,t])}({content:t,isBlogPostPage:s});return(0,r.jsx)(o.Provider,{value:n,children:e})}function h(){const e=(0,a.useContext)(o);if(null===e)throw new n.dV("BlogPostProvider");return e}var m=s(6025),u=s(4586);const d=e=>new Date(e).toISOString();function g(e){const t=e.map(v);return{author:1===t.length?t[0]:t}}function x(e,t,s){return e?{image:w({imageUrl:t(e,{absolute:!0}),caption:`title image for the blog post: ${s}`})}:{}}function f(e){const{siteConfig:t}=(0,u.A)(),{withBaseUrl:s}=(0,m.hH)(),{metadata:{blogDescription:a,blogTitle:n,permalink:i}}=e,r=`${t.url}${i}`;return{"@context":"https://schema.org","@type":"Blog","@id":r,mainEntityOfPage:r,headline:n,description:a,blogPost:e.items.map(e=>function(e,t,s){const{assets:a,frontMatter:n,metadata:i}=e,{date:r,title:l,description:o,lastUpdatedAt:c}=i,h=a.image??n.image,m=n.keywords??[],u=`${t.url}${i.permalink}`,f=c?d(c):void 0;return{"@type":"BlogPosting","@id":u,mainEntityOfPage:u,url:u,headline:l,name:l,description:o,datePublished:r,...f?{dateModified:f}:{},...g(i.authors),...x(h,s,l),...m?{keywords:m}:{}}}(e.content,t,s))}}function p(){const e=l(),{assets:t,metadata:s}=h(),{siteConfig:a}=(0,u.A)(),{withBaseUrl:n}=(0,m.hH)(),{date:i,title:r,description:o,frontMatter:c,lastUpdatedAt:f}=s,p=t.image??c.image,v=c.keywords??[],w=f?d(f):void 0,j=`${a.url}${s.permalink}`;return{"@context":"https://schema.org","@type":"BlogPosting","@id":j,mainEntityOfPage:j,url:j,headline:r,name:r,description:o,datePublished:i,...w?{dateModified:w}:{},...g(s.authors),...x(p,n,r),...v?{keywords:v}:{},isPartOf:{"@type":"Blog","@id":`${a.url}${e.blogBasePath}`,name:e.blogTitle}}}function v(e){return{"@type":"Person",...e.name?{name:e.name}:{},...e.title?{description:e.title}:{},...e.url?{url:e.url}:{},...e.email?{email:e.email}:{},...e.imageURL?{image:e.imageURL}:{}}}function w({imageUrl:e,caption:t}){return{"@type":"ImageObject","@id":e,url:e,contentUrl:e,caption:t}}var j=s(6347),b=s(8774),C=s(1682),M=s(9169);function N(e){const{pathname:t}=(0,j.zy)();return(0,a.useMemo)(()=>e.filter(e=>function(e,t){return!(e.unlisted&&!(0,M.ys)(e.permalink,t))}(e,t)),[e,t])}function k(e){const t=(0,C.$z)(e,e=>`${new Date(e.date).getFullYear()}`),s=Object.entries(t);return s.reverse(),s}function A({items:e,ulClassName:t,liClassName:s,linkClassName:a,linkActiveClassName:n}){return(0,r.jsx)("ul",{className:t,children:e.map(e=>(0,r.jsx)("li",{className:s,children:(0,r.jsx)(b.A,{isNavLink:!0,to:e.permalink,className:a,activeClassName:n,children:e.title})},e.permalink))})}},5846:(e,t,s)=>{s.d(t,{W:()=>c});var a=s(6540),n=s(4586);const i=["zero","one","two","few","many","other"];function r(e){return i.filter(t=>e.includes(t))}const l={locale:"en",pluralForms:r(["one","other"]),select:e=>1===e?"one":"other"};function o(){const{i18n:{currentLocale:e}}=(0,n.A)();return(0,a.useMemo)(()=>{try{return function(e){const t=new Intl.PluralRules(e);return{locale:e,pluralForms:r(t.resolvedOptions().pluralCategories),select:e=>t.select(e)}}(e)}catch(t){return console.error(`Failed to use Intl.PluralRules for locale "${e}".\nDocusaurus will fallback to the default (English) implementation.\nError: ${t.message}\n`),l}},[e])}function c(){const e=o();return{selectMessage:(t,s)=>function(e,t,s){const a=e.split("|");if(1===a.length)return a[0];a.length>s.pluralForms.length&&console.error(`For locale=${s.locale}, a maximum of ${s.pluralForms.length} plural forms are expected (${s.pluralForms.join(",")}), but the message contains ${a.length}: ${e}`);const n=s.select(t),i=s.pluralForms.indexOf(n);return a[Math.min(i,a.length-1)]}(s,t,e)}}},6382:(e,t,s)=>{s.d(t,{A:()=>C});var a=s(6540),n=s(4164),i=s(8774),r=s(4848);const l="githubSvg_Uu4N";const o="xSvg_y3PF";const c=function(e){return(0,r.jsxs)("svg",{xmlns:"http://www.w3.org/2000/svg",width:"1em",height:"1em",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",...e,children:[(0,r.jsx)("path",{stroke:"none",d:"M0 0h24v24H0z",fill:"none"}),(0,r.jsx)("path",{d:"M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0"}),(0,r.jsx)("path",{d:"M3.6 9h16.8"}),(0,r.jsx)("path",{d:"M3.6 15h16.8"}),(0,r.jsx)("path",{d:"M11.5 3a17 17 0 0 0 0 18"}),(0,r.jsx)("path",{d:"M12.5 3a17 17 0 0 1 0 18"})]})};const h="instagramSvg_YC40";const m="threadsSvg_PTXY";const u={authorSocials:"authorSocials_rSDt",authorSocialLink:"authorSocialLink_owbf",authorSocialIcon:"authorSocialIcon_XYv3"},d={twitter:{Icon:function(e){return(0,r.jsx)("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 256 209",width:"1em",height:"1em",preserveAspectRatio:"xMidYMid",...e,children:(0,r.jsx)("path",{d:"M256 25.45c-9.42 4.177-19.542 7-30.166 8.27 10.845-6.5 19.172-16.793 23.093-29.057a105.183 105.183 0 0 1-33.351 12.745C205.995 7.201 192.346.822 177.239.822c-29.006 0-52.523 23.516-52.523 52.52 0 4.117.465 8.125 1.36 11.97-43.65-2.191-82.35-23.1-108.255-54.876-4.52 7.757-7.11 16.78-7.11 26.404 0 18.222 9.273 34.297 23.365 43.716a52.312 52.312 0 0 1-23.79-6.57c-.003.22-.003.44-.003.661 0 25.447 18.104 46.675 42.13 51.5a52.592 52.592 0 0 1-23.718.9c6.683 20.866 26.08 36.05 49.062 36.475-17.975 14.086-40.622 22.483-65.228 22.483-4.24 0-8.42-.249-12.529-.734 23.243 14.902 50.85 23.597 80.51 23.597 96.607 0 149.434-80.031 149.434-149.435 0-2.278-.05-4.543-.152-6.795A106.748 106.748 0 0 0 256 25.45",fill:"#55acee"})})},label:"Twitter"},github:{Icon:function(e){return(0,r.jsx)("svg",{xmlns:"http://www.w3.org/2000/svg",width:"1em",height:"1em",viewBox:"0 0 256 250",preserveAspectRatio:"xMidYMid",style:{"--dark":"#000","--light":"#fff"},...e,className:(0,n.A)(e.className,l),children:(0,r.jsx)("path",{d:"M128.001 0C57.317 0 0 57.307 0 128.001c0 56.554 36.676 104.535 87.535 121.46 6.397 1.185 8.746-2.777 8.746-6.158 0-3.052-.12-13.135-.174-23.83-35.61 7.742-43.124-15.103-43.124-15.103-5.823-14.795-14.213-18.73-14.213-18.73-11.613-7.944.876-7.78.876-7.78 12.853.902 19.621 13.19 19.621 13.19 11.417 19.568 29.945 13.911 37.249 10.64 1.149-8.272 4.466-13.92 8.127-17.116-28.431-3.236-58.318-14.212-58.318-63.258 0-13.975 5-25.394 13.188-34.358-1.329-3.224-5.71-16.242 1.24-33.874 0 0 10.749-3.44 35.21 13.121 10.21-2.836 21.16-4.258 32.038-4.307 10.878.049 21.837 1.47 32.066 4.307 24.431-16.56 35.165-13.12 35.165-13.12 6.967 17.63 2.584 30.65 1.255 33.873 8.207 8.964 13.173 20.383 13.173 34.358 0 49.163-29.944 59.988-58.447 63.157 4.591 3.972 8.682 11.762 8.682 23.704 0 17.126-.148 30.91-.148 35.126 0 3.407 2.304 7.398 8.792 6.14C219.37 232.5 256 184.537 256 128.002 256 57.307 198.691 0 128.001 0Zm-80.06 182.34c-.282.636-1.283.827-2.194.39-.929-.417-1.45-1.284-1.15-1.922.276-.655 1.279-.838 2.205-.399.93.418 1.46 1.293 1.139 1.931Zm6.296 5.618c-.61.566-1.804.303-2.614-.591-.837-.892-.994-2.086-.375-2.66.63-.566 1.787-.301 2.626.591.838.903 1 2.088.363 2.66Zm4.32 7.188c-.785.545-2.067.034-2.86-1.104-.784-1.138-.784-2.503.017-3.05.795-.547 2.058-.055 2.861 1.075.782 1.157.782 2.522-.019 3.08Zm7.304 8.325c-.701.774-2.196.566-3.29-.49-1.119-1.032-1.43-2.496-.726-3.27.71-.776 2.213-.558 3.315.49 1.11 1.03 1.45 2.505.701 3.27Zm9.442 2.81c-.31 1.003-1.75 1.459-3.199 1.033-1.448-.439-2.395-1.613-2.103-2.626.301-1.01 1.747-1.484 3.207-1.028 1.446.436 2.396 1.602 2.095 2.622Zm10.744 1.193c.036 1.055-1.193 1.93-2.715 1.95-1.53.034-2.769-.82-2.786-1.86 0-1.065 1.202-1.932 2.733-1.958 1.522-.03 2.768.818 2.768 1.868Zm10.555-.405c.182 1.03-.875 2.088-2.387 2.37-1.485.271-2.861-.365-3.05-1.386-.184-1.056.893-2.114 2.376-2.387 1.514-.263 2.868.356 3.061 1.403Z"})})},label:"GitHub"},stackoverflow:{Icon:function(e){return(0,r.jsxs)("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 169.61 200",width:"1em",height:"1em",...e,children:[(0,r.jsx)("path",{d:"M140.44 178.38v-48.65h21.61V200H0v-70.27h21.61v48.65z",fill:"#bcbbbb"}),(0,r.jsx)("path",{d:"M124.24 140.54l4.32-16.22-86.97-17.83-3.78 17.83zM49.7 82.16L130.72 120l7.56-16.22-81.02-37.83zm22.68-40l68.06 57.3 11.35-13.51-68.6-57.3-11.35 13.51zM116.14 0l-14.59 10.81 53.48 71.89 14.58-10.81zM37.81 162.16h86.43v-16.21H37.81z",fill:"#f48024"})]})},label:"Stack Overflow"},linkedin:{Icon:function(e){return(0,r.jsx)("svg",{xmlns:"http://www.w3.org/2000/svg",width:"1em",height:"1em",preserveAspectRatio:"xMidYMid",viewBox:"0 0 256 256",...e,children:(0,r.jsx)("path",{d:"M218.123 218.127h-37.931v-59.403c0-14.165-.253-32.4-19.728-32.4-19.756 0-22.779 15.434-22.779 31.369v60.43h-37.93V95.967h36.413v16.694h.51a39.907 39.907 0 0 1 35.928-19.733c38.445 0 45.533 25.288 45.533 58.186l-.016 67.013ZM56.955 79.27c-12.157.002-22.014-9.852-22.016-22.009-.002-12.157 9.851-22.014 22.008-22.016 12.157-.003 22.014 9.851 22.016 22.008A22.013 22.013 0 0 1 56.955 79.27m18.966 138.858H37.95V95.967h37.97v122.16ZM237.033.018H18.89C8.58-.098.125 8.161-.001 18.471v219.053c.122 10.315 8.576 18.582 18.89 18.474h218.144c10.336.128 18.823-8.139 18.966-18.474V18.454c-.147-10.33-8.635-18.588-18.966-18.453",fill:"#0A66C2"})})},label:"LinkedIn"},x:{Icon:function(e){return(0,r.jsx)("svg",{xmlns:"http://www.w3.org/2000/svg",width:"1em",height:"1em",fill:"none",viewBox:"0 0 1200 1227",style:{"--dark":"#000","--light":"#fff"},...e,className:(0,n.A)(e.className,o),children:(0,r.jsx)("path",{d:"M714.163 519.284 1160.89 0h-105.86L667.137 450.887 357.328 0H0l468.492 681.821L0 1226.37h105.866l409.625-476.152 327.181 476.152H1200L714.137 519.284h.026ZM569.165 687.828l-47.468-67.894-377.686-540.24h162.604l304.797 435.991 47.468 67.894 396.2 566.721H892.476L569.165 687.854v-.026Z"})})},label:"X"},bluesky:{Icon:function(e){return(0,r.jsx)("svg",{xmlns:"http://www.w3.org/2000/svg",width:"1em",height:"1em",preserveAspectRatio:"xMidYMid",viewBox:"0 0 256 226",...e,children:(0,r.jsx)("path",{fill:"#1185FE",d:"M55.491 15.172c29.35 22.035 60.917 66.712 72.509 90.686 11.592-23.974 43.159-68.651 72.509-90.686C221.686-.727 256-13.028 256 26.116c0 7.818-4.482 65.674-7.111 75.068-9.138 32.654-42.436 40.983-72.057 35.942 51.775 8.812 64.946 38 36.501 67.187-54.021 55.433-77.644-13.908-83.696-31.676-1.11-3.257-1.63-4.78-1.637-3.485-.008-1.296-.527.228-1.637 3.485-6.052 17.768-29.675 87.11-83.696 31.676-28.445-29.187-15.274-58.375 36.5-67.187-29.62 5.041-62.918-3.288-72.056-35.942C4.482 91.79 0 33.934 0 26.116 0-13.028 34.314-.727 55.491 15.172Z"})})},label:"Bluesky"},instagram:{Icon:function(e){return(0,r.jsx)("svg",{xmlns:"http://www.w3.org/2000/svg",width:"1em",height:"1em",preserveAspectRatio:"xMidYMid",viewBox:"0 0 256 256",style:{"--dark":"#000","--light":"#fff"},...e,className:(0,n.A)(e.className,h),children:(0,r.jsx)("path",{d:"M128 23.064c34.177 0 38.225.13 51.722.745 12.48.57 19.258 2.655 23.769 4.408 5.974 2.322 10.238 5.096 14.717 9.575 4.48 4.479 7.253 8.743 9.575 14.717 1.753 4.511 3.838 11.289 4.408 23.768.615 13.498.745 17.546.745 51.723 0 34.178-.13 38.226-.745 51.723-.57 12.48-2.655 19.257-4.408 23.768-2.322 5.974-5.096 10.239-9.575 14.718-4.479 4.479-8.743 7.253-14.717 9.574-4.511 1.753-11.289 3.839-23.769 4.408-13.495.616-17.543.746-51.722.746-34.18 0-38.228-.13-51.723-.746-12.48-.57-19.257-2.655-23.768-4.408-5.974-2.321-10.239-5.095-14.718-9.574-4.479-4.48-7.253-8.744-9.574-14.718-1.753-4.51-3.839-11.288-4.408-23.768-.616-13.497-.746-17.545-.746-51.723 0-34.177.13-38.225.746-51.722.57-12.48 2.655-19.258 4.408-23.769 2.321-5.974 5.095-10.238 9.574-14.717 4.48-4.48 8.744-7.253 14.718-9.575 4.51-1.753 11.288-3.838 23.768-4.408 13.497-.615 17.545-.745 51.723-.745M128 0C93.237 0 88.878.147 75.226.77c-13.625.622-22.93 2.786-31.071 5.95-8.418 3.271-15.556 7.648-22.672 14.764C14.367 28.6 9.991 35.738 6.72 44.155 3.555 52.297 1.392 61.602.77 75.226.147 88.878 0 93.237 0 128c0 34.763.147 39.122.77 52.774.622 13.625 2.785 22.93 5.95 31.071 3.27 8.417 7.647 15.556 14.763 22.672 7.116 7.116 14.254 11.492 22.672 14.763 8.142 3.165 17.446 5.328 31.07 5.95 13.653.623 18.012.77 52.775.77s39.122-.147 52.774-.77c13.624-.622 22.929-2.785 31.07-5.95 8.418-3.27 15.556-7.647 22.672-14.763 7.116-7.116 11.493-14.254 14.764-22.672 3.164-8.142 5.328-17.446 5.95-31.07.623-13.653.77-18.012.77-52.775s-.147-39.122-.77-52.774c-.622-13.624-2.786-22.929-5.95-31.07-3.271-8.418-7.648-15.556-14.764-22.672C227.4 14.368 220.262 9.99 211.845 6.72c-8.142-3.164-17.447-5.328-31.071-5.95C167.122.147 162.763 0 128 0Zm0 62.27C91.698 62.27 62.27 91.7 62.27 128c0 36.302 29.428 65.73 65.73 65.73 36.301 0 65.73-29.428 65.73-65.73 0-36.301-29.429-65.73-65.73-65.73Zm0 108.397c-23.564 0-42.667-19.103-42.667-42.667S104.436 85.333 128 85.333s42.667 19.103 42.667 42.667-19.103 42.667-42.667 42.667Zm83.686-110.994c0 8.484-6.876 15.36-15.36 15.36-8.483 0-15.36-6.876-15.36-15.36 0-8.483 6.877-15.36 15.36-15.36 8.484 0 15.36 6.877 15.36 15.36Z"})})},label:"Instagram"},threads:{Icon:function(e){return(0,r.jsx)("svg",{xmlns:"http://www.w3.org/2000/svg","aria-label":"Threads",viewBox:"0 0 192 192",width:"1em",fill:"none",height:"1em",style:{"--dark":"#000","--light":"#fff"},...e,className:(0,n.A)(e.className,m),children:(0,r.jsx)("path",{d:"M141.537 88.988a66.667 66.667 0 0 0-2.518-1.143c-1.482-27.307-16.403-42.94-41.457-43.1h-.34c-14.986 0-27.449 6.396-35.12 18.036l13.779 9.452c5.73-8.695 14.724-10.548 21.348-10.548h.229c8.249.053 14.474 2.452 18.503 7.129 2.932 3.405 4.893 8.111 5.864 14.05-7.314-1.243-15.224-1.626-23.68-1.14-23.82 1.371-39.134 15.264-38.105 34.568.522 9.792 5.4 18.216 13.735 23.719 7.047 4.652 16.124 6.927 25.557 6.412 12.458-.683 22.231-5.436 29.049-14.127 5.178-6.6 8.453-15.153 9.899-25.93 5.937 3.583 10.337 8.298 12.767 13.966 4.132 9.635 4.373 25.468-8.546 38.376-11.319 11.308-24.925 16.2-45.488 16.351-22.809-.169-40.06-7.484-51.275-21.742C35.236 139.966 29.808 120.682 29.605 96c.203-24.682 5.63-43.966 16.133-57.317C56.954 24.425 74.204 17.11 97.013 16.94c22.975.17 40.526 7.52 52.171 21.847 5.71 7.026 10.015 15.86 12.853 26.162l16.147-4.308c-3.44-12.68-8.853-23.606-16.219-32.668C147.036 9.607 125.202.195 97.07 0h-.113C68.882.194 47.292 9.642 32.788 28.08 19.882 44.485 13.224 67.315 13.001 95.932L13 96v.067c.224 28.617 6.882 51.447 19.788 67.854C47.292 182.358 68.882 191.806 96.957 192h.113c24.96-.173 42.554-6.708 57.048-21.189 18.963-18.945 18.392-42.692 12.142-57.27-4.484-10.454-13.033-18.945-24.723-24.553ZM98.44 129.507c-10.44.588-21.286-4.098-21.82-14.135-.397-7.442 5.296-15.746 22.461-16.735 1.966-.114 3.895-.169 5.79-.169 6.235 0 12.068.606 17.371 1.765-1.978 24.702-13.58 28.713-23.802 29.274Z"})})},label:"Threads"},mastodon:{Icon:function(e){const t=(0,a.useId)();return(0,r.jsxs)("svg",{xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 61 65",width:"1em",height:"1em",...e,children:[(0,r.jsx)("path",{fill:`url(#${t})`,d:"M60.754 14.39C59.814 7.406 53.727 1.903 46.512.836 45.294.656 40.682 0 29.997 0h-.08C19.23 0 16.938.656 15.72.836 8.705 1.873 2.299 6.82.745 13.886c-.748 3.48-.828 7.338-.689 10.877.198 5.075.237 10.142.697 15.197a71.482 71.482 0 0 0 1.664 9.968c1.477 6.056 7.458 11.096 13.317 13.152a35.718 35.718 0 0 0 19.484 1.028 28.365 28.365 0 0 0 2.107-.576c1.572-.5 3.413-1.057 4.766-2.038a.154.154 0 0 0 .062-.118v-4.899a.146.146 0 0 0-.055-.111.145.145 0 0 0-.122-.028 54 54 0 0 1-12.644 1.478c-7.328 0-9.298-3.478-9.863-4.925a15.258 15.258 0 0 1-.857-3.882.142.142 0 0 1 .178-.145 52.976 52.976 0 0 0 12.437 1.477c1.007 0 2.012 0 3.02-.026 4.213-.119 8.654-.334 12.8-1.144.103-.02.206-.038.295-.065 6.539-1.255 12.762-5.196 13.394-15.176.024-.393.083-4.115.083-4.523.003-1.386.446-9.829-.065-15.017Z"}),(0,r.jsx)("path",{fill:"#fff",d:"M50.394 22.237v17.35H43.52V22.749c0-3.545-1.478-5.353-4.483-5.353-3.303 0-4.958 2.139-4.958 6.364v9.217h-6.835V23.76c0-4.225-1.657-6.364-4.96-6.364-2.988 0-4.48 1.808-4.48 5.353v16.84H10.93V22.237c0-3.545.905-6.362 2.715-8.45 1.868-2.082 4.317-3.152 7.358-3.152 3.519 0 6.178 1.354 7.951 4.057l1.711 2.871 1.714-2.871c1.773-2.704 4.432-4.056 7.945-4.056 3.038 0 5.487 1.069 7.36 3.152 1.81 2.085 2.712 4.902 2.71 8.449Z"}),(0,r.jsx)("defs",{children:(0,r.jsxs)("linearGradient",{id:t,x1:30.5,x2:30.5,y1:0,y2:65,gradientUnits:"userSpaceOnUse",children:[(0,r.jsx)("stop",{stopColor:"#6364FF"}),(0,r.jsx)("stop",{offset:1,stopColor:"#563ACC"})]})})]})},label:"Mastodon"},youtube:{Icon:function(e){return(0,r.jsxs)("svg",{viewBox:"0 0 256 180",width:"1em",height:"1em",xmlns:"http://www.w3.org/2000/svg",preserveAspectRatio:"xMidYMid",...e,children:[(0,r.jsx)("path",{d:"M250.346 28.075A32.18 32.18 0 0 0 227.69 5.418C207.824 0 127.87 0 127.87 0S47.912.164 28.046 5.582A32.18 32.18 0 0 0 5.39 28.24c-6.009 35.298-8.34 89.084.165 122.97a32.18 32.18 0 0 0 22.656 22.657c19.866 5.418 99.822 5.418 99.822 5.418s79.955 0 99.82-5.418a32.18 32.18 0 0 0 22.657-22.657c6.338-35.348 8.291-89.1-.164-123.134Z",fill:"red"}),(0,r.jsx)("path",{fill:"#FFF",d:"m102.421 128.06 66.328-38.418-66.328-38.418z"})]})},label:"YouTube"},twitch:{Icon:function(e){return(0,r.jsxs)("svg",{xmlns:"http://www.w3.org/2000/svg",x:0,y:0,viewBox:"0 0 2400 2800",width:"1em",height:"1em",...e,children:[(0,r.jsx)("path",{d:"m2200 1300-400 400h-400l-350 350v-350H600V200h1600z",fill:"#fff"}),(0,r.jsxs)("g",{children:[(0,r.jsx)("path",{d:"M500 0 0 500v1800h600v500l500-500h400l900-900V0H500zm1700 1300-400 400h-400l-350 350v-350H600V200h1600v1100z",fill:"#9146ff"}),(0,r.jsx)("path",{d:"M1700 550h200v600h-200zM1150 550h200v600h-200z",fill:"#9146ff"})]})]})},label:"Twitch"}};function g({platform:e,link:t}){const{Icon:s,label:a}=d[l=e]??{Icon:c,label:l};var l;return(0,r.jsx)(i.A,{className:u.authorSocialLink,href:t,title:a,children:(0,r.jsx)(s,{className:(0,n.A)(u.authorSocialLink)})})}function x({author:e}){const t=Object.entries(e.socials??{});return(0,r.jsx)("div",{className:u.authorSocials,children:t.map(([e,t])=>(0,r.jsx)(g,{platform:e,link:t},e))})}var f=s(1107);const p={authorImage:"authorImage_XqGP","author-as-h1":"author-as-h1_n9oJ","author-as-h2":"author-as-h2_gXvM",authorDetails:"authorDetails_lV9A",authorName:"authorName_yefp",authorTitle:"authorTitle_nd0D",authorBlogPostCount:"authorBlogPostCount_iiJ5"};function v(e){return e.href?(0,r.jsx)(i.A,{...e}):(0,r.jsx)(r.Fragment,{children:e.children})}function w({title:e}){return(0,r.jsx)("small",{className:p.authorTitle,title:e,children:e})}function j({name:e,as:t}){return t?(0,r.jsx)(f.A,{as:t,className:p.authorName,children:e}):(0,r.jsx)("span",{className:p.authorName,children:e})}function b({count:e}){return(0,r.jsx)("span",{className:(0,n.A)(p.authorBlogPostCount),children:e})}function C({as:e,author:t,className:s,count:a}){const{name:i,title:l,url:o,imageURL:c,email:h,page:m}=t,u=m?.permalink||o||h&&`mailto:${h}`||void 0;return(0,r.jsxs)("div",{className:(0,n.A)("avatar margin-bottom--sm",s,p[`author-as-${e}`]),children:[c&&(0,r.jsx)(v,{href:u,className:"avatar__photo-link",children:(0,r.jsx)("img",{className:(0,n.A)("avatar__photo",p.authorImage),src:c,alt:i})}),(i||l)&&(0,r.jsxs)("div",{className:(0,n.A)("avatar__intro",p.authorDetails),children:[(0,r.jsxs)("div",{className:"avatar__name",children:[i&&(0,r.jsx)(v,{href:u,children:(0,r.jsx)(j,{name:i,as:e})}),void 0!==a&&(0,r.jsx)(b,{count:a})]}),!!l&&(0,r.jsx)(w,{title:l}),(0,r.jsx)(x,{author:t})]})]})}},8027:(e,t,s)=>{s.d(t,{A:()=>L});var a=s(6540),n=s(4164),i=s(1656),r=s(4581),l=s(1312),o=s(4096),c=s(6342),h=s(1107),m=s(4848);function u({year:e,yearGroupHeadingClassName:t,children:s}){return(0,m.jsxs)("div",{role:"group",children:[(0,m.jsx)(h.A,{as:"h3",className:t,children:e}),s]})}function d({items:e,yearGroupHeadingClassName:t,ListComponent:s}){if((0,c.p)().blog.sidebar.groupByYear){const a=(0,o.Ki)(e);return(0,m.jsx)(m.Fragment,{children:a.map(([e,a])=>(0,m.jsx)(u,{year:e,yearGroupHeadingClassName:t,children:(0,m.jsx)(s,{items:a})},e))})}return(0,m.jsx)(s,{items:e})}const g=(0,a.memo)(d),x="sidebar_re4s",f="sidebarItemTitle_pO2u",p="sidebarItemList_Yudw",v="sidebarItem__DBe",w="sidebarItemLink_mo7H",j="sidebarItemLinkActive_I1ZP",b="yearGroupHeading_rMGB",C=({items:e})=>(0,m.jsx)(o.OU,{items:e,ulClassName:(0,n.A)(p,"clean-list"),liClassName:v,linkClassName:w,linkActiveClassName:j});function M({sidebar:e}){const t=(0,o.Gx)(e.items);return(0,m.jsx)("aside",{className:"col col--3",children:(0,m.jsxs)("nav",{className:(0,n.A)(x,"thin-scrollbar"),"aria-label":(0,l.T)({id:"theme.blog.sidebar.navAriaLabel",message:"Blog recent posts navigation",description:"The ARIA label for recent posts in the blog sidebar"}),children:[(0,m.jsx)("div",{className:(0,n.A)(f,"margin-bottom--md"),children:e.title}),(0,m.jsx)(g,{items:t,ListComponent:C,yearGroupHeadingClassName:b})]})})}const N=(0,a.memo)(M);var k=s(5600);const A="yearGroupHeading_QT03",_=({items:e})=>(0,m.jsx)(o.OU,{items:e,ulClassName:"menu__list",liClassName:"menu__list-item",linkClassName:"menu__link",linkActiveClassName:"menu__link--active"});function y({sidebar:e}){const t=(0,o.Gx)(e.items);return(0,m.jsx)(g,{items:t,ListComponent:_,yearGroupHeadingClassName:A})}function B(e){return(0,m.jsx)(k.GX,{component:y,props:e})}const I=(0,a.memo)(B);function P({sidebar:e}){const t=(0,r.l)();return e?.items.length?"mobile"===t?(0,m.jsx)(I,{sidebar:e}):(0,m.jsx)(N,{sidebar:e}):null}function L(e){const{sidebar:t,toc:s,children:a,...r}=e,l=t&&t.items.length>0;return(0,m.jsx)(i.A,{...r,children:(0,m.jsx)("div",{className:"container margin-vert--lg",children:(0,m.jsxs)("div",{className:"row",children:[(0,m.jsx)(P,{sidebar:t}),(0,m.jsx)("main",{className:(0,n.A)("col",{"col--7":l,"col--9 col--offset-1":!l}),children:a}),s&&(0,m.jsx)("div",{className:"col col--2",children:s})]})})})}}}]); \ No newline at end of file diff --git a/docs/assets/js/769c1945.d427d253.js b/docs/assets/js/769c1945.d427d253.js new file mode 100644 index 00000000..199f08b2 --- /dev/null +++ b/docs/assets/js/769c1945.d427d253.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[6267],{1822:(e,t,i)=>{i.d(t,{A:()=>n});const n=i.p+"assets/images/bms-7399e8796d2cd24617c432518ce3f312.png"},2437:e=>{e.exports=JSON.parse('{"permalink":"/BharatMLStack/blog/llm-inference-optimization-sub-sec-latency","editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/blog/bharatmlstack-history/llm-inference-optimization/index.md","source":"@site/blog/bharatmlstack-history/llm-inference-optimization/index.md","title":"LLM Inference Optimization Techniques: Engineering Sub-Second Latency at Scale","description":"A practical guide to the optimization techniques behind sub-second LLM inference\u2014covering paged KV caching, INT4 AWQ and FP8 quantization, kernel fusion, inflight batching, parallelism strategies, and speculative decoding, with production benchmarks on L4 and A100 GPUs.","date":"2025-06-02T00:00:00.000Z","tags":[{"inline":true,"label":"llm","permalink":"/BharatMLStack/blog/tags/llm"},{"inline":true,"label":"vllm","permalink":"/BharatMLStack/blog/tags/vllm"},{"inline":true,"label":"tensorrt-llm","permalink":"/BharatMLStack/blog/tags/tensorrt-llm"},{"inline":true,"label":"mlplatform","permalink":"/BharatMLStack/blog/tags/mlplatform"},{"inline":true,"label":"meesho","permalink":"/BharatMLStack/blog/tags/meesho"},{"inline":true,"label":"bharatmlstack","permalink":"/BharatMLStack/blog/tags/bharatmlstack"}],"readingTime":4.88,"hasTruncateMarker":false,"authors":[{"name":"Jaya Kumar","title":"Lead ML Engineer @ Meesho","url":"https://github.com/jayakommuru","imageURL":"https://github.com/jayakommuru.png","key":"jaya","page":null}],"frontMatter":{"title":"LLM Inference Optimization Techniques: Engineering Sub-Second Latency at Scale","description":"A practical guide to the optimization techniques behind sub-second LLM inference\u2014covering paged KV caching, INT4 AWQ and FP8 quantization, kernel fusion, inflight batching, parallelism strategies, and speculative decoding, with production benchmarks on L4 and A100 GPUs.","authors":["jaya"],"slug":"llm-inference-optimization-sub-sec-latency","date":"2025-6-2","tags":["llm","vllm","tensorrt-llm","mlplatform","meesho","bharatmlstack"]},"unlisted":false,"prevItem":{"title":"Beyond Vector RAG: Building Agent Memory That Learns From Experience.","permalink":"/BharatMLStack/blog/episodic-memory-for-agents"},"nextItem":{"title":"Designing a Production-Grade LLM Inference Platform: From Model Weights to Scalable GPU Serving","permalink":"/BharatMLStack/blog/multi-engine-llm-inferencing-platform"}}')},7879:(e,t,i)=>{i.r(t),i.d(t,{assets:()=>a,contentTitle:()=>d,default:()=>o,frontMatter:()=>r,metadata:()=>n,toc:()=>c});var n=i(2437),s=i(4848),l=i(8453);const r={title:"LLM Inference Optimization Techniques: Engineering Sub-Second Latency at Scale",description:"A practical guide to the optimization techniques behind sub-second LLM inference\u2014covering paged KV caching, INT4 AWQ and FP8 quantization, kernel fusion, inflight batching, parallelism strategies, and speculative decoding, with production benchmarks on L4 and A100 GPUs.",authors:["jaya"],slug:"llm-inference-optimization-sub-sec-latency",date:"2025-6-2",tags:["llm","vllm","tensorrt-llm","mlplatform","meesho","bharatmlstack"]},d=void 0,a={authorsImageUrls:[void 0]},c=[{value:"1. Advanced Memory Management: Paged & Prefix KV Caching",id:"1-advanced-memory-management-paged--prefix-kv-caching",level:2},{value:"Paged KV caching",id:"paged-kv-caching",level:3},{value:"KV cache quantization",id:"kv-cache-quantization",level:3},{value:"Prefix caching (the "voice bot" optimizer)",id:"prefix-caching-the-voice-bot-optimizer",level:3},{value:"2. Aggressive Quantization (INT4 AWQ & FP8)",id:"2-aggressive-quantization-int4-awq--fp8",level:2},{value:"INT4 AWQ (Activation-aware Weight Quantization)",id:"int4-awq-activation-aware-weight-quantization",level:3},{value:"FP8 precision",id:"fp8-precision",level:3},{value:"3. Kernel Fusion & Custom Plugins",id:"3-kernel-fusion--custom-plugins",level:2},{value:"4. Inflight (Continuous) Batching",id:"4-inflight-continuous-batching",level:2},{value:"5. Parallelism Strategies: Scaling Beyond One GPU",id:"5-parallelism-strategies-scaling-beyond-one-gpu",level:2},{value:"6. Speculative Decoding",id:"6-speculative-decoding",level:2},{value:"Few Benchmarks",id:"few-benchmarks",level:2},{value:"Search query rewriting",id:"search-query-rewriting",level:3},{value:"Voice bot query",id:"voice-bot-query",level:3},{value:"Conclusion",id:"conclusion",level:2}];function h(e){const t={h2:"h2",h3:"h3",img:"img",li:"li",p:"p",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,l.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.img,{alt:"BharatMLStack",src:i(1822).A+"",width:"1396",height:"460"}),"\nRaw execution of Large Language Models is inherently expensive and memory-intensive. To achieve sub-second latency and high throughput, we implement a multi-layered optimization strategy that targets the entire inference stack\u2014from memory management to kernel execution."]}),"\n",(0,s.jsx)(t.h2,{id:"1-advanced-memory-management-paged--prefix-kv-caching",children:"1. Advanced Memory Management: Paged & Prefix KV Caching"}),"\n",(0,s.jsx)(t.p,{children:"The most significant bottleneck in LLM inference is not always compute, but memory bandwidth\u2014specifically managing the Key-Value (KV) cache."}),"\n",(0,s.jsx)(t.h3,{id:"paged-kv-caching",children:"Paged KV caching"}),"\n",(0,s.jsxs)(t.p,{children:["Standard caching suffers from fragmentation. We use ",(0,s.jsx)(t.strong,{children:"Paged KV caching"}),", which operates similarly to an operating system's virtual memory: the KV cache is divided into non-contiguous blocks. This lets us serve larger batch sizes without running out of memory."]}),"\n",(0,s.jsx)(t.h3,{id:"kv-cache-quantization",children:"KV cache quantization"}),"\n",(0,s.jsxs)(t.p,{children:["To further maximize available memory, we implement ",(0,s.jsx)(t.strong,{children:"KV cache quantization"})," (e.g., FP8). By compressing stored attention keys and values from 16-bit to 8-bit, we nearly double the effective context window capacity of the GPU, allowing longer conversations or larger batches without materially degrading quality."]}),"\n",(0,s.jsx)(t.h3,{id:"prefix-caching-the-voice-bot-optimizer",children:'Prefix caching (the "voice bot" optimizer)'}),"\n",(0,s.jsxs)(t.p,{children:['For use cases like GenAI voice bots where the system prompt (e.g., "You are a helpful assistant...") is static across thousands of requests, we enable ',(0,s.jsx)(t.strong,{children:"prefix caching"}),"."]}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"Impact"}),": By reusing pre-computed KV states for common prefixes, we achieve a cache hit rate of ~90%. This reduces ",(0,s.jsx)(t.strong,{children:"Time To First Token (TTFT)"})," by skipping redundant computation of the system prompt."]}),"\n"]}),"\n",(0,s.jsx)(t.h2,{id:"2-aggressive-quantization-int4-awq--fp8",children:"2. Aggressive Quantization (INT4 AWQ & FP8)"}),"\n",(0,s.jsx)(t.p,{children:"Running models in their native 16-bit precision (BF16) restricts maximum batch size and throughput. We use quantization to shrink model weights without sacrificing accuracy."}),"\n",(0,s.jsx)(t.h3,{id:"int4-awq-activation-aware-weight-quantization",children:"INT4 AWQ (Activation-aware Weight Quantization)"}),"\n",(0,s.jsxs)(t.p,{children:["For the Llama 3 family, we use ",(0,s.jsx)(t.strong,{children:"AWQ"})," to compress weights to 4 bits. This reduces model size by ~75%, allowing larger models to fit into L4 GPU memory and significantly improving token generation speed."]}),"\n",(0,s.jsx)(t.h3,{id:"fp8-precision",children:"FP8 precision"}),"\n",(0,s.jsxs)(t.p,{children:["For NVIDIA Hopper (H100) architectures, we are exploring ",(0,s.jsx)(t.strong,{children:"FP8 quantization"}),", leveraging native FP8 tensor cores to accelerate matrix multiplications while maintaining a higher dynamic range than integer quantization."]}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"Verification"}),": We validate quantized models by comparing dot-product similarity of embeddings against the FP16 baseline, consistently achieving ",(0,s.jsx)(t.strong,{children:">99% similarity"}),"."]}),"\n"]}),"\n",(0,s.jsx)(t.h2,{id:"3-kernel-fusion--custom-plugins",children:"3. Kernel Fusion & Custom Plugins"}),"\n",(0,s.jsx)(t.p,{children:"To minimize overhead from launching thousands of small GPU operations, we fuse them into monolithic kernels using NVIDIA TensorRT plugins."}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"Flash attention & FMHA"}),": We enable ",(0,s.jsx)(t.strong,{children:"Fused Multi-Head Attention (FMHA)"})," combined with flash attention to reduce memory reads/writes."]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"GEMM plugins"}),": We use specialized ",(0,s.jsx)(t.strong,{children:"GEMM"})," plugins to accelerate transformer linear layers."]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"Removing input padding"}),": Instead of padding short sequences to match the longest, we remove input padding so the GPU processes only valid tokens."]}),"\n"]}),"\n",(0,s.jsx)(t.h2,{id:"4-inflight-continuous-batching",children:"4. Inflight (Continuous) Batching"}),"\n",(0,s.jsx)(t.p,{children:"Traditional static batching waits for all requests in a batch to finish before returning results\u2014so one long response delays everyone else."}),"\n",(0,s.jsxs)(t.p,{children:["We implement ",(0,s.jsx)(t.strong,{children:"inflight batching"}),": as soon as one request completes, its slot is freed and filled by a new request from the queue. This keeps GPUs saturated and decouples latency of short queries from long ones."]}),"\n",(0,s.jsx)(t.h2,{id:"5-parallelism-strategies-scaling-beyond-one-gpu",children:"5. Parallelism Strategies: Scaling Beyond One GPU"}),"\n",(0,s.jsx)(t.p,{children:"For large models (e.g., 70B+ parameters) that cannot fit into the VRAM of a single GPU, we use parallelism strategies."}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"Tensor parallelism (TP)"}),": Split weight matrices across multiple GPUs (e.g., 4\xd7 L4 or 8\xd7 A100). Each GPU computes a shard and outputs are reduced at every layer."]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"Pipeline parallelism (PP)"}),": Split model layers across GPUs to pipeline compute (e.g., while one GPU computes later layers for Request A, another starts early layers for Request B)."]}),"\n"]}),"\n",(0,s.jsx)(t.h2,{id:"6-speculative-decoding",children:"6. Speculative Decoding"}),"\n",(0,s.jsxs)(t.p,{children:["To reduce inter-token latency (ITL), we explore ",(0,s.jsx)(t.strong,{children:"speculative decoding"}),"."]}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"Mechanism"}),': A smaller, faster "draft" model speculatively generates a short token sequence (e.g., 5 tokens).']}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"Verification"}),": The larger target model verifies those tokens in one parallel forward pass. If correct, we effectively generate multiple tokens per large-model step; if not, we discard and regenerate. This is effective for predictable text, improving perceived generation speed."]}),"\n"]}),"\n",(0,s.jsx)(t.h2,{id:"few-benchmarks",children:"Few Benchmarks"}),"\n",(0,s.jsx)(t.p,{children:"Below are a couple of representative use cases and performance numbers."}),"\n",(0,s.jsx)(t.h3,{id:"search-query-rewriting",children:"Search query rewriting"}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"LLM"}),": Fine-tuned llama-3.2-1B"]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"Input & output token length"}),": ~10\u201320"]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"Response type"}),": Non-streaming"]}),"\n"]}),"\n",(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Inference runtime"}),(0,s.jsx)(t.th,{children:"Hardware"}),(0,s.jsx)(t.th,{style:{textAlign:"right"},children:"Max requests/sec"}),(0,s.jsx)(t.th,{style:{textAlign:"right"},children:"Max p99 latency"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{children:"4 \xd7 L4 GPUs (multi-GPU)"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"1000"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"95 ms"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{children:"1 \xd7 A100 40 GB GPU"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"1000"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"69 ms"})]})]})]}),"\n",(0,s.jsx)(t.h3,{id:"voice-bot-query",children:"Voice bot query"}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"LLM"}),": Llama-3.1-8B"]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"Input token length"}),": ~1900\u20132000"]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"Output token length"}),": ~200"]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"Response type"}),": Streaming"]}),"\n"]}),"\n",(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Inference runtime"}),(0,s.jsx)(t.th,{style:{textAlign:"right"},children:"Concurrency"}),(0,s.jsx)(t.th,{style:{textAlign:"right"},children:"p99 TTFT (ms)"}),(0,s.jsx)(t.th,{style:{textAlign:"right"},children:"p99 ITL (ms)"}),(0,s.jsx)(t.th,{style:{textAlign:"right"},children:"Token throughput (tokens/sec)"}),(0,s.jsx)(t.th,{style:{textAlign:"right"},children:"Request throughput (req/sec)"}),(0,s.jsx)(t.th,{children:"Hardware"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"1"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"36.27"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"22.78"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"45.66"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"0.23"}),(0,s.jsx)(t.td,{children:"L4"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"2"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"49.81"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"23.21"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"89.37"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"0.45"}),(0,s.jsx)(t.td,{children:"L4"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"4"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"55.33"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"36.62"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"153.39"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"0.78"}),(0,s.jsx)(t.td,{children:"L4"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"8"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"66.5"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"39.11"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"279.88"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"1.47"}),(0,s.jsx)(t.td,{children:"L4"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"16"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"131.8"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"30.39"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"547.8"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"2.77"}),(0,s.jsx)(t.td,{children:"L4"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"32"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"277.22"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"48.02"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"925.7"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"4.78"}),(0,s.jsx)(t.td,{children:"L4"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"64"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"498.52"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"71.62"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"1,164.40"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"6.2"}),(0,s.jsx)(t.td,{children:"L4"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"128"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"677.31"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"120.37"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"1,445.18"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"7.69"}),(0,s.jsx)(t.td,{children:"L4"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"256"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"1,926.31"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"216.88"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"1,600.81"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"8.52"}),(0,s.jsx)(t.td,{children:"L4"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"1"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"21.17"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"9.24"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"130.05"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"0.68"}),(0,s.jsx)(t.td,{children:"A100"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"2"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"25.78"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"9.21"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"264.5"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"1.35"}),(0,s.jsx)(t.td,{children:"A100"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"4"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"28.52"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"10.99"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"437.69"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"2.27"}),(0,s.jsx)(t.td,{children:"A100"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"8"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"34.4"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"12.61"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"760.49"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"3.96"}),(0,s.jsx)(t.td,{children:"A100"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"16"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"68.03"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"14.32"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"1,343.80"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"7.01"}),(0,s.jsx)(t.td,{children:"A100"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"32"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"185.96"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"16.82"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"2,287.30"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"11.92"}),(0,s.jsx)(t.td,{children:"A100"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"64"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"136.87"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"21.17"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"3,625.22"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"18.89"}),(0,s.jsx)(t.td,{children:"A100"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"128"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"463.78"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"34.15"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"4,456.51"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"23.24"}),(0,s.jsx)(t.td,{children:"A100"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"256"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"890.12"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"59.18"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"5,188.24"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"27.05"}),(0,s.jsx)(t.td,{children:"A100"})]})]})]}),"\n",(0,s.jsx)(t.h2,{id:"conclusion",children:"Conclusion"}),"\n",(0,s.jsx)(t.p,{children:"High-performance LLM inference is fundamentally a systems engineering problem: memory efficiency, kernel execution, batching strategy, and parallelism determine real-world latency and throughput. Techniques such as paged KV caching, aggressive quantization, kernel fusion, and inflight batching improve GPU utilization while reducing latency and memory pressure."}),"\n",(0,s.jsx)(t.p,{children:"These optimizations enable the platform to deliver sub-second responses, sustain high concurrency, and efficiently serve both lightweight and long-context workloads. By continuously optimizing across the full inference stack, we keep LLM serving scalable, cost-efficient, and production-ready for real-time AI applications."})]})}function o(e={}){const{wrapper:t}={...(0,l.R)(),...e.components};return t?(0,s.jsx)(t,{...e,children:(0,s.jsx)(h,{...e})}):h(e)}},8453:(e,t,i)=>{i.d(t,{R:()=>r,x:()=>d});var n=i(6540);const s={},l=n.createContext(s);function r(e){const t=n.useContext(l);return n.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function d(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:r(e.components),n.createElement(l.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/7fa80e1c.b2123398.js b/docs/assets/js/7fa80e1c.b2123398.js new file mode 100644 index 00000000..e11b5d62 --- /dev/null +++ b/docs/assets/js/7fa80e1c.b2123398.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[3322],{9189:a=>{a.exports=JSON.parse('{"tags":[{"label":"ai-agents","permalink":"/BharatMLStack/blog/tags/ai-agents","count":1},{"label":"memory","permalink":"/BharatMLStack/blog/tags/memory","count":1},{"label":"architecture","permalink":"/BharatMLStack/blog/tags/architecture","count":1},{"label":"llm","permalink":"/BharatMLStack/blog/tags/llm","count":3},{"label":"episodic-memory","permalink":"/BharatMLStack/blog/tags/episodic-memory","count":1},{"label":"vllm","permalink":"/BharatMLStack/blog/tags/vllm","count":2},{"label":"tensorrt-llm","permalink":"/BharatMLStack/blog/tags/tensorrt-llm","count":2},{"label":"mlplatform","permalink":"/BharatMLStack/blog/tags/mlplatform","count":5},{"label":"meesho","permalink":"/BharatMLStack/blog/tags/meesho","count":5},{"label":"bharatmlstack","permalink":"/BharatMLStack/blog/tags/bharatmlstack","count":4},{"label":"model-inference","permalink":"/BharatMLStack/blog/tags/model-inference","count":1},{"label":"embedding-search","permalink":"/BharatMLStack/blog/tags/embedding-search","count":1},{"label":"inferflow","permalink":"/BharatMLStack/blog/tags/inferflow","count":1},{"label":"interaction-store","permalink":"/BharatMLStack/blog/tags/interaction-store","count":2},{"label":"online-feature-store","permalink":"/BharatMLStack/blog/tags/online-feature-store","count":1}]}')}}]); \ No newline at end of file diff --git a/docs/assets/js/7fa80e1c.b5f726bf.js b/docs/assets/js/7fa80e1c.b5f726bf.js deleted file mode 100644 index 2761db28..00000000 --- a/docs/assets/js/7fa80e1c.b5f726bf.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[3322],{9189:a=>{a.exports=JSON.parse('{"tags":[{"label":"online-feature-store","permalink":"/BharatMLStack/blog/tags/online-feature-store","count":1},{"label":"interaction-store","permalink":"/BharatMLStack/blog/tags/interaction-store","count":1},{"label":"mlplatform","permalink":"/BharatMLStack/blog/tags/mlplatform","count":1},{"label":"meesho","permalink":"/BharatMLStack/blog/tags/meesho","count":1}]}')}}]); \ No newline at end of file diff --git a/docs/assets/js/814f3328.9351096c.js b/docs/assets/js/814f3328.9351096c.js new file mode 100644 index 00000000..1fdf3d44 --- /dev/null +++ b/docs/assets/js/814f3328.9351096c.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[7472],{5513:e=>{e.exports=JSON.parse('{"title":"Recent posts","items":[{"title":"Beyond Vector RAG: Building Agent Memory That Learns From Experience.","permalink":"/BharatMLStack/blog/episodic-memory-for-agents","unlisted":false,"date":"2026-02-19T00:00:00.000Z"},{"title":"LLM Inference Optimization Techniques: Engineering Sub-Second Latency at Scale","permalink":"/BharatMLStack/blog/llm-inference-optimization-sub-sec-latency","unlisted":false,"date":"2025-06-02T00:00:00.000Z"},{"title":"Designing a Production-Grade LLM Inference Platform: From Model Weights to Scalable GPU Serving","permalink":"/BharatMLStack/blog/multi-engine-llm-inferencing-platform","unlisted":false,"date":"2025-03-29T00:00:00.000Z"},{"title":"Cracking the Code: Scaling Model Inference & Real-Time Embedding Search","permalink":"/BharatMLStack/blog/scaling-model-inference-and-embedding-search","unlisted":false,"date":"2024-05-21T00:00:00.000Z"},{"title":"Building Meesho\u2019s ML Platform: Lessons from the First-Gen System (Part 2)","permalink":"/BharatMLStack/blog/building-meeshos-mlplatform-lessons-from-first-gen","unlisted":false,"date":"2023-04-10T00:00:00.000Z"},{"title":"Building Meesho\u2019s ML Platform: From Chaos to Cutting-Edge (Part 1)","permalink":"/BharatMLStack/blog/building-meeshos-mlplatform","unlisted":false,"date":"2022-11-15T00:00:00.000Z"}]}')}}]); \ No newline at end of file diff --git a/docs/assets/js/814f3328.b45803b6.js b/docs/assets/js/814f3328.b45803b6.js deleted file mode 100644 index ad85a959..00000000 --- a/docs/assets/js/814f3328.b45803b6.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[7472],{5513:e=>{e.exports=JSON.parse('{"title":"Recent posts","items":[{"title":"Building Meesho\u2019s ML Platform: From Chaos to Cutting-Edge (Part 1)","permalink":"/BharatMLStack/blog/post-one","unlisted":false,"date":"2022-11-15T00:00:00.000Z"}]}')}}]); \ No newline at end of file diff --git a/docs/assets/js/845957d4.8290eece.js b/docs/assets/js/845957d4.8290eece.js new file mode 100644 index 00000000..a62182a7 --- /dev/null +++ b/docs/assets/js/845957d4.8290eece.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[9],{1187:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/bms-7399e8796d2cd24617c432518ce3f312.png"},2516:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/llm-plat-9ac69c0ffd8c387d177e582611b8c775.png"},3562:e=>{e.exports=JSON.parse('{"permalink":"/BharatMLStack/blog/multi-engine-llm-inferencing-platform","editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/blog/bharatmlstack-history/llm-inferencing-platform/index.md","source":"@site/blog/bharatmlstack-history/llm-inferencing-platform/index.md","title":"Designing a Production-Grade LLM Inference Platform: From Model Weights to Scalable GPU Serving","description":"A deep dive into building a production-grade LLM inference platform\u2014covering the full LLMOps lifecycle from model onboarding and automated compilation to multi-engine serving with TensorRT-LLM, vLLM, and Dynamo, along with cold-start mitigation and LLM-specific observability.","date":"2025-03-29T00:00:00.000Z","tags":[{"inline":true,"label":"llm","permalink":"/BharatMLStack/blog/tags/llm"},{"inline":true,"label":"vllm","permalink":"/BharatMLStack/blog/tags/vllm"},{"inline":true,"label":"tensorrt-llm","permalink":"/BharatMLStack/blog/tags/tensorrt-llm"},{"inline":true,"label":"mlplatform","permalink":"/BharatMLStack/blog/tags/mlplatform"},{"inline":true,"label":"meesho","permalink":"/BharatMLStack/blog/tags/meesho"},{"inline":true,"label":"bharatmlstack","permalink":"/BharatMLStack/blog/tags/bharatmlstack"}],"readingTime":13.31,"hasTruncateMarker":false,"authors":[{"name":"Jaya Kumar","title":"Lead ML Engineer @ Meesho","url":"https://github.com/jayakommuru","imageURL":"https://github.com/jayakommuru.png","key":"jaya","page":null}],"frontMatter":{"title":"Designing a Production-Grade LLM Inference Platform: From Model Weights to Scalable GPU Serving","description":"A deep dive into building a production-grade LLM inference platform\u2014covering the full LLMOps lifecycle from model onboarding and automated compilation to multi-engine serving with TensorRT-LLM, vLLM, and Dynamo, along with cold-start mitigation and LLM-specific observability.","authors":["jaya"],"slug":"multi-engine-llm-inferencing-platform","date":"2025-3-29","tags":["llm","vllm","tensorrt-llm","mlplatform","meesho","bharatmlstack"]},"unlisted":false,"prevItem":{"title":"LLM Inference Optimization Techniques: Engineering Sub-Second Latency at Scale","permalink":"/BharatMLStack/blog/llm-inference-optimization-sub-sec-latency"},"nextItem":{"title":"Cracking the Code: Scaling Model Inference & Real-Time Embedding Search","permalink":"/BharatMLStack/blog/scaling-model-inference-and-embedding-search"}}')},8453:(e,n,i)=>{i.d(n,{R:()=>s,x:()=>o});var t=i(6540);const r={},a=t.createContext(r);function s(e){const n=t.useContext(a);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:s(e.components),t.createElement(a.Provider,{value:n},e.children)}},9354:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>l,contentTitle:()=>o,default:()=>h,frontMatter:()=>s,metadata:()=>t,toc:()=>c});var t=i(3562),r=i(4848),a=i(8453);const s={title:"Designing a Production-Grade LLM Inference Platform: From Model Weights to Scalable GPU Serving",description:"A deep dive into building a production-grade LLM inference platform\u2014covering the full LLMOps lifecycle from model onboarding and automated compilation to multi-engine serving with TensorRT-LLM, vLLM, and Dynamo, along with cold-start mitigation and LLM-specific observability.",authors:["jaya"],slug:"multi-engine-llm-inferencing-platform",date:"2025-3-29",tags:["llm","vllm","tensorrt-llm","mlplatform","meesho","bharatmlstack"]},o=void 0,l={authorsImageUrls:[void 0]},c=[{value:"Why LLM Inference Is not just bigger ML model serving",id:"why-llm-inference-is-not-just-bigger-ml-model-serving",level:2},{value:"Autoregressive Generation and Sequential Computation:",id:"autoregressive-generation-and-sequential-computation",level:3},{value:"Prefill and Decode Phases:",id:"prefill-and-decode-phases",level:3},{value:"Context Management and KV Caching:",id:"context-management-and-kv-caching",level:3},{value:"Dynamic and Irregular Workloads:",id:"dynamic-and-irregular-workloads",level:3},{value:"Streaming and User Experience Constraints:",id:"streaming-and-user-experience-constraints",level:3},{value:"LLMOps: High-Level Architecture",id:"llmops-high-level-architecture",level:2},{value:"Supported Inference backends (TensorRT LLM, Dynamo & vLLM)",id:"supported-inference-backends-tensorrt-llm--dynamo--vllm",level:2},{value:"Conclusion",id:"conclusion",level:2},{value:"Future Explorations",id:"future-explorations",level:2}];function d(e){const n={h2:"h2",h3:"h3",img:"img",li:"li",ol:"ol",p:"p",ul:"ul",...(0,a.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.img,{alt:"BharatMLStack",src:i(1187).A+"",width:"1396",height:"460"}),"\nServing large language models in production introduces new challenges across infrastructure, performance optimization, and operational lifecycle management. The LLM Inference Platform addresses these challenges by providing a unified system for deploying and managing open-source and fine-tuned LLMs at scale."]}),"\n",(0,r.jsx)(n.p,{children:"The platform implements a complete LLMOps lifecycle \u2014 from model registration and automated compilation to deployment, runtime optimization, and monitoring. Designed as a self-service environment, users can onboard models directly from open repositories such as Hugging Face or upload custom fine-tuned models, and deploy them using a single-click workflow with no manual infrastructure or configuration steps required."}),"\n",(0,r.jsx)(n.p,{children:"In addition to fully automated deployment, the platform allows users to select and apply custom inference optimization techniques \u2014 such as quantization strategies, batching configurations, and runtime-specific performance enhancements \u2014 enabling teams to balance latency, throughput, and cost based on their use case. The goal is to reduce operational friction while enabling high-performance, production-grade LLM inference."}),"\n",(0,r.jsx)(n.h2,{id:"why-llm-inference-is-not-just-bigger-ml-model-serving",children:"Why LLM Inference Is not just bigger ML model serving"}),"\n",(0,r.jsx)(n.p,{children:"Large language model (LLM) inference introduces a fundamentally different set of challenges compared to traditional machine learning inference. While classical ML models typically perform a single forward pass to produce a fixed prediction, LLMs operate as autoregressive systems, generating outputs token by token based on previously generated context. This difference dramatically changes how inference systems must be designed, optimized, and scaled."}),"\n",(0,r.jsx)(n.h3,{id:"autoregressive-generation-and-sequential-computation",children:"Autoregressive Generation and Sequential Computation:"}),"\n",(0,r.jsx)(n.p,{children:"Unlike traditional models such as classifiers or recommenders \u2014 where inference cost is relatively constant \u2014 LLMs generate responses incrementally. Each new token depends on all previously generated tokens, making inference inherently sequential and dynamic. This means latency and compute requirements vary significantly depending on prompt length and output size, introducing complexity in scheduling and resource allocation.\nBecause tokens cannot be generated fully in parallel during decoding, GPUs may become underutilized without specialized batching and scheduling strategies. This has led to the development of dedicated LLM inference engines optimized for token-level execution."}),"\n",(0,r.jsx)(n.h3,{id:"prefill-and-decode-phases",children:"Prefill and Decode Phases:"}),"\n",(0,r.jsx)(n.p,{children:"LLM inference typically consists of two distinct stages:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Prefill phase \u2014 the model processes the input prompt and builds internal representations. This stage is compute-heavy and highly parallelizable."}),"\n",(0,r.jsx)(n.li,{children:"Decode phase \u2014 the model generates tokens sequentially, predicting one token at a time using previously generated context."}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"The decode stage often becomes memory-bound rather than compute-bound, which creates new performance bottlenecks compared to traditional ML workloads."}),"\n",(0,r.jsx)(n.h3,{id:"context-management-and-kv-caching",children:"Context Management and KV Caching:"}),"\n",(0,r.jsx)(n.p,{children:"Another fundamental difference lies in how LLMs maintain context. Transformer-based models rely on attention mechanisms that require access to past token representations. To avoid recomputing these representations repeatedly, inference engines use key-value (KV) caching, which stores intermediate activations from previous tokens.\nKV caching significantly improves performance by eliminating redundant computation, but it introduces new challenges:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Memory consumption grows with sequence length and batch size"}),"\n",(0,r.jsx)(n.li,{children:"GPU memory becomes a critical bottleneck"}),"\n",(0,r.jsx)(n.li,{children:"Efficient memory management becomes essential for scaling concurrent requests"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"This tradeoff between compute efficiency and memory usage is unique to LLM inference workloads."}),"\n",(0,r.jsx)(n.h3,{id:"dynamic-and-irregular-workloads",children:"Dynamic and Irregular Workloads:"}),"\n",(0,r.jsx)(n.p,{children:"Traditional ML inference typically operates on fixed-size inputs with predictable latency. In contrast, LLM requests vary widely in prompt length, output length, and runtime behavior. As a result:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Batch sizes must be dynamic rather than static"}),"\n",(0,r.jsx)(n.li,{children:"Requests may enter and leave batches asynchronously"}),"\n",(0,r.jsx)(n.li,{children:"Scheduling systems must continuously rebalance workloads to maximize GPU utilization"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"These characteristics require specialized serving architectures that differ significantly from standard ML serving pipelines."}),"\n",(0,r.jsx)(n.h3,{id:"streaming-and-user-experience-constraints",children:"Streaming and User Experience Constraints:"}),"\n",(0,r.jsx)(n.p,{children:"Another distinguishing factor is the expectation of real-time streaming responses. Instead of returning a single output, LLM systems often stream tokens to users as they are generated.\nBecause of these differences \u2014 sequential generation, growing memory requirements, dynamic workloads, and streaming constraints \u2014 LLM inference cannot be treated as a simple extension of existing ML serving systems. Production platforms must incorporate specialized runtime engines, advanced optimization techniques, and observability tailored specifically to LLM workloads."}),"\n",(0,r.jsx)(n.h2,{id:"llmops-high-level-architecture",children:"LLMOps: High-Level Architecture"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.img,{alt:"LLM Architecture",src:i(2516).A+"",width:"1302",height:"830"})}),"\n",(0,r.jsx)(n.p,{children:"The LLM Inference Framework is designed as a fully automated, end-to-end system for deploying and operating open-source and fine-tuned large language models at scale. The architecture abstracts the complexity of model optimization, hardware selection, deployment, and runtime management into a unified workflow that enables users to move from raw model weights to production-ready inference endpoints with minimal manual intervention."}),"\n",(0,r.jsx)(n.p,{children:"Our LLM Inference Framework is architected not just as a serving engine, but as a complete lifecycle management system. As illustrated in the high-level design below, the platform automates the journey of a model through seven distinct stages, ensuring reproducibility, performance, and scalability."}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"Onboarding & Registration (The Source of Truth)"}),"\n",(0,r.jsx)(n.p,{children:"The lifecycle begins with the Data Scientist or engineer."}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Model Ingestion: Users onboard models\u2014whether open-source (Hugging Face, NeMo) or internally fine-tuned\u2014via the Truffle Box SDK/UI."}),"\n",(0,r.jsx)(n.li,{children:'LLM + Prompt Registry: Unlike traditional systems that only track model weights, our registry is a unified control plane. It stores both the Model Artifacts and the Prompt Templates. This allows Data Scientists to register and version-control prompts (e.g., "customer_support_v2") independently of the application code.'}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:'The "Black Box" Build Engine'}),"\n",(0,r.jsx)(n.p,{children:"Once a model is registered, the Automated LLM Compiler + Quantizer Module kicks off a background job on ephemeral GPU resources."}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Transformation: The raw model is converted into a TRT-LLM Checkpoint."}),"\n",(0,r.jsx)(n.li,{children:"Quantization: The system automatically applies quantization algorithms (like INT4 AWQ or FP8) to reduce memory footprint."}),"\n",(0,r.jsx)(n.li,{children:"Engine Building: Finally, it compiles a highly optimized TRT Engine specifically tuned for the target hardware."}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"Intelligent Profiling & Validation"}),"\n",(0,r.jsx)(n.p,{children:"Before deployment, the new engine passes through the Hardware & Inference Runtime Profiler."}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Benchmarking: This module empirically tests the engine against various hardware configurations (L4 vs. A100) and runtimes (TRT-LLM vs. vLLM)."}),"\n",(0,r.jsx)(n.li,{children:"Optimization: It recommends the optimal configuration that meets latency SLAs (Time-To-First-Token) while minimizing cost."}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"Smart Artifact Generation & Distribution"}),"\n",(0,r.jsx)(n.p,{children:'To solve the Kubernetes "Cold Start" problem, the LLM Serving Artifacts Generation module packages the model using a bifurcated strategy:'}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Standard Models: Artifacts are uploaded to Cloud Storage (GCS) and downloaded by pods at startup."}),"\n",(0,r.jsx)(n.li,{children:"Very Large Models: For massive models (>8GB) where network downloads are too slow, the system pre-caches the model onto Secondary Boot Disks. These disks are attached directly to new GPU nodes during autoscaling, eliminating download wait times."}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"Image Streaming & Deployment"}),"\n",(0,r.jsx)(n.p,{children:"Simultaneously, the inference runtime container images are pulled from the Artifact Registry."}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Image Streaming: We utilize container image streaming to allow pods to start initializing while the massive Triton/Dynamo container layers are still downloading, further shaving seconds off the startup time. link"}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"The Inference Runtime (Kubernetes)"}),"\n",(0,r.jsx)(n.p,{children:"The workload lands on Kubernetes with Autoscaling."}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Dynamic Backends: Depending on the profile generated in Stage 3, the pod initializes either TensorRT-LLM (for throughput) or vLLM (for flexibility), or spins up a Dynamo worker for distributed inference."}),"\n",(0,r.jsx)(n.li,{children:'Data Loading: The pod either downloads the model from Cloud Storage or mounts the pre-warmed Secondary Boot Disk ("Pull from Disk").'}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"Client Interaction & Observability"}),"\n",(0,r.jsx)(n.p,{children:"Finally, the LLM Inference Client executes the request."}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Prompt Injection: The client pulls the specific prompt template ID from the Registry, ensuring the exact versioned instructions are used."}),"\n",(0,r.jsx)(n.li,{children:"Streaming Response: The request is sent via gRPC, and tokens are streamed back to the user in real-time."}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"Observability: Monitoring the Pulse of GenAI"}),"\n",(0,r.jsx)(n.p,{children:"In traditional microservices, success is measured by CPU utilization and request latency (p99). For Large Language Models, these metrics are insufficient. A user doesn't care if the GPU is at 80% utilization; they care about how fast the first word appears and how smoothly the rest of the sentence follows."}),"\n",(0,r.jsx)(n.p,{children:"To capture the true user experience, our platform instrumentation focuses on three critical LLM-specific metrics:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"Time to First Token (TTFT)"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Definition: TTFT measures the time elapsed from the moment a request is received until the very first token is generated and streamed back to the user."}),"\n",(0,r.jsx)(n.li,{children:'Why it matters: This represents the "Prefill Phase" latency\u2014the time the model takes to process the input prompt and load weights. A high TTFT makes the application feel unresponsive or "hung."'}),"\n",(0,r.jsx)(n.li,{children:"Optimization: We closely monitor TTFT to ensure our Prefix Caching is effective (aiming for high cache hitrates), which drastically lowers this metric by skipping redundant prompt processing."}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"Inter-Token Latency (ITL)"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:'Definition: ITL measures the average time interval between the generation of consecutive tokens during the "Decode Phase".'}),"\n",(0,r.jsx)(n.li,{children:'Why it matters: This defines the "perceived speed" of reading. Even if the first token is fast (low TTFT), high ITL makes the text generation look "jerky" or slow to the user.'}),"\n",(0,r.jsx)(n.li,{children:"Benchmarks: In our testing with Llama 3.1, we track p99 ITL to ensure it stays below human reading speeds to maintain a natural conversational flow."}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"Token Throughput vs. Request Throughput"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"We distinguish between two types of throughput to balance system efficiency with user load:"}),"\n",(0,r.jsx)(n.li,{children:"Token Throughput (tokens/sec): The total number of tokens generated across all concurrent requests. This measures the raw compute efficiency of the GPU and the effectiveness of batching."}),"\n",(0,r.jsx)(n.li,{children:"Request Throughput (req/sec): The number of distinct user queries served per second. We use this to determine autoscaling thresholds, ensuring we scale out before the queue depth impacts ITL."}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"The Monitoring Stack"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:'Real-time Dashboards: We utilize Grafana to visualize these streaming metrics in real-time, allowing on-call engineers to spot "slow generation" incidents that generic "500 error" alerts would miss.'}),"\n",(0,r.jsx)(n.li,{children:'Request Tracing: Since Triton Inference Server does not log request payloads by default, we integrate a Helix Client to asynchronously publish request logs to Log Tables. This allows us to trace a specific "slow" request back to its prompt to understand if a complex input caused the latency spike.'}),"\n"]}),"\n"]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"supported-inference-backends-tensorrt-llm--dynamo--vllm",children:"Supported Inference backends (TensorRT LLM, Dynamo & vLLM)"}),"\n",(0,r.jsx)(n.p,{children:'Tailored for the Use Case: We do not believe in a "one-size-fits-all" approach to inference. Different use cases\u2014whether a real-time voice bot requiring ultra-lowsub-second latency or a massive reasoning task requiring huge context windows\u2014demand different runtime characteristics. Our platform is designed to be runtime-agnostic, allowing us to automatically select and tailor the best engine based on the specific requirements of the application:'}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"TensorRT-LLM: The High-Performance Standard"}),"\n",(0,r.jsx)(n.p,{children:"Suitable for: High-throughput production workloads where latency is critical (e.g., customer support chat, real-time voice bots)."}),"\n",(0,r.jsx)(n.p,{children:"TensorRT-LLM serves as our default backend for these scenarios. Our internal benchmarks on Llama 3.1 and 3.2 models demonstrated that a tuned TensorRT-LLM engine significantly outperforms standard runtimes, especially when utilizing INT4 AWQ and FP8 quantization ."}),"\n",(0,r.jsx)(n.p,{children:"Key optimizations we tailor for these high-load cases include:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Optimized execution via TensorRT engine compilation"}),"\n",(0,r.jsx)(n.li,{children:"Quantization-aware execution for reduced memory usage and improved throughput"}),"\n",(0,r.jsx)(n.li,{children:"Inflight Batching: Allowing requests to be processed continuously without waiting for the entire batch to finish, drastically improving GPU utilization ."}),"\n",(0,r.jsx)(n.li,{children:"Custom Plugins: Enabling specific NVIDIA plugins like the GEMM plugin and GPT Attention plugin to accelerate matrix multiplications and attention mechanisms ."}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"Dynamo: Distributed Inference for Reasoning Models"}),"\n",(0,r.jsx)(n.p,{children:'Suitable for: Very large "reasoning" models (70B+) or scenarios requiring massive context windows where a single GPU\'s memory is insufficient.'}),"\n",(0,r.jsx)(n.p,{children:"For these memory-bound tasks, we utilize Dynamo, a low-latency distributed inference framework . Unlike monolithic servers, Dynamo disaggregates the inference process to scale resources horizontally:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"KV Aware Routing: A specialized router directs requests to workers that already hold the relevant Key-Value (KV) cache, minimizing redundant computation ."}),"\n",(0,r.jsx)(n.li,{children:'Prefill vs. Decode Split: The workload is divided into Prefill Workers (processing the prompt) and Decode Workers (generating tokens), allowing us to scale the compute-heavy "reading" phase independently from the memory-heavy "writing" phase .'}),"\n",(0,r.jsx)(n.li,{children:"Distributed execution across multiple GPU resources"}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"vLLM: The Flexible Baseline"}),"\n",(0,r.jsx)(n.p,{children:"Suitable for: Rapid prototyping, testing new model architectures, or low-traffic internal tools where ease of deployment outweighs raw throughput."}),"\n",(0,r.jsx)(n.p,{children:"While TensorRT-LLM is optimized for maximum speed, vLLM provides a robust and flexible baseline ."}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"High throughput through dynamic batching and efficient memory utilization"}),"\n",(0,r.jsx)(n.li,{children:"Paged KV cache management for handling long contexts and concurrent requests"}),"\n",(0,r.jsx)(n.li,{children:"Strong support for open-source model ecosystems"}),"\n",(0,r.jsx)(n.li,{children:"Rapid Adoption: It allows us to onboard new model architectures immediately without waiting for a custom TensorRT build."}),"\n",(0,r.jsx)(n.li,{children:"Benchmarking Insight: In our internal tests, vLLM provided a strong baseline but often lacked the specific max-token optimizations present in our custom TRT engines . We use it strategically for initial testing before committing to a full TensorRT optimization pipeline."}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"conclusion",children:"Conclusion"}),"\n",(0,r.jsx)(n.p,{children:"Large language model inference introduces a fundamentally new class of infrastructure challenges\u2014where performance is governed not just by raw compute, but by memory efficiency, intelligent scheduling, runtime specialization, and lifecycle automation. Unlike traditional ML serving, LLM inference requires systems that understand token-level execution, manage rapidly growing context state, and continuously balance latency, throughput, and cost under highly dynamic workloads."}),"\n",(0,r.jsx)(n.p,{children:"The LLM Inference Framework addresses these challenges by transforming inference into a fully automated, reproducible lifecycle\u2014from model onboarding and compilation to deployment, optimization, and observability. By integrating automated quantization and engine compilation, intelligent runtime selection, cold-start mitigation strategies, and LLM-specific observability metrics such as Time-to-First-Token and Inter-Token Latency, the platform ensures both high performance and operational simplicity."}),"\n",(0,r.jsx)(n.p,{children:"Equally important, the framework is designed with flexibility and future evolution in mind. Its runtime-agnostic architecture enables seamless adoption of emerging inference engines, hardware accelerators, and optimization techniques without requiring platform redesign. This ensures that teams can continuously leverage advancements in the rapidly evolving LLM ecosystem while maintaining consistent operational workflows."}),"\n",(0,r.jsx)(n.p,{children:"Ultimately, the goal of the platform is to make production-scale LLM deployment as seamless and reliable as traditional software deployment\u2014allowing teams to focus on building intelligent applications rather than managing infrastructure complexity. By combining lifecycle automation, runtime optimization, and deep observability, the LLM Inference Framework provides a scalable foundation for delivering fast, cost-efficient, and production-ready LLM experiences."}),"\n",(0,r.jsx)(n.h2,{id:"future-explorations",children:"Future Explorations"}),"\n",(0,r.jsx)(n.p,{children:"While we have achieved significant milestones in latency and throughput, the landscape of GenAI is evolving rapidly. Our roadmap focuses on increasing flexibility, reducing costs, and enhancing reliability for enterprise-grade workloads. Here is what we are building next:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"TPU Support: To diversify our hardware supply chain and further optimize cost-per-token, we are evaluating Google Cloud TPUs to bake it into our platform. By leveraging the JAX and PyTorch/XLA ecosystems, we aim to unlock the massive throughput potential of TPU v5e chips, particularly for our open-source Llama models. This will allow the hardware profiler to dynamically choose between NVIDIA GPUs and Google TPUs based on real-time availability and price-performance metrics."}),"\n",(0,r.jsx)(n.li,{children:'Multi-LoRA Serving (Serverless Experience): Currently, deploying a fine-tuned model requires a dedicated GPU. We are building support for Multi-LoRA serving, which will allow us to serve hundreds of unique, fine-tuned adapters on top of a single frozen base model. This will drastically reduce costs for multi-tenant applications, enabling a "serverless" experience where specific fine-tunes are hot-swapped instantly per request.'}),"\n",(0,r.jsx)(n.li,{children:"Spot Instance Orchestration: To further optimize cloud costs, we are developing fault-tolerant mechanisms to run inference workloads on Spot Instances. By implementing aggressive checkpointing and seamless request draining, we aim to leverage cheaper, preemptible compute capacity without interrupting the user's streaming experience."}),"\n",(0,r.jsx)(n.li,{children:'Semantic Caching Layer: We plan to move beyond standard Prefix Caching to implement Semantic Caching. By using a vector database to fetch responses for semantically similar queries (e.g., "How do I reset my password?" vs. "Password reset steps"), we can bypass the GPU entirely for repetitive queries, reducing latency to near-zero.'}),"\n",(0,r.jsx)(n.li,{children:"Context-Aware Autoscaling: Standard CPU/GPU utilization metrics are often insufficient signals for scaling LLMs. We are working on KV-cache pressure metrics for autoscaling. This ensures that we scale out before the memory fills up, preventing eviction-based slowdowns during traffic spikes."}),"\n",(0,r.jsx)(n.li,{children:'Online Evaluation & Guardrails: We are integrating a lightweight "Trust Layer" into the proxy. This will allow for low-latency input/output filtering (Guardrails) and asynchronous "LLM-as-a-Judge" evaluation pipelines to monitor response quality in production, not just system health.'}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,a.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(d,{...e})}):d(e)}}}]); \ No newline at end of file diff --git a/docs/assets/js/8ac6191a.6f3973a2.js b/docs/assets/js/8ac6191a.6f3973a2.js deleted file mode 100644 index 8f8e88c7..00000000 --- a/docs/assets/js/8ac6191a.6f3973a2.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[8465],{4540:e=>{e.exports=JSON.parse('{"categoryGeneratedIndex":{"title":"Online Feature Store","description":"Online-feature-store is a high-performance, scalable, and production-grade feature store built for modern machine learning systems. It supports both real-time and batch workflows, with a strong emphasis on developer experience, system observability, and low-latency feature retrieval.","slug":"/category/online-feature-store","permalink":"/BharatMLStack/category/online-feature-store","sidebar":"tutorialSidebar","navigation":{"next":{"title":"v1.0.0","permalink":"/BharatMLStack/online-feature-store/v1.0.0"}}}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/8ac6191a.8ac511aa.js b/docs/assets/js/8ac6191a.8ac511aa.js new file mode 100644 index 00000000..ec5af0a3 --- /dev/null +++ b/docs/assets/js/8ac6191a.8ac511aa.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[8465],{4540:e=>{e.exports=JSON.parse('{"categoryGeneratedIndex":{"title":"Online Feature Store","description":"Online-feature-store is a high-performance, scalable, and production-grade feature store built for modern machine learning systems. It supports both real-time and batch workflows, with a strong emphasis on developer experience, system observability, and low-latency feature retrieval.","slug":"/category/online-feature-store","permalink":"/BharatMLStack/category/online-feature-store","sidebar":"tutorialSidebar","navigation":{"previous":{"title":"BharatMLStack Documentation","permalink":"/BharatMLStack/intro"},"next":{"title":"v1.0.0","permalink":"/BharatMLStack/online-feature-store/v1.0.0"}}}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/8dd2df60.f10b075c.js b/docs/assets/js/8dd2df60.f10b075c.js new file mode 100644 index 00000000..98a10338 --- /dev/null +++ b/docs/assets/js/8dd2df60.f10b075c.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[1537],{3359:e=>{e.exports=JSON.parse('{"categoryGeneratedIndex":{"title":"Inferflow","description":"Inferflow is a graph-driven feature retrieval and model inference orchestration engine. It dynamically resolves entity relationships via configurable DAGs, retrieves features from the Online Feature Store, and orchestrates model scoring \u2014 all without custom code.","slug":"/category/inferflow","permalink":"/BharatMLStack/category/inferflow","sidebar":"tutorialSidebar","navigation":{"previous":{"title":"Release Notes","permalink":"/BharatMLStack/online-feature-store/v1.0.0/release-notes"},"next":{"title":"v1.0.0","permalink":"/BharatMLStack/inferflow/v1.0.0"}}}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/8ea48c46.119645ab.js b/docs/assets/js/8ea48c46.119645ab.js new file mode 100644 index 00000000..93031643 --- /dev/null +++ b/docs/assets/js/8ea48c46.119645ab.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[9824],{7956:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>a,contentTitle:()=>l,default:()=>h,frontMatter:()=>o,metadata:()=>s,toc:()=>c});const s=JSON.parse('{"id":"numerix/v1.0.0/release-notes","title":"Release Notes","description":"Version 1.0.0 \ud83d\ude80","source":"@site/docs/numerix/v1.0.0/release-notes.md","sourceDirName":"numerix/v1.0.0","slug":"/numerix/v1.0.0/release-notes","permalink":"/BharatMLStack/numerix/v1.0.0/release-notes","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/numerix/v1.0.0/release-notes.md","tags":[],"version":"current","sidebarPosition":5,"frontMatter":{"title":"Release Notes","sidebar_position":5},"sidebar":"tutorialSidebar","previous":{"title":"Key Functionalities","permalink":"/BharatMLStack/numerix/v1.0.0/functionalities"},"next":{"title":"Predator","permalink":"/BharatMLStack/category/predator"}}');var r=i(4848),t=i(8453);const o={title:"Release Notes",sidebar_position:5},l="Numerix - Release Notes",a={},c=[{value:"Version 1.0.0 \ud83d\ude80",id:"version-100-",level:2},{value:"\ud83c\udfaf What's New",id:"-whats-new",level:2},{value:"Core Engine",id:"core-engine",level:3},{value:"API Surface",id:"api-surface",level:3},{value:"Observability",id:"observability",level:3},{value:"\ud83d\ude80 Performance & Optimization",id:"-performance--optimization",level:2},{value:"\ud83d\udee0\ufe0f APIs",id:"\ufe0f-apis",level:2},{value:"gRPC",id:"grpc",level:3},{value:"\ud83c\udfd7\ufe0f Deployment & Configuration",id:"\ufe0f-deployment--configuration",level:2},{value:"Environment",id:"environment",level:3},{value:"Containers",id:"containers",level:3},{value:"\ud83d\udd04 Compatibility",id:"-compatibility",level:2},{value:"\ud83d\udc1b Known Issues",id:"-known-issues",level:2},{value:"Contributing",id:"contributing",level:2},{value:"Community & Support",id:"community--support",level:2},{value:"License",id:"license",level:2}];function d(e){const n={a:"a",br:"br",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,t.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"numerix---release-notes",children:"Numerix - Release Notes"})}),"\n",(0,r.jsx)(n.h2,{id:"version-100-",children:"Version 1.0.0 \ud83d\ude80"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Release Date"}),": September 2025",(0,r.jsx)(n.br,{}),"\n",(0,r.jsx)(n.strong,{children:"Status"}),": General Availability (GA)"]}),"\n",(0,r.jsxs)(n.p,{children:["The first stable release of ",(0,r.jsx)(n.strong,{children:"Numerix"})," \u2014 a Rust-based compute service for evaluating mathematical expressions over feature matrices with very low latency. Numerix executes postfix expressions from an etcd-backed registry using a stack-based evaluator and compiler-assisted SIMD."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"-whats-new",children:"\ud83c\udfaf What's New"}),"\n",(0,r.jsx)(n.h3,{id:"core-engine",children:"Core Engine"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Postfix Expression Execution"}),": ",(0,r.jsx)(n.code,{children:"compute_id \u2192 postfix"})," mapping in etcd; parser-free request path."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Stack-Based Evaluator"}),": Linear-time execution over aligned vectors for predictable latency."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Compiler-Assisted SIMD"}),": Relies on LLVM autovectorization (NEON/SVE on ARM; SSE/AVX on x86); no handwritten intrinsics."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Typed Evaluation"}),": Internal conversion to ",(0,r.jsx)(n.code,{children:"fp32"}),"/",(0,r.jsx)(n.code,{children:"fp64"})," for consistent performance/precision."]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"api-surface",children:"API Surface"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"gRPC"}),": Single RPC \u2014 ",(0,r.jsx)(n.code,{children:"numerix.Numerix/Compute"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Input Formats"}),": Strings for ease, bytes for performance; both map to vectorized math internally."]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"observability",children:"Observability"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Datadog/DogStatsD"})," metrics: Latency (P50/P95/P99), RPS, error rate."]}),"\n",(0,r.jsxs)(n.li,{children:["Minimal HTTP diagnostics: ",(0,r.jsx)(n.code,{children:"/health"})," (and optional ",(0,r.jsx)(n.code,{children:"/metrics"}),")."]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"-performance--optimization",children:"\ud83d\ude80 Performance & Optimization"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Autovectorized Loops"}),": Tight loops over contiguous memory enable the compiler to emit SIMD instructions automatically."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"ARM Focus Option"}),": Excellent results with AArch64; builds can enable NEON/SVE/SVE2:"]}),"\n"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:'RUSTFLAGS="-C target-feature=+neon,+sve,+sve2" \\\ncargo build --release --target aarch64-unknown-linux-gnu\n'})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Deterministic Runtime"}),": No dynamic parsing in hot path; O(n) across tokens with vectorized inner ops."]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"\ufe0f-apis",children:"\ud83d\udee0\ufe0f APIs"}),"\n",(0,r.jsx)(n.h3,{id:"grpc",children:"gRPC"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-protobuf",children:"service Numerix {\n rpc Compute(NumerixRequestProto) returns (NumerixResponseProto);\n}\n"})}),"\n",(0,r.jsx)(n.p,{children:"Example call:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:'grpcurl -plaintext \\\n -import-path ./numerix/src/protos/proto \\\n -proto numerix.proto \\\n -d \'{\n "entityScoreData": {\n "schema": ["feature1", "feature2"],\n "entityScores": [ { "stringData": { "values": ["1.0", "2.0"] } } ],\n "computeId": "1001",\n "dataType": "fp32"\n }\n }\' \\\n localhost:8080 numerix.Numerix/Compute\n'})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"\ufe0f-deployment--configuration",children:"\ud83c\udfd7\ufe0f Deployment & Configuration"}),"\n",(0,r.jsx)(n.h3,{id:"environment",children:"Environment"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"APPLICATION_PORT=8083\nAPP_ENV=prd\nAPP_LOG_LEVEL=ERROR\nAPP_NAME=numerix\n\n# Performance\nCHANNEL_BUFFER_SIZE=10000\n\n# etcd\nETCD_SERVERS=127.0.0.1:2379\n\n# Metrics\nMETRIC_SAMPLING_RATE=1\nTELEGRAF_UDP_HOST=127.0.0.1\nTELEGRAF_UDP_PORT=8125\n"})}),"\n",(0,r.jsx)(n.h3,{id:"containers",children:"Containers"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Multi-arch images: ",(0,r.jsx)(n.code,{children:"linux/amd64"}),", ",(0,r.jsx)(n.code,{children:"linux/arm64"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Build targets example: ",(0,r.jsx)(n.code,{children:"x86_64-unknown-linux-gnu"}),", ",(0,r.jsx)(n.code,{children:"aarch64-unknown-linux-gnu"}),"."]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"-compatibility",children:"\ud83d\udd04 Compatibility"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Clients"}),": Any language with gRPC + generated stubs."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Architectures"}),": amd64 and arm64; ARM builds can enable NEON/SVE/SVE2."]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"-known-issues",children:"\ud83d\udc1b Known Issues"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsx)(n.li,{children:"Introduce a configurable log sampling rate to reduce pod memory usage during computation errors."}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"contributing",children:"Contributing"}),"\n",(0,r.jsxs)(n.p,{children:["We welcome contributions from the community! Please see our ",(0,r.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTING.md",children:"Contributing Guide"})," for details on how to get started."]}),"\n",(0,r.jsx)(n.h2,{id:"community--support",children:"Community & Support"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["\ud83d\udcac ",(0,r.jsx)(n.strong,{children:"Discord"}),": Join our ",(0,r.jsx)(n.a,{href:"https://discord.gg/XkT7XsV2AU",children:"community chat"})]}),"\n",(0,r.jsxs)(n.li,{children:["\ud83d\udc1b ",(0,r.jsx)(n.strong,{children:"Issues"}),": Report bugs and request features on ",(0,r.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/issues",children:"GitHub Issues"})]}),"\n",(0,r.jsxs)(n.li,{children:["\ud83d\udce7 ",(0,r.jsx)(n.strong,{children:"Email"}),": Contact us at ",(0,r.jsx)(n.a,{href:"mailto:ml-oss@meesho.com",children:"ml-oss@meesho.com"})]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"license",children:"License"}),"\n",(0,r.jsxs)(n.p,{children:["BharatMLStack is open-source software licensed under the ",(0,r.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md",children:"BharatMLStack Business Source License 1.1"}),"."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)("div",{align:"center",children:(0,r.jsx)("strong",{children:"Built with \u2764\ufe0f for the ML community from Meesho"})}),"\n",(0,r.jsx)("div",{align:"center",children:(0,r.jsx)("strong",{children:"If you find this useful, \u2b50\ufe0f the repo \u2014 your support means the world to us!"})})]})}function h(e={}){const{wrapper:n}={...(0,t.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(d,{...e})}):d(e)}},8453:(e,n,i)=>{i.d(n,{R:()=>o,x:()=>l});var s=i(6540);const r={},t=s.createContext(r);function o(e){const n=s.useContext(t);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:o(e.components),s.createElement(t.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/93f344c7.7cebeb9e.js b/docs/assets/js/93f344c7.7cebeb9e.js new file mode 100644 index 00000000..2a74fffc --- /dev/null +++ b/docs/assets/js/93f344c7.7cebeb9e.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[4416],{5232:a=>{a.exports=JSON.parse('{"tag":{"label":"inferflow","permalink":"/BharatMLStack/blog/tags/inferflow","allTagsPath":"/BharatMLStack/blog/tags","count":1,"unlisted":false},"listMetadata":{"permalink":"/BharatMLStack/blog/tags/inferflow","page":1,"postsPerPage":10,"totalPages":1,"totalCount":1,"blogDescription":"Blog","blogTitle":"Blog"}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/9796f4b8.34772e40.js b/docs/assets/js/9796f4b8.34772e40.js new file mode 100644 index 00000000..641510bc --- /dev/null +++ b/docs/assets/js/9796f4b8.34772e40.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[5430],{8453:(e,i,n)=>{n.d(i,{R:()=>a,x:()=>l});var t=n(6540);const s={},r=t.createContext(s);function a(e){const i=t.useContext(r);return t.useMemo(function(){return"function"==typeof e?e(i):{...i,...e}},[i,e])}function l(e){let i;return i=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:a(e.components),t.createElement(r.Provider,{value:i},e.children)}},9352:(e,i,n)=>{n.r(i),n.d(i,{assets:()=>o,contentTitle:()=>l,default:()=>h,frontMatter:()=>a,metadata:()=>t,toc:()=>d});const t=JSON.parse('{"id":"skye/v1.0.0/functionalities","title":"Functionalities","description":"Core Capabilities","source":"@site/docs/skye/v1.0.0/functionalities.md","sourceDirName":"skye/v1.0.0","slug":"/skye/v1.0.0/functionalities","permalink":"/BharatMLStack/skye/v1.0.0/functionalities","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/skye/v1.0.0/functionalities.md","tags":[],"version":"current","sidebarPosition":2,"frontMatter":{"title":"Functionalities","sidebar_position":2},"sidebar":"tutorialSidebar","previous":{"title":"Architecture","permalink":"/BharatMLStack/skye/v1.0.0/architecture"},"next":{"title":"Release Notes","permalink":"/BharatMLStack/skye/v1.0.0/release-notes"}}');var s=n(4848),r=n(8453);const a={title:"Functionalities",sidebar_position:2},l="Skye - Functionalities",o={},d=[{value:"Core Capabilities",id:"core-capabilities",level:2},{value:"1. Vector Similarity Search",id:"1-vector-similarity-search",level:3},{value:"2. Pluggable Vector Database Support",id:"2-pluggable-vector-database-support",level:3},{value:"3. Model and Variant Management",id:"3-model-and-variant-management",level:3},{value:"Model Registration",id:"model-registration",level:4},{value:"Variant Registration",id:"variant-registration",level:4},{value:"Model Promotion",id:"model-promotion",level:4},{value:"4. Embedding Ingestion",id:"4-embedding-ingestion",level:3},{value:"Batch Ingestion (Reset/Delta Jobs)",id:"batch-ingestion-resetdelta-jobs",level:4},{value:"Real-Time Ingestion",id:"real-time-ingestion",level:4},{value:"5. Real-Time Data Aggregation",id:"5-real-time-data-aggregation",level:3},{value:"6. Intelligent Caching",id:"6-intelligent-caching",level:3},{value:"7. Embedded Storage",id:"7-embedded-storage",level:3},{value:"8. Retry and Fault Tolerance",id:"8-retry-and-fault-tolerance",level:3},{value:"9. Experiment Isolation",id:"9-experiment-isolation",level:3},{value:"10. Centralized Cluster Management",id:"10-centralized-cluster-management",level:3},{value:"Onboarding Flow",id:"onboarding-flow",level:2},{value:"Step-by-step Process",id:"step-by-step-process",level:3},{value:"Extending to New Tenants",id:"extending-to-new-tenants",level:3}];function c(e){const i={code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",hr:"hr",li:"li",ol:"ol",p:"p",strong:"strong",ul:"ul",...(0,r.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(i.header,{children:(0,s.jsx)(i.h1,{id:"skye---functionalities",children:"Skye - Functionalities"})}),"\n",(0,s.jsx)(i.h2,{id:"core-capabilities",children:"Core Capabilities"}),"\n",(0,s.jsx)(i.h3,{id:"1-vector-similarity-search",children:"1. Vector Similarity Search"}),"\n",(0,s.jsx)(i.p,{children:"Skye provides real-time nearest-neighbor search across high-dimensional vector spaces. It supports:"}),"\n",(0,s.jsxs)(i.ul,{children:["\n",(0,s.jsxs)(i.li,{children:[(0,s.jsx)(i.strong,{children:"Configurable distance functions"}),": DOT product, Cosine similarity, Euclidean distance"]}),"\n",(0,s.jsxs)(i.li,{children:[(0,s.jsx)(i.strong,{children:"Configurable vector dimensions"}),": Per-model vector dimension settings"]}),"\n",(0,s.jsxs)(i.li,{children:[(0,s.jsx)(i.strong,{children:"Indexed-only search"}),": Queries only search within fully indexed space, avoiding brute-force fallback on partially built indexes"]}),"\n",(0,s.jsxs)(i.li,{children:[(0,s.jsx)(i.strong,{children:"Pagination support"}),": Service-level pagination for clients, even when the underlying vector DB does not natively support it"]}),"\n"]}),"\n",(0,s.jsx)(i.h3,{id:"2-pluggable-vector-database-support",children:"2. Pluggable Vector Database Support"}),"\n",(0,s.jsx)(i.p,{children:"The platform is designed to be vector DB agnostic:"}),"\n",(0,s.jsxs)(i.ul,{children:["\n",(0,s.jsxs)(i.li,{children:[(0,s.jsx)(i.strong,{children:"Generic vector config"}),": A ",(0,s.jsx)(i.code,{children:"vector_db_type"})," field and generic ",(0,s.jsx)(i.code,{children:"vectordb_config"})," replace vendor-specific configurations"]}),"\n",(0,s.jsxs)(i.li,{children:[(0,s.jsx)(i.strong,{children:"Current support"}),": Qdrant with official Go client"]}),"\n",(0,s.jsxs)(i.li,{children:[(0,s.jsx)(i.strong,{children:"Extensibility"}),": New vector databases can be integrated by implementing the vector DB interface"]}),"\n"]}),"\n",(0,s.jsx)(i.h3,{id:"3-model-and-variant-management",children:"3. Model and Variant Management"}),"\n",(0,s.jsx)(i.h4,{id:"model-registration",children:"Model Registration"}),"\n",(0,s.jsxs)(i.ul,{children:["\n",(0,s.jsx)(i.li,{children:"Models are registered via API with entity type, embedding configuration, distance function, vector dimension, and training data path"}),"\n",(0,s.jsx)(i.li,{children:"Each model is associated with a store ID mapping to specific embedding and aggregator tables"}),"\n"]}),"\n",(0,s.jsx)(i.h4,{id:"variant-registration",children:"Variant Registration"}),"\n",(0,s.jsxs)(i.ul,{children:["\n",(0,s.jsx)(i.li,{children:"Variants represent different views/filters of the same model (e.g., organic, ad, commerce)"}),"\n",(0,s.jsx)(i.li,{children:"Each variant has its own filter criteria, vector DB cluster, job frequency, and version tracking"}),"\n",(0,s.jsx)(i.li,{children:"Variants share the same embeddings, eliminating data redundancy"}),"\n"]}),"\n",(0,s.jsx)(i.h4,{id:"model-promotion",children:"Model Promotion"}),"\n",(0,s.jsxs)(i.ul,{children:["\n",(0,s.jsx)(i.li,{children:"Successful experiments can be promoted from experiment clusters to production clusters via API"}),"\n"]}),"\n",(0,s.jsx)(i.h3,{id:"4-embedding-ingestion",children:"4. Embedding Ingestion"}),"\n",(0,s.jsx)(i.h4,{id:"batch-ingestion-resetdelta-jobs",children:"Batch Ingestion (Reset/Delta Jobs)"}),"\n",(0,s.jsxs)(i.ul,{children:["\n",(0,s.jsx)(i.li,{children:"Triggered via Databricks jobs that read from GCS paths"}),"\n",(0,s.jsx)(i.li,{children:"Supports separate index-space and search-space embeddings"}),"\n",(0,s.jsxs)(i.li,{children:["Per-variant ",(0,s.jsx)(i.code,{children:"to_be_indexed"})," flags control which embeddings are indexed for each variant"]}),"\n",(0,s.jsx)(i.li,{children:"EOF markers sent to all Kafka partitions ensure complete data consumption"}),"\n"]}),"\n",(0,s.jsx)(i.h4,{id:"real-time-ingestion",children:"Real-Time Ingestion"}),"\n",(0,s.jsxs)(i.ul,{children:["\n",(0,s.jsx)(i.li,{children:"Generic Kafka schema for all real-time consumers"}),"\n",(0,s.jsx)(i.li,{children:"Entity-based aggregation data (e.g., is_live_ad, out_of_stock) updates in real time"}),"\n",(0,s.jsx)(i.li,{children:"During model resets, real-time consumers continue pushing data to the latest collection (no pausing)"}),"\n"]}),"\n",(0,s.jsx)(i.h3,{id:"5-real-time-data-aggregation",children:"5. Real-Time Data Aggregation"}),"\n",(0,s.jsxs)(i.ul,{children:["\n",(0,s.jsx)(i.li,{children:"Entity-wise (catalog, product, user) real-time aggregation via ScyllaDB"}),"\n",(0,s.jsx)(i.li,{children:"Generic approach: aggregator tables are entity-level, not model/version-specific"}),"\n",(0,s.jsx)(i.li,{children:"All real-time data is consistent across models sharing the same entity"}),"\n"]}),"\n",(0,s.jsx)(i.h3,{id:"6-intelligent-caching",children:"6. Intelligent Caching"}),"\n",(0,s.jsxs)(i.ul,{children:["\n",(0,s.jsxs)(i.li,{children:[(0,s.jsx)(i.strong,{children:"In-memory cache"}),": First layer, reduces load on distributed cache"]}),"\n",(0,s.jsxs)(i.li,{children:[(0,s.jsx)(i.strong,{children:"Distributed cache (Redis)"}),": Second layer for cached similarity results"]}),"\n",(0,s.jsx)(i.li,{children:"Hit rate monitoring and cache effectiveness metrics per model"}),"\n"]}),"\n",(0,s.jsx)(i.h3,{id:"7-embedded-storage",children:"7. Embedded Storage"}),"\n",(0,s.jsxs)(i.ul,{children:["\n",(0,s.jsx)(i.li,{children:"Optional embedding storage with configurable TTL"}),"\n",(0,s.jsx)(i.li,{children:"Enables embedding lookup APIs for downstream consumers"}),"\n",(0,s.jsx)(i.li,{children:"Stored in ScyllaDB with efficient binary serialization"}),"\n"]}),"\n",(0,s.jsx)(i.h3,{id:"8-retry-and-fault-tolerance",children:"8. Retry and Fault Tolerance"}),"\n",(0,s.jsxs)(i.ul,{children:["\n",(0,s.jsxs)(i.li,{children:[(0,s.jsx)(i.strong,{children:"Retry topic"}),": Failed ingestion events are published to a dedicated retry topic"]}),"\n",(0,s.jsxs)(i.li,{children:[(0,s.jsx)(i.strong,{children:"Event-driven state management"}),": Model states persist in SQL DB, surviving pod restarts"]}),"\n",(0,s.jsxs)(i.li,{children:[(0,s.jsx)(i.strong,{children:"Kafka-based admin"}),": Asynchronous processing with automatic re-consumption on failure"]}),"\n"]}),"\n",(0,s.jsx)(i.h3,{id:"9-experiment-isolation",children:"9. Experiment Isolation"}),"\n",(0,s.jsxs)(i.ul,{children:["\n",(0,s.jsxs)(i.li,{children:["Dedicated EKS cluster (",(0,s.jsx)(i.code,{children:"skye-service-experiments"}),") for experiments"]}),"\n",(0,s.jsx)(i.li,{children:"Dedicated vector DB cluster for experiment workloads"}),"\n",(0,s.jsx)(i.li,{children:"Clean separation from production: experiments do not impact production performance"}),"\n",(0,s.jsx)(i.li,{children:"Promotion path from experiment to production after load analysis"}),"\n"]}),"\n",(0,s.jsx)(i.h3,{id:"10-centralized-cluster-management",children:"10. Centralized Cluster Management"}),"\n",(0,s.jsxs)(i.ul,{children:["\n",(0,s.jsx)(i.li,{children:"Automated cluster provisioning via scripts (collaboration with DevOps)"}),"\n",(0,s.jsx)(i.li,{children:"Consistent configurations across all clusters (eliminates consensus issues)"}),"\n",(0,s.jsx)(i.li,{children:"Horizontal scaling support: generic scripts for adding nodes to existing clusters"}),"\n"]}),"\n",(0,s.jsx)(i.hr,{}),"\n",(0,s.jsx)(i.h2,{id:"onboarding-flow",children:"Onboarding Flow"}),"\n",(0,s.jsx)(i.h3,{id:"step-by-step-process",children:"Step-by-step Process"}),"\n",(0,s.jsxs)(i.ol,{children:["\n",(0,s.jsxs)(i.li,{children:[(0,s.jsx)(i.strong,{children:"Data Scientist"})," provides a base GCS path where model embeddings will be pushed"]}),"\n",(0,s.jsxs)(i.li,{children:[(0,s.jsx)(i.strong,{children:"Register Model"})," via ",(0,s.jsx)(i.code,{children:"POST /register-model"})," with entity type, column mappings, model config"]}),"\n",(0,s.jsxs)(i.li,{children:[(0,s.jsx)(i.strong,{children:"Register Variant(s)"})," via ",(0,s.jsx)(i.code,{children:"POST /register-variant"})," with filter criteria, vector DB config, job frequency"]}),"\n",(0,s.jsxs)(i.li,{children:[(0,s.jsx)(i.strong,{children:"Schedule Databricks Job"})," to read data from GCS path and ingest into Skye platform"]}),"\n",(0,s.jsxs)(i.li,{children:[(0,s.jsx)(i.strong,{children:"Reset Model"})," via ",(0,s.jsx)(i.code,{children:"POST /reset-model"})," to trigger the first full ingestion"]}),"\n",(0,s.jsxs)(i.li,{children:[(0,s.jsx)(i.strong,{children:"Trigger Model Machine"})," via ",(0,s.jsx)(i.code,{children:"POST /trigger-model-machine"})," to start the indexing pipeline"]}),"\n"]}),"\n",(0,s.jsx)(i.h3,{id:"extending-to-new-tenants",children:"Extending to New Tenants"}),"\n",(0,s.jsx)(i.p,{children:"With the variant system, extending a model to a new tenant only requires registering a new variant with appropriate filters -- no re-ingestion of embeddings is needed."})]})}function h(e={}){const{wrapper:i}={...(0,r.R)(),...e.components};return i?(0,s.jsx)(i,{...e,children:(0,s.jsx)(c,{...e})}):c(e)}}}]); \ No newline at end of file diff --git a/docs/assets/js/982cae12.44c377b8.js b/docs/assets/js/982cae12.44c377b8.js new file mode 100644 index 00000000..3ab82022 --- /dev/null +++ b/docs/assets/js/982cae12.44c377b8.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[7290],{5394:a=>{a.exports=JSON.parse('{"tag":{"label":"architecture","permalink":"/BharatMLStack/blog/tags/architecture","allTagsPath":"/BharatMLStack/blog/tags","count":1,"unlisted":false},"listMetadata":{"permalink":"/BharatMLStack/blog/tags/architecture","page":1,"postsPerPage":10,"totalPages":1,"totalCount":1,"blogDescription":"Blog","blogTitle":"Blog"}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/99009a21.1a57fa22.js b/docs/assets/js/99009a21.1a57fa22.js new file mode 100644 index 00000000..168befca --- /dev/null +++ b/docs/assets/js/99009a21.1a57fa22.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[4064],{1161:t=>{t.exports=JSON.parse('{"tag":{"label":"tensorrt-llm","permalink":"/BharatMLStack/blog/tags/tensorrt-llm","allTagsPath":"/BharatMLStack/blog/tags","count":2,"unlisted":false},"listMetadata":{"permalink":"/BharatMLStack/blog/tags/tensorrt-llm","page":1,"postsPerPage":10,"totalPages":1,"totalCount":2,"blogDescription":"Blog","blogTitle":"Blog"}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/9aed321e.0ede45c0.js b/docs/assets/js/9aed321e.0ede45c0.js new file mode 100644 index 00000000..292183d0 --- /dev/null +++ b/docs/assets/js/9aed321e.0ede45c0.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[2951],{8453:(e,n,r)=>{r.d(n,{R:()=>l,x:()=>o});var i=r(6540);const s={},t=i.createContext(s);function l(e){const n=i.useContext(t);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:l(e.components),i.createElement(t.Provider,{value:n},e.children)}},9059:(e,n,r)=>{r.r(n),r.d(n,{assets:()=>d,contentTitle:()=>o,default:()=>h,frontMatter:()=>l,metadata:()=>i,toc:()=>c});const i=JSON.parse('{"id":"inferflow/v1.0.0/release-notes","title":"Release Notes","description":"Version 1.0.0","source":"@site/docs/inferflow/v1.0.0/release-notes.md","sourceDirName":"inferflow/v1.0.0","slug":"/inferflow/v1.0.0/release-notes","permalink":"/BharatMLStack/inferflow/v1.0.0/release-notes","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/inferflow/v1.0.0/release-notes.md","tags":[],"version":"current","sidebarPosition":4,"frontMatter":{"title":"Release Notes","sidebar_position":4},"sidebar":"tutorialSidebar","previous":{"title":"Configuration Guide","permalink":"/BharatMLStack/inferflow/v1.0.0/configuration"},"next":{"title":"Quick Start","permalink":"/BharatMLStack/category/quick-start"}}');var s=r(4848),t=r(8453);const l={title:"Release Notes",sidebar_position:4},o="Inferflow - Release Notes",d={},c=[{value:"Version 1.0.0",id:"version-100",level:2},{value:"What's New",id:"whats-new",level:2},{value:"Config-Driven DAG Executor",id:"config-driven-dag-executor",level:3},{value:"Multi-Pattern Inference APIs",id:"multi-pattern-inference-apis",level:3},{value:"Component System",id:"component-system",level:3},{value:"Online Feature Store Integration",id:"online-feature-store-integration",level:3},{value:"In-Memory Feature Caching",id:"in-memory-feature-caching",level:3},{value:"Inference Logging",id:"inference-logging",level:3},{value:"Performance",id:"performance",level:2},{value:"Built in Go",id:"built-in-go",level:3},{value:"Concurrency",id:"concurrency",level:3},{value:"Serialization",id:"serialization",level:3},{value:"APIs & Protocols",id:"apis--protocols",level:2},{value:"gRPC API",id:"grpc-api",level:3},{value:"Data Types Supported",id:"data-types-supported",level:3},{value:"Enterprise Features",id:"enterprise-features",level:2},{value:"Production Readiness",id:"production-readiness",level:3},{value:"Monitoring & Observability",id:"monitoring--observability",level:3},{value:"Configuration Management",id:"configuration-management",level:3},{value:"Deployment",id:"deployment",level:2},{value:"Container Support",id:"container-support",level:3},{value:"Supported Environments",id:"supported-environments",level:3},{value:"Compatibility",id:"compatibility",level:2},{value:"Supported Go Versions",id:"supported-go-versions",level:3},{value:"External Dependencies",id:"external-dependencies",level:3},{value:"Download & Installation",id:"download--installation",level:2},{value:"Source Code",id:"source-code",level:3},{value:"Build",id:"build",level:3},{value:"Docker",id:"docker",level:3},{value:"Contributing",id:"contributing",level:2},{value:"Community & Support",id:"community--support",level:2},{value:"License",id:"license",level:2}];function a(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",li:"li",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,t.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.header,{children:(0,s.jsx)(n.h1,{id:"inferflow---release-notes",children:"Inferflow - Release Notes"})}),"\n",(0,s.jsx)(n.h2,{id:"version-100",children:"Version 1.0.0"}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Release Date"}),": June 2025\n",(0,s.jsx)(n.strong,{children:"Status"}),": General Availability (GA)"]}),"\n",(0,s.jsxs)(n.p,{children:["We're excited to announce the first stable release of ",(0,s.jsx)(n.strong,{children:"Inferflow"})," \u2014 a graph-driven feature retrieval and model inference orchestration engine, part of BharatMLStack."]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"whats-new",children:"What's New"}),"\n",(0,s.jsx)(n.h3,{id:"config-driven-dag-executor",children:"Config-Driven DAG Executor"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"No-code feature retrieval"}),": Onboard new models with config changes only \u2014 no custom code required"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"DAG topology execution"}),": Define component dependency graphs that are executed concurrently using Kahn's algorithm"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Hot reload"}),": Model configurations stored in etcd are watched and reloaded live \u2014 no redeployment needed"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"DAG caching"}),": Topologies are cached using Murmur3 hashing with Ristretto for minimal overhead"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"multi-pattern-inference-apis",children:"Multi-Pattern Inference APIs"}),"\n",(0,s.jsx)(n.p,{children:"Three structured inference patterns via the Predict API:"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"API"}),(0,s.jsx)(n.th,{children:"Pattern"}),(0,s.jsx)(n.th,{children:"Use Case"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"InferPointWise"})}),(0,s.jsx)(n.td,{children:"Score each target independently"}),(0,s.jsx)(n.td,{children:"CTR prediction, fraud scoring"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"InferPairWise"})}),(0,s.jsx)(n.td,{children:"Score pairs of targets"}),(0,s.jsx)(n.td,{children:"Preference learning, comparison ranking"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"InferSlateWise"})}),(0,s.jsx)(n.td,{children:"Score groups of targets together"}),(0,s.jsx)(n.td,{children:"Whole-page optimization, diversity-aware ranking"})]})]})]}),"\n",(0,s.jsxs)(n.p,{children:["Plus the entity-based ",(0,s.jsx)(n.code,{children:"RetrieveModelScore"})," API for direct feature retrieval and scoring."]}),"\n",(0,s.jsx)(n.h3,{id:"component-system",children:"Component System"}),"\n",(0,s.jsx)(n.p,{children:"Four built-in component types:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"FeatureInitComponent"})," \u2014 Initializes the shared ComponentMatrix"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"FeatureComponent"})," \u2014 Fetches features from the Online Feature Store (OnFS)"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"PredatorComponent"})," \u2014 Calls model serving endpoints with percentage-based traffic routing"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"NumerixComponent"})," \u2014 Calls compute engine for operations like reranking"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"online-feature-store-integration",children:"Online Feature Store Integration"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["gRPC-based feature retrieval via ",(0,s.jsx)(n.code,{children:"FeatureService.RetrieveFeatures"})]}),"\n",(0,s.jsx)(n.li,{children:"Batched retrieval with configurable batch size and deadline"}),"\n",(0,s.jsx)(n.li,{children:"Token-based authentication"}),"\n",(0,s.jsx)(n.li,{children:"Dynamic key resolution from the ComponentMatrix"}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"in-memory-feature-caching",children:"In-Memory Feature Caching"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Optional per-component caching to reduce OnFS load"}),"\n",(0,s.jsx)(n.li,{children:"Configurable TTL per component"}),"\n",(0,s.jsx)(n.li,{children:"Zero-GC-overhead cache option (freecache)"}),"\n",(0,s.jsx)(n.li,{children:"Cache hit/miss metrics"}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"inference-logging",children:"Inference Logging"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Async logging to Kafka for model monitoring and debugging"}),"\n",(0,s.jsxs)(n.li,{children:["Three serialization formats: ",(0,s.jsx)(n.strong,{children:"Proto"}),", ",(0,s.jsx)(n.strong,{children:"Arrow"}),", ",(0,s.jsx)(n.strong,{children:"Parquet"})]}),"\n",(0,s.jsx)(n.li,{children:"Configurable sampling rate and feature selection"}),"\n",(0,s.jsx)(n.li,{children:"Batched log message grouping"}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"performance",children:"Performance"}),"\n",(0,s.jsx)(n.h3,{id:"built-in-go",children:"Built in Go"}),"\n",(0,s.jsx)(n.p,{children:"Inferflow is written entirely in Go, delivering:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"~80% lower memory usage compared to equivalent Java services"}),"\n",(0,s.jsx)(n.li,{children:"Lower CPU utilization"}),"\n",(0,s.jsx)(n.li,{children:"Faster, more efficient deployments"}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"concurrency",children:"Concurrency"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"DAG components at the same level execute concurrently in goroutines"}),"\n",(0,s.jsx)(n.li,{children:"Feature retrieval parallelized across entity types"}),"\n",(0,s.jsx)(n.li,{children:"Connection pooling for all external gRPC calls"}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"serialization",children:"Serialization"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"gRPC with Proto3 for all APIs"}),"\n",(0,s.jsx)(n.li,{children:"Binary feature encoding in the ComponentMatrix"}),"\n",(0,s.jsx)(n.li,{children:"Configurable compression for Kafka logging (ZSTD support)"}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"apis--protocols",children:"APIs & Protocols"}),"\n",(0,s.jsx)(n.h3,{id:"grpc-api",children:"gRPC API"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"Inferflow Service:"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-protobuf",children:"service Inferflow {\n rpc RetrieveModelScore(InferflowRequestProto) returns (InferflowResponseProto);\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"Predict Service:"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-protobuf",children:"service PredictService {\n rpc InferPointWise(PredictRequest) returns (PredictResponse);\n rpc InferPairWise(PredictRequest) returns (PredictResponse);\n rpc InferSlateWise(PredictRequest) returns (PredictResponse);\n}\n"})}),"\n",(0,s.jsx)(n.h3,{id:"data-types-supported",children:"Data Types Supported"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Variants"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Integers"}),(0,s.jsx)(n.td,{children:"int8, int16, int32, int64"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Floats"}),(0,s.jsx)(n.td,{children:"float8 (e4m3, e5m2), float16, float32, float64"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Strings"}),(0,s.jsx)(n.td,{children:"Variable length"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Booleans"}),(0,s.jsx)(n.td,{children:"Bit-packed"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Vectors"}),(0,s.jsx)(n.td,{children:"All scalar types"})]})]})]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"enterprise-features",children:"Enterprise Features"}),"\n",(0,s.jsx)(n.h3,{id:"production-readiness",children:"Production Readiness"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Health checks"}),": HTTP health endpoints via cmux"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Graceful shutdown"}),": Clean resource cleanup"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Structured logging"}),": JSON-formatted logs via zerolog"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Signal handling"}),": SIGTERM/SIGINT support for container environments"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"monitoring--observability",children:"Monitoring & Observability"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"StatsD / Telegraf integration"}),": Request rates, latencies, error rates"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Per-component metrics"}),": Execution time, feature counts, cache hit rates"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"External API metrics"}),": OnFS, Predator, Numerix call tracking"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Kafka logging metrics"}),": Messages sent, errors"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"configuration-management",children:"Configuration Management"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"etcd-based"}),": All model configs stored in etcd"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Watch & reload"}),": Live config updates without restart"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Multi-model support"}),": Multiple ",(0,s.jsx)(n.code,{children:"model_config_id"})," entries served concurrently"]}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"deployment",children:"Deployment"}),"\n",(0,s.jsx)(n.h3,{id:"container-support",children:"Container Support"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Docker image"}),": Multi-stage build (Go Alpine builder + Debian runtime)"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Optional Kafka"}),": librdkafka support via build flag"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Static binary"}),": Single binary deployment"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"supported-environments",children:"Supported Environments"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Kubernetes (K8s)"}),"\n",(0,s.jsx)(n.li,{children:"Google Kubernetes Engine (GKE)"}),"\n",(0,s.jsx)(n.li,{children:"Amazon EKS"}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"compatibility",children:"Compatibility"}),"\n",(0,s.jsx)(n.h3,{id:"supported-go-versions",children:"Supported Go Versions"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Minimum"}),": Go 1.19"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Recommended"}),": Go 1.24+"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"external-dependencies",children:"External Dependencies"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Service"}),(0,s.jsx)(n.th,{children:"Version"}),(0,s.jsx)(n.th,{children:"Protocol"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"etcd"}),(0,s.jsx)(n.td,{children:"3.5+"}),(0,s.jsx)(n.td,{children:"gRPC"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Online Feature Store (OnFS)"}),(0,s.jsx)(n.td,{children:"1.0+"}),(0,s.jsx)(n.td,{children:"gRPC"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Predator (Helix)"}),(0,s.jsx)(n.td,{children:"1.0+"}),(0,s.jsx)(n.td,{children:"gRPC"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Numerix"}),(0,s.jsx)(n.td,{children:"1.0+"}),(0,s.jsx)(n.td,{children:"gRPC"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Kafka"}),(0,s.jsx)(n.td,{children:"2.0+"}),(0,s.jsx)(n.td,{children:"TCP"})]})]})]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"download--installation",children:"Download & Installation"}),"\n",(0,s.jsx)(n.h3,{id:"source-code",children:"Source Code"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"git clone https://github.com/Meesho/BharatMLStack.git\ncd BharatMLStack/inferflow\n"})}),"\n",(0,s.jsx)(n.h3,{id:"build",children:"Build"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"go build -o inferflow-server cmd/inferflow/main.go\n"})}),"\n",(0,s.jsx)(n.h3,{id:"docker",children:"Docker"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"docker build -t inferflow:latest .\n"})}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"contributing",children:"Contributing"}),"\n",(0,s.jsxs)(n.p,{children:["We welcome contributions from the community! Please see our ",(0,s.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTING.md",children:"Contributing Guide"})," for details on how to get started."]}),"\n",(0,s.jsx)(n.h2,{id:"community--support",children:"Community & Support"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Discord"}),": Join our ",(0,s.jsx)(n.a,{href:"https://discord.gg/XkT7XsV2AU",children:"community chat"})]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Issues"}),": Report bugs and request features on ",(0,s.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/issues",children:"GitHub Issues"})]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Email"}),": Contact us at ",(0,s.jsx)(n.a,{href:"mailto:ml-oss@meesho.com",children:"ml-oss@meesho.com"})]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"license",children:"License"}),"\n",(0,s.jsxs)(n.p,{children:["BharatMLStack is open-source software licensed under the ",(0,s.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md",children:"BharatMLStack Business Source License 1.1"}),"."]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)("div",{align:"center",children:(0,s.jsx)("strong",{children:"Built with \u2764\ufe0f for the ML community from Meesho"})}),"\n",(0,s.jsx)("div",{align:"center",children:(0,s.jsx)("strong",{children:"If you find this useful, \u2b50\ufe0f the repo \u2014 your support means the world to us!"})})]})}function h(e={}){const{wrapper:n}={...(0,t.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(a,{...e})}):a(e)}}}]); \ No newline at end of file diff --git a/docs/assets/js/9d13045e.3f255bd8.js b/docs/assets/js/9d13045e.3f255bd8.js new file mode 100644 index 00000000..c5b9c674 --- /dev/null +++ b/docs/assets/js/9d13045e.3f255bd8.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[8014],{7791:(e,n,r)=>{r.r(n),r.d(n,{assets:()=>l,contentTitle:()=>t,default:()=>a,frontMatter:()=>c,metadata:()=>i,toc:()=>o});const i=JSON.parse('{"id":"inferflow/v1.0.0/configuration","title":"Configuration Guide","description":"Inferflow is fully config-driven. All model onboarding, feature retrieval logic, DAG topology, and inference behavior are controlled through configuration stored in etcd \u2014 with zero code changes required.","source":"@site/docs/inferflow/v1.0.0/configuration.md","sourceDirName":"inferflow/v1.0.0","slug":"/inferflow/v1.0.0/configuration","permalink":"/BharatMLStack/inferflow/v1.0.0/configuration","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/inferflow/v1.0.0/configuration.md","tags":[],"version":"current","sidebarPosition":3,"frontMatter":{"title":"Configuration Guide","sidebar_position":3},"sidebar":"tutorialSidebar","previous":{"title":"Key Functionalities","permalink":"/BharatMLStack/inferflow/v1.0.0/functionalities"},"next":{"title":"Release Notes","permalink":"/BharatMLStack/inferflow/v1.0.0/release-notes"}}');var s=r(4848),d=r(8453);const c={title:"Configuration Guide",sidebar_position:3},t="Inferflow - Configuration Guide",l={},o=[{value:"Configuration Overview",id:"configuration-overview",level:2},{value:"Static Configuration (Environment Variables)",id:"static-configuration-environment-variables",level:2},{value:"Server",id:"server",level:3},{value:"etcd",id:"etcd",level:3},{value:"Online Feature Store (OnFS)",id:"online-feature-store-onfs",level:3},{value:"Predator (Model Serving)",id:"predator-model-serving",level:3},{value:"Numerix (Compute Engine)",id:"numerix-compute-engine",level:3},{value:"Kafka (Inference Logging)",id:"kafka-inference-logging",level:3},{value:"Metrics (StatsD / Telegraf)",id:"metrics-statsd--telegraf",level:3},{value:"In-Memory Cache",id:"in-memory-cache",level:3},{value:"Dynamic Configuration (etcd Model Config)",id:"dynamic-configuration-etcd-model-config",level:2},{value:"Config Structure",id:"config-structure",level:3},{value:"DAG Execution Config",id:"dag-execution-config",level:3},{value:"Feature Component Config",id:"feature-component-config",level:3},{value:"Predator Component Config",id:"predator-component-config",level:3},{value:"Numerix Component Config",id:"numerix-component-config",level:3},{value:"Response Config",id:"response-config",level:3},{value:"Service-Level Config",id:"service-level-config",level:3},{value:"Example: Onboarding a New Model",id:"example-onboarding-a-new-model",level:2},{value:"Contributing",id:"contributing",level:2},{value:"Community & Support",id:"community--support",level:2},{value:"License",id:"license",level:2}];function h(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,d.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.header,{children:(0,s.jsx)(n.h1,{id:"inferflow---configuration-guide",children:"Inferflow - Configuration Guide"})}),"\n",(0,s.jsxs)(n.p,{children:["Inferflow is fully config-driven. All model onboarding, feature retrieval logic, DAG topology, and inference behavior are controlled through configuration stored in ",(0,s.jsx)(n.strong,{children:"etcd"})," \u2014 with zero code changes required."]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"configuration-overview",children:"Configuration Overview"}),"\n",(0,s.jsx)(n.p,{children:"Inferflow configuration is organized into two layers:"}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Static config"})," \u2014 Environment variables loaded at startup (via Viper)"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Dynamic config"})," \u2014 Model configurations stored in etcd, hot-reloaded on change"]}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"static-configuration-environment-variables",children:"Static Configuration (Environment Variables)"}),"\n",(0,s.jsx)(n.p,{children:"These are set at deployment time and require a restart to change."}),"\n",(0,s.jsx)(n.h3,{id:"server",children:"Server"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Variable"}),(0,s.jsx)(n.th,{children:"Description"}),(0,s.jsx)(n.th,{children:"Example"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"APP_PORT"})}),(0,s.jsx)(n.td,{children:"gRPC/HTTP server port"}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"50051"})})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"APP_ENV"})}),(0,s.jsx)(n.td,{children:"Environment name"}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"production"})})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"etcd",children:"etcd"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Variable"}),(0,s.jsx)(n.th,{children:"Description"}),(0,s.jsx)(n.th,{children:"Example"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"ETCD_ENDPOINTS"})}),(0,s.jsx)(n.td,{children:"Comma-separated etcd endpoints"}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"etcd-0:2379,etcd-1:2379"})})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"ETCD_DIAL_TIMEOUT"})}),(0,s.jsx)(n.td,{children:"Connection timeout"}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"5s"})})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"online-feature-store-onfs",children:"Online Feature Store (OnFS)"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Variable"}),(0,s.jsx)(n.th,{children:"Description"}),(0,s.jsx)(n.th,{children:"Example"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"externalServiceOnFs_host"})}),(0,s.jsx)(n.td,{children:"OnFS gRPC host"}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"onfs-api:50051"})})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"externalServiceOnFs_callerId"})}),(0,s.jsx)(n.td,{children:"Caller ID for auth"}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"inferflow"})})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"externalServiceOnFs_callerToken"})}),(0,s.jsx)(n.td,{children:"Caller token for auth"}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"<token>"})})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"externalServiceOnFs_batchSize"})}),(0,s.jsx)(n.td,{children:"Batch size for feature retrieval"}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"100"})})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"externalServiceOnFs_deadline"})}),(0,s.jsx)(n.td,{children:"Request deadline"}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"200ms"})})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"predator-model-serving",children:"Predator (Model Serving)"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Variable"}),(0,s.jsx)(n.th,{children:"Description"}),(0,s.jsx)(n.th,{children:"Example"})]})}),(0,s.jsx)(n.tbody,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"externalServicePredator_defaultDeadline"})}),(0,s.jsx)(n.td,{children:"Default inference deadline"}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"100ms"})})]})})]}),"\n",(0,s.jsx)(n.h3,{id:"numerix-compute-engine",children:"Numerix (Compute Engine)"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Variable"}),(0,s.jsx)(n.th,{children:"Description"}),(0,s.jsx)(n.th,{children:"Example"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"numerixClientV1_host"})}),(0,s.jsx)(n.td,{children:"Numerix gRPC host"}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"numerix:50052"})})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"numerixClientV1_deadline"})}),(0,s.jsx)(n.td,{children:"Request deadline"}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"100ms"})})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"kafka-inference-logging",children:"Kafka (Inference Logging)"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Variable"}),(0,s.jsx)(n.th,{children:"Description"}),(0,s.jsx)(n.th,{children:"Example"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"KafkaBootstrapServers"})}),(0,s.jsx)(n.td,{children:"Kafka broker addresses"}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"kafka-0:9092,kafka-1:9092"})})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"KafkaLoggingTopic"})}),(0,s.jsx)(n.td,{children:"Topic for inference logs"}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"inferflow-logs"})})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"metrics-statsd--telegraf",children:"Metrics (StatsD / Telegraf)"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Variable"}),(0,s.jsx)(n.th,{children:"Description"}),(0,s.jsx)(n.th,{children:"Example"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"TELEGRAF_HOST"})}),(0,s.jsx)(n.td,{children:"StatsD host"}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"telegraf"})})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"TELEGRAF_PORT"})}),(0,s.jsx)(n.td,{children:"StatsD port"}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"8125"})})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"in-memory-cache",children:"In-Memory Cache"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Variable"}),(0,s.jsx)(n.th,{children:"Description"}),(0,s.jsx)(n.th,{children:"Example"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"CACHE_SIZE_MB"})}),(0,s.jsx)(n.td,{children:"Cache size in MB"}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"512"})})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"CACHE_TYPE"})}),(0,s.jsx)(n.td,{children:"Cache implementation"}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"freecache"})})]})]})]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"dynamic-configuration-etcd-model-config",children:"Dynamic Configuration (etcd Model Config)"}),"\n",(0,s.jsxs)(n.p,{children:["Model configurations are stored in etcd and hot-reloaded. Each model is identified by a ",(0,s.jsx)(n.code,{children:"model_config_id"}),"."]}),"\n",(0,s.jsx)(n.h3,{id:"config-structure",children:"Config Structure"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n "model_config_id_example": {\n "dag_execution_config": {\n "component_dependency": {\n "feature_initializer": ["fs_user", "fs_product"],\n "fs_user": ["ranker_model"],\n "fs_product": ["ranker_model"],\n "ranker_model": []\n }\n },\n "component_config": {\n "feature_component_config": {\n "fs_user": { ... },\n "fs_product": { ... }\n },\n "predator_component_config": {\n "ranker_model": { ... }\n },\n "numerix_component_config": {},\n "cache_enabled": true,\n "cache_version": "v1",\n "cache_ttl": 300,\n "error_logging_percent": 10\n },\n "response_config": {\n "features": ["ranker_model:score"],\n "model_schema_perc": 100,\n "logging_perc": 5,\n "log_features": ["fs_user:profile:age", "ranker_model:score"],\n "log_batch_size": 100\n }\n }\n}\n'})}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h3,{id:"dag-execution-config",children:"DAG Execution Config"}),"\n",(0,s.jsx)(n.p,{children:"Defines the component dependency graph."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n "component_dependency": {\n "<parent_component>": ["<child_1>", "<child_2>"],\n "<child_1>": ["<grandchild>"],\n "<child_2>": ["<grandchild>"],\n "<grandchild>": []\n }\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"Rules:"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"The graph must be a valid DAG (no cycles)"}),"\n",(0,s.jsx)(n.li,{children:"Components with no parents (zero in-degree) execute first"}),"\n",(0,s.jsxs)(n.li,{children:["Components with empty dependency arrays ",(0,s.jsx)(n.code,{children:"[]"})," are leaf nodes"]}),"\n",(0,s.jsxs)(n.li,{children:["All component names must match registered components in the ",(0,s.jsx)(n.code,{children:"ComponentConfig"})]}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h3,{id:"feature-component-config",children:"Feature Component Config"}),"\n",(0,s.jsx)(n.p,{children:"Configures how features are fetched from the Online Feature Store."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n "fs_user": {\n "fs_keys": {\n "schema": ["user_id"],\n "col": "context:user:user_id"\n },\n "fs_request": {\n "entity_label": "user",\n "feature_groups": [\n {\n "label": "demographics",\n "feature_labels": ["age", "location", "income_bracket"]\n },\n {\n "label": "behavior",\n "feature_labels": ["click_rate", "purchase_freq"]\n }\n ]\n },\n "fs_flatten_resp_keys": ["user_id"],\n "col_name_prefix": "user",\n "comp_cache_enabled": true,\n "comp_cache_ttl": 600,\n "composite_id": false\n }\n}\n'})}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Field"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"fs_keys"})}),(0,s.jsxs)(n.td,{children:["How to extract lookup keys from the matrix. ",(0,s.jsx)(n.code,{children:"schema"})," defines key column names; ",(0,s.jsx)(n.code,{children:"col"})," references a matrix column"]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"fs_request"})}),(0,s.jsx)(n.td,{children:"OnFS query: entity label + feature groups with specific features"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"fs_flatten_resp_keys"})}),(0,s.jsx)(n.td,{children:"Keys to flatten in response mapping"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"col_name_prefix"})}),(0,s.jsxs)(n.td,{children:["Prefix for matrix column names (e.g., ",(0,s.jsx)(n.code,{children:"user:demographics:age"}),")"]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"comp_cache_enabled"})}),(0,s.jsx)(n.td,{children:"Enable in-memory caching for this component"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"comp_cache_ttl"})}),(0,s.jsx)(n.td,{children:"Cache TTL in seconds"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"composite_id"})}),(0,s.jsx)(n.td,{children:"Whether entity keys are composite"})]})]})]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h3,{id:"predator-component-config",children:"Predator Component Config"}),"\n",(0,s.jsx)(n.p,{children:"Configures model inference endpoints."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n "ranker_model": {\n "model_name": "product_ranker_v3",\n "model_endpoint": "predator-ranker:8080",\n "model_end_points": {\n "predator-ranker-v3:8080": 80,\n "predator-ranker-v4:8080": 20\n },\n "deadline": 100,\n "batch_size": 50,\n "calibration": {\n "enabled": false\n },\n "inputs": {\n "feature_map": {\n "user:demographics:age": "INT32",\n "user:behavior:click_rate": "FP32",\n "product:attributes:category_id": "INT32"\n }\n },\n "outputs": {\n "score_columns": ["score", "confidence"]\n },\n "slate_component": false\n }\n}\n'})}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Field"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"model_name"})}),(0,s.jsx)(n.td,{children:"Model identifier on the serving platform"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"model_endpoint"})}),(0,s.jsx)(n.td,{children:"Primary model serving endpoint"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"model_end_points"})}),(0,s.jsx)(n.td,{children:"Multiple endpoints with percentage-based traffic routing"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"deadline"})}),(0,s.jsx)(n.td,{children:"Inference timeout in milliseconds"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"batch_size"})}),(0,s.jsx)(n.td,{children:"Max items per inference batch"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"calibration"})}),(0,s.jsx)(n.td,{children:"Score calibration settings"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"inputs.feature_map"})}),(0,s.jsx)(n.td,{children:"Map of matrix column \u2192 data type for model input"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"outputs.score_columns"})}),(0,s.jsx)(n.td,{children:"Column names for model output scores"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"slate_component"})}),(0,s.jsx)(n.td,{children:"If true, runs per-slate inference"})]})]})]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h3,{id:"numerix-component-config",children:"Numerix Component Config"}),"\n",(0,s.jsx)(n.p,{children:"Configures compute operations (e.g., reranking)."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n "reranker": {\n "score_column": "final_score",\n "data_type": "FP32",\n "score_mapping": {\n "ranker_model:score": "FP32",\n "user:behavior:click_rate": "FP32"\n },\n "compute_id": "diversity_rerank_v1",\n "slate_component": false\n }\n}\n'})}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Field"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"score_column"})}),(0,s.jsx)(n.td,{children:"Output column name for the computed score"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"data_type"})}),(0,s.jsx)(n.td,{children:"Output data type"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"score_mapping"})}),(0,s.jsx)(n.td,{children:"Map of matrix columns to include as compute inputs"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"compute_id"})}),(0,s.jsx)(n.td,{children:"Identifies the compute operation on Numerix"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"slate_component"})}),(0,s.jsx)(n.td,{children:"If true, runs per-slate compute"})]})]})]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h3,{id:"response-config",children:"Response Config"}),"\n",(0,s.jsx)(n.p,{children:"Controls what data is returned to the client and what is logged."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n "features": ["ranker_model:score", "reranker:final_score"],\n "model_schema_perc": 100,\n "logging_perc": 5,\n "log_features": [\n "user:demographics:age",\n "ranker_model:score",\n "reranker:final_score"\n ],\n "log_batch_size": 100\n}\n'})}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Field"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"features"})}),(0,s.jsx)(n.td,{children:"Matrix columns to include in the gRPC response"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"model_schema_perc"})}),(0,s.jsx)(n.td,{children:"Percentage of requests that include full schema in response"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"logging_perc"})}),(0,s.jsx)(n.td,{children:"Percentage of requests to send to Kafka for logging"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"log_features"})}),(0,s.jsx)(n.td,{children:"Specific features to include in log messages"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"log_batch_size"})}),(0,s.jsx)(n.td,{children:"Batch size for grouped log messages"})]})]})]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h3,{id:"service-level-config",children:"Service-Level Config"}),"\n",(0,s.jsx)(n.p,{children:"Global settings that apply across all models."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n "v2_logging_type": "proto",\n "compression_enabled": false\n}\n'})}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Field"}),(0,s.jsx)(n.th,{children:"Values"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"v2_logging_type"})}),(0,s.jsxs)(n.td,{children:[(0,s.jsx)(n.code,{children:"proto"}),", ",(0,s.jsx)(n.code,{children:"arrow"}),", ",(0,s.jsx)(n.code,{children:"parquet"})]}),(0,s.jsx)(n.td,{children:"Serialization format for Kafka inference logs"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"compression_enabled"})}),(0,s.jsxs)(n.td,{children:[(0,s.jsx)(n.code,{children:"true"}),", ",(0,s.jsx)(n.code,{children:"false"})]}),(0,s.jsx)(n.td,{children:"Enable compression for log messages"})]})]})]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"example-onboarding-a-new-model",children:"Example: Onboarding a New Model"}),"\n",(0,s.jsx)(n.p,{children:"To onboard a new ranking model, update the etcd config:"}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Step 1:"})," Define the feature retrieval graph"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'"component_dependency": {\n "feature_initializer": ["fs_user", "fs_product", "fs_user_x_category"],\n "fs_product": ["fs_user_x_category"],\n "fs_user": ["new_ranker"],\n "fs_user_x_category": ["new_ranker"],\n "new_ranker": []\n}\n'})}),"\n",(0,s.jsxs)(n.p,{children:["Here ",(0,s.jsx)(n.code,{children:"fs_user_x_category"})," depends on ",(0,s.jsx)(n.code,{children:"fs_product"})," because it needs the category ID extracted from the product entity to resolve the user x category key."]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Step 2:"})," Configure each component (feature groups, model endpoints, etc.)"]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Step 3:"})," Push the config to etcd \u2014 Inferflow picks it up automatically via watchers."]}),"\n",(0,s.jsx)(n.p,{children:"No code changes. No redeployment. The new model is live."}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"contributing",children:"Contributing"}),"\n",(0,s.jsxs)(n.p,{children:["We welcome contributions from the community! Please see our ",(0,s.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTING.md",children:"Contributing Guide"})," for details on how to get started."]}),"\n",(0,s.jsx)(n.h2,{id:"community--support",children:"Community & Support"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Discord"}),": Join our ",(0,s.jsx)(n.a,{href:"https://discord.gg/XkT7XsV2AU",children:"community chat"})]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Issues"}),": Report bugs and request features on ",(0,s.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/issues",children:"GitHub Issues"})]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Email"}),": Contact us at ",(0,s.jsx)(n.a,{href:"mailto:ml-oss@meesho.com",children:"ml-oss@meesho.com"})]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"license",children:"License"}),"\n",(0,s.jsxs)(n.p,{children:["BharatMLStack is open-source software licensed under the ",(0,s.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md",children:"BharatMLStack Business Source License 1.1"}),"."]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)("div",{align:"center",children:(0,s.jsx)("strong",{children:"Built with \u2764\ufe0f for the ML community from Meesho"})}),"\n",(0,s.jsx)("div",{align:"center",children:(0,s.jsx)("strong",{children:"If you find this useful, \u2b50\ufe0f the repo \u2014 your support means the world to us!"})})]})}function a(e={}){const{wrapper:n}={...(0,d.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(h,{...e})}):h(e)}},8453:(e,n,r)=>{r.d(n,{R:()=>c,x:()=>t});var i=r(6540);const s={},d=i.createContext(s);function c(e){const n=i.useContext(d);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function t(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:c(e.components),i.createElement(d.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/9e4087bc.342bf9bc.js b/docs/assets/js/9e4087bc.342bf9bc.js new file mode 100644 index 00000000..4364a70b --- /dev/null +++ b/docs/assets/js/9e4087bc.342bf9bc.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[2711],{6266:(e,r,a)=>{a.d(r,{i:()=>s});var t=a(4586);function s(e={}){const{i18n:{currentLocale:r}}=(0,t.A)(),a=function(){const{i18n:{currentLocale:e,localeConfigs:r}}=(0,t.A)();return r[e].calendar}();return new Intl.DateTimeFormat(r,{calendar:a,...e})}},9331:(e,r,a)=>{a.r(r),a.d(r,{default:()=>m});a(6540);var t=a(8774),s=a(1312),n=a(5500),i=a(6266),c=a(1656),l=a(1107),o=a(4848);function d({year:e,posts:r}){const a=(0,i.i)({day:"numeric",month:"long",timeZone:"UTC"});return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(l.A,{as:"h3",id:e,children:e}),(0,o.jsx)("ul",{children:r.map(e=>{return(0,o.jsx)("li",{children:(0,o.jsxs)(t.A,{to:e.metadata.permalink,children:[(r=e.metadata.date,a.format(new Date(r)))," - ",e.metadata.title]})},e.metadata.date);var r})})]})}function h({years:e}){return(0,o.jsx)("section",{className:"margin-vert--lg",children:(0,o.jsx)("div",{className:"container",children:(0,o.jsx)("div",{className:"row",children:e.map((e,r)=>(0,o.jsx)("div",{className:"col col--4 margin-vert--lg",children:(0,o.jsx)(d,{...e})},r))})})})}function m({archive:e}){const r=(0,s.T)({id:"theme.blog.archive.title",message:"Archive",description:"The page & hero title of the blog archive page"}),a=(0,s.T)({id:"theme.blog.archive.description",message:"Archive",description:"The page & hero description of the blog archive page"}),t=function(e){const r=e.reduce((e,r)=>{const a=r.metadata.date.split("-")[0],t=e.get(a)??[];return e.set(a,[r,...t])},new Map);return Array.from(r,([e,r])=>({year:e,posts:r}))}(e.blogPosts);return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(n.be,{title:r,description:a}),(0,o.jsxs)(c.A,{children:[(0,o.jsx)("header",{className:"hero hero--primary",children:(0,o.jsxs)("div",{className:"container",children:[(0,o.jsx)(l.A,{as:"h1",className:"hero__title",children:r}),(0,o.jsx)("p",{className:"hero__subtitle",children:a})]})}),(0,o.jsx)("main",{children:t.length>0&&(0,o.jsx)(h,{years:t})})]})]})}}}]); \ No newline at end of file diff --git a/docs/assets/js/9e4087bc.b154716b.js b/docs/assets/js/9e4087bc.b154716b.js deleted file mode 100644 index 9a388f81..00000000 --- a/docs/assets/js/9e4087bc.b154716b.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[2711],{6266:(e,r,a)=>{a.d(r,{i:()=>s});var t=a(4586);function s(e={}){const{i18n:{currentLocale:r}}=(0,t.A)(),a=function(){const{i18n:{currentLocale:e,localeConfigs:r}}=(0,t.A)();return r[e].calendar}();return new Intl.DateTimeFormat(r,{calendar:a,...e})}},9331:(e,r,a)=>{a.r(r),a.d(r,{default:()=>m});a(6540);var t=a(8774),s=a(1312),n=a(5500),i=a(6266),c=a(1656),l=a(1107),o=a(4848);function d({year:e,posts:r}){const a=(0,i.i)({day:"numeric",month:"long",timeZone:"UTC"});return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(l.A,{as:"h3",id:e,children:e}),(0,o.jsx)("ul",{children:r.map((e=>{return(0,o.jsx)("li",{children:(0,o.jsxs)(t.A,{to:e.metadata.permalink,children:[(r=e.metadata.date,a.format(new Date(r)))," - ",e.metadata.title]})},e.metadata.date);var r}))})]})}function h({years:e}){return(0,o.jsx)("section",{className:"margin-vert--lg",children:(0,o.jsx)("div",{className:"container",children:(0,o.jsx)("div",{className:"row",children:e.map(((e,r)=>(0,o.jsx)("div",{className:"col col--4 margin-vert--lg",children:(0,o.jsx)(d,{...e})},r)))})})})}function m({archive:e}){const r=(0,s.T)({id:"theme.blog.archive.title",message:"Archive",description:"The page & hero title of the blog archive page"}),a=(0,s.T)({id:"theme.blog.archive.description",message:"Archive",description:"The page & hero description of the blog archive page"}),t=function(e){const r=e.reduce(((e,r)=>{const a=r.metadata.date.split("-")[0],t=e.get(a)??[];return e.set(a,[r,...t])}),new Map);return Array.from(r,(([e,r])=>({year:e,posts:r})))}(e.blogPosts);return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(n.be,{title:r,description:a}),(0,o.jsxs)(c.A,{children:[(0,o.jsx)("header",{className:"hero hero--primary",children:(0,o.jsxs)("div",{className:"container",children:[(0,o.jsx)(l.A,{as:"h1",className:"hero__title",children:r}),(0,o.jsx)("p",{className:"hero__subtitle",children:a})]})}),(0,o.jsx)("main",{children:t.length>0&&(0,o.jsx)(h,{years:t})})]})]})}}}]); \ No newline at end of file diff --git a/docs/assets/js/a1ba6e62.00332bec.js b/docs/assets/js/a1ba6e62.00332bec.js new file mode 100644 index 00000000..7bb30dfc --- /dev/null +++ b/docs/assets/js/a1ba6e62.00332bec.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[4197],{1828:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/first-gen-arch-7c0b286810aecb7eff42b48f51caee1f.png"},1965:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/online-feature-store-v0-86ec0010947ae24621f39ebd0d1729ca.png"},4959:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/interaction-store-v0-68167b64c6e462ef2f177f0f86d55bda.png"},6496:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/bms-7399e8796d2cd24617c432518ce3f312.png"},7121:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/old-batch-arch-bc2cedbc1fed0fc6f08479ba8fe52996.png"},7882:e=>{e.exports=JSON.parse('{"permalink":"/BharatMLStack/blog/building-meeshos-mlplatform","editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/blog/bharatmlstack-history/building-meeshos-mlplatform-from-chaos-to-cutting-edge/index.md","source":"@site/blog/bharatmlstack-history/building-meeshos-mlplatform-from-chaos-to-cutting-edge/index.md","title":"Building Meesho\u2019s ML Platform: From Chaos to Cutting-Edge (Part 1)","description":"How Meesho transitioned from batch-based recommendations to a real-time ML platform\u2014building an Online Feature Store, Interaction Store, and DAG execution framework that became BharatMLStack.","date":"2022-11-15T00:00:00.000Z","tags":[{"inline":true,"label":"online-feature-store","permalink":"/BharatMLStack/blog/tags/online-feature-store"},{"inline":true,"label":"interaction-store","permalink":"/BharatMLStack/blog/tags/interaction-store"},{"inline":true,"label":"mlplatform","permalink":"/BharatMLStack/blog/tags/mlplatform"},{"inline":true,"label":"meesho","permalink":"/BharatMLStack/blog/tags/meesho"}],"readingTime":10.19,"hasTruncateMarker":false,"authors":[{"name":"Adarsha Das","title":"Senior Architect @ Meesho","url":"https://github.com/a0d00kc","imageURL":"https://github.com/a0d00kc.png","key":"adarsha","page":null},{"name":"Aditya Kumar","title":"Lead Software Engineer @ Meesho","url":"https://github.com/Adit2607","imageURL":"https://github.com/Adit2607.png","key":"aditya","page":null},{"name":"Bhawani Singh","title":"Architect @ Meesho","url":"https://github.com/singh-bhawani","imageURL":"https://github.com/singh-bhawani.png","key":"bhawani","page":null},{"name":"Jigar Dave","title":"Lead Software Engineer @ Meesho","url":"https://github.com/jigarpatel26","imageURL":"https://github.com/jigarpatel26.png","key":"jigar","page":null}],"frontMatter":{"title":"Building Meesho\u2019s ML Platform: From Chaos to Cutting-Edge (Part 1)","description":"How Meesho transitioned from batch-based recommendations to a real-time ML platform\u2014building an Online Feature Store, Interaction Store, and DAG execution framework that became BharatMLStack.","slug":"building-meeshos-mlplatform","authors":["adarsha","aditya","bhawani","jigar"],"date":"2022-11-15T00:00:00.000Z","tags":["online-feature-store","interaction-store","mlplatform","meesho"]},"unlisted":false,"prevItem":{"title":"Building Meesho\u2019s ML Platform: Lessons from the First-Gen System (Part 2)","permalink":"/BharatMLStack/blog/building-meeshos-mlplatform-lessons-from-first-gen"}}')},8449:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/schema-d699efc400ed0f83bba421c1f55ab211.png"},8453:(e,n,i)=>{i.d(n,{R:()=>a,x:()=>o});var t=i(6540);const s={},r=t.createContext(s);function a(e){const n=t.useContext(r);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:a(e.components),t.createElement(r.Provider,{value:n},e.children)}},9037:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>l,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>t,toc:()=>d});var t=i(7882),s=i(4848),r=i(8453);const a={title:"Building Meesho\u2019s ML Platform: From Chaos to Cutting-Edge (Part 1)",description:"How Meesho transitioned from batch-based recommendations to a real-time ML platform\u2014building an Online Feature Store, Interaction Store, and DAG execution framework that became BharatMLStack.",slug:"building-meeshos-mlplatform",authors:["adarsha","aditya","bhawani","jigar"],date:new Date("2022-11-15T00:00:00.000Z"),tags:["online-feature-store","interaction-store","mlplatform","meesho"]},o=void 0,l={authorsImageUrls:[void 0,void 0,void 0,void 0]},d=[{value:"The Turning Point: From Batch to Real-Time",id:"the-turning-point-from-batch-to-real-time",level:2},{value:"First Generation Design",id:"first-generation-design",level:2},{value:"1. IOP Framework: A Real-Time DAG Executor",id:"1-iop-framework-a-real-time-dag-executor",level:3},{value:"2. Online Feature Store - 0th Version",id:"2-online-feature-store---0th-version",level:3},{value:"3. Interaction Store - 0th Version",id:"3-interaction-store---0th-version",level:3},{value:"Building the Online Feature Store - 0th Version",id:"building-the-online-feature-store---0th-version",level:2},{value:"Choosing the Right Tech Stack",id:"choosing-the-right-tech-stack",level:3},{value:"Streamlining the Data Flow",id:"streamlining-the-data-flow",level:3},{value:"The Challenges: Data Format and Storage",id:"the-challenges-data-format-and-storage",level:2},{value:"Feature Consistency",id:"feature-consistency",level:3},{value:"TTL Granularity",id:"ttl-granularity",level:3},{value:"Extensibility Across Databases",id:"extensibility-across-databases",level:3},{value:"Overcoming Technical Constraints",id:"overcoming-technical-constraints",level:2},{value:"The Solution: Schema Separation",id:"the-solution-schema-separation",level:2},{value:"Tracking Changes in Feature Groups",id:"tracking-changes-in-feature-groups",level:2},{value:"Common Real-World Scenarios:",id:"common-real-world-scenarios",level:3},{value:"The Solution: Schema Versioning",id:"the-solution-schema-versioning",level:2},{value:"Backward Compatibility",id:"backward-compatibility",level:3},{value:"Partial Availability Handling",id:"partial-availability-handling",level:3},{value:"Safe Writes Without Pipeline Pauses",id:"safe-writes-without-pipeline-pauses",level:3},{value:"Interaction Store - 0th Version",id:"interaction-store---0th-version",level:2},{value:"Event Ingestion",id:"event-ingestion",level:2},{value:"Storage Design",id:"storage-design",level:2},{value:"Why Redis?",id:"why-redis",level:3},{value:"Storage Structure",id:"storage-structure",level:3},{value:"Built-in Guardrails",id:"built-in-guardrails",level:3},{value:"Conclusion: Laying the Foundation for Real-Time ML",id:"conclusion-laying-the-foundation-for-real-time-ml",level:2}];function c(e){const n={a:"a",br:"br",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",hr:"hr",img:"img",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,r.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.img,{alt:"BharatMLStack",src:i(6496).A+"",width:"1396",height:"460"}),"\nIt all started in early 2022, over a casual Friday evening catch-up. Like many great origin stories, this one began with friendly banter between a group of backend engineers and data scientists. As the conversations unfolded, so did the roasting\u2014until one remark hit a little too close to home:"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.em,{children:'"Why are we still crunching data for Monthly Active Users (MAU) when the next day it\u2019s all about Daily Active Users (DAU)?"'})}),"\n",(0,s.jsx)(n.p,{children:"The laughter died down, and the question lingered. When we regrouped on Monday\u2014clear-headed and slightly reflective\u2014we decided to dig into the numbers. What they discovered was quite revealing: a large portion of compute resources wasn\u2019t being put to good use.\nMuch of the system\u2019s effort was spent supporting users who weren\u2019t actively engaging, and even for new users, the experience wasn\u2019t optimized to make a meaningful impact."}),"\n",(0,s.jsxs)(n.p,{children:["At the same time, Meesho had just launched a company-wide initiative to reduce costs\u2014and every team had to contribute. This realization sparked the journey that would eventually lead to the ",(0,s.jsx)(n.strong,{children:"Meesho ML Platform"}),", known today as ",(0,s.jsx)(n.strong,{children:"BharatMLStack"}),"."]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Alt Text",src:i(7121).A+"",width:"1600",height:"1078"})}),"\n",(0,s.jsx)(n.p,{children:"Before the ML Platform, our recommendation and ranking pipelines followed a batch processing approach:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Data Ingestion"}),": The Data Platform team executed ETL jobs to ingest raw user data\u2014including user profiles, interaction logs, and product impressions\u2014into designated S3 buckets."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Layer 1"}),": Embedding Generation: On the Data Science side, Spark jobs pulled data from multiple S3 sources, cleaned and preprocessed it, and applied matrix factorization to generate user and item embeddings. The processed data and embeddings were then stored back in S3 in a structured format."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Layer 2"}),": Candidate Generation (CG): In this stage, Spark jobs leveraged embeddings and historical interaction data to generate candidate recommendations for users. These candidate lists were subsequently written to S3."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Layer 3"}),": Ranking and Merging \u2013 A final round of processing ranked the generated candidates using ML models, combined different candidate lists, and stored the final ranked recommendations in a caching system."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Serving"}),': A microservice retrieved ranked recommendations from an in-memory data store via exposed APIs, delivering personalized listings across key surfaces such as "For You" and Category Landing Pages (CLP).']}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"This approach held up well\u2014until Meesho started seeing a significant surge in traffic."}),"\n",(0,s.jsx)(n.h2,{id:"the-turning-point-from-batch-to-real-time",children:"The Turning Point: From Batch to Real-Time"}),"\n",(0,s.jsxs)(n.p,{children:["At this time, the team was iterating on new ",(0,s.jsx)(n.strong,{children:"Ranker models"}),", and real-time inference seemed like the next logical step. But Rankers needed ",(0,s.jsx)(n.strong,{children:"real-time feature retrieval"}),", which meant an ",(0,s.jsx)(n.strong,{children:"online feature store"})," had to be built first."]}),"\n",(0,s.jsxs)(n.p,{children:["Exploring open-source options led to ",(0,s.jsx)(n.strong,{children:"cost vs. performance trade-offs"}),", but Meesho\u2019s surging traffic meant that ",(0,s.jsx)(n.strong,{children:"latency and stability were non-negotiable"}),". After multiple debates and stakeholder discussions, a bold decision was made:"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.em,{children:"We would build our own feature store."})}),"\n",(0,s.jsxs)(n.p,{children:["Meanwhile, efforts began to bring ",(0,s.jsx)(n.strong,{children:"Candidate Generators (CGs)"})," to real-time. The challenge? ",(0,s.jsx)(n.strong,{children:"Storing and retrieving user interactions quickly enough"})," to power real-time recommendations."]}),"\n",(0,s.jsxs)(n.p,{children:["As the team dove deeper, a new roadblock emerged:",(0,s.jsx)(n.br,{}),"\n","Our ML jobs were orchestrated using ",(0,s.jsx)(n.strong,{children:"Airflow DAGs"}),", giving data scientists flexibility in experimentation. But transitioning to real-time execution threatened this agility. Every change would now require backend engineering support, ",(0,s.jsx)(n.strong,{children:"slowing down iteration cycles"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["That\u2019s when the idea struck:",(0,s.jsx)(n.br,{}),"\n","We needed a ",(0,s.jsx)(n.strong,{children:"framework for real-time DAG execution"}),"\u2014one that preserved the same flexibility as Airflow but worked for ",(0,s.jsx)(n.strong,{children:"streaming data"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["This moment shaped the ",(0,s.jsx)(n.strong,{children:"next phase of our journey"}),"."]}),"\n",(0,s.jsx)(n.h2,{id:"first-generation-design",children:"First Generation Design"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Alt Text",src:i(1828).A+"",width:"1600",height:"1006"})}),"\n",(0,s.jsx)(n.h1,{id:"laying-the-groundwork-the-first-gen-ml-platform",children:"Laying the Groundwork: The First-Gen ML Platform"}),"\n",(0,s.jsx)(n.p,{children:"To solve these challenges, the team built three foundational components:"}),"\n",(0,s.jsx)(n.h3,{id:"1-iop-framework-a-real-time-dag-executor",children:"1. IOP Framework: A Real-Time DAG Executor"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Reusable Nodes"}),": Each DAG node (e.g., an invocation to a CG service, a ranker, or a filter) had to be implemented only once. After that, it could be reused across any workflow by referencing it in config."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Config-driven Dynamic Graphs"}),": Execution graphs were defined as adjacency lists stored in ",(0,s.jsx)(n.strong,{children:"ZooKeeper"}),", allowing teams to modify the sequence or structure of operations without touching application code."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Plug-and-play CGs"}),": The Candidate Generator interface was preserved, so a single CG node could call any CG service by passing ",(0,s.jsx)(n.code,{children:"cg_name"})," in the request. This drastically reduced the code surface area and improved maintainability."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Production-Grade DAGs"}),": DAGs were designed to execute in ",(0,s.jsx)(n.strong,{children:"low-latency real-time environments"}),", with support for ",(0,s.jsx)(n.strong,{children:"parallel execution, retries, and branching"}),"."]}),"\n"]}),"\n",(0,s.jsx)("u",{children:(0,s.jsx)(n.a,{href:"https://www.meesho.io/blog/rebuilding-meeshos-ranking-platform",children:"More about IOP DAG"})}),"\n",(0,s.jsx)(n.h3,{id:"2-online-feature-store---0th-version",children:"2. Online Feature Store - 0th Version"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Used ",(0,s.jsx)(n.strong,{children:"Cassandra"})," and ",(0,s.jsx)(n.strong,{children:"Redis"})," for low-latency feature serving."]}),"\n",(0,s.jsxs)(n.li,{children:["Maintained feature consistency using ",(0,s.jsx)(n.strong,{children:"Feature Groups"})," with TTL-based expiry."]}),"\n",(0,s.jsxs)(n.li,{children:["A hybrid schema was used: feature keys stored in ",(0,s.jsx)(n.strong,{children:"ZooKeeper"}),", data stored in ",(0,s.jsx)(n.strong,{children:"compact arrays"}),"."]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"3-interaction-store---0th-version",children:"3. Interaction Store - 0th Version"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Captured real-time user interactions like clicks, orders, and add-to-cart events."}),"\n",(0,s.jsxs)(n.li,{children:["Stored event data in ",(0,s.jsx)(n.strong,{children:"Redis ZSETs (sorted sets)"})," to enable fast lookups for recommendation engines."]}),"\n",(0,s.jsxs)(n.li,{children:["Provided an API to fetch a user's ",(0,s.jsxs)(n.strong,{children:["last ",(0,s.jsx)(n.em,{children:"k"})," interactions"]})," or ",(0,s.jsx)(n.strong,{children:"interactions within a time window"}),"."]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["With these components in place, ",(0,s.jsx)(n.strong,{children:"real-time ML at Meesho became a reality"}),"."]}),"\n",(0,s.jsx)(n.p,{children:"This was just the beginning."}),"\n",(0,s.jsx)(n.h2,{id:"building-the-online-feature-store---0th-version",children:"Building the Online Feature Store - 0th Version"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Alt text",src:i(1965).A+"",width:"1574",height:"562"})}),"\n",(0,s.jsx)(n.h3,{id:"choosing-the-right-tech-stack",children:"Choosing the Right Tech Stack"}),"\n",(0,s.jsxs)(n.p,{children:["We spent considerable time evaluating various databases, caches, and communication protocols for our ",(0,s.jsx)(n.strong,{children:"online feature store"}),". After carefully weighing ",(0,s.jsx)(n.strong,{children:"cost, latency, throughput"}),", and ",(0,s.jsx)(n.strong,{children:"operational stability"}),", we settled on a combination of:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Cassandra"})," and ",(0,s.jsx)(n.strong,{children:"Redis"})," for storage"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"gRPC + Proto3"})," as our communication layer"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"streamlining-the-data-flow",children:"Streamlining the Data Flow"}),"\n",(0,s.jsx)(n.p,{children:"To keep things simple in the initial version:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Feature engineering jobs"})," wrote raw outputs to an ",(0,s.jsx)(n.strong,{children:"S3 bucket"})]}),"\n",(0,s.jsxs)(n.li,{children:["A ",(0,s.jsx)(n.strong,{children:"daily feature push job"}),":","\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Read from S3"}),"\n",(0,s.jsxs)(n.li,{children:["Grouped related features into ",(0,s.jsx)(n.strong,{children:"Feature Groups"})," (ensuring consistency)"]}),"\n",(0,s.jsxs)(n.li,{children:["Pushed them to ",(0,s.jsx)(n.strong,{children:"Kafka"})]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"For features requiring frequent updates:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Ad-hoc jobs"})," computed features in higher frequency"]}),"\n",(0,s.jsxs)(n.li,{children:["These jobs pushed to both ",(0,s.jsx)(n.strong,{children:"Kafka"})," and ",(0,s.jsx)(n.strong,{children:"S3"})," (S3 preserved historical data for future model training)"]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"the-challenges-data-format-and-storage",children:"The Challenges: Data Format and Storage"}),"\n",(0,s.jsxs)(n.p,{children:["One of the most critical design challenges was how to store feature data ",(0,s.jsx)(n.strong,{children:"efficiently and consistently"}),", especially in databases like ",(0,s.jsx)(n.strong,{children:"Cassandra"})," and ",(0,s.jsx)(n.strong,{children:"Redis"}),", which come with unique storage constraints."]}),"\n",(0,s.jsx)(n.p,{children:"We had to solve for three key requirements:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.h3,{id:"feature-consistency",children:"Feature Consistency"}),"\n",(0,s.jsxs)(n.p,{children:["When a feature group contains features like ",(0,s.jsx)(n.code,{children:"order_count_1h"})," and ",(0,s.jsx)(n.code,{children:"click_count_1h"}),", both must reflect the ",(0,s.jsx)(n.strong,{children:"same time window"}),". Inconsistent updates would lead to ",(0,s.jsx)(n.strong,{children:"unreliable model predictions"}),"."]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.h3,{id:"ttl-granularity",children:"TTL Granularity"}),"\n",(0,s.jsxs)(n.p,{children:["Each feature group required an ",(0,s.jsx)(n.strong,{children:"expiry timestamp"}),", so that ",(0,s.jsx)(n.strong,{children:"all features within it expired together"}),"\u2014preserving consistency during reads."]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.h3,{id:"extensibility-across-databases",children:"Extensibility Across Databases"}),"\n",(0,s.jsxs)(n.p,{children:["We anticipated that infra needs would evolve. To future-proof our system, the data format was designed to be ",(0,s.jsx)(n.strong,{children:"decoupled from DB-specific layouts"}),", enabling portability to systems like ",(0,s.jsx)(n.strong,{children:"ScyllaDB"}),", ",(0,s.jsx)(n.strong,{children:"DynamoDB"}),", ",(0,s.jsx)(n.strong,{children:"HBase"}),", or ",(0,s.jsx)(n.strong,{children:"BigTable"}),"."]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"overcoming-technical-constraints",children:"Overcoming Technical Constraints"}),"\n",(0,s.jsx)(n.p,{children:'At the time, we were using Cassandra, which not only imposed a soft limit of 75 columns per row, but also exhibited significant performance degradation as the number of columns increased further, particularly in memory constrained machines. Wide rows caused high memory usage during reads, unpredictable latencies due to heavy deserialization overhead, and inefficiencies during compactions and repairs. This ruled out the naive "one column per feature" approach. We needed a format that was compact, minimized the number of columns, and remained efficient and portable across different storage systems.'}),"\n",(0,s.jsx)(n.h2,{id:"the-solution-schema-separation",children:"The Solution: Schema Separation"}),"\n",(0,s.jsx)(n.p,{children:"We introduced the concept of Feature Groups\u2014logical groupings of features that must remain consistent with one another.\nTo represent these groups efficiently, we adopted a layered storage approach:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Feature Labels (Keys)"})," were stored in ZooKeeper, serving as the schema."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Feature Values"})," were stored as a comma-separated string array in Cassandra or Redis."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Expiry Timestamp and Schema Version"})," were appended using a semi-colon delimiter at the end of the string."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Example:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"feature_1_value,feature_2_value,feature_3_value;expiry_ts\n"})}),"\n",(0,s.jsx)(n.p,{children:"This format allowed:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Consistent writes and reads at the group level"}),"\n",(0,s.jsx)(n.li,{children:"Easy parsing of feature values using the schema lookup from ZooKeeper"}),"\n",(0,s.jsx)(n.li,{children:"Efficient storage with minimal DB column usage"}),"\n",(0,s.jsx)(n.li,{children:"Support for per-group TTLs and schema evolution"}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"tracking-changes-in-feature-groups",children:"Tracking Changes in Feature Groups"}),"\n",(0,s.jsx)(n.p,{children:"Feature groups don\u2019t stay static. As models evolve, features get added, renamed, or removed. But schema changes often go live before the data is ready\u2014and stopping ingestion just to wait for everything to align isn't feasible."}),"\n",(0,s.jsx)(n.h3,{id:"common-real-world-scenarios",children:"Common Real-World Scenarios:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"A new feature is added to the schema, but ingestion jobs still use the older schema version."}),"\n",(0,s.jsx)(n.li,{children:"Ongoing writes don\u2019t include the newly added feature, and stopping ingestion would break freshness for existing features."}),"\n",(0,s.jsx)(n.li,{children:"During serving, models request a mix of old and new features, depending on rollout stages."}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"the-solution-schema-versioning",children:"The Solution: Schema Versioning"}),"\n",(0,s.jsx)(n.p,{children:"We solved this with versioned feature group schemas, which unlocked several capabilities:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.h3,{id:"backward-compatibility",children:"Backward Compatibility"}),"\n","Older ingestion jobs can continue writing using older schema versions. During reads, the system uses the schema version embedded in the value to interpret the data correctly."]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.h3,{id:"partial-availability-handling",children:"Partial Availability Handling"}),"\n","During inference, if some features in the request aren\u2019t available (due to rollout delays or missing data), the system serves default values, ensuring the inference call doesn\u2019t fail."]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.h3,{id:"safe-writes-without-pipeline-pauses",children:"Safe Writes Without Pipeline Pauses"}),"\n","With schema versioning, we no longer had to stop ingestion pipelines for schema updates. Writes using previous versions can continue safely, and downstream consumers evolve independently.\nThis design gave us the flexibility to move fast without breaking things\u2014preserving data quality, enabling experimentation, and ensuring reliability at scale."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Alt Text",src:i(8449).A+"",width:"1600",height:"599"})}),"\n",(0,s.jsx)(n.h2,{id:"interaction-store---0th-version",children:"Interaction Store - 0th Version"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Alt Text",src:i(4959).A+"",width:"1600",height:"518"})}),"\n",(0,s.jsxs)(n.p,{children:["To power real-time Candidate Generators (CGs), we needed fast access to user behavior signals\u2014like what a user recently clicked, ordered, or added to their cart. These interactions form the basis for many real-time recommendations, such as ",(0,s.jsx)(n.strong,{children:"Similar Products"}),", ",(0,s.jsx)(n.strong,{children:"People Also Viewed"}),", or ",(0,s.jsx)(n.strong,{children:"Recently Ordered Again"}),".\nFor the ",(0,s.jsx)(n.strong,{children:"0th version"})," of the Interaction Store, we focused on a design that was ",(0,s.jsx)(n.strong,{children:"simple, fast, and reliable"})," \u2014 optimized for high-throughput ingestion and low-latency lookups."]}),"\n",(0,s.jsx)(n.h2,{id:"event-ingestion",children:"Event Ingestion"}),"\n",(0,s.jsx)(n.p,{children:"We instrumented our backend services to emit key user interaction events to Kafka in real time. These included:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Click"}),"\n",(0,s.jsx)(n.li,{children:"Order"}),"\n",(0,s.jsx)(n.li,{children:"Add to Cart"}),"\n",(0,s.jsx)(n.li,{children:"Wishlist"}),"\n",(0,s.jsx)(n.li,{children:"Share"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Each event carried essential metadata:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"userId \u2014 uniquely identifies the user"}),"\n",(0,s.jsx)(n.li,{children:"productId \u2014 the item being interacted with"}),"\n",(0,s.jsx)(n.li,{children:"timestamp \u2014 the moment the interaction occurred"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"This decoupled the interaction logging from storage, allowing ingestion and consumption to scale independently."}),"\n",(0,s.jsx)(n.h2,{id:"storage-design",children:"Storage Design"}),"\n",(0,s.jsx)(n.p,{children:"To store these events, we built Kafka consumers that processed the incoming streams and wrote the data into Redis, using sorted sets (ZSETs) as the primary data structure."}),"\n",(0,s.jsx)(n.h3,{id:"why-redis",children:"Why Redis?"}),"\n",(0,s.jsx)(n.p,{children:"Redis gave us:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Low-latency"})," reads and writes"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Time-ordered data"})," using ZSETs (via score = timestamp)"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Native TTL support"}),", if needed in later versions"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"In-memory performance"})," \u2014ideal for real-time CGs"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"storage-structure",children:"Storage Structure"}),"\n",(0,s.jsx)(n.p,{children:"Each user\u2019s interactions were stored using a composite key format, uniquely identifying the user and interaction type. This structure allowed efficient organization and quick retrieval of recent activity for recommendation generation:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"userId_eventType \u2192 ZSET[...(pid, ts)...]\n"})}),"\n",(0,s.jsx)(n.p,{children:"Within each ZSET:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["The ",(0,s.jsx)(n.strong,{children:"timestamp"})," served as the score, maintaining temporal order"]}),"\n",(0,s.jsxs)(n.li,{children:["The ",(0,s.jsx)(n.strong,{children:"productId"})," (optionally with metadata) was the ",(0,s.jsx)(n.strong,{children:"value"})]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"This allowed us to efficiently retrieve the interactions with HTTP-based API server with two query modes:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Fetch the ",(0,s.jsx)(n.strong,{children:"last k interactions"})," of a specific type for a given user with ",(0,s.jsx)(n.code,{children:"ZREVRANGE(userId_eventType, count)"})]}),"\n",(0,s.jsxs)(n.li,{children:["Retrieve ",(0,s.jsx)(n.strong,{children:"all interactions within a time range"})," (e.g., last 24 hours) with ",(0,s.jsx)(n.code,{children:"ZREVRANGEBYSCORE(userId_eventType, timeRange)"})]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"built-in-guardrails",children:"Built-in Guardrails"}),"\n",(0,s.jsx)(n.p,{children:"Since Redis was the sole store, we implemented High Availability (HA) to prevent data loss. To optimize memory usage, we also enforced size limits per event type\u2014only storing the last k interactions per user, with older entries getting truncated."}),"\n",(0,s.jsx)(n.h2,{id:"conclusion-laying-the-foundation-for-real-time-ml",children:"Conclusion: Laying the Foundation for Real-Time ML"}),"\n",(0,s.jsxs)(n.p,{children:["In this first phase, we tackled the ",(0,s.jsx)(n.strong,{children:"fundamentals"}),"\u2014shifting from batch-based recommendations to a ",(0,s.jsx)(n.strong,{children:"real-time Recommendation"})," using ML platform that could keep up with Meesho\u2019s growth."]}),"\n",(0,s.jsxs)(n.p,{children:["With the ",(0,s.jsx)(n.strong,{children:"IOP Framework"}),", ",(0,s.jsx)(n.strong,{children:"Online Feature Store"}),", and ",(0,s.jsx)(n.strong,{children:"Interaction Store"}),", we built the core infrastructure to support real-time personalization at scale. These wins have already unlocked:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"\u2705 Faster, more dynamic recommendations for millions of users."}),"\n",(0,s.jsx)(n.li,{children:"\u2705 Better infrastructure efficiency, reducing wasted compute power."}),"\n",(0,s.jsx)(n.li,{children:"\u2705 A flexible, modular system that allows for further experimentation."}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["But this is just the beginning. While we've solved key challenges, ",(0,s.jsx)(n.strong,{children:"certain roadblocks remain"})," \u2014from optimizing ",(0,s.jsx)(n.strong,{children:"cost-performance trade-offs"})," to ",(0,s.jsx)(n.strong,{children:"seamlessly evolving schemas"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["This foundational work laid the path for a reliable and scalable ",(0,s.jsx)(n.strong,{children:"real-time feature serving layer"}),"."]})]})}function h(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(c,{...e})}):c(e)}}}]); \ No newline at end of file diff --git a/docs/assets/js/a2d4c71d.c8f92a2a.js b/docs/assets/js/a2d4c71d.c8f92a2a.js new file mode 100644 index 00000000..7ce6a84e --- /dev/null +++ b/docs/assets/js/a2d4c71d.c8f92a2a.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[8643],{2209:a=>{a.exports=JSON.parse('{"tag":{"label":"episodic-memory","permalink":"/BharatMLStack/blog/tags/episodic-memory","allTagsPath":"/BharatMLStack/blog/tags","count":1,"unlisted":false},"listMetadata":{"permalink":"/BharatMLStack/blog/tags/episodic-memory","page":1,"postsPerPage":10,"totalPages":1,"totalCount":1,"blogDescription":"Blog","blogTitle":"Blog"}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/a6aa9e1f.7671586a.js b/docs/assets/js/a6aa9e1f.7671586a.js new file mode 100644 index 00000000..9f3adf31 --- /dev/null +++ b/docs/assets/js/a6aa9e1f.7671586a.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[7643],{2907:(e,t,a)=>{a.d(t,{A:()=>U});a(6540);var n=a(4164),s=a(4096),r=a(4848);function i({children:e,className:t}){return(0,r.jsx)("article",{className:t,children:e})}var l=a(8774);const o={title:"title_f1Hy"};function c({className:e}){const{metadata:t,isBlogPostPage:a}=(0,s.e7)(),{permalink:i,title:c}=t,d=a?"h1":"h2";return(0,r.jsx)(d,{className:(0,n.A)(o.title,e),children:a?c:(0,r.jsx)(l.A,{to:i,children:c})})}var d=a(1312),g=a(5846),m=a(6266);const u={container:"container_mt6G"};function h({readingTime:e}){const t=function(){const{selectMessage:e}=(0,g.W)();return t=>{const a=Math.ceil(t);return e(a,(0,d.T)({id:"theme.blog.post.readingTime.plurals",description:'Pluralized label for "{readingTime} min read". Use as much plural forms (separated by "|") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)',message:"One min read|{readingTime} min read"},{readingTime:a}))}}();return(0,r.jsx)(r.Fragment,{children:t(e)})}function p({date:e,formattedDate:t}){return(0,r.jsx)("time",{dateTime:e,children:t})}function x(){return(0,r.jsx)(r.Fragment,{children:" \xb7 "})}function j({className:e}){const{metadata:t}=(0,s.e7)(),{date:a,readingTime:i}=t,l=(0,m.i)({day:"numeric",month:"long",year:"numeric",timeZone:"UTC"});return(0,r.jsxs)("div",{className:(0,n.A)(u.container,"margin-vert--md",e),children:[(0,r.jsx)(p,{date:a,formattedDate:(o=a,l.format(new Date(o)))}),void 0!==i&&(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(x,{}),(0,r.jsx)(h,{readingTime:i})]})]});var o}var A=a(6382);const b={authorCol:"authorCol_Hf19",imageOnlyAuthorRow:"imageOnlyAuthorRow_pa_O",imageOnlyAuthorCol:"imageOnlyAuthorCol_G86a"};function f({className:e}){const{metadata:{authors:t},assets:a}=(0,s.e7)();if(0===t.length)return null;const i=t.every(({name:e})=>!e),l=1===t.length;return(0,r.jsx)("div",{className:(0,n.A)("margin-top--md margin-bottom--sm",i?b.imageOnlyAuthorRow:"row",e),children:t.map((e,t)=>(0,r.jsx)("div",{className:(0,n.A)(!i&&(l?"col col--12":"col col--6"),i?b.imageOnlyAuthorCol:b.authorCol),children:(0,r.jsx)(A.A,{author:{...e,imageURL:a.authorsImageUrls[t]??e.imageURL}})},t))})}function v(){return(0,r.jsxs)("header",{children:[(0,r.jsx)(c,{}),(0,r.jsx)(j,{}),(0,r.jsx)(f,{})]})}var N=a(440),_=a(3253);function T({children:e,className:t}){const{isBlogPostPage:a}=(0,s.e7)();return(0,r.jsx)("div",{id:a?N.LU:void 0,className:(0,n.A)("markdown",t),children:(0,r.jsx)(_.A,{children:e})})}var k=a(7559),w=a(4336),y=a(4434);function P(){return(0,r.jsx)("b",{children:(0,r.jsx)(d.A,{id:"theme.blog.post.readMore",description:"The label used in blog post item excerpts to link to full blog posts",children:"Read more"})})}function R(e){const{blogPostTitle:t,...a}=e;return(0,r.jsx)(l.A,{"aria-label":(0,d.T)({message:"Read more about {title}",id:"theme.blog.post.readMoreLabel",description:"The ARIA label for the link to full blog posts from excerpts"},{title:t}),...a,children:(0,r.jsx)(P,{})})}function C(){const{metadata:e,isBlogPostPage:t}=(0,s.e7)(),{tags:a,title:i,editUrl:l,hasTruncateMarker:o,lastUpdatedBy:c,lastUpdatedAt:d}=e,g=!t&&o,m=a.length>0;if(!(m||g||l))return null;if(t){const e=!!(l||d||c);return(0,r.jsxs)("footer",{className:"docusaurus-mt-lg",children:[m&&(0,r.jsx)("div",{className:(0,n.A)("row","margin-top--sm",k.G.blog.blogFooterEditMetaRow),children:(0,r.jsx)("div",{className:"col",children:(0,r.jsx)(y.A,{tags:a})})}),e&&(0,r.jsx)(w.A,{className:(0,n.A)("margin-top--sm",k.G.blog.blogFooterEditMetaRow),editUrl:l,lastUpdatedAt:d,lastUpdatedBy:c})]})}return(0,r.jsxs)("footer",{className:"row docusaurus-mt-lg",children:[m&&(0,r.jsx)("div",{className:(0,n.A)("col",{"col--9":g}),children:(0,r.jsx)(y.A,{tags:a})}),g&&(0,r.jsx)("div",{className:(0,n.A)("col text--right",{"col--3":m}),children:(0,r.jsx)(R,{blogPostTitle:i,to:e.permalink})})]})}function U({children:e,className:t}){const a=function(){const{isBlogPostPage:e}=(0,s.e7)();return e?void 0:"margin-bottom--xl"}();return(0,r.jsxs)(i,{className:(0,n.A)(a,t),children:[(0,r.jsx)(v,{}),(0,r.jsx)(T,{children:e}),(0,r.jsx)(C,{})]})}},3892:(e,t,a)=>{a.d(t,{A:()=>i});a(6540);var n=a(4096),s=a(2907),r=a(4848);function i({items:e,component:t=s.A}){return(0,r.jsx)(r.Fragment,{children:e.map(({content:e})=>(0,r.jsx)(n.in,{content:e,children:(0,r.jsx)(t,{children:(0,r.jsx)(e,{})})},e.metadata.permalink))})}},4434:(e,t,a)=>{a.d(t,{A:()=>o});a(6540);var n=a(4164),s=a(1312),r=a(6133);const i={tags:"tags_jXut",tag:"tag_QGVx"};var l=a(4848);function o({tags:e}){return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)("b",{children:(0,l.jsx)(s.A,{id:"theme.tags.tagsListLabel",description:"The label alongside a tag list",children:"Tags:"})}),(0,l.jsx)("ul",{className:(0,n.A)(i.tags,"padding--none","margin-left--sm"),children:e.map(e=>(0,l.jsx)("li",{className:i.tag,children:(0,l.jsx)(r.A,{...e})},e.permalink))})]})}},5124:(e,t,a)=>{a.r(t),a.d(t,{default:()=>j});a(6540);var n=a(4164),s=a(4586),r=a(5500),i=a(7559),l=a(8027),o=a(7713),c=a(1463),d=a(3892),g=a(5260),m=a(4096),u=a(4848);function h(e){const t=(0,m.kJ)(e);return(0,u.jsx)(g.A,{children:(0,u.jsx)("script",{type:"application/ld+json",children:JSON.stringify(t)})})}function p(e){const{metadata:t}=e,{siteConfig:{title:a}}=(0,s.A)(),{blogDescription:n,blogTitle:i,permalink:l}=t,o="/"===l?a:i;return(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)(r.be,{title:o,description:n}),(0,u.jsx)(c.A,{tag:"blog_posts_list"})]})}function x(e){const{metadata:t,items:a,sidebar:n}=e;return(0,u.jsxs)(l.A,{sidebar:n,children:[(0,u.jsx)(d.A,{items:a}),(0,u.jsx)(o.A,{metadata:t})]})}function j(e){return(0,u.jsxs)(r.e3,{className:(0,n.A)(i.G.wrapper.blogPages,i.G.page.blogListPage),children:[(0,u.jsx)(p,{...e}),(0,u.jsx)(h,{...e}),(0,u.jsx)(x,{...e})]})}},6133:(e,t,a)=>{a.d(t,{A:()=>l});a(6540);var n=a(4164),s=a(8774);const r={tag:"tag_zVej",tagRegular:"tagRegular_sFm0",tagWithCount:"tagWithCount_h2kH"};var i=a(4848);function l({permalink:e,label:t,count:a,description:l}){return(0,i.jsxs)(s.A,{rel:"tag",href:e,title:l,className:(0,n.A)(r.tag,a?r.tagWithCount:r.tagRegular),children:[t,a&&(0,i.jsx)("span",{children:a})]})}},7713:(e,t,a)=>{a.d(t,{A:()=>i});a(6540);var n=a(1312),s=a(9022),r=a(4848);function i(e){const{metadata:t}=e,{previousPage:a,nextPage:i}=t;return(0,r.jsxs)("nav",{className:"pagination-nav","aria-label":(0,n.T)({id:"theme.blog.paginator.navAriaLabel",message:"Blog list page navigation",description:"The ARIA label for the blog pagination"}),children:[a&&(0,r.jsx)(s.A,{permalink:a,title:(0,r.jsx)(n.A,{id:"theme.blog.paginator.newerEntries",description:"The label used to navigate to the newer blog posts page (previous page)",children:"Newer entries"})}),i&&(0,r.jsx)(s.A,{permalink:i,title:(0,r.jsx)(n.A,{id:"theme.blog.paginator.olderEntries",description:"The label used to navigate to the older blog posts page (next page)",children:"Older entries"}),isNext:!0})]})}},9022:(e,t,a)=>{a.d(t,{A:()=>i});a(6540);var n=a(4164),s=a(8774),r=a(4848);function i(e){const{permalink:t,title:a,subLabel:i,isNext:l}=e;return(0,r.jsxs)(s.A,{className:(0,n.A)("pagination-nav__link",l?"pagination-nav__link--next":"pagination-nav__link--prev"),to:t,children:[i&&(0,r.jsx)("div",{className:"pagination-nav__sublabel",children:i}),(0,r.jsx)("div",{className:"pagination-nav__label",children:a})]})}}}]); \ No newline at end of file diff --git a/docs/assets/js/a6aa9e1f.a34fe105.js b/docs/assets/js/a6aa9e1f.a34fe105.js deleted file mode 100644 index 05cd3173..00000000 --- a/docs/assets/js/a6aa9e1f.a34fe105.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[7643],{2053:(e,t,a)=>{a.d(t,{A:()=>o});a(6540);var n=a(4164),s=a(1312),r=a(6133);const i={tags:"tags_jXut",tag:"tag_QGVx"};var l=a(4848);function o({tags:e}){return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)("b",{children:(0,l.jsx)(s.A,{id:"theme.tags.tagsListLabel",description:"The label alongside a tag list",children:"Tags:"})}),(0,l.jsx)("ul",{className:(0,n.A)(i.tags,"padding--none","margin-left--sm"),children:e.map((e=>(0,l.jsx)("li",{className:i.tag,children:(0,l.jsx)(r.A,{...e})},e.permalink)))})]})}},2907:(e,t,a)=>{a.d(t,{A:()=>U});a(6540);var n=a(4164),s=a(4096),r=a(4848);function i({children:e,className:t}){return(0,r.jsx)("article",{className:t,children:e})}var l=a(8774);const o={title:"title_f1Hy"};function c({className:e}){const{metadata:t,isBlogPostPage:a}=(0,s.e7)(),{permalink:i,title:c}=t,d=a?"h1":"h2";return(0,r.jsx)(d,{className:(0,n.A)(o.title,e),children:a?c:(0,r.jsx)(l.A,{to:i,children:c})})}var d=a(1312),g=a(5846),m=a(6266);const u={container:"container_mt6G"};function h({readingTime:e}){const t=function(){const{selectMessage:e}=(0,g.W)();return t=>{const a=Math.ceil(t);return e(a,(0,d.T)({id:"theme.blog.post.readingTime.plurals",description:'Pluralized label for "{readingTime} min read". Use as much plural forms (separated by "|") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)',message:"One min read|{readingTime} min read"},{readingTime:a}))}}();return(0,r.jsx)(r.Fragment,{children:t(e)})}function p({date:e,formattedDate:t}){return(0,r.jsx)("time",{dateTime:e,children:t})}function x(){return(0,r.jsx)(r.Fragment,{children:" \xb7 "})}function j({className:e}){const{metadata:t}=(0,s.e7)(),{date:a,readingTime:i}=t,l=(0,m.i)({day:"numeric",month:"long",year:"numeric",timeZone:"UTC"});return(0,r.jsxs)("div",{className:(0,n.A)(u.container,"margin-vert--md",e),children:[(0,r.jsx)(p,{date:a,formattedDate:(o=a,l.format(new Date(o)))}),void 0!==i&&(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(x,{}),(0,r.jsx)(h,{readingTime:i})]})]});var o}var A=a(6382);const b={authorCol:"authorCol_Hf19",imageOnlyAuthorRow:"imageOnlyAuthorRow_pa_O",imageOnlyAuthorCol:"imageOnlyAuthorCol_G86a"};function f({className:e}){const{metadata:{authors:t},assets:a}=(0,s.e7)();if(0===t.length)return null;const i=t.every((({name:e})=>!e)),l=1===t.length;return(0,r.jsx)("div",{className:(0,n.A)("margin-top--md margin-bottom--sm",i?b.imageOnlyAuthorRow:"row",e),children:t.map(((e,t)=>(0,r.jsx)("div",{className:(0,n.A)(!i&&(l?"col col--12":"col col--6"),i?b.imageOnlyAuthorCol:b.authorCol),children:(0,r.jsx)(A.A,{author:{...e,imageURL:a.authorsImageUrls[t]??e.imageURL}})},t)))})}function v(){return(0,r.jsxs)("header",{children:[(0,r.jsx)(c,{}),(0,r.jsx)(j,{}),(0,r.jsx)(f,{})]})}var N=a(440),_=a(3253);function T({children:e,className:t}){const{isBlogPostPage:a}=(0,s.e7)();return(0,r.jsx)("div",{id:a?N.LU:void 0,className:(0,n.A)("markdown",t),children:(0,r.jsx)(_.A,{children:e})})}var k=a(7559),w=a(4336),y=a(2053);function P(){return(0,r.jsx)("b",{children:(0,r.jsx)(d.A,{id:"theme.blog.post.readMore",description:"The label used in blog post item excerpts to link to full blog posts",children:"Read more"})})}function R(e){const{blogPostTitle:t,...a}=e;return(0,r.jsx)(l.A,{"aria-label":(0,d.T)({message:"Read more about {title}",id:"theme.blog.post.readMoreLabel",description:"The ARIA label for the link to full blog posts from excerpts"},{title:t}),...a,children:(0,r.jsx)(P,{})})}function C(){const{metadata:e,isBlogPostPage:t}=(0,s.e7)(),{tags:a,title:i,editUrl:l,hasTruncateMarker:o,lastUpdatedBy:c,lastUpdatedAt:d}=e,g=!t&&o,m=a.length>0;if(!(m||g||l))return null;if(t){const e=!!(l||d||c);return(0,r.jsxs)("footer",{className:"docusaurus-mt-lg",children:[m&&(0,r.jsx)("div",{className:(0,n.A)("row","margin-top--sm",k.G.blog.blogFooterEditMetaRow),children:(0,r.jsx)("div",{className:"col",children:(0,r.jsx)(y.A,{tags:a})})}),e&&(0,r.jsx)(w.A,{className:(0,n.A)("margin-top--sm",k.G.blog.blogFooterEditMetaRow),editUrl:l,lastUpdatedAt:d,lastUpdatedBy:c})]})}return(0,r.jsxs)("footer",{className:"row docusaurus-mt-lg",children:[m&&(0,r.jsx)("div",{className:(0,n.A)("col",{"col--9":g}),children:(0,r.jsx)(y.A,{tags:a})}),g&&(0,r.jsx)("div",{className:(0,n.A)("col text--right",{"col--3":m}),children:(0,r.jsx)(R,{blogPostTitle:i,to:e.permalink})})]})}function U({children:e,className:t}){const a=function(){const{isBlogPostPage:e}=(0,s.e7)();return e?void 0:"margin-bottom--xl"}();return(0,r.jsxs)(i,{className:(0,n.A)(a,t),children:[(0,r.jsx)(v,{}),(0,r.jsx)(T,{children:e}),(0,r.jsx)(C,{})]})}},3892:(e,t,a)=>{a.d(t,{A:()=>i});a(6540);var n=a(4096),s=a(2907),r=a(4848);function i({items:e,component:t=s.A}){return(0,r.jsx)(r.Fragment,{children:e.map((({content:e})=>(0,r.jsx)(n.in,{content:e,children:(0,r.jsx)(t,{children:(0,r.jsx)(e,{})})},e.metadata.permalink)))})}},5124:(e,t,a)=>{a.r(t),a.d(t,{default:()=>j});a(6540);var n=a(4164),s=a(4586),r=a(5500),i=a(7559),l=a(8027),o=a(7713),c=a(1463),d=a(3892),g=a(5260),m=a(4096),u=a(4848);function h(e){const t=(0,m.kJ)(e);return(0,u.jsx)(g.A,{children:(0,u.jsx)("script",{type:"application/ld+json",children:JSON.stringify(t)})})}function p(e){const{metadata:t}=e,{siteConfig:{title:a}}=(0,s.A)(),{blogDescription:n,blogTitle:i,permalink:l}=t,o="/"===l?a:i;return(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)(r.be,{title:o,description:n}),(0,u.jsx)(c.A,{tag:"blog_posts_list"})]})}function x(e){const{metadata:t,items:a,sidebar:n}=e;return(0,u.jsxs)(l.A,{sidebar:n,children:[(0,u.jsx)(d.A,{items:a}),(0,u.jsx)(o.A,{metadata:t})]})}function j(e){return(0,u.jsxs)(r.e3,{className:(0,n.A)(i.G.wrapper.blogPages,i.G.page.blogListPage),children:[(0,u.jsx)(p,{...e}),(0,u.jsx)(h,{...e}),(0,u.jsx)(x,{...e})]})}},6133:(e,t,a)=>{a.d(t,{A:()=>l});a(6540);var n=a(4164),s=a(8774);const r={tag:"tag_zVej",tagRegular:"tagRegular_sFm0",tagWithCount:"tagWithCount_h2kH"};var i=a(4848);function l({permalink:e,label:t,count:a,description:l}){return(0,i.jsxs)(s.A,{rel:"tag",href:e,title:l,className:(0,n.A)(r.tag,a?r.tagWithCount:r.tagRegular),children:[t,a&&(0,i.jsx)("span",{children:a})]})}},7713:(e,t,a)=>{a.d(t,{A:()=>i});a(6540);var n=a(1312),s=a(9022),r=a(4848);function i(e){const{metadata:t}=e,{previousPage:a,nextPage:i}=t;return(0,r.jsxs)("nav",{className:"pagination-nav","aria-label":(0,n.T)({id:"theme.blog.paginator.navAriaLabel",message:"Blog list page navigation",description:"The ARIA label for the blog pagination"}),children:[a&&(0,r.jsx)(s.A,{permalink:a,title:(0,r.jsx)(n.A,{id:"theme.blog.paginator.newerEntries",description:"The label used to navigate to the newer blog posts page (previous page)",children:"Newer entries"})}),i&&(0,r.jsx)(s.A,{permalink:i,title:(0,r.jsx)(n.A,{id:"theme.blog.paginator.olderEntries",description:"The label used to navigate to the older blog posts page (next page)",children:"Older entries"}),isNext:!0})]})}},9022:(e,t,a)=>{a.d(t,{A:()=>i});a(6540);var n=a(4164),s=a(8774),r=a(4848);function i(e){const{permalink:t,title:a,subLabel:i,isNext:l}=e;return(0,r.jsxs)(s.A,{className:(0,n.A)("pagination-nav__link",l?"pagination-nav__link--next":"pagination-nav__link--prev"),to:t,children:[i&&(0,r.jsx)("div",{className:"pagination-nav__sublabel",children:i}),(0,r.jsx)("div",{className:"pagination-nav__label",children:a})]})}}}]); \ No newline at end of file diff --git a/docs/assets/js/a94703ab.3a38a667.js b/docs/assets/js/a94703ab.3a38a667.js new file mode 100644 index 00000000..dbe14b8d --- /dev/null +++ b/docs/assets/js/a94703ab.3a38a667.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[9048],{1377:(e,t,n)=>{n.r(t),n.d(t,{default:()=>pe});var a=n(6540),o=n(4164),i=n(5500),s=n(7559),r=n(6972),c=n(609),l=n(1312),d=n(3104),u=n(5062);const m={backToTopButton:"backToTopButton_sjWU",backToTopButtonShow:"backToTopButtonShow_xfvO"};var b=n(4848);function h(){const{shown:e,scrollToTop:t}=function({threshold:e}){const[t,n]=(0,a.useState)(!1),o=(0,a.useRef)(!1),{startScroll:i,cancelScroll:s}=(0,d.gk)();return(0,d.Mq)(({scrollY:t},a)=>{const i=a?.scrollY;i&&(o.current?o.current=!1:t>=i?(s(),n(!1)):t<e?n(!1):t+window.innerHeight<document.documentElement.scrollHeight&&n(!0))}),(0,u.$)(e=>{e.location.hash&&(o.current=!0,n(!1))}),{shown:t,scrollToTop:()=>i(0)}}({threshold:300});return(0,b.jsx)("button",{"aria-label":(0,l.T)({id:"theme.BackToTopButton.buttonAriaLabel",message:"Scroll back to top",description:"The ARIA label for the back to top button"}),className:(0,o.A)("clean-btn",s.G.common.backToTopButton,m.backToTopButton,e&&m.backToTopButtonShow),type:"button",onClick:t})}var p=n(3109),x=n(6347),f=n(4581),j=n(6342),v=n(3465);function _(e){return(0,b.jsx)("svg",{width:"20",height:"20","aria-hidden":"true",...e,children:(0,b.jsxs)("g",{fill:"#7a7a7a",children:[(0,b.jsx)("path",{d:"M9.992 10.023c0 .2-.062.399-.172.547l-4.996 7.492a.982.982 0 01-.828.454H1c-.55 0-1-.453-1-1 0-.2.059-.403.168-.551l4.629-6.942L.168 3.078A.939.939 0 010 2.528c0-.548.45-.997 1-.997h2.996c.352 0 .649.18.828.45L9.82 9.472c.11.148.172.347.172.55zm0 0"}),(0,b.jsx)("path",{d:"M19.98 10.023c0 .2-.058.399-.168.547l-4.996 7.492a.987.987 0 01-.828.454h-3c-.547 0-.996-.453-.996-1 0-.2.059-.403.168-.551l4.625-6.942-4.625-6.945a.939.939 0 01-.168-.55 1 1 0 01.996-.997h3c.348 0 .649.18.828.45l4.996 7.492c.11.148.168.347.168.55zm0 0"})]})})}const g="collapseSidebarButton_PEFL",A="collapseSidebarButtonIcon_kv0_";function C({onClick:e}){return(0,b.jsx)("button",{type:"button",title:(0,l.T)({id:"theme.docs.sidebar.collapseButtonTitle",message:"Collapse sidebar",description:"The title attribute for collapse button of doc sidebar"}),"aria-label":(0,l.T)({id:"theme.docs.sidebar.collapseButtonAriaLabel",message:"Collapse sidebar",description:"The title attribute for collapse button of doc sidebar"}),className:(0,o.A)("button button--secondary button--outline",g),onClick:e,children:(0,b.jsx)(_,{className:A})})}var k=n(5041),S=n(9532);const T=Symbol("EmptyContext"),N=a.createContext(T);function I({children:e}){const[t,n]=(0,a.useState)(null),o=(0,a.useMemo)(()=>({expandedItem:t,setExpandedItem:n}),[t]);return(0,b.jsx)(N.Provider,{value:o,children:e})}var y=n(1422),B=n(9169),w=n(8774),L=n(2303);function E({collapsed:e,categoryLabel:t,onClick:n}){return(0,b.jsx)("button",{"aria-label":e?(0,l.T)({id:"theme.DocSidebarItem.expandCategoryAriaLabel",message:"Expand sidebar category '{label}'",description:"The ARIA label to expand the sidebar category"},{label:t}):(0,l.T)({id:"theme.DocSidebarItem.collapseCategoryAriaLabel",message:"Collapse sidebar category '{label}'",description:"The ARIA label to collapse the sidebar category"},{label:t}),"aria-expanded":!e,type:"button",className:"clean-btn menu__caret",onClick:n})}function M({item:e,onItemClick:t,activePath:n,level:i,index:c,...l}){const{items:d,label:u,collapsible:m,className:h,href:p}=e,{docs:{sidebar:{autoCollapseCategories:x}}}=(0,j.p)(),f=function(e){const t=(0,L.A)();return(0,a.useMemo)(()=>e.href&&!e.linkUnlisted?e.href:!t&&e.collapsible?(0,r.Nr)(e):void 0,[e,t])}(e),v=(0,r.w8)(e,n),_=(0,B.ys)(p,n),{collapsed:g,setCollapsed:A}=(0,y.u)({initialState:()=>!!m&&(!v&&e.collapsed)}),{expandedItem:C,setExpandedItem:k}=function(){const e=(0,a.useContext)(N);if(e===T)throw new S.dV("DocSidebarItemsExpandedStateProvider");return e}(),I=(e=!g)=>{k(e?null:c),A(e)};return function({isActive:e,collapsed:t,updateCollapsed:n}){const o=(0,S.ZC)(e);(0,a.useEffect)(()=>{e&&!o&&t&&n(!1)},[e,o,t,n])}({isActive:v,collapsed:g,updateCollapsed:I}),(0,a.useEffect)(()=>{m&&null!=C&&C!==c&&x&&A(!0)},[m,C,c,A,x]),(0,b.jsxs)("li",{className:(0,o.A)(s.G.docs.docSidebarItemCategory,s.G.docs.docSidebarItemCategoryLevel(i),"menu__list-item",{"menu__list-item--collapsed":g},h),children:[(0,b.jsxs)("div",{className:(0,o.A)("menu__list-item-collapsible",{"menu__list-item-collapsible--active":_}),children:[(0,b.jsx)(w.A,{className:(0,o.A)("menu__link",{"menu__link--sublist":m,"menu__link--sublist-caret":!p&&m,"menu__link--active":v}),onClick:m?n=>{t?.(e),p?_?(n.preventDefault(),I()):I(!1):(n.preventDefault(),I())}:()=>{t?.(e)},"aria-current":_?"page":void 0,role:m&&!p?"button":void 0,"aria-expanded":m&&!p?!g:void 0,href:m?f??"#":f,...l,children:u}),p&&m&&(0,b.jsx)(E,{collapsed:g,categoryLabel:u,onClick:e=>{e.preventDefault(),I()}})]}),(0,b.jsx)(y.N,{lazy:!0,as:"ul",className:"menu__list",collapsed:g,children:(0,b.jsx)(V,{items:d,tabIndex:g?-1:0,onItemClick:t,activePath:n,level:i+1})})]})}var H=n(6654),G=n(3186);const P="menuExternalLink_NmtK";function R({item:e,onItemClick:t,activePath:n,level:a,index:i,...c}){const{href:l,label:d,className:u,autoAddBaseUrl:m}=e,h=(0,r.w8)(e,n),p=(0,H.A)(l);return(0,b.jsx)("li",{className:(0,o.A)(s.G.docs.docSidebarItemLink,s.G.docs.docSidebarItemLinkLevel(a),"menu__list-item",u),children:(0,b.jsxs)(w.A,{className:(0,o.A)("menu__link",!p&&P,{"menu__link--active":h}),autoAddBaseUrl:m,"aria-current":h?"page":void 0,to:l,...p&&{onClick:t?()=>t(e):void 0},...c,children:[d,!p&&(0,b.jsx)(G.A,{})]})},d)}const W="menuHtmlItem_M9Kj";function D({item:e,level:t,index:n}){const{value:a,defaultStyle:i,className:r}=e;return(0,b.jsx)("li",{className:(0,o.A)(s.G.docs.docSidebarItemLink,s.G.docs.docSidebarItemLinkLevel(t),i&&[W,"menu__list-item"],r),dangerouslySetInnerHTML:{__html:a}},n)}function F({item:e,...t}){switch(e.type){case"category":return(0,b.jsx)(M,{item:e,...t});case"html":return(0,b.jsx)(D,{item:e,...t});default:return(0,b.jsx)(R,{item:e,...t})}}function U({items:e,...t}){const n=(0,r.Y)(e,t.activePath);return(0,b.jsx)(I,{children:n.map((e,n)=>(0,b.jsx)(F,{item:e,index:n,...t},n))})}const V=(0,a.memo)(U),Y="menu_SIkG",K="menuWithAnnouncementBar_GW3s";function z({path:e,sidebar:t,className:n}){const i=function(){const{isActive:e}=(0,k.M)(),[t,n]=(0,a.useState)(e);return(0,d.Mq)(({scrollY:t})=>{e&&n(0===t)},[e]),e&&t}();return(0,b.jsx)("nav",{"aria-label":(0,l.T)({id:"theme.docs.sidebar.navAriaLabel",message:"Docs sidebar",description:"The ARIA label for the sidebar navigation"}),className:(0,o.A)("menu thin-scrollbar",Y,i&&K,n),children:(0,b.jsx)("ul",{className:(0,o.A)(s.G.docs.docSidebarMenu,"menu__list"),children:(0,b.jsx)(V,{items:t,activePath:e,level:1})})})}const q="sidebar_njMd",O="sidebarWithHideableNavbar_wUlq",J="sidebarHidden_VK0M",Q="sidebarLogo_isFc";function X({path:e,sidebar:t,onCollapse:n,isHidden:a}){const{navbar:{hideOnScroll:i},docs:{sidebar:{hideable:s}}}=(0,j.p)();return(0,b.jsxs)("div",{className:(0,o.A)(q,i&&O,a&&J),children:[i&&(0,b.jsx)(v.A,{tabIndex:-1,className:Q}),(0,b.jsx)(z,{path:e,sidebar:t}),s&&(0,b.jsx)(C,{onClick:n})]})}const Z=a.memo(X);var $=n(5600),ee=n(9876);const te=({sidebar:e,path:t})=>{const n=(0,ee.M)();return(0,b.jsx)("ul",{className:(0,o.A)(s.G.docs.docSidebarMenu,"menu__list"),children:(0,b.jsx)(V,{items:e,activePath:t,onItemClick:e=>{"category"===e.type&&e.href&&n.toggle(),"link"===e.type&&n.toggle()},level:1})})};function ne(e){return(0,b.jsx)($.GX,{component:te,props:e})}const ae=a.memo(ne);function oe(e){const t=(0,f.l)(),n="desktop"===t||"ssr"===t,a="mobile"===t;return(0,b.jsxs)(b.Fragment,{children:[n&&(0,b.jsx)(Z,{...e}),a&&(0,b.jsx)(ae,{...e})]})}const ie={expandButton:"expandButton_TmdG",expandButtonIcon:"expandButtonIcon_i1dp"};function se({toggleSidebar:e}){return(0,b.jsx)("div",{className:ie.expandButton,title:(0,l.T)({id:"theme.docs.sidebar.expandButtonTitle",message:"Expand sidebar",description:"The ARIA label and title attribute for expand button of doc sidebar"}),"aria-label":(0,l.T)({id:"theme.docs.sidebar.expandButtonAriaLabel",message:"Expand sidebar",description:"The ARIA label and title attribute for expand button of doc sidebar"}),tabIndex:0,role:"button",onKeyDown:e,onClick:e,children:(0,b.jsx)(_,{className:ie.expandButtonIcon})})}const re={docSidebarContainer:"docSidebarContainer_YfHR",docSidebarContainerHidden:"docSidebarContainerHidden_DPk8",sidebarViewport:"sidebarViewport_aRkj"};function ce({children:e}){const t=(0,c.t)();return(0,b.jsx)(a.Fragment,{children:e},t?.name??"noSidebar")}function le({sidebar:e,hiddenSidebarContainer:t,setHiddenSidebarContainer:n}){const{pathname:i}=(0,x.zy)(),[r,c]=(0,a.useState)(!1),l=(0,a.useCallback)(()=>{r&&c(!1),!r&&(0,p.O)()&&c(!0),n(e=>!e)},[n,r]);return(0,b.jsx)("aside",{className:(0,o.A)(s.G.docs.docSidebarContainer,re.docSidebarContainer,t&&re.docSidebarContainerHidden),onTransitionEnd:e=>{e.currentTarget.classList.contains(re.docSidebarContainer)&&t&&c(!0)},children:(0,b.jsx)(ce,{children:(0,b.jsxs)("div",{className:(0,o.A)(re.sidebarViewport,r&&re.sidebarViewportHidden),children:[(0,b.jsx)(oe,{sidebar:e,path:i,onCollapse:l,isHidden:r}),r&&(0,b.jsx)(se,{toggleSidebar:l})]})})})}const de={docMainContainer:"docMainContainer_TBSr",docMainContainerEnhanced:"docMainContainerEnhanced_lQrH",docItemWrapperEnhanced:"docItemWrapperEnhanced_JWYK"};function ue({hiddenSidebarContainer:e,children:t}){const n=(0,c.t)();return(0,b.jsx)("main",{className:(0,o.A)(de.docMainContainer,(e||!n)&&de.docMainContainerEnhanced),children:(0,b.jsx)("div",{className:(0,o.A)("container padding-top--md padding-bottom--lg",de.docItemWrapper,e&&de.docItemWrapperEnhanced),children:t})})}const me={docRoot:"docRoot_UBD9",docsWrapper:"docsWrapper_hBAB"};function be({children:e}){const t=(0,c.t)(),[n,o]=(0,a.useState)(!1);return(0,b.jsxs)("div",{className:me.docsWrapper,children:[(0,b.jsx)(h,{}),(0,b.jsxs)("div",{className:me.docRoot,children:[t&&(0,b.jsx)(le,{sidebar:t.items,hiddenSidebarContainer:n,setHiddenSidebarContainer:o}),(0,b.jsx)(ue,{hiddenSidebarContainer:n,children:e})]})]})}var he=n(3363);function pe(e){const t=(0,r.B5)(e);if(!t)return(0,b.jsx)(he.A,{});const{docElement:n,sidebarName:a,sidebarItems:l}=t;return(0,b.jsx)(i.e3,{className:(0,o.A)(s.G.page.docsDocPage),children:(0,b.jsx)(c.V,{name:a,items:l,children:(0,b.jsx)(be,{children:n})})})}},3363:(e,t,n)=>{n.d(t,{A:()=>r});n(6540);var a=n(4164),o=n(1312),i=n(1107),s=n(4848);function r({className:e}){return(0,s.jsx)("main",{className:(0,a.A)("container margin-vert--xl",e),children:(0,s.jsx)("div",{className:"row",children:(0,s.jsxs)("div",{className:"col col--6 col--offset-3",children:[(0,s.jsx)(i.A,{as:"h1",className:"hero__title",children:(0,s.jsx)(o.A,{id:"theme.NotFound.title",description:"The title of the 404 page",children:"Page Not Found"})}),(0,s.jsx)("p",{children:(0,s.jsx)(o.A,{id:"theme.NotFound.p1",description:"The first paragraph of the 404 page",children:"We could not find what you were looking for."})}),(0,s.jsx)("p",{children:(0,s.jsx)(o.A,{id:"theme.NotFound.p2",description:"The 2nd paragraph of the 404 page",children:"Please contact the owner of the site that linked you to the original URL and let them know their link is broken."})})]})})})}}}]); \ No newline at end of file diff --git a/docs/assets/js/a94703ab.50a268a2.js b/docs/assets/js/a94703ab.50a268a2.js deleted file mode 100644 index 331a960f..00000000 --- a/docs/assets/js/a94703ab.50a268a2.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[9048],{1377:(e,t,n)=>{n.r(t),n.d(t,{default:()=>pe});var a=n(6540),o=n(4164),i=n(5500),s=n(7559),r=n(6972),c=n(609),l=n(1312),d=n(3104),u=n(5062);const m={backToTopButton:"backToTopButton_sjWU",backToTopButtonShow:"backToTopButtonShow_xfvO"};var b=n(4848);function h(){const{shown:e,scrollToTop:t}=function({threshold:e}){const[t,n]=(0,a.useState)(!1),o=(0,a.useRef)(!1),{startScroll:i,cancelScroll:s}=(0,d.gk)();return(0,d.Mq)((({scrollY:t},a)=>{const i=a?.scrollY;i&&(o.current?o.current=!1:t>=i?(s(),n(!1)):t<e?n(!1):t+window.innerHeight<document.documentElement.scrollHeight&&n(!0))})),(0,u.$)((e=>{e.location.hash&&(o.current=!0,n(!1))})),{shown:t,scrollToTop:()=>i(0)}}({threshold:300});return(0,b.jsx)("button",{"aria-label":(0,l.T)({id:"theme.BackToTopButton.buttonAriaLabel",message:"Scroll back to top",description:"The ARIA label for the back to top button"}),className:(0,o.A)("clean-btn",s.G.common.backToTopButton,m.backToTopButton,e&&m.backToTopButtonShow),type:"button",onClick:t})}var p=n(3109),x=n(6347),f=n(4581),j=n(6342),v=n(3465);function _(e){return(0,b.jsx)("svg",{width:"20",height:"20","aria-hidden":"true",...e,children:(0,b.jsxs)("g",{fill:"#7a7a7a",children:[(0,b.jsx)("path",{d:"M9.992 10.023c0 .2-.062.399-.172.547l-4.996 7.492a.982.982 0 01-.828.454H1c-.55 0-1-.453-1-1 0-.2.059-.403.168-.551l4.629-6.942L.168 3.078A.939.939 0 010 2.528c0-.548.45-.997 1-.997h2.996c.352 0 .649.18.828.45L9.82 9.472c.11.148.172.347.172.55zm0 0"}),(0,b.jsx)("path",{d:"M19.98 10.023c0 .2-.058.399-.168.547l-4.996 7.492a.987.987 0 01-.828.454h-3c-.547 0-.996-.453-.996-1 0-.2.059-.403.168-.551l4.625-6.942-4.625-6.945a.939.939 0 01-.168-.55 1 1 0 01.996-.997h3c.348 0 .649.18.828.45l4.996 7.492c.11.148.168.347.168.55zm0 0"})]})})}const g="collapseSidebarButton_PEFL",A="collapseSidebarButtonIcon_kv0_";function C({onClick:e}){return(0,b.jsx)("button",{type:"button",title:(0,l.T)({id:"theme.docs.sidebar.collapseButtonTitle",message:"Collapse sidebar",description:"The title attribute for collapse button of doc sidebar"}),"aria-label":(0,l.T)({id:"theme.docs.sidebar.collapseButtonAriaLabel",message:"Collapse sidebar",description:"The title attribute for collapse button of doc sidebar"}),className:(0,o.A)("button button--secondary button--outline",g),onClick:e,children:(0,b.jsx)(_,{className:A})})}var k=n(5041),S=n(9532);const T=Symbol("EmptyContext"),N=a.createContext(T);function I({children:e}){const[t,n]=(0,a.useState)(null),o=(0,a.useMemo)((()=>({expandedItem:t,setExpandedItem:n})),[t]);return(0,b.jsx)(N.Provider,{value:o,children:e})}var y=n(1422),B=n(9169),w=n(8774),L=n(2303);function E({collapsed:e,categoryLabel:t,onClick:n}){return(0,b.jsx)("button",{"aria-label":e?(0,l.T)({id:"theme.DocSidebarItem.expandCategoryAriaLabel",message:"Expand sidebar category '{label}'",description:"The ARIA label to expand the sidebar category"},{label:t}):(0,l.T)({id:"theme.DocSidebarItem.collapseCategoryAriaLabel",message:"Collapse sidebar category '{label}'",description:"The ARIA label to collapse the sidebar category"},{label:t}),"aria-expanded":!e,type:"button",className:"clean-btn menu__caret",onClick:n})}function M({item:e,onItemClick:t,activePath:n,level:i,index:c,...l}){const{items:d,label:u,collapsible:m,className:h,href:p}=e,{docs:{sidebar:{autoCollapseCategories:x}}}=(0,j.p)(),f=function(e){const t=(0,L.A)();return(0,a.useMemo)((()=>e.href&&!e.linkUnlisted?e.href:!t&&e.collapsible?(0,r.Nr)(e):void 0),[e,t])}(e),v=(0,r.w8)(e,n),_=(0,B.ys)(p,n),{collapsed:g,setCollapsed:A}=(0,y.u)({initialState:()=>!!m&&(!v&&e.collapsed)}),{expandedItem:C,setExpandedItem:k}=function(){const e=(0,a.useContext)(N);if(e===T)throw new S.dV("DocSidebarItemsExpandedStateProvider");return e}(),I=(e=!g)=>{k(e?null:c),A(e)};return function({isActive:e,collapsed:t,updateCollapsed:n}){const o=(0,S.ZC)(e);(0,a.useEffect)((()=>{e&&!o&&t&&n(!1)}),[e,o,t,n])}({isActive:v,collapsed:g,updateCollapsed:I}),(0,a.useEffect)((()=>{m&&null!=C&&C!==c&&x&&A(!0)}),[m,C,c,A,x]),(0,b.jsxs)("li",{className:(0,o.A)(s.G.docs.docSidebarItemCategory,s.G.docs.docSidebarItemCategoryLevel(i),"menu__list-item",{"menu__list-item--collapsed":g},h),children:[(0,b.jsxs)("div",{className:(0,o.A)("menu__list-item-collapsible",{"menu__list-item-collapsible--active":_}),children:[(0,b.jsx)(w.A,{className:(0,o.A)("menu__link",{"menu__link--sublist":m,"menu__link--sublist-caret":!p&&m,"menu__link--active":v}),onClick:m?n=>{t?.(e),p?_?(n.preventDefault(),I()):I(!1):(n.preventDefault(),I())}:()=>{t?.(e)},"aria-current":_?"page":void 0,role:m&&!p?"button":void 0,"aria-expanded":m&&!p?!g:void 0,href:m?f??"#":f,...l,children:u}),p&&m&&(0,b.jsx)(E,{collapsed:g,categoryLabel:u,onClick:e=>{e.preventDefault(),I()}})]}),(0,b.jsx)(y.N,{lazy:!0,as:"ul",className:"menu__list",collapsed:g,children:(0,b.jsx)(V,{items:d,tabIndex:g?-1:0,onItemClick:t,activePath:n,level:i+1})})]})}var H=n(6654),G=n(3186);const P="menuExternalLink_NmtK";function R({item:e,onItemClick:t,activePath:n,level:a,index:i,...c}){const{href:l,label:d,className:u,autoAddBaseUrl:m}=e,h=(0,r.w8)(e,n),p=(0,H.A)(l);return(0,b.jsx)("li",{className:(0,o.A)(s.G.docs.docSidebarItemLink,s.G.docs.docSidebarItemLinkLevel(a),"menu__list-item",u),children:(0,b.jsxs)(w.A,{className:(0,o.A)("menu__link",!p&&P,{"menu__link--active":h}),autoAddBaseUrl:m,"aria-current":h?"page":void 0,to:l,...p&&{onClick:t?()=>t(e):void 0},...c,children:[d,!p&&(0,b.jsx)(G.A,{})]})},d)}const W="menuHtmlItem_M9Kj";function D({item:e,level:t,index:n}){const{value:a,defaultStyle:i,className:r}=e;return(0,b.jsx)("li",{className:(0,o.A)(s.G.docs.docSidebarItemLink,s.G.docs.docSidebarItemLinkLevel(t),i&&[W,"menu__list-item"],r),dangerouslySetInnerHTML:{__html:a}},n)}function F({item:e,...t}){switch(e.type){case"category":return(0,b.jsx)(M,{item:e,...t});case"html":return(0,b.jsx)(D,{item:e,...t});default:return(0,b.jsx)(R,{item:e,...t})}}function U({items:e,...t}){const n=(0,r.Y)(e,t.activePath);return(0,b.jsx)(I,{children:n.map(((e,n)=>(0,b.jsx)(F,{item:e,index:n,...t},n)))})}const V=(0,a.memo)(U),Y="menu_SIkG",K="menuWithAnnouncementBar_GW3s";function z({path:e,sidebar:t,className:n}){const i=function(){const{isActive:e}=(0,k.M)(),[t,n]=(0,a.useState)(e);return(0,d.Mq)((({scrollY:t})=>{e&&n(0===t)}),[e]),e&&t}();return(0,b.jsx)("nav",{"aria-label":(0,l.T)({id:"theme.docs.sidebar.navAriaLabel",message:"Docs sidebar",description:"The ARIA label for the sidebar navigation"}),className:(0,o.A)("menu thin-scrollbar",Y,i&&K,n),children:(0,b.jsx)("ul",{className:(0,o.A)(s.G.docs.docSidebarMenu,"menu__list"),children:(0,b.jsx)(V,{items:t,activePath:e,level:1})})})}const q="sidebar_njMd",O="sidebarWithHideableNavbar_wUlq",J="sidebarHidden_VK0M",Q="sidebarLogo_isFc";function X({path:e,sidebar:t,onCollapse:n,isHidden:a}){const{navbar:{hideOnScroll:i},docs:{sidebar:{hideable:s}}}=(0,j.p)();return(0,b.jsxs)("div",{className:(0,o.A)(q,i&&O,a&&J),children:[i&&(0,b.jsx)(v.A,{tabIndex:-1,className:Q}),(0,b.jsx)(z,{path:e,sidebar:t}),s&&(0,b.jsx)(C,{onClick:n})]})}const Z=a.memo(X);var $=n(5600),ee=n(9876);const te=({sidebar:e,path:t})=>{const n=(0,ee.M)();return(0,b.jsx)("ul",{className:(0,o.A)(s.G.docs.docSidebarMenu,"menu__list"),children:(0,b.jsx)(V,{items:e,activePath:t,onItemClick:e=>{"category"===e.type&&e.href&&n.toggle(),"link"===e.type&&n.toggle()},level:1})})};function ne(e){return(0,b.jsx)($.GX,{component:te,props:e})}const ae=a.memo(ne);function oe(e){const t=(0,f.l)(),n="desktop"===t||"ssr"===t,a="mobile"===t;return(0,b.jsxs)(b.Fragment,{children:[n&&(0,b.jsx)(Z,{...e}),a&&(0,b.jsx)(ae,{...e})]})}const ie={expandButton:"expandButton_TmdG",expandButtonIcon:"expandButtonIcon_i1dp"};function se({toggleSidebar:e}){return(0,b.jsx)("div",{className:ie.expandButton,title:(0,l.T)({id:"theme.docs.sidebar.expandButtonTitle",message:"Expand sidebar",description:"The ARIA label and title attribute for expand button of doc sidebar"}),"aria-label":(0,l.T)({id:"theme.docs.sidebar.expandButtonAriaLabel",message:"Expand sidebar",description:"The ARIA label and title attribute for expand button of doc sidebar"}),tabIndex:0,role:"button",onKeyDown:e,onClick:e,children:(0,b.jsx)(_,{className:ie.expandButtonIcon})})}const re={docSidebarContainer:"docSidebarContainer_YfHR",docSidebarContainerHidden:"docSidebarContainerHidden_DPk8",sidebarViewport:"sidebarViewport_aRkj"};function ce({children:e}){const t=(0,c.t)();return(0,b.jsx)(a.Fragment,{children:e},t?.name??"noSidebar")}function le({sidebar:e,hiddenSidebarContainer:t,setHiddenSidebarContainer:n}){const{pathname:i}=(0,x.zy)(),[r,c]=(0,a.useState)(!1),l=(0,a.useCallback)((()=>{r&&c(!1),!r&&(0,p.O)()&&c(!0),n((e=>!e))}),[n,r]);return(0,b.jsx)("aside",{className:(0,o.A)(s.G.docs.docSidebarContainer,re.docSidebarContainer,t&&re.docSidebarContainerHidden),onTransitionEnd:e=>{e.currentTarget.classList.contains(re.docSidebarContainer)&&t&&c(!0)},children:(0,b.jsx)(ce,{children:(0,b.jsxs)("div",{className:(0,o.A)(re.sidebarViewport,r&&re.sidebarViewportHidden),children:[(0,b.jsx)(oe,{sidebar:e,path:i,onCollapse:l,isHidden:r}),r&&(0,b.jsx)(se,{toggleSidebar:l})]})})})}const de={docMainContainer:"docMainContainer_TBSr",docMainContainerEnhanced:"docMainContainerEnhanced_lQrH",docItemWrapperEnhanced:"docItemWrapperEnhanced_JWYK"};function ue({hiddenSidebarContainer:e,children:t}){const n=(0,c.t)();return(0,b.jsx)("main",{className:(0,o.A)(de.docMainContainer,(e||!n)&&de.docMainContainerEnhanced),children:(0,b.jsx)("div",{className:(0,o.A)("container padding-top--md padding-bottom--lg",de.docItemWrapper,e&&de.docItemWrapperEnhanced),children:t})})}const me={docRoot:"docRoot_UBD9",docsWrapper:"docsWrapper_hBAB"};function be({children:e}){const t=(0,c.t)(),[n,o]=(0,a.useState)(!1);return(0,b.jsxs)("div",{className:me.docsWrapper,children:[(0,b.jsx)(h,{}),(0,b.jsxs)("div",{className:me.docRoot,children:[t&&(0,b.jsx)(le,{sidebar:t.items,hiddenSidebarContainer:n,setHiddenSidebarContainer:o}),(0,b.jsx)(ue,{hiddenSidebarContainer:n,children:e})]})]})}var he=n(3363);function pe(e){const t=(0,r.B5)(e);if(!t)return(0,b.jsx)(he.A,{});const{docElement:n,sidebarName:a,sidebarItems:l}=t;return(0,b.jsx)(i.e3,{className:(0,o.A)(s.G.page.docsDocPage),children:(0,b.jsx)(c.V,{name:a,items:l,children:(0,b.jsx)(be,{children:n})})})}},3363:(e,t,n)=>{n.d(t,{A:()=>r});n(6540);var a=n(4164),o=n(1312),i=n(1107),s=n(4848);function r({className:e}){return(0,s.jsx)("main",{className:(0,a.A)("container margin-vert--xl",e),children:(0,s.jsx)("div",{className:"row",children:(0,s.jsxs)("div",{className:"col col--6 col--offset-3",children:[(0,s.jsx)(i.A,{as:"h1",className:"hero__title",children:(0,s.jsx)(o.A,{id:"theme.NotFound.title",description:"The title of the 404 page",children:"Page Not Found"})}),(0,s.jsx)("p",{children:(0,s.jsx)(o.A,{id:"theme.NotFound.p1",description:"The first paragraph of the 404 page",children:"We could not find what you were looking for."})}),(0,s.jsx)("p",{children:(0,s.jsx)(o.A,{id:"theme.NotFound.p2",description:"The 2nd paragraph of the 404 page",children:"Please contact the owner of the site that linked you to the original URL and let them know their link is broken."})})]})})})}}}]); \ No newline at end of file diff --git a/docs/assets/js/aaabe254.ba3e9f5f.js b/docs/assets/js/aaabe254.ba3e9f5f.js new file mode 100644 index 00000000..40f78d18 --- /dev/null +++ b/docs/assets/js/aaabe254.ba3e9f5f.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[770],{1982:a=>{a.exports=JSON.parse('{"tag":{"label":"bharatmlstack","permalink":"/BharatMLStack/blog/tags/bharatmlstack","allTagsPath":"/BharatMLStack/blog/tags","count":4,"unlisted":false},"listMetadata":{"permalink":"/BharatMLStack/blog/tags/bharatmlstack","page":1,"postsPerPage":10,"totalPages":1,"totalCount":4,"blogDescription":"Blog","blogTitle":"Blog"}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/ac51638e.0b4da379.js b/docs/assets/js/ac51638e.0b4da379.js new file mode 100644 index 00000000..e2381bfd --- /dev/null +++ b/docs/assets/js/ac51638e.0b4da379.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[9473],{6692:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>l,contentTitle:()=>o,default:()=>p,frontMatter:()=>s,metadata:()=>t,toc:()=>c});const t=JSON.parse('{"id":"sdks/python/v1.0.0/spark_feature_push_client","title":"Spark client","description":"PyPI version","source":"@site/docs/sdks/python/v1.0.0/spark_feature_push_client.md","sourceDirName":"sdks/python/v1.0.0","slug":"/sdks/python/v1.0.0/spark_feature_push_client","permalink":"/BharatMLStack/sdks/python/v1.0.0/spark_feature_push_client","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/sdks/python/v1.0.0/spark_feature_push_client.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"Spark client","sidebar_position":1},"sidebar":"tutorialSidebar","previous":{"title":"GRPC Feature client","permalink":"/BharatMLStack/sdks/python/v1.0.0/grpc_feature_client"},"next":{"title":"Skye","permalink":"/BharatMLStack/category/skye"}}');var r=a(4848),i=a(8453);const s={title:"Spark client",sidebar_position:1},o="Spark Feature Push Client",l={},c=[{value:"Installation",id:"installation",level:2},{value:"Dependencies",id:"dependencies",level:2},{value:"Architecture Role",id:"architecture-role",level:2},{value:"Features",id:"features",level:2},{value:"When to Use This Client",id:"when-to-use-this-client",level:2},{value:"Quick Start",id:"quick-start",level:2},{value:"Related Packages",id:"related-packages",level:2},{value:"License",id:"license",level:2},{value:"Contributing",id:"contributing",level:2},{value:"Prerequisites",id:"prerequisites",level:2},{value:"Supported Data Sources",id:"supported-data-sources",level:2},{value:"1. Database Tables",id:"1-database-tables",level:3},{value:"2. Cloud Storage - Parquet",id:"2-cloud-storage---parquet",level:3},{value:"3. Cloud Storage - Delta",id:"3-cloud-storage---delta",level:3},{value:"Configuration Examples",id:"configuration-examples",level:2},{value:"Basic Pipeline",id:"basic-pipeline",level:3},{value:"Reading from Multiple Sources",id:"reading-from-multiple-sources",level:3},{value:"Protobuf Serialization & Kafka Publishing",id:"protobuf-serialization--kafka-publishing",level:3},{value:"Data Type Handling",id:"data-type-handling",level:2},{value:"Scalar Types",id:"scalar-types",level:3},{value:"Vector Types",id:"vector-types",level:3},{value:"Production Pipeline Example",id:"production-pipeline-example",level:2},{value:"Configuration Options",id:"configuration-options",level:2},{value:"Client Configuration",id:"client-configuration",level:3},{value:"Protobuf Serialization Options",id:"protobuf-serialization-options",level:3},{value:"Kafka Publishing Options",id:"kafka-publishing-options",level:3},{value:"Performance Tuning",id:"performance-tuning",level:2},{value:"Spark Optimizations",id:"spark-optimizations",level:3},{value:"Memory Management",id:"memory-management",level:3},{value:"Kafka Throughput",id:"kafka-throughput",level:3},{value:"Monitoring & Debugging",id:"monitoring--debugging",level:2},{value:"DataFrame Inspection",id:"dataframe-inspection",level:3},{value:"Error Handling",id:"error-handling",level:3},{value:"Integration with Other SDKs",id:"integration-with-other-sdks",level:2},{value:"With gRPC Feature Client",id:"with-grpc-feature-client",level:3},{value:"With HTTP Feature Client (bharatml_common)",id:"with-http-feature-client-bharatml_common",level:3},{value:"Common Use Cases",id:"common-use-cases",level:2},{value:"1. Daily Batch ETL",id:"1-daily-batch-etl",level:3},{value:"2. Historical Backfill",id:"2-historical-backfill",level:3},{value:"3. Real-time Streaming (Advanced)",id:"3-real-time-streaming-advanced",level:3},{value:"Troubleshooting",id:"troubleshooting",level:2},{value:"Common Issues",id:"common-issues",level:3},{value:"Debug Mode",id:"debug-mode",level:3},{value:"Migration from Legacy Clients",id:"migration-from-legacy-clients",level:2},{value:"Best Practices",id:"best-practices",level:2},{value:"Contributing",id:"contributing-1",level:2},{value:"Community & Support",id:"community--support",level:2},{value:"License",id:"license-1",level:2}];function d(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,i.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"spark-feature-push-client",children:"Spark Feature Push Client"})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://badge.fury.io/py/spark_feature_push_client",children:(0,r.jsx)(n.img,{src:"https://img.shields.io/pypi/v/spark_feature_push_client?label=pypi-package&color=light%20green",alt:"PyPI version"})}),"\n",(0,r.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/actions/workflows/py-sdk.yml",children:(0,r.jsx)(n.img,{src:"https://github.com/Meesho/BharatMLStack/actions/workflows/py-sdk.yml/badge.svg",alt:"Build Status"})}),"\n",(0,r.jsx)(n.a,{href:"https://www.python.org/downloads/",children:(0,r.jsx)(n.img,{src:"https://img.shields.io/badge/python-3.7+-blue.svg",alt:"Python 3.7+"})}),"\n",(0,r.jsx)(n.a,{href:"https://discord.gg/XkT7XsV2AU",children:(0,r.jsx)(n.img,{src:"https://img.shields.io/badge/Discord-Join%20Chat-7289da?style=flat&logo=discord&logoColor=white",alt:"Discord"})}),"\n",(0,r.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md",children:(0,r.jsx)(n.img,{src:"https://img.shields.io/badge/License-BharatMLStack%20BSL%201.1-blue.svg",alt:"License"})})]}),"\n",(0,r.jsxs)(n.p,{children:["Apache Spark-based client for pushing ML features from offline batch sources to the BharatML Stack Online Feature Store via Kafka. This client is designed for ",(0,r.jsx)(n.strong,{children:"data pipeline operations"})," - reading from batch sources and publishing to Kafka for online consumption."]}),"\n",(0,r.jsx)(n.h2,{id:"installation",children:"Installation"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"pip install spark_feature_push_client\n"})}),"\n",(0,r.jsx)(n.h2,{id:"dependencies",children:"Dependencies"}),"\n",(0,r.jsx)(n.p,{children:"This package depends on:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"https://pypi.org/project/bharatml_commons/",children:"bharatml_commons"})}),": Common utilities and protobuf definitions"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"PySpark 3.0+"}),": For distributed data processing"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"architecture-role",children:"Architecture Role"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 Batch Sources \u2502\u2500\u2500\u2500\u25b6\u2502 Spark Feature Push \u2502\u2500\u2500\u2500\u25b6\u2502 Kafka \u2502\u2500\u2500\u2500\u25b6\u2502 Online Feature \u2502\n\u2502 \u2022 Tables \u2502 \u2502 Client \u2502 \u2502 \u2502 \u2502 Store \u2502\n\u2502 \u2022 Parquet \u2502 \u2502 \u2022 Read & Transform \u2502 \u2502 \u2502 \u2502 \u2502\n\u2502 \u2022 Delta \u2502 \u2502 \u2022 Protobuf Serialize \u2502 \u2502 \u2502 \u2502 \u2502\n\u2502 \u2022 S3/GCS/ADLS \u2502 \u2502 \u2022 Batch Processing \u2502 \u2502 \u2502 \u2502 \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u25b2\n \u2502\n \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 grpc_feature_ \u2502\n \u2502 client \u2502\n \u2502 (Real-time) \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n"})}),"\n",(0,r.jsx)(n.h2,{id:"features",children:"Features"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Batch Source Integration"}),": Read from Tables (Hive/Delta), Parquet, and Delta files on cloud storage"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Spark Processing"}),": Leverage Apache Spark for distributed data processing"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Protobuf Serialization"}),": Convert feature data to protobuf format using bharatml_commons schemas"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Kafka Publishing"}),": Push serialized features to Kafka topics for online consumption"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Metadata Integration"}),": Fetch feature schemas and configurations via REST API"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Data Type Support"}),": Handle scalar and vector types (strings, numbers, booleans, arrays)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Batch Optimization"}),": Configurable batch sizes for optimal Kafka throughput"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"when-to-use-this-client",children:"When to Use This Client"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Use spark_feature_push_client for:"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["\ud83d\udd04 ",(0,r.jsx)(n.strong,{children:"Batch ETL Pipelines"}),": Scheduled feature computation and publishing"]}),"\n",(0,r.jsxs)(n.li,{children:["\ud83d\udcca ",(0,r.jsx)(n.strong,{children:"Historical Data Backfill"}),": Loading historical features into online store"]}),"\n",(0,r.jsxs)(n.li,{children:["\ud83c\udfd7\ufe0f ",(0,r.jsx)(n.strong,{children:"Data Engineering"}),": Spark-based feature transformations"]}),"\n",(0,r.jsxs)(n.li,{children:["\ud83d\udcc8 ",(0,r.jsx)(n.strong,{children:"Large Scale Processing"}),": Processing millions of records efficiently"]}),"\n",(0,r.jsxs)(n.li,{children:["\u26a1 ",(0,r.jsx)(n.strong,{children:"Offline-to-Online"}),": Bridge between batch and real-time systems"]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Use grpc_feature_client for:"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["\ud83d\ude80 ",(0,r.jsx)(n.strong,{children:"Real-time Operations"}),": Direct persist/retrieve operations"]}),"\n",(0,r.jsxs)(n.li,{children:["\ud83d\udd0d ",(0,r.jsx)(n.strong,{children:"Interactive Queries"}),": Low-latency feature lookups"]}),"\n",(0,r.jsxs)(n.li,{children:["\ud83c\udfaf ",(0,r.jsx)(n.strong,{children:"API Integration"}),": Service-to-service communication"]}),"\n",(0,r.jsxs)(n.li,{children:["\ud83d\udca8 ",(0,r.jsx)(n.strong,{children:"Single Records"}),": Persisting individual feature records"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"quick-start",children:"Quick Start"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'from spark_feature_push_client import OnlineFeatureStorePyClient\n\n# Initialize client with metadata source\nclient = OnlineFeatureStorePyClient(\n features_metadata_source_url="https://api.example.com/metadata",\n job_id="feature-pipeline-job",\n job_token="your-auth-token"\n)\n\n# Get feature configuration \nfeature_details = client.get_features_details()\n\n# Process your Spark DataFrame\nproto_df = client.generate_df_with_protobuf_messages(your_spark_df)\n\n# Push to Kafka\nclient.write_protobuf_df_to_kafka(\n proto_df,\n kafka_bootstrap_servers="localhost:9092",\n kafka_topic="features.user_features"\n)\n'})}),"\n",(0,r.jsx)(n.h2,{id:"related-packages",children:"Related Packages"}),"\n",(0,r.jsx)(n.p,{children:"This package is part of the BharatML Stack ecosystem:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"https://pypi.org/project/bharatml_commons/",children:"bharatml_commons"})}),": Common utilities and protobuf definitions (required dependency)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"https://pypi.org/project/grpc_feature_client/",children:"grpc_feature_client"})}),": High-performance gRPC client for real-time operations"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"license",children:"License"}),"\n",(0,r.jsxs)(n.p,{children:["Licensed under the BharatMLStack Business Source License 1.1. See ",(0,r.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md",children:"LICENSE"})," for details."]}),"\n",(0,r.jsx)(n.h2,{id:"contributing",children:"Contributing"}),"\n",(0,r.jsxs)(n.p,{children:["We welcome contributions! Please see our ",(0,r.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTION.md",children:"Contributing Guide"})," for details."]}),"\n",(0,r.jsx)(n.h2,{id:"prerequisites",children:"Prerequisites"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Apache Spark 3.0+"}),": For distributed processing"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Kafka Connector"}),": ",(0,r.jsx)(n.code,{children:"spark-sql-kafka"})," for Kafka integration"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Java 8/11"}),": Required by Spark"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"bharatml_common"}),": For protobuf schemas"]}),"\n"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:'# Example Spark session setup\nspark = SparkSession.builder \\\n .appName("FeaturePipeline") \\\n .config("spark.jars.packages", "org.apache.spark:spark-sql-kafka-0-10_2.12:3.4.0") \\\n .getOrCreate()\n'})}),"\n",(0,r.jsx)(n.h2,{id:"supported-data-sources",children:"Supported Data Sources"}),"\n",(0,r.jsx)(n.h3,{id:"1-database-tables",children:"1. Database Tables"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'# Hive/Delta tables\ndf = spark.sql("SELECT * FROM feature_db.user_features")\n'})}),"\n",(0,r.jsx)(n.h3,{id:"2-cloud-storage---parquet",children:"2. Cloud Storage - Parquet"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'# AWS S3\ndf = spark.read.parquet("s3a://bucket/path/to/features/")\n\n# Google Cloud Storage \ndf = spark.read.parquet("gs://bucket/path/to/features/")\n\n# Azure Data Lake\ndf = spark.read.parquet("abfss://container@account.dfs.core.windows.net/path/")\n'})}),"\n",(0,r.jsx)(n.h3,{id:"3-cloud-storage---delta",children:"3. Cloud Storage - Delta"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'# Delta format on cloud storage\ndf = spark.read.format("delta").load("s3a://bucket/delta-table/")\n'})}),"\n",(0,r.jsx)(n.h2,{id:"configuration-examples",children:"Configuration Examples"}),"\n",(0,r.jsx)(n.h3,{id:"basic-pipeline",children:"Basic Pipeline"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'from pyspark.sql import SparkSession\nfrom spark_feature_push_client import OnlineFeatureStorePyClient\n\n# Create Spark session\nspark = SparkSession.builder \\\n .appName("FeatureETL") \\\n .config("spark.jars.packages", "org.apache.spark:spark-sql-kafka-0-10_2.12:3.4.0") \\\n .getOrCreate()\n\n# Initialize client\nclient = OnlineFeatureStorePyClient(\n features_metadata_source_url="https://metadata-service.example.com/api/v1/features",\n job_id="daily-feature-pipeline",\n job_token="pipeline-secret-token",\n fgs_to_consider=["user_demographics", "user_behavior"] # Optional: filter feature groups\n)\n\n# Get metadata and column mappings\n(\n offline_src_type_columns,\n offline_col_to_default_values_map, \n entity_column_names\n) = client.get_features_details()\n\nprint(f"Entity columns: {entity_column_names}")\nprint(f"Feature mappings: {offline_src_type_columns}")\n'})}),"\n",(0,r.jsx)(n.h3,{id:"reading-from-multiple-sources",children:"Reading from Multiple Sources"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'def get_features_from_all_sources(spark, entity_columns, feature_mapping, default_values):\n """\n Read and combine features from multiple offline sources\n """\n dataframes = []\n \n for source_info in feature_mapping:\n table_name, source_type, feature_list = source_info\n \n if source_type == "TABLE":\n # Read from Hive/Delta table\n df = spark.table(table_name)\n \n elif source_type.startswith("PARQUET_"):\n # Read from Parquet files\n df = spark.read.parquet(table_name)\n \n elif source_type.startswith("DELTA_"):\n # Read from Delta files\n df = spark.read.format("delta").load(table_name)\n \n # Select and rename columns\n select_cols = entity_columns.copy()\n for original_col, renamed_col in feature_list:\n if original_col in df.columns:\n df = df.withColumnRenamed(original_col, renamed_col)\n select_cols.append(renamed_col)\n \n df = df.select(select_cols)\n dataframes.append(df)\n \n # Union all dataframes\n if dataframes:\n combined_df = dataframes[0]\n for df in dataframes[1:]:\n combined_df = combined_df.unionByName(df, allowMissingColumns=True)\n \n # Fill missing values with defaults\n for col, default_val in default_values.items():\n if col in combined_df.columns:\n combined_df = combined_df.fillna({col: default_val})\n \n return combined_df\n \n return None\n\n# Use the function\ndf = get_features_from_all_sources(\n spark, \n entity_column_names, \n offline_src_type_columns, \n offline_col_to_default_values_map\n)\n'})}),"\n",(0,r.jsx)(n.h3,{id:"protobuf-serialization--kafka-publishing",children:"Protobuf Serialization & Kafka Publishing"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'# Convert DataFrame to protobuf messages\n# This creates binary protobuf messages suitable for Kafka\nproto_df = client.generate_df_with_protobuf_messages(\n df, \n intra_batch_size=20 # Batch size for serialization\n)\n\n# The proto_df has schema: [value: binary, intra_batch_id: long]\nproto_df.printSchema()\n# root\n# |-- value: binary (nullable = false) \n# |-- intra_batch_id: long (nullable = false)\n\n# Write to Kafka with batching for better throughput\nclient.write_protobuf_df_to_kafka(\n proto_df,\n kafka_bootstrap_servers="broker1:9092,broker2:9092,broker3:9092",\n kafka_topic="features.user_features",\n additional_options={\n "kafka.acks": "all",\n "kafka.retries": "3",\n "kafka.compression.type": "snappy"\n },\n kafka_num_batches=4 # Split into 4 parallel Kafka writes\n)\n'})}),"\n",(0,r.jsx)(n.h2,{id:"data-type-handling",children:"Data Type Handling"}),"\n",(0,r.jsx)(n.p,{children:"The client automatically handles the protobuf data type mappings:"}),"\n",(0,r.jsx)(n.h3,{id:"scalar-types",children:"Scalar Types"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'# Example DataFrame with different types\ndata = [\n ("user123", 25, 185.5, True, "premium"), # int, float, bool, string\n ("user456", 30, 170.0, False, "basic")\n]\ndf = spark.createDataFrame(data, ["user_id", "age", "height", "is_premium", "tier"])\n\n# Automatically mapped to protobuf:\n# age -> int32_values\n# height -> fp32_values \n# is_premium -> bool_values\n# tier -> string_values\n'})}),"\n",(0,r.jsx)(n.h3,{id:"vector-types",children:"Vector Types"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'# Example with vector/array features\nfrom pyspark.sql.functions import array, lit\n\ndf = spark.createDataFrame([\n ("user123", [0.1, 0.2, 0.3], ["tech", "sports"], [1, 2, 3])\n], ["user_id", "embeddings", "interests", "scores"])\n\n# Automatically mapped to protobuf vectors:\n# embeddings -> fp32_values in Vector\n# interests -> string_values in Vector\n# scores -> int32_values in Vector\n'})}),"\n",(0,r.jsx)(n.h2,{id:"production-pipeline-example",children:"Production Pipeline Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'def run_feature_pipeline():\n """\n Complete feature pipeline from batch sources to Kafka\n """\n \n # 1. Initialize Spark\n spark = SparkSession.builder \\\n .appName("DailyFeaturePipeline") \\\n .config("spark.sql.adaptive.enabled", "true") \\\n .config("spark.jars.packages", "org.apache.spark:spark-sql-kafka-0-10_2.12:3.4.0") \\\n .getOrCreate()\n \n try:\n # 2. Initialize feature client\n client = OnlineFeatureStorePyClient(\n features_metadata_source_url=os.getenv("METADATA_URL"),\n job_id=os.getenv("JOB_ID"),\n job_token=os.getenv("JOB_TOKEN")\n )\n \n # 3. Get feature configuration\n feature_mapping, default_values, entity_columns = client.get_features_details()\n \n # 4. Read and process data\n df = get_features_from_all_sources(spark, entity_columns, feature_mapping, default_values)\n \n if df is None or df.count() == 0:\n raise ValueError("No data found in sources")\n \n # 5. Convert to protobuf\n proto_df = client.generate_df_with_protobuf_messages(df, intra_batch_size=50)\n \n # 6. Publish to Kafka\n client.write_protobuf_df_to_kafka(\n proto_df,\n kafka_bootstrap_servers=os.getenv("KAFKA_BROKERS"),\n kafka_topic=os.getenv("KAFKA_TOPIC"),\n additional_options={\n "kafka.acks": "all",\n "kafka.compression.type": "snappy",\n "kafka.max.request.size": "10485760" # 10MB\n },\n kafka_num_batches=int(os.getenv("KAFKA_BATCHES", "4"))\n )\n \n print(f"\u2705 Successfully processed {df.count()} records")\n \n finally:\n spark.stop()\n\nif __name__ == "__main__":\n run_feature_pipeline()\n'})}),"\n",(0,r.jsx)(n.h2,{id:"configuration-options",children:"Configuration Options"}),"\n",(0,r.jsx)(n.h3,{id:"client-configuration",children:"Client Configuration"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'client = OnlineFeatureStorePyClient(\n features_metadata_source_url="https://api.example.com/metadata", # Required\n job_id="pipeline-job-001", # Required \n job_token="secret-token-123", # Required\n fgs_to_consider=["user_features", "item_features"] # Optional: filter feature groups\n)\n'})}),"\n",(0,r.jsx)(n.h3,{id:"protobuf-serialization-options",children:"Protobuf Serialization Options"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:"proto_df = client.generate_df_with_protobuf_messages(\n df,\n intra_batch_size=20 # Records per protobuf message (default: 20)\n)\n"})}),"\n",(0,r.jsx)(n.h3,{id:"kafka-publishing-options",children:"Kafka Publishing Options"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'client.write_protobuf_df_to_kafka(\n proto_df,\n kafka_bootstrap_servers="localhost:9092",\n kafka_topic="features.topic",\n additional_options={\n "kafka.acks": "all", # Acknowledgment level\n "kafka.retries": "3", # Retry attempts\n "kafka.compression.type": "snappy", # Compression\n "kafka.batch.size": "16384", # Batch size\n "kafka.linger.ms": "100", # Batching delay\n "kafka.max.request.size": "10485760" # Max message size\n },\n kafka_num_batches=1 # Number of parallel Kafka writers (default: 1)\n)\n'})}),"\n",(0,r.jsx)(n.h2,{id:"performance-tuning",children:"Performance Tuning"}),"\n",(0,r.jsx)(n.h3,{id:"spark-optimizations",children:"Spark Optimizations"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'spark = SparkSession.builder \\\n .appName("FeaturePipeline") \\\n .config("spark.sql.adaptive.enabled", "true") \\\n .config("spark.sql.adaptive.coalescePartitions.enabled", "true") \\\n .config("spark.sql.adaptive.skewJoin.enabled", "true") \\\n .config("spark.serializer", "org.apache.spark.serializer.KryoSerializer") \\\n .getOrCreate()\n'})}),"\n",(0,r.jsx)(n.h3,{id:"memory-management",children:"Memory Management"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:"# For large datasets, consider:\ndf = df.repartition(200) # Optimal partition count\ndf.cache() # Cache if reused multiple times\n"})}),"\n",(0,r.jsx)(n.h3,{id:"kafka-throughput",children:"Kafka Throughput"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'# For high-throughput scenarios:\nclient.write_protobuf_df_to_kafka(\n proto_df,\n kafka_bootstrap_servers="brokers",\n kafka_topic="topic", \n kafka_num_batches=8, # Increase parallel writers\n additional_options={\n "kafka.batch.size": "65536", # Larger batches\n "kafka.linger.ms": "100", # Allow batching delay\n "kafka.compression.type": "lz4" # Fast compression\n }\n)\n'})}),"\n",(0,r.jsx)(n.h2,{id:"monitoring--debugging",children:"Monitoring & Debugging"}),"\n",(0,r.jsx)(n.h3,{id:"dataframe-inspection",children:"DataFrame Inspection"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'# Check data before processing\nprint(f"Records: {df.count()}")\nprint(f"Columns: {df.columns}")\ndf.printSchema()\ndf.show(5)\n\n# Check protobuf output\nproto_df.show(5, truncate=False)\nprint(f"Protobuf messages: {proto_df.count()}")\n'})}),"\n",(0,r.jsx)(n.h3,{id:"error-handling",children:"Error Handling"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'try:\n proto_df = client.generate_df_with_protobuf_messages(df)\n client.write_protobuf_df_to_kafka(proto_df, brokers, topic)\n \nexcept Exception as e:\n print(f"Pipeline failed: {e}")\n # Log to monitoring system\n # Send alerts\n raise\n'})}),"\n",(0,r.jsx)(n.h2,{id:"integration-with-other-sdks",children:"Integration with Other SDKs"}),"\n",(0,r.jsx)(n.h3,{id:"with-grpc-feature-client",children:"With gRPC Feature Client"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:"# Spark client pushes features to Kafka\nspark_client = OnlineFeatureStorePyClient(...)\nspark_client.write_protobuf_df_to_kafka(proto_df, brokers, topic)\n\n# gRPC client retrieves features in real-time\nfrom grpc_feature_client import GRPCFeatureClient\ngrpc_client = GRPCFeatureClient(config)\nfeatures = grpc_client.retrieve_decoded_features(...)\n"})}),"\n",(0,r.jsx)(n.h3,{id:"with-http-feature-client-bharatml_common",children:"With HTTP Feature Client (bharatml_common)"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:"# Use HTTP client for metadata validation\nfrom bharatml_common import HTTPFeatureClient\nhttp_client = HTTPFeatureClient(base_url, job_id, token)\nmetadata = http_client.get_feature_metadata()\n\n# Validate feature names using shared utilities\nfrom bharatml_common import clean_column_name\nclean_features = [clean_column_name(name) for name in feature_names]\n\n# Process with Spark client\nspark_client.generate_df_with_protobuf_messages(df)\n"})}),"\n",(0,r.jsx)(n.h2,{id:"common-use-cases",children:"Common Use Cases"}),"\n",(0,r.jsx)(n.h3,{id:"1-daily-batch-etl",children:"1. Daily Batch ETL"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"# Cron job: 0 2 * * * (daily at 2 AM)\nspark-submit \\\n --packages org.apache.spark:spark-sql-kafka-0-10_2.12:3.4.0 \\\n --conf spark.sql.adaptive.enabled=true \\\n daily_feature_pipeline.py\n"})}),"\n",(0,r.jsx)(n.h3,{id:"2-historical-backfill",children:"2. Historical Backfill"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'# Backfill last 30 days\nfrom datetime import datetime, timedelta\n\nfor i in range(30):\n date = datetime.now() - timedelta(days=i)\n df = spark.sql(f"""\n SELECT * FROM features \n WHERE date = \'{date.strftime(\'%Y-%m-%d\')}\'\n """)\n \n proto_df = client.generate_df_with_protobuf_messages(df)\n client.write_protobuf_df_to_kafka(proto_df, brokers, f"backfill.{date.strftime(\'%Y%m%d\')}")\n'})}),"\n",(0,r.jsx)(n.h3,{id:"3-real-time-streaming-advanced",children:"3. Real-time Streaming (Advanced)"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'# Read from streaming source, process, and publish\nstreaming_df = spark.readStream \\\n .format("kafka") \\\n .option("kafka.bootstrap.servers", input_brokers) \\\n .option("subscribe", input_topic) \\\n .load()\n\n# Process streaming DataFrame\nprocessed_df = streaming_df.select(...)\n\n# Write to output Kafka (requires structured streaming)\nquery = processed_df.writeStream \\\n .format("kafka") \\\n .option("kafka.bootstrap.servers", output_brokers) \\\n .option("topic", output_topic) \\\n .start()\n'})}),"\n",(0,r.jsx)(n.h2,{id:"troubleshooting",children:"Troubleshooting"}),"\n",(0,r.jsx)(n.h3,{id:"common-issues",children:"Common Issues"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"OutOfMemoryError"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'# Increase driver memory or reduce partition size\nspark.conf.set("spark.sql.adaptive.coalescePartitions.minPartitionNum", "50")\n'})}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Kafka Connection Timeout"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'# Check network connectivity and broker addresses\nadditional_options = {\n "kafka.request.timeout.ms": "60000",\n "kafka.session.timeout.ms": "30000"\n}\n'})}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Protobuf Serialization Errors"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'# Check data types and null values\ndf = df.fillna({"string_col": "", "numeric_col": 0})\n'})}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Metadata API Errors"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:"# Verify job_id, job_token, and URL\n# Check API server logs\n"})}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"debug-mode",children:"Debug Mode"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'import logging\nlogging.basicConfig(level=logging.DEBUG)\n\n# Enable Spark SQL logging\nspark.sparkContext.setLogLevel("INFO")\n'})}),"\n",(0,r.jsx)(n.h2,{id:"migration-from-legacy-clients",children:"Migration from Legacy Clients"}),"\n",(0,r.jsx)(n.p,{children:"If migrating from older versions:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:"# Old import\n# from online_feature_store_py_client import OnlineFeatureStorePyClient\n\n# New import (same interface)\nfrom spark_feature_push_client import OnlineFeatureStorePyClient\n\n# API remains the same - no code changes needed!\n"})}),"\n",(0,r.jsx)(n.h2,{id:"best-practices",children:"Best Practices"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Resource Management"}),": Always stop Spark sessions"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Error Handling"}),": Implement proper exception handling and retries"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Monitoring"}),": Add metrics and logging to your pipelines"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Testing"}),": Test with sample data before production runs"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Security"}),": Use secure Kafka configurations in production"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Performance"}),": Monitor Spark UI for optimization opportunities"]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"The Spark Feature Push Client is your gateway from batch data sources to the real-time online feature store! \ud83d\ude80"}),"\n",(0,r.jsx)(n.h2,{id:"contributing-1",children:"Contributing"}),"\n",(0,r.jsxs)(n.p,{children:["We welcome contributions from the community! Please see our ",(0,r.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTING.md",children:"Contributing Guide"})," for details on how to get started."]}),"\n",(0,r.jsx)(n.h2,{id:"community--support",children:"Community & Support"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["\ud83d\udcac ",(0,r.jsx)(n.strong,{children:"Discord"}),": Join our ",(0,r.jsx)(n.a,{href:"https://discord.gg/XkT7XsV2AU",children:"community chat"})]}),"\n",(0,r.jsxs)(n.li,{children:["\ud83d\udc1b ",(0,r.jsx)(n.strong,{children:"Issues"}),": Report bugs and request features on ",(0,r.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/issues",children:"GitHub Issues"})]}),"\n",(0,r.jsxs)(n.li,{children:["\ud83d\udce7 ",(0,r.jsx)(n.strong,{children:"Email"}),": Contact us at ",(0,r.jsx)(n.a,{href:"mailto:ml-oss@meesho.com",children:"ml-oss@meesho.com"})]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"license-1",children:"License"}),"\n",(0,r.jsxs)(n.p,{children:["BharatMLStack is open-source software licensed under the ",(0,r.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md",children:"BharatMLStack Business Source License 1.1"}),"."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)("div",{align:"center",children:(0,r.jsx)("strong",{children:"Built with \u2764\ufe0f for the ML community from Meesho"})}),"\n",(0,r.jsx)("div",{align:"center",children:(0,r.jsx)("strong",{children:"If you find this useful, \u2b50\ufe0f the repo \u2014 your support means the world to us!"})})]})}function p(e={}){const{wrapper:n}={...(0,i.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(d,{...e})}):d(e)}},8453:(e,n,a)=>{a.d(n,{R:()=>s,x:()=>o});var t=a(6540);const r={},i=t.createContext(r);function s(e){const n=t.useContext(i);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:s(e.components),t.createElement(i.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/ac51638e.cd23cca0.js b/docs/assets/js/ac51638e.cd23cca0.js deleted file mode 100644 index 524d3cba..00000000 --- a/docs/assets/js/ac51638e.cd23cca0.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[9473],{6692:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>l,contentTitle:()=>o,default:()=>p,frontMatter:()=>s,metadata:()=>t,toc:()=>c});const t=JSON.parse('{"id":"sdks/python/v1.0.0/spark_feature_push_client","title":"Spark client","description":"PyPI version","source":"@site/docs/sdks/python/v1.0.0/spark_feature_push_client.md","sourceDirName":"sdks/python/v1.0.0","slug":"/sdks/python/v1.0.0/spark_feature_push_client","permalink":"/BharatMLStack/sdks/python/v1.0.0/spark_feature_push_client","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/sdks/python/v1.0.0/spark_feature_push_client.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"Spark client","sidebar_position":1},"sidebar":"tutorialSidebar","previous":{"title":"GRPC Feature client","permalink":"/BharatMLStack/sdks/python/v1.0.0/grpc_feature_client"}}');var r=a(4848),i=a(8453);const s={title:"Spark client",sidebar_position:1},o="Spark Feature Push Client",l={},c=[{value:"Installation",id:"installation",level:2},{value:"Dependencies",id:"dependencies",level:2},{value:"Architecture Role",id:"architecture-role",level:2},{value:"Features",id:"features",level:2},{value:"When to Use This Client",id:"when-to-use-this-client",level:2},{value:"Quick Start",id:"quick-start",level:2},{value:"Related Packages",id:"related-packages",level:2},{value:"License",id:"license",level:2},{value:"Contributing",id:"contributing",level:2},{value:"Prerequisites",id:"prerequisites",level:2},{value:"Supported Data Sources",id:"supported-data-sources",level:2},{value:"1. Database Tables",id:"1-database-tables",level:3},{value:"2. Cloud Storage - Parquet",id:"2-cloud-storage---parquet",level:3},{value:"3. Cloud Storage - Delta",id:"3-cloud-storage---delta",level:3},{value:"Configuration Examples",id:"configuration-examples",level:2},{value:"Basic Pipeline",id:"basic-pipeline",level:3},{value:"Reading from Multiple Sources",id:"reading-from-multiple-sources",level:3},{value:"Protobuf Serialization & Kafka Publishing",id:"protobuf-serialization--kafka-publishing",level:3},{value:"Data Type Handling",id:"data-type-handling",level:2},{value:"Scalar Types",id:"scalar-types",level:3},{value:"Vector Types",id:"vector-types",level:3},{value:"Production Pipeline Example",id:"production-pipeline-example",level:2},{value:"Configuration Options",id:"configuration-options",level:2},{value:"Client Configuration",id:"client-configuration",level:3},{value:"Protobuf Serialization Options",id:"protobuf-serialization-options",level:3},{value:"Kafka Publishing Options",id:"kafka-publishing-options",level:3},{value:"Performance Tuning",id:"performance-tuning",level:2},{value:"Spark Optimizations",id:"spark-optimizations",level:3},{value:"Memory Management",id:"memory-management",level:3},{value:"Kafka Throughput",id:"kafka-throughput",level:3},{value:"Monitoring & Debugging",id:"monitoring--debugging",level:2},{value:"DataFrame Inspection",id:"dataframe-inspection",level:3},{value:"Error Handling",id:"error-handling",level:3},{value:"Integration with Other SDKs",id:"integration-with-other-sdks",level:2},{value:"With gRPC Feature Client",id:"with-grpc-feature-client",level:3},{value:"With HTTP Feature Client (bharatml_common)",id:"with-http-feature-client-bharatml_common",level:3},{value:"Common Use Cases",id:"common-use-cases",level:2},{value:"1. Daily Batch ETL",id:"1-daily-batch-etl",level:3},{value:"2. Historical Backfill",id:"2-historical-backfill",level:3},{value:"3. Real-time Streaming (Advanced)",id:"3-real-time-streaming-advanced",level:3},{value:"Troubleshooting",id:"troubleshooting",level:2},{value:"Common Issues",id:"common-issues",level:3},{value:"Debug Mode",id:"debug-mode",level:3},{value:"Migration from Legacy Clients",id:"migration-from-legacy-clients",level:2},{value:"Best Practices",id:"best-practices",level:2},{value:"Contributing",id:"contributing-1",level:2},{value:"Community & Support",id:"community--support",level:2},{value:"License",id:"license-1",level:2}];function d(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,i.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"spark-feature-push-client",children:"Spark Feature Push Client"})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://badge.fury.io/py/spark_feature_push_client",children:(0,r.jsx)(n.img,{src:"https://img.shields.io/pypi/v/spark_feature_push_client?label=pypi-package&color=light%20green",alt:"PyPI version"})}),"\n",(0,r.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/actions/workflows/py-sdk.yml",children:(0,r.jsx)(n.img,{src:"https://github.com/Meesho/BharatMLStack/actions/workflows/py-sdk.yml/badge.svg",alt:"Build Status"})}),"\n",(0,r.jsx)(n.a,{href:"https://www.python.org/downloads/",children:(0,r.jsx)(n.img,{src:"https://img.shields.io/badge/python-3.7+-blue.svg",alt:"Python 3.7+"})}),"\n",(0,r.jsx)(n.a,{href:"https://discord.gg/XkT7XsV2AU",children:(0,r.jsx)(n.img,{src:"https://img.shields.io/badge/Discord-Join%20Chat-7289da?style=flat&logo=discord&logoColor=white",alt:"Discord"})}),"\n",(0,r.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md",children:(0,r.jsx)(n.img,{src:"https://img.shields.io/badge/License-BharatMLStack%20BSL%201.1-blue.svg",alt:"License"})})]}),"\n",(0,r.jsxs)(n.p,{children:["Apache Spark-based client for pushing ML features from offline batch sources to the BharatML Stack Online Feature Store via Kafka. This client is designed for ",(0,r.jsx)(n.strong,{children:"data pipeline operations"})," - reading from batch sources and publishing to Kafka for online consumption."]}),"\n",(0,r.jsx)(n.h2,{id:"installation",children:"Installation"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"pip install spark_feature_push_client\n"})}),"\n",(0,r.jsx)(n.h2,{id:"dependencies",children:"Dependencies"}),"\n",(0,r.jsx)(n.p,{children:"This package depends on:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"https://pypi.org/project/bharatml_commons/",children:"bharatml_commons"})}),": Common utilities and protobuf definitions"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"PySpark 3.0+"}),": For distributed data processing"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"architecture-role",children:"Architecture Role"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 Batch Sources \u2502\u2500\u2500\u2500\u25b6\u2502 Spark Feature Push \u2502\u2500\u2500\u2500\u25b6\u2502 Kafka \u2502\u2500\u2500\u2500\u25b6\u2502 Online Feature \u2502\n\u2502 \u2022 Tables \u2502 \u2502 Client \u2502 \u2502 \u2502 \u2502 Store \u2502\n\u2502 \u2022 Parquet \u2502 \u2502 \u2022 Read & Transform \u2502 \u2502 \u2502 \u2502 \u2502\n\u2502 \u2022 Delta \u2502 \u2502 \u2022 Protobuf Serialize \u2502 \u2502 \u2502 \u2502 \u2502\n\u2502 \u2022 S3/GCS/ADLS \u2502 \u2502 \u2022 Batch Processing \u2502 \u2502 \u2502 \u2502 \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u25b2\n \u2502\n \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 grpc_feature_ \u2502\n \u2502 client \u2502\n \u2502 (Real-time) \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n"})}),"\n",(0,r.jsx)(n.h2,{id:"features",children:"Features"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Batch Source Integration"}),": Read from Tables (Hive/Delta), Parquet, and Delta files on cloud storage"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Spark Processing"}),": Leverage Apache Spark for distributed data processing"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Protobuf Serialization"}),": Convert feature data to protobuf format using bharatml_commons schemas"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Kafka Publishing"}),": Push serialized features to Kafka topics for online consumption"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Metadata Integration"}),": Fetch feature schemas and configurations via REST API"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Data Type Support"}),": Handle scalar and vector types (strings, numbers, booleans, arrays)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Batch Optimization"}),": Configurable batch sizes for optimal Kafka throughput"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"when-to-use-this-client",children:"When to Use This Client"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Use spark_feature_push_client for:"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["\ud83d\udd04 ",(0,r.jsx)(n.strong,{children:"Batch ETL Pipelines"}),": Scheduled feature computation and publishing"]}),"\n",(0,r.jsxs)(n.li,{children:["\ud83d\udcca ",(0,r.jsx)(n.strong,{children:"Historical Data Backfill"}),": Loading historical features into online store"]}),"\n",(0,r.jsxs)(n.li,{children:["\ud83c\udfd7\ufe0f ",(0,r.jsx)(n.strong,{children:"Data Engineering"}),": Spark-based feature transformations"]}),"\n",(0,r.jsxs)(n.li,{children:["\ud83d\udcc8 ",(0,r.jsx)(n.strong,{children:"Large Scale Processing"}),": Processing millions of records efficiently"]}),"\n",(0,r.jsxs)(n.li,{children:["\u26a1 ",(0,r.jsx)(n.strong,{children:"Offline-to-Online"}),": Bridge between batch and real-time systems"]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Use grpc_feature_client for:"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["\ud83d\ude80 ",(0,r.jsx)(n.strong,{children:"Real-time Operations"}),": Direct persist/retrieve operations"]}),"\n",(0,r.jsxs)(n.li,{children:["\ud83d\udd0d ",(0,r.jsx)(n.strong,{children:"Interactive Queries"}),": Low-latency feature lookups"]}),"\n",(0,r.jsxs)(n.li,{children:["\ud83c\udfaf ",(0,r.jsx)(n.strong,{children:"API Integration"}),": Service-to-service communication"]}),"\n",(0,r.jsxs)(n.li,{children:["\ud83d\udca8 ",(0,r.jsx)(n.strong,{children:"Single Records"}),": Persisting individual feature records"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"quick-start",children:"Quick Start"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'from spark_feature_push_client import OnlineFeatureStorePyClient\n\n# Initialize client with metadata source\nclient = OnlineFeatureStorePyClient(\n features_metadata_source_url="https://api.example.com/metadata",\n job_id="feature-pipeline-job",\n job_token="your-auth-token"\n)\n\n# Get feature configuration \nfeature_details = client.get_features_details()\n\n# Process your Spark DataFrame\nproto_df = client.generate_df_with_protobuf_messages(your_spark_df)\n\n# Push to Kafka\nclient.write_protobuf_df_to_kafka(\n proto_df,\n kafka_bootstrap_servers="localhost:9092",\n kafka_topic="features.user_features"\n)\n'})}),"\n",(0,r.jsx)(n.h2,{id:"related-packages",children:"Related Packages"}),"\n",(0,r.jsx)(n.p,{children:"This package is part of the BharatML Stack ecosystem:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"https://pypi.org/project/bharatml_commons/",children:"bharatml_commons"})}),": Common utilities and protobuf definitions (required dependency)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"https://pypi.org/project/grpc_feature_client/",children:"grpc_feature_client"})}),": High-performance gRPC client for real-time operations"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"license",children:"License"}),"\n",(0,r.jsxs)(n.p,{children:["Licensed under the BharatMLStack Business Source License 1.1. See ",(0,r.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md",children:"LICENSE"})," for details."]}),"\n",(0,r.jsx)(n.h2,{id:"contributing",children:"Contributing"}),"\n",(0,r.jsxs)(n.p,{children:["We welcome contributions! Please see our ",(0,r.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTION.md",children:"Contributing Guide"})," for details."]}),"\n",(0,r.jsx)(n.h2,{id:"prerequisites",children:"Prerequisites"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Apache Spark 3.0+"}),": For distributed processing"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Kafka Connector"}),": ",(0,r.jsx)(n.code,{children:"spark-sql-kafka"})," for Kafka integration"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Java 8/11"}),": Required by Spark"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"bharatml_common"}),": For protobuf schemas"]}),"\n"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:'# Example Spark session setup\nspark = SparkSession.builder \\\n .appName("FeaturePipeline") \\\n .config("spark.jars.packages", "org.apache.spark:spark-sql-kafka-0-10_2.12:3.4.0") \\\n .getOrCreate()\n'})}),"\n",(0,r.jsx)(n.h2,{id:"supported-data-sources",children:"Supported Data Sources"}),"\n",(0,r.jsx)(n.h3,{id:"1-database-tables",children:"1. Database Tables"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'# Hive/Delta tables\ndf = spark.sql("SELECT * FROM feature_db.user_features")\n'})}),"\n",(0,r.jsx)(n.h3,{id:"2-cloud-storage---parquet",children:"2. Cloud Storage - Parquet"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'# AWS S3\ndf = spark.read.parquet("s3a://bucket/path/to/features/")\n\n# Google Cloud Storage \ndf = spark.read.parquet("gs://bucket/path/to/features/")\n\n# Azure Data Lake\ndf = spark.read.parquet("abfss://container@account.dfs.core.windows.net/path/")\n'})}),"\n",(0,r.jsx)(n.h3,{id:"3-cloud-storage---delta",children:"3. Cloud Storage - Delta"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'# Delta format on cloud storage\ndf = spark.read.format("delta").load("s3a://bucket/delta-table/")\n'})}),"\n",(0,r.jsx)(n.h2,{id:"configuration-examples",children:"Configuration Examples"}),"\n",(0,r.jsx)(n.h3,{id:"basic-pipeline",children:"Basic Pipeline"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'from pyspark.sql import SparkSession\nfrom spark_feature_push_client import OnlineFeatureStorePyClient\n\n# Create Spark session\nspark = SparkSession.builder \\\n .appName("FeatureETL") \\\n .config("spark.jars.packages", "org.apache.spark:spark-sql-kafka-0-10_2.12:3.4.0") \\\n .getOrCreate()\n\n# Initialize client\nclient = OnlineFeatureStorePyClient(\n features_metadata_source_url="https://metadata-service.example.com/api/v1/features",\n job_id="daily-feature-pipeline",\n job_token="pipeline-secret-token",\n fgs_to_consider=["user_demographics", "user_behavior"] # Optional: filter feature groups\n)\n\n# Get metadata and column mappings\n(\n offline_src_type_columns,\n offline_col_to_default_values_map, \n entity_column_names\n) = client.get_features_details()\n\nprint(f"Entity columns: {entity_column_names}")\nprint(f"Feature mappings: {offline_src_type_columns}")\n'})}),"\n",(0,r.jsx)(n.h3,{id:"reading-from-multiple-sources",children:"Reading from Multiple Sources"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'def get_features_from_all_sources(spark, entity_columns, feature_mapping, default_values):\n """\n Read and combine features from multiple offline sources\n """\n dataframes = []\n \n for source_info in feature_mapping:\n table_name, source_type, feature_list = source_info\n \n if source_type == "TABLE":\n # Read from Hive/Delta table\n df = spark.table(table_name)\n \n elif source_type.startswith("PARQUET_"):\n # Read from Parquet files\n df = spark.read.parquet(table_name)\n \n elif source_type.startswith("DELTA_"):\n # Read from Delta files\n df = spark.read.format("delta").load(table_name)\n \n # Select and rename columns\n select_cols = entity_columns.copy()\n for original_col, renamed_col in feature_list:\n if original_col in df.columns:\n df = df.withColumnRenamed(original_col, renamed_col)\n select_cols.append(renamed_col)\n \n df = df.select(select_cols)\n dataframes.append(df)\n \n # Union all dataframes\n if dataframes:\n combined_df = dataframes[0]\n for df in dataframes[1:]:\n combined_df = combined_df.unionByName(df, allowMissingColumns=True)\n \n # Fill missing values with defaults\n for col, default_val in default_values.items():\n if col in combined_df.columns:\n combined_df = combined_df.fillna({col: default_val})\n \n return combined_df\n \n return None\n\n# Use the function\ndf = get_features_from_all_sources(\n spark, \n entity_column_names, \n offline_src_type_columns, \n offline_col_to_default_values_map\n)\n'})}),"\n",(0,r.jsx)(n.h3,{id:"protobuf-serialization--kafka-publishing",children:"Protobuf Serialization & Kafka Publishing"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'# Convert DataFrame to protobuf messages\n# This creates binary protobuf messages suitable for Kafka\nproto_df = client.generate_df_with_protobuf_messages(\n df, \n intra_batch_size=20 # Batch size for serialization\n)\n\n# The proto_df has schema: [value: binary, intra_batch_id: long]\nproto_df.printSchema()\n# root\n# |-- value: binary (nullable = false) \n# |-- intra_batch_id: long (nullable = false)\n\n# Write to Kafka with batching for better throughput\nclient.write_protobuf_df_to_kafka(\n proto_df,\n kafka_bootstrap_servers="broker1:9092,broker2:9092,broker3:9092",\n kafka_topic="features.user_features",\n additional_options={\n "kafka.acks": "all",\n "kafka.retries": "3",\n "kafka.compression.type": "snappy"\n },\n kafka_num_batches=4 # Split into 4 parallel Kafka writes\n)\n'})}),"\n",(0,r.jsx)(n.h2,{id:"data-type-handling",children:"Data Type Handling"}),"\n",(0,r.jsx)(n.p,{children:"The client automatically handles the protobuf data type mappings:"}),"\n",(0,r.jsx)(n.h3,{id:"scalar-types",children:"Scalar Types"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'# Example DataFrame with different types\ndata = [\n ("user123", 25, 185.5, True, "premium"), # int, float, bool, string\n ("user456", 30, 170.0, False, "basic")\n]\ndf = spark.createDataFrame(data, ["user_id", "age", "height", "is_premium", "tier"])\n\n# Automatically mapped to protobuf:\n# age -> int32_values\n# height -> fp32_values \n# is_premium -> bool_values\n# tier -> string_values\n'})}),"\n",(0,r.jsx)(n.h3,{id:"vector-types",children:"Vector Types"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'# Example with vector/array features\nfrom pyspark.sql.functions import array, lit\n\ndf = spark.createDataFrame([\n ("user123", [0.1, 0.2, 0.3], ["tech", "sports"], [1, 2, 3])\n], ["user_id", "embeddings", "interests", "scores"])\n\n# Automatically mapped to protobuf vectors:\n# embeddings -> fp32_values in Vector\n# interests -> string_values in Vector\n# scores -> int32_values in Vector\n'})}),"\n",(0,r.jsx)(n.h2,{id:"production-pipeline-example",children:"Production Pipeline Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'def run_feature_pipeline():\n """\n Complete feature pipeline from batch sources to Kafka\n """\n \n # 1. Initialize Spark\n spark = SparkSession.builder \\\n .appName("DailyFeaturePipeline") \\\n .config("spark.sql.adaptive.enabled", "true") \\\n .config("spark.jars.packages", "org.apache.spark:spark-sql-kafka-0-10_2.12:3.4.0") \\\n .getOrCreate()\n \n try:\n # 2. Initialize feature client\n client = OnlineFeatureStorePyClient(\n features_metadata_source_url=os.getenv("METADATA_URL"),\n job_id=os.getenv("JOB_ID"),\n job_token=os.getenv("JOB_TOKEN")\n )\n \n # 3. Get feature configuration\n feature_mapping, default_values, entity_columns = client.get_features_details()\n \n # 4. Read and process data\n df = get_features_from_all_sources(spark, entity_columns, feature_mapping, default_values)\n \n if df is None or df.count() == 0:\n raise ValueError("No data found in sources")\n \n # 5. Convert to protobuf\n proto_df = client.generate_df_with_protobuf_messages(df, intra_batch_size=50)\n \n # 6. Publish to Kafka\n client.write_protobuf_df_to_kafka(\n proto_df,\n kafka_bootstrap_servers=os.getenv("KAFKA_BROKERS"),\n kafka_topic=os.getenv("KAFKA_TOPIC"),\n additional_options={\n "kafka.acks": "all",\n "kafka.compression.type": "snappy",\n "kafka.max.request.size": "10485760" # 10MB\n },\n kafka_num_batches=int(os.getenv("KAFKA_BATCHES", "4"))\n )\n \n print(f"\u2705 Successfully processed {df.count()} records")\n \n finally:\n spark.stop()\n\nif __name__ == "__main__":\n run_feature_pipeline()\n'})}),"\n",(0,r.jsx)(n.h2,{id:"configuration-options",children:"Configuration Options"}),"\n",(0,r.jsx)(n.h3,{id:"client-configuration",children:"Client Configuration"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'client = OnlineFeatureStorePyClient(\n features_metadata_source_url="https://api.example.com/metadata", # Required\n job_id="pipeline-job-001", # Required \n job_token="secret-token-123", # Required\n fgs_to_consider=["user_features", "item_features"] # Optional: filter feature groups\n)\n'})}),"\n",(0,r.jsx)(n.h3,{id:"protobuf-serialization-options",children:"Protobuf Serialization Options"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:"proto_df = client.generate_df_with_protobuf_messages(\n df,\n intra_batch_size=20 # Records per protobuf message (default: 20)\n)\n"})}),"\n",(0,r.jsx)(n.h3,{id:"kafka-publishing-options",children:"Kafka Publishing Options"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'client.write_protobuf_df_to_kafka(\n proto_df,\n kafka_bootstrap_servers="localhost:9092",\n kafka_topic="features.topic",\n additional_options={\n "kafka.acks": "all", # Acknowledgment level\n "kafka.retries": "3", # Retry attempts\n "kafka.compression.type": "snappy", # Compression\n "kafka.batch.size": "16384", # Batch size\n "kafka.linger.ms": "100", # Batching delay\n "kafka.max.request.size": "10485760" # Max message size\n },\n kafka_num_batches=1 # Number of parallel Kafka writers (default: 1)\n)\n'})}),"\n",(0,r.jsx)(n.h2,{id:"performance-tuning",children:"Performance Tuning"}),"\n",(0,r.jsx)(n.h3,{id:"spark-optimizations",children:"Spark Optimizations"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'spark = SparkSession.builder \\\n .appName("FeaturePipeline") \\\n .config("spark.sql.adaptive.enabled", "true") \\\n .config("spark.sql.adaptive.coalescePartitions.enabled", "true") \\\n .config("spark.sql.adaptive.skewJoin.enabled", "true") \\\n .config("spark.serializer", "org.apache.spark.serializer.KryoSerializer") \\\n .getOrCreate()\n'})}),"\n",(0,r.jsx)(n.h3,{id:"memory-management",children:"Memory Management"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:"# For large datasets, consider:\ndf = df.repartition(200) # Optimal partition count\ndf.cache() # Cache if reused multiple times\n"})}),"\n",(0,r.jsx)(n.h3,{id:"kafka-throughput",children:"Kafka Throughput"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'# For high-throughput scenarios:\nclient.write_protobuf_df_to_kafka(\n proto_df,\n kafka_bootstrap_servers="brokers",\n kafka_topic="topic", \n kafka_num_batches=8, # Increase parallel writers\n additional_options={\n "kafka.batch.size": "65536", # Larger batches\n "kafka.linger.ms": "100", # Allow batching delay\n "kafka.compression.type": "lz4" # Fast compression\n }\n)\n'})}),"\n",(0,r.jsx)(n.h2,{id:"monitoring--debugging",children:"Monitoring & Debugging"}),"\n",(0,r.jsx)(n.h3,{id:"dataframe-inspection",children:"DataFrame Inspection"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'# Check data before processing\nprint(f"Records: {df.count()}")\nprint(f"Columns: {df.columns}")\ndf.printSchema()\ndf.show(5)\n\n# Check protobuf output\nproto_df.show(5, truncate=False)\nprint(f"Protobuf messages: {proto_df.count()}")\n'})}),"\n",(0,r.jsx)(n.h3,{id:"error-handling",children:"Error Handling"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'try:\n proto_df = client.generate_df_with_protobuf_messages(df)\n client.write_protobuf_df_to_kafka(proto_df, brokers, topic)\n \nexcept Exception as e:\n print(f"Pipeline failed: {e}")\n # Log to monitoring system\n # Send alerts\n raise\n'})}),"\n",(0,r.jsx)(n.h2,{id:"integration-with-other-sdks",children:"Integration with Other SDKs"}),"\n",(0,r.jsx)(n.h3,{id:"with-grpc-feature-client",children:"With gRPC Feature Client"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:"# Spark client pushes features to Kafka\nspark_client = OnlineFeatureStorePyClient(...)\nspark_client.write_protobuf_df_to_kafka(proto_df, brokers, topic)\n\n# gRPC client retrieves features in real-time\nfrom grpc_feature_client import GRPCFeatureClient\ngrpc_client = GRPCFeatureClient(config)\nfeatures = grpc_client.retrieve_decoded_features(...)\n"})}),"\n",(0,r.jsx)(n.h3,{id:"with-http-feature-client-bharatml_common",children:"With HTTP Feature Client (bharatml_common)"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:"# Use HTTP client for metadata validation\nfrom bharatml_common import HTTPFeatureClient\nhttp_client = HTTPFeatureClient(base_url, job_id, token)\nmetadata = http_client.get_feature_metadata()\n\n# Validate feature names using shared utilities\nfrom bharatml_common import clean_column_name\nclean_features = [clean_column_name(name) for name in feature_names]\n\n# Process with Spark client\nspark_client.generate_df_with_protobuf_messages(df)\n"})}),"\n",(0,r.jsx)(n.h2,{id:"common-use-cases",children:"Common Use Cases"}),"\n",(0,r.jsx)(n.h3,{id:"1-daily-batch-etl",children:"1. Daily Batch ETL"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"# Cron job: 0 2 * * * (daily at 2 AM)\nspark-submit \\\n --packages org.apache.spark:spark-sql-kafka-0-10_2.12:3.4.0 \\\n --conf spark.sql.adaptive.enabled=true \\\n daily_feature_pipeline.py\n"})}),"\n",(0,r.jsx)(n.h3,{id:"2-historical-backfill",children:"2. Historical Backfill"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'# Backfill last 30 days\nfrom datetime import datetime, timedelta\n\nfor i in range(30):\n date = datetime.now() - timedelta(days=i)\n df = spark.sql(f"""\n SELECT * FROM features \n WHERE date = \'{date.strftime(\'%Y-%m-%d\')}\'\n """)\n \n proto_df = client.generate_df_with_protobuf_messages(df)\n client.write_protobuf_df_to_kafka(proto_df, brokers, f"backfill.{date.strftime(\'%Y%m%d\')}")\n'})}),"\n",(0,r.jsx)(n.h3,{id:"3-real-time-streaming-advanced",children:"3. Real-time Streaming (Advanced)"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'# Read from streaming source, process, and publish\nstreaming_df = spark.readStream \\\n .format("kafka") \\\n .option("kafka.bootstrap.servers", input_brokers) \\\n .option("subscribe", input_topic) \\\n .load()\n\n# Process streaming DataFrame\nprocessed_df = streaming_df.select(...)\n\n# Write to output Kafka (requires structured streaming)\nquery = processed_df.writeStream \\\n .format("kafka") \\\n .option("kafka.bootstrap.servers", output_brokers) \\\n .option("topic", output_topic) \\\n .start()\n'})}),"\n",(0,r.jsx)(n.h2,{id:"troubleshooting",children:"Troubleshooting"}),"\n",(0,r.jsx)(n.h3,{id:"common-issues",children:"Common Issues"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"OutOfMemoryError"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'# Increase driver memory or reduce partition size\nspark.conf.set("spark.sql.adaptive.coalescePartitions.minPartitionNum", "50")\n'})}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Kafka Connection Timeout"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'# Check network connectivity and broker addresses\nadditional_options = {\n "kafka.request.timeout.ms": "60000",\n "kafka.session.timeout.ms": "30000"\n}\n'})}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Protobuf Serialization Errors"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'# Check data types and null values\ndf = df.fillna({"string_col": "", "numeric_col": 0})\n'})}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Metadata API Errors"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:"# Verify job_id, job_token, and URL\n# Check API server logs\n"})}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"debug-mode",children:"Debug Mode"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'import logging\nlogging.basicConfig(level=logging.DEBUG)\n\n# Enable Spark SQL logging\nspark.sparkContext.setLogLevel("INFO")\n'})}),"\n",(0,r.jsx)(n.h2,{id:"migration-from-legacy-clients",children:"Migration from Legacy Clients"}),"\n",(0,r.jsx)(n.p,{children:"If migrating from older versions:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:"# Old import\n# from online_feature_store_py_client import OnlineFeatureStorePyClient\n\n# New import (same interface)\nfrom spark_feature_push_client import OnlineFeatureStorePyClient\n\n# API remains the same - no code changes needed!\n"})}),"\n",(0,r.jsx)(n.h2,{id:"best-practices",children:"Best Practices"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Resource Management"}),": Always stop Spark sessions"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Error Handling"}),": Implement proper exception handling and retries"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Monitoring"}),": Add metrics and logging to your pipelines"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Testing"}),": Test with sample data before production runs"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Security"}),": Use secure Kafka configurations in production"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Performance"}),": Monitor Spark UI for optimization opportunities"]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"The Spark Feature Push Client is your gateway from batch data sources to the real-time online feature store! \ud83d\ude80"}),"\n",(0,r.jsx)(n.h2,{id:"contributing-1",children:"Contributing"}),"\n",(0,r.jsxs)(n.p,{children:["We welcome contributions from the community! Please see our ",(0,r.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTING.md",children:"Contributing Guide"})," for details on how to get started."]}),"\n",(0,r.jsx)(n.h2,{id:"community--support",children:"Community & Support"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["\ud83d\udcac ",(0,r.jsx)(n.strong,{children:"Discord"}),": Join our ",(0,r.jsx)(n.a,{href:"https://discord.gg/XkT7XsV2AU",children:"community chat"})]}),"\n",(0,r.jsxs)(n.li,{children:["\ud83d\udc1b ",(0,r.jsx)(n.strong,{children:"Issues"}),": Report bugs and request features on ",(0,r.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/issues",children:"GitHub Issues"})]}),"\n",(0,r.jsxs)(n.li,{children:["\ud83d\udce7 ",(0,r.jsx)(n.strong,{children:"Email"}),": Contact us at ",(0,r.jsx)(n.a,{href:"mailto:ml-oss@meesho.com",children:"ml-oss@meesho.com"})]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"license-1",children:"License"}),"\n",(0,r.jsxs)(n.p,{children:["BharatMLStack is open-source software licensed under the ",(0,r.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md",children:"BharatMLStack Business Source License 1.1"}),"."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)("div",{align:"center",children:(0,r.jsx)("strong",{children:"Built with \u2764\ufe0f for the ML community from Meesho"})}),"\n",(0,r.jsx)("div",{align:"center",children:(0,r.jsx)("strong",{children:"If you find this useful, \u2b50\ufe0f the repo \u2014 your support means the world to us!"})})]})}function p(e={}){const{wrapper:n}={...(0,i.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(d,{...e})}):d(e)}},8453:(e,n,a)=>{a.d(n,{R:()=>s,x:()=>o});var t=a(6540);const r={},i=t.createContext(r);function s(e){const n=t.useContext(i);return t.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:s(e.components),t.createElement(i.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/adb039a4.88a13880.js b/docs/assets/js/adb039a4.88a13880.js new file mode 100644 index 00000000..04feafd6 --- /dev/null +++ b/docs/assets/js/adb039a4.88a13880.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[7609],{926:a=>{a.exports=JSON.parse('{"tag":{"label":"llm","permalink":"/BharatMLStack/blog/tags/llm","allTagsPath":"/BharatMLStack/blog/tags","count":3,"unlisted":false},"listMetadata":{"permalink":"/BharatMLStack/blog/tags/llm","page":1,"postsPerPage":10,"totalPages":1,"totalCount":3,"blogDescription":"Blog","blogTitle":"Blog"}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/ae7a6e8a.58ed54b2.js b/docs/assets/js/ae7a6e8a.58ed54b2.js new file mode 100644 index 00000000..8cc02b7b --- /dev/null +++ b/docs/assets/js/ae7a6e8a.58ed54b2.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[8591],{4795:(e,t,r)=>{r.d(t,{A:()=>j});r(6540);var n=r(4164),s=r(6972),o=r(8774),i=r(5846),c=r(6654),a=r(1312),l=r(1107);const u={cardContainer:"cardContainer_fWXF",cardTitle:"cardTitle_rnsV",cardDescription:"cardDescription_PWke"};var d=r(4848);function f({className:e,href:t,children:r}){return(0,d.jsx)(o.A,{href:t,className:(0,n.A)("card padding--lg",u.cardContainer,e),children:r})}function m({className:e,href:t,icon:r,title:s,description:o}){return(0,d.jsxs)(f,{href:t,className:e,children:[(0,d.jsxs)(l.A,{as:"h2",className:(0,n.A)("text--truncate",u.cardTitle),title:s,children:[r," ",s]}),o&&(0,d.jsx)("p",{className:(0,n.A)("text--truncate",u.cardDescription),title:o,children:o})]})}function h({item:e}){const t=(0,s.Nr)(e),r=function(){const{selectMessage:e}=(0,i.W)();return t=>e(t,(0,a.T)({message:"1 item|{count} items",id:"theme.docs.DocCard.categoryDescription.plurals",description:"The default description for a category card in the generated index about how many items this category includes"},{count:t}))}();return t?(0,d.jsx)(m,{className:e.className,href:t,icon:"\ud83d\uddc3\ufe0f",title:e.label,description:e.description??r(e.items.length)}):null}function p({item:e}){const t=(0,c.A)(e.href)?"\ud83d\udcc4\ufe0f":"\ud83d\udd17",r=(0,s.cC)(e.docId??void 0);return(0,d.jsx)(m,{className:e.className,href:e.href,icon:t,title:e.label,description:e.description??r?.description})}function v({item:e}){switch(e.type){case"link":return(0,d.jsx)(p,{item:e});case"category":return(0,d.jsx)(h,{item:e});default:throw new Error(`unknown item type ${JSON.stringify(e)}`)}}const x={docCardListItem:"docCardListItem_W1sv"};function g({className:e}){const t=(0,s.a4)();return(0,d.jsx)(j,{items:t,className:e})}function w({item:e}){return(0,d.jsx)("article",{className:(0,n.A)(x.docCardListItem,"col col--6"),children:(0,d.jsx)(v,{item:e})})}function j(e){const{items:t,className:r}=e;if(!t)return(0,d.jsx)(g,{...e});const o=(0,s.d1)(t);return(0,d.jsx)("section",{className:(0,n.A)("row",r),children:o.map((e,t)=>(0,d.jsx)(w,{item:e},t))})}},5846:(e,t,r)=>{r.d(t,{W:()=>l});var n=r(6540),s=r(4586);const o=["zero","one","two","few","many","other"];function i(e){return o.filter(t=>e.includes(t))}const c={locale:"en",pluralForms:i(["one","other"]),select:e=>1===e?"one":"other"};function a(){const{i18n:{currentLocale:e}}=(0,s.A)();return(0,n.useMemo)(()=>{try{return function(e){const t=new Intl.PluralRules(e);return{locale:e,pluralForms:i(t.resolvedOptions().pluralCategories),select:e=>t.select(e)}}(e)}catch(t){return console.error(`Failed to use Intl.PluralRules for locale "${e}".\nDocusaurus will fallback to the default (English) implementation.\nError: ${t.message}\n`),c}},[e])}function l(){const e=a();return{selectMessage:(t,r)=>function(e,t,r){const n=e.split("|");if(1===n.length)return n[0];n.length>r.pluralForms.length&&console.error(`For locale=${r.locale}, a maximum of ${r.pluralForms.length} plural forms are expected (${r.pluralForms.join(",")}), but the message contains ${n.length}: ${e}`);const s=r.select(t),o=r.pluralForms.indexOf(s);return n[Math.min(o,n.length-1)]}(r,t,e)}}},8453:(e,t,r)=>{r.d(t,{R:()=>i,x:()=>c});var n=r(6540);const s={},o=n.createContext(s);function i(e){const t=n.useContext(o);return n.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function c(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:i(e.components),n.createElement(o.Provider,{value:t},e.children)}},8670:(e,t,r)=>{r.r(t),r.d(t,{assets:()=>l,contentTitle:()=>a,default:()=>f,frontMatter:()=>c,metadata:()=>n,toc:()=>u});const n=JSON.parse('{"id":"inferflow/v1.0.0/index","title":"v1.0.0","description":"Inferflow v1.0.0","source":"@site/docs/inferflow/v1.0.0/index.md","sourceDirName":"inferflow/v1.0.0","slug":"/inferflow/v1.0.0","permalink":"/BharatMLStack/inferflow/v1.0.0","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/inferflow/v1.0.0/index.md","tags":[],"version":"current","sidebarPosition":0,"frontMatter":{"title":"v1.0.0","description":"Inferflow v1.0.0","sidebar_position":0,"slug":"/inferflow/v1.0.0"},"sidebar":"tutorialSidebar","previous":{"title":"Inferflow","permalink":"/BharatMLStack/category/inferflow"},"next":{"title":"Architecture","permalink":"/BharatMLStack/inferflow/v1.0.0/architecture"}}');var s=r(4848),o=r(8453),i=r(4795);const c={title:"v1.0.0",description:"Inferflow v1.0.0",sidebar_position:0,slug:"/inferflow/v1.0.0"},a="Inferflow v1.0.0",l={},u=[];function d(e){const t={h1:"h1",header:"header",p:"p",...(0,o.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(t.header,{children:(0,s.jsx)(t.h1,{id:"inferflow-v100",children:"Inferflow v1.0.0"})}),"\n",(0,s.jsx)(t.p,{children:"Inferflow is a graph-driven feature retrieval and model inference orchestration engine. It dynamically resolves entity relationships via configurable DAGs, retrieves features from the Online Feature Store, and orchestrates model scoring."}),"\n",(0,s.jsx)(i.A,{})]})}function f(e={}){const{wrapper:t}={...(0,o.R)(),...e.components};return t?(0,s.jsx)(t,{...e,children:(0,s.jsx)(d,{...e})}):d(e)}}}]); \ No newline at end of file diff --git a/docs/assets/js/b0267ac9.2ed3e1de.js b/docs/assets/js/b0267ac9.2ed3e1de.js new file mode 100644 index 00000000..225e475f --- /dev/null +++ b/docs/assets/js/b0267ac9.2ed3e1de.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[1965],{2604:(e,t,r)=>{r.r(t),r.d(t,{assets:()=>l,contentTitle:()=>a,default:()=>d,frontMatter:()=>o,metadata:()=>n,toc:()=>u});const n=JSON.parse('{"id":"numerix/v1.0.0/index","title":"v1.0.0","description":"Numerix v1.0.0","source":"@site/docs/numerix/v1.0.0/index.md","sourceDirName":"numerix/v1.0.0","slug":"/numerix/v1.0.0","permalink":"/BharatMLStack/numerix/v1.0.0","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/numerix/v1.0.0/index.md","tags":[],"version":"current","sidebarPosition":0,"frontMatter":{"title":"v1.0.0","description":"Numerix v1.0.0","sidebar_position":0,"slug":"/numerix/v1.0.0"},"sidebar":"tutorialSidebar","previous":{"title":"Numerix","permalink":"/BharatMLStack/category/numerix"},"next":{"title":"Architecture","permalink":"/BharatMLStack/numerix/v1.0.0/architecture"}}');var s=r(4848),i=r(8453),c=r(4795);const o={title:"v1.0.0",description:"Numerix v1.0.0",sidebar_position:0,slug:"/numerix/v1.0.0"},a="Numerix v1.0.0",l={},u=[];function m(e){const t={h1:"h1",header:"header",p:"p",...(0,i.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(t.header,{children:(0,s.jsx)(t.h1,{id:"numerix-v100",children:"Numerix v1.0.0"})}),"\n",(0,s.jsx)(t.p,{children:"Numerix is a mathematical compute engine for BharatML Stack. It is used to perform mathematical operations on matrices and vectors."}),"\n",(0,s.jsx)(c.A,{})]})}function d(e={}){const{wrapper:t}={...(0,i.R)(),...e.components};return t?(0,s.jsx)(t,{...e,children:(0,s.jsx)(m,{...e})}):m(e)}},4795:(e,t,r)=>{r.d(t,{A:()=>j});r(6540);var n=r(4164),s=r(6972),i=r(8774),c=r(5846),o=r(6654),a=r(1312),l=r(1107);const u={cardContainer:"cardContainer_fWXF",cardTitle:"cardTitle_rnsV",cardDescription:"cardDescription_PWke"};var m=r(4848);function d({className:e,href:t,children:r}){return(0,m.jsx)(i.A,{href:t,className:(0,n.A)("card padding--lg",u.cardContainer,e),children:r})}function h({className:e,href:t,icon:r,title:s,description:i}){return(0,m.jsxs)(d,{href:t,className:e,children:[(0,m.jsxs)(l.A,{as:"h2",className:(0,n.A)("text--truncate",u.cardTitle),title:s,children:[r," ",s]}),i&&(0,m.jsx)("p",{className:(0,n.A)("text--truncate",u.cardDescription),title:i,children:i})]})}function p({item:e}){const t=(0,s.Nr)(e),r=function(){const{selectMessage:e}=(0,c.W)();return t=>e(t,(0,a.T)({message:"1 item|{count} items",id:"theme.docs.DocCard.categoryDescription.plurals",description:"The default description for a category card in the generated index about how many items this category includes"},{count:t}))}();return t?(0,m.jsx)(h,{className:e.className,href:t,icon:"\ud83d\uddc3\ufe0f",title:e.label,description:e.description??r(e.items.length)}):null}function f({item:e}){const t=(0,o.A)(e.href)?"\ud83d\udcc4\ufe0f":"\ud83d\udd17",r=(0,s.cC)(e.docId??void 0);return(0,m.jsx)(h,{className:e.className,href:e.href,icon:t,title:e.label,description:e.description??r?.description})}function x({item:e}){switch(e.type){case"link":return(0,m.jsx)(f,{item:e});case"category":return(0,m.jsx)(p,{item:e});default:throw new Error(`unknown item type ${JSON.stringify(e)}`)}}const v={docCardListItem:"docCardListItem_W1sv"};function g({className:e}){const t=(0,s.a4)();return(0,m.jsx)(j,{items:t,className:e})}function N({item:e}){return(0,m.jsx)("article",{className:(0,n.A)(v.docCardListItem,"col col--6"),children:(0,m.jsx)(x,{item:e})})}function j(e){const{items:t,className:r}=e;if(!t)return(0,m.jsx)(g,{...e});const i=(0,s.d1)(t);return(0,m.jsx)("section",{className:(0,n.A)("row",r),children:i.map((e,t)=>(0,m.jsx)(N,{item:e},t))})}},5846:(e,t,r)=>{r.d(t,{W:()=>l});var n=r(6540),s=r(4586);const i=["zero","one","two","few","many","other"];function c(e){return i.filter(t=>e.includes(t))}const o={locale:"en",pluralForms:c(["one","other"]),select:e=>1===e?"one":"other"};function a(){const{i18n:{currentLocale:e}}=(0,s.A)();return(0,n.useMemo)(()=>{try{return function(e){const t=new Intl.PluralRules(e);return{locale:e,pluralForms:c(t.resolvedOptions().pluralCategories),select:e=>t.select(e)}}(e)}catch(t){return console.error(`Failed to use Intl.PluralRules for locale "${e}".\nDocusaurus will fallback to the default (English) implementation.\nError: ${t.message}\n`),o}},[e])}function l(){const e=a();return{selectMessage:(t,r)=>function(e,t,r){const n=e.split("|");if(1===n.length)return n[0];n.length>r.pluralForms.length&&console.error(`For locale=${r.locale}, a maximum of ${r.pluralForms.length} plural forms are expected (${r.pluralForms.join(",")}), but the message contains ${n.length}: ${e}`);const s=r.select(t),i=r.pluralForms.indexOf(s);return n[Math.min(i,n.length-1)]}(r,t,e)}}},8453:(e,t,r)=>{r.d(t,{R:()=>c,x:()=>o});var n=r(6540);const s={},i=n.createContext(s);function c(e){const t=n.useContext(i);return n.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function o(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:c(e.components),n.createElement(i.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/bba9e323.c204228f.js b/docs/assets/js/bba9e323.c204228f.js new file mode 100644 index 00000000..2bacd6f0 --- /dev/null +++ b/docs/assets/js/bba9e323.c204228f.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[6812],{2486:(e,n,r)=>{r.r(n),r.d(n,{assets:()=>l,contentTitle:()=>a,default:()=>h,frontMatter:()=>i,metadata:()=>t,toc:()=>c});const t=JSON.parse('{"id":"predator/v1.0.0/release-notes","title":"Release Notes","description":"Version 1.0.0","source":"@site/docs/predator/v1.0.0/release-notes.md","sourceDirName":"predator/v1.0.0","slug":"/predator/v1.0.0/release-notes","permalink":"/BharatMLStack/predator/v1.0.0/release-notes","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/predator/v1.0.0/release-notes.md","tags":[],"version":"current","sidebarPosition":3,"frontMatter":{"title":"Release Notes","sidebar_position":3},"sidebar":"tutorialSidebar","previous":{"title":"Key Functionalities","permalink":"/BharatMLStack/predator/v1.0.0/functionalities"}}');var s=r(4848),o=r(8453);const i={title:"Release Notes",sidebar_position:3},a="Predator - Release Notes",l={},c=[{value:"Version 1.0.0",id:"version-100",level:2},{value:"What's New",id:"whats-new",level:3}];function d(e){const n={br:"br",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",p:"p",strong:"strong",ul:"ul",...(0,o.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.header,{children:(0,s.jsx)(n.h1,{id:"predator---release-notes",children:"Predator - Release Notes"})}),"\n",(0,s.jsx)(n.h2,{id:"version-100",children:"Version 1.0.0"}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Release Date"}),": June 2025",(0,s.jsx)(n.br,{}),"\n",(0,s.jsx)(n.strong,{children:"Status"}),": General Availability (GA)"]}),"\n",(0,s.jsxs)(n.p,{children:["First stable release of ",(0,s.jsx)(n.strong,{children:"Predator"})," \u2014 scalable model inference service built around ",(0,s.jsx)(n.strong,{children:"NVIDIA Triton Inference Server"}),", part of BharatMLStack. Serves Deep Learning and tree-based models with low latency in ",(0,s.jsx)(n.strong,{children:"Kubernetes"}),"; integrates with ",(0,s.jsx)(n.strong,{children:"OnFS"})," and ",(0,s.jsx)(n.strong,{children:"Interflow"}),"; clients use the ",(0,s.jsx)(n.strong,{children:"Helix client"})," over gRPC."]}),"\n",(0,s.jsx)(n.h3,{id:"whats-new",children:"What's New"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Triton inference engine"}),": Unified runtime for DL and tree-based models on CPU/GPU; model repository via Init Container from GCS; gRPC API via Helix client."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Multi-backend support"}),": TensorRT, PyTorch, ONNX Runtime, TensorFlow, Python, FIL, DALI, Custom."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Dynamic batching & concurrency"}),": Configurable via ",(0,s.jsx)(n.code,{children:"config.pbtxt"}),"; model versioning and ensembles."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Kubernetes deployment"}),": Helm-based; Init Container + Triton container; custom Triton images from Artifact Registry; health probes; CPU/GPU autoscaling."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Observability"}),": Prometheus metrics, Grafana; warmup requests for cold-start avoidance."]}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(d,{...e})}):d(e)}},8453:(e,n,r)=>{r.d(n,{R:()=>i,x:()=>a});var t=r(6540);const s={},o=t.createContext(s);function i(e){const n=t.useContext(o);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function a(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:i(e.components),t.createElement(o.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/bcee635f.b2209c62.js b/docs/assets/js/bcee635f.b2209c62.js new file mode 100644 index 00000000..ea1f3e3c --- /dev/null +++ b/docs/assets/js/bcee635f.b2209c62.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[4164],{4459:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>a,default:()=>m,frontMatter:()=>i,metadata:()=>r,toc:()=>d});const r=JSON.parse('{"id":"sdks/go/v1.0.0/index","title":"v1.0.0","description":"Go SDK v1.0.0","source":"@site/docs/sdks/go/v1.0.0/index.md","sourceDirName":"sdks/go/v1.0.0","slug":"/sdks/go/v1.0.0","permalink":"/BharatMLStack/sdks/go/v1.0.0","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/sdks/go/v1.0.0/index.md","tags":[],"version":"current","sidebarPosition":0,"frontMatter":{"title":"v1.0.0","description":"Go SDK v1.0.0","sidebar_position":0,"slug":"/sdks/go/v1.0.0"},"sidebar":"tutorialSidebar","previous":{"title":"Go SDK","permalink":"/BharatMLStack/category/go-sdk"},"next":{"title":"GRPC Feature client","permalink":"/BharatMLStack/sdks/go/v1.0.0/feature_client"}}');var s=n(4848),o=n(8453),c=n(4795);const i={title:"v1.0.0",description:"Go SDK v1.0.0",sidebar_position:0,slug:"/sdks/go/v1.0.0"},a="Go SDK v1.0.0",l={},d=[];function u(e){const t={h1:"h1",header:"header",p:"p",...(0,o.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(t.header,{children:(0,s.jsx)(t.h1,{id:"go-sdk-v100",children:"Go SDK v1.0.0"})}),"\n",(0,s.jsx)(t.p,{children:"Go client libraries and packages for interacting with the BharatML Stack online feature store, including gRPC clients and protocol buffer definitions."}),"\n",(0,s.jsx)(c.A,{})]})}function m(e={}){const{wrapper:t}={...(0,o.R)(),...e.components};return t?(0,s.jsx)(t,{...e,children:(0,s.jsx)(u,{...e})}):u(e)}},4795:(e,t,n)=>{n.d(t,{A:()=>j});n(6540);var r=n(4164),s=n(6972),o=n(8774),c=n(5846),i=n(6654),a=n(1312),l=n(1107);const d={cardContainer:"cardContainer_fWXF",cardTitle:"cardTitle_rnsV",cardDescription:"cardDescription_PWke"};var u=n(4848);function m({className:e,href:t,children:n}){return(0,u.jsx)(o.A,{href:t,className:(0,r.A)("card padding--lg",d.cardContainer,e),children:n})}function h({className:e,href:t,icon:n,title:s,description:o}){return(0,u.jsxs)(m,{href:t,className:e,children:[(0,u.jsxs)(l.A,{as:"h2",className:(0,r.A)("text--truncate",d.cardTitle),title:s,children:[n," ",s]}),o&&(0,u.jsx)("p",{className:(0,r.A)("text--truncate",d.cardDescription),title:o,children:o})]})}function f({item:e}){const t=(0,s.Nr)(e),n=function(){const{selectMessage:e}=(0,c.W)();return t=>e(t,(0,a.T)({message:"1 item|{count} items",id:"theme.docs.DocCard.categoryDescription.plurals",description:"The default description for a category card in the generated index about how many items this category includes"},{count:t}))}();return t?(0,u.jsx)(h,{className:e.className,href:t,icon:"\ud83d\uddc3\ufe0f",title:e.label,description:e.description??n(e.items.length)}):null}function p({item:e}){const t=(0,i.A)(e.href)?"\ud83d\udcc4\ufe0f":"\ud83d\udd17",n=(0,s.cC)(e.docId??void 0);return(0,u.jsx)(h,{className:e.className,href:e.href,icon:t,title:e.label,description:e.description??n?.description})}function g({item:e}){switch(e.type){case"link":return(0,u.jsx)(p,{item:e});case"category":return(0,u.jsx)(f,{item:e});default:throw new Error(`unknown item type ${JSON.stringify(e)}`)}}const x={docCardListItem:"docCardListItem_W1sv"};function v({className:e}){const t=(0,s.a4)();return(0,u.jsx)(j,{items:t,className:e})}function k({item:e}){return(0,u.jsx)("article",{className:(0,r.A)(x.docCardListItem,"col col--6"),children:(0,u.jsx)(g,{item:e})})}function j(e){const{items:t,className:n}=e;if(!t)return(0,u.jsx)(v,{...e});const o=(0,s.d1)(t);return(0,u.jsx)("section",{className:(0,r.A)("row",n),children:o.map((e,t)=>(0,u.jsx)(k,{item:e},t))})}},5846:(e,t,n)=>{n.d(t,{W:()=>l});var r=n(6540),s=n(4586);const o=["zero","one","two","few","many","other"];function c(e){return o.filter(t=>e.includes(t))}const i={locale:"en",pluralForms:c(["one","other"]),select:e=>1===e?"one":"other"};function a(){const{i18n:{currentLocale:e}}=(0,s.A)();return(0,r.useMemo)(()=>{try{return function(e){const t=new Intl.PluralRules(e);return{locale:e,pluralForms:c(t.resolvedOptions().pluralCategories),select:e=>t.select(e)}}(e)}catch(t){return console.error(`Failed to use Intl.PluralRules for locale "${e}".\nDocusaurus will fallback to the default (English) implementation.\nError: ${t.message}\n`),i}},[e])}function l(){const e=a();return{selectMessage:(t,n)=>function(e,t,n){const r=e.split("|");if(1===r.length)return r[0];r.length>n.pluralForms.length&&console.error(`For locale=${n.locale}, a maximum of ${n.pluralForms.length} plural forms are expected (${n.pluralForms.join(",")}), but the message contains ${r.length}: ${e}`);const s=n.select(t),o=n.pluralForms.indexOf(s);return r[Math.min(o,r.length-1)]}(n,t,e)}}},8453:(e,t,n)=>{n.d(t,{R:()=>c,x:()=>i});var r=n(6540);const s={},o=r.createContext(s);function c(e){const t=r.useContext(o);return r.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function i(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:c(e.components),r.createElement(o.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/bd5b7851.1913daf9.js b/docs/assets/js/bd5b7851.1913daf9.js new file mode 100644 index 00000000..182284a9 --- /dev/null +++ b/docs/assets/js/bd5b7851.1913daf9.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[6063],{8453:(e,n,r)=>{r.d(n,{R:()=>l,x:()=>c});var s=r(6540);const i={},t=s.createContext(i);function l(e){const n=s.useContext(t);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function c(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:l(e.components),s.createElement(t.Provider,{value:n},e.children)}},9042:(e,n,r)=>{r.r(n),r.d(n,{assets:()=>a,contentTitle:()=>c,default:()=>h,frontMatter:()=>l,metadata:()=>s,toc:()=>d});const s=JSON.parse('{"id":"skye/v1.0.0/release-notes","title":"Release Notes","description":"v1.0.0","source":"@site/docs/skye/v1.0.0/release-notes.md","sourceDirName":"skye/v1.0.0","slug":"/skye/v1.0.0/release-notes","permalink":"/BharatMLStack/skye/v1.0.0/release-notes","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/skye/v1.0.0/release-notes.md","tags":[],"version":"current","sidebarPosition":3,"frontMatter":{"title":"Release Notes","sidebar_position":3},"sidebar":"tutorialSidebar","previous":{"title":"Functionalities","permalink":"/BharatMLStack/skye/v1.0.0/functionalities"},"next":{"title":"Numerix","permalink":"/BharatMLStack/category/numerix"}}');var i=r(4848),t=r(8453);const l={title:"Release Notes",sidebar_position:3},c="Skye - Release Notes",a={},d=[{value:"v1.0.0",id:"v100",level:2},{value:"Overview",id:"overview",level:3},{value:"What's New",id:"whats-new",level:3},{value:"Architecture",id:"architecture",level:4},{value:"Serving",id:"serving",level:4},{value:"Ingestion",id:"ingestion",level:4},{value:"Operations",id:"operations",level:4},{value:"Improvements Over Previous Architecture",id:"improvements-over-previous-architecture",level:3},{value:"Known Limitations",id:"known-limitations",level:3},{value:"Technology Stack",id:"technology-stack",level:3}];function o(e){const n={code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",p:"p",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,t.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.header,{children:(0,i.jsx)(n.h1,{id:"skye---release-notes",children:"Skye - Release Notes"})}),"\n",(0,i.jsx)(n.h2,{id:"v100",children:"v1.0.0"}),"\n",(0,i.jsx)(n.h3,{id:"overview",children:"Overview"}),"\n",(0,i.jsx)(n.p,{children:"Initial open-source release of Skye, BharatMLStack's vector similarity search platform. This release represents a complete re-architecture of the internal VSS (Vector Similarity Search) service, addressing scalability, resilience, and operational efficiency challenges from the previous generation."}),"\n",(0,i.jsx)(n.h3,{id:"whats-new",children:"What's New"}),"\n",(0,i.jsx)(n.h4,{id:"architecture",children:"Architecture"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Model-first hierarchy"}),": Models at the base level with variants nested within, eliminating embedding duplication across tenants"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Entity-based data split"}),": Separate embedding and aggregator tables per entity type (catalog, product, user)"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Event-driven admin flows"}),": Kafka-based model lifecycle management with SQL-backed state persistence"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Pluggable vector DB support"}),": Generic vector database abstraction replacing vendor-specific tight coupling"]}),"\n"]}),"\n",(0,i.jsx)(n.h4,{id:"serving",children:"Serving"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Multi-layer caching"}),": In-memory cache + Redis distributed cache for low-latency similarity search"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Indexed-only search"}),": ",(0,i.jsx)(n.code,{children:"search_indexed_only"})," flag prevents brute-force fallback on partially indexed collections"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Pagination support"}),": Service-level pagination for clients"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Separate search/index embeddings"}),": Models can use different embedding spaces for search and indexing"]}),"\n"]}),"\n",(0,i.jsx)(n.h4,{id:"ingestion",children:"Ingestion"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Shared embeddings across variants"}),": Single ingestion per model with parallel variant processing"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Generic RT consumer schema"}),": Simplified onboarding for new real-time data sources"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Retry topic"}),": Automatic capture and reprocessing of failed ingestion events"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"EOF to all partitions"}),": Ensures complete data consumption before processing completion"]}),"\n"]}),"\n",(0,i.jsx)(n.h4,{id:"operations",children:"Operations"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"API-based model onboarding"}),": Register models and variants via REST API (replaces manual Databricks-only flow)"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Automated cluster provisioning"}),": Scripted setup for consistent vector DB cluster configurations"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Experiment isolation"}),": Dedicated EKS and vector DB clusters for experiments"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Comprehensive observability"}),": Per-model + per-variant metrics for latency, throughput, error rates, and cache effectiveness"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"improvements-over-previous-architecture",children:"Improvements Over Previous Architecture"}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Area"}),(0,i.jsx)(n.th,{children:"Before"}),(0,i.jsx)(n.th,{children:"After"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Embedding storage"}),(0,i.jsx)(n.td,{children:"Duplicated per tenant"}),(0,i.jsx)(n.td,{children:"Shared per model"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Vector DB coupling"}),(0,i.jsx)(n.td,{children:"Tightly coupled to Qdrant"}),(0,i.jsx)(n.td,{children:"Pluggable via generic interface"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"State management"}),(0,i.jsx)(n.td,{children:"In-pod synchronous thread"}),(0,i.jsx)(n.td,{children:"Event-driven with SQL backing"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Consumer handling"}),(0,i.jsx)(n.td,{children:"Paused during ingestion"}),(0,i.jsx)(n.td,{children:"No pausing; concurrent writes"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Cluster setup"}),(0,i.jsx)(n.td,{children:"Manual, error-prone"}),(0,i.jsx)(n.td,{children:"Automated, consistent"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Experiment infra"}),(0,i.jsx)(n.td,{children:"Shared with production"}),(0,i.jsx)(n.td,{children:"Isolated clusters"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Failure recovery"}),(0,i.jsx)(n.td,{children:"Manual intervention"}),(0,i.jsx)(n.td,{children:"Retry topics + snapshots"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Observability"}),(0,i.jsx)(n.td,{children:"Generic alerts"}),(0,i.jsx)(n.td,{children:"Model + variant level metrics"})]})]})]}),"\n",(0,i.jsx)(n.h3,{id:"known-limitations",children:"Known Limitations"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Snapshot restore is currently supported for smaller indexes only"}),"\n",(0,i.jsx)(n.li,{children:"Pagination is handled at the service level (not natively by the vector DB)"}),"\n",(0,i.jsx)(n.li,{children:"Horizontal scaling of vector DB clusters requires running provisioning scripts"}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"technology-stack",children:"Technology Stack"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Language"}),": Go"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Vector Database"}),": Qdrant (pluggable)"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Storage"}),": ScyllaDB"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Cache"}),": Redis + In-Memory"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Message Queue"}),": Kafka"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Configuration"}),": ZooKeeper / etcd"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Orchestration"}),": Kubernetes (EKS)"]}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,t.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(o,{...e})}):o(e)}}}]); \ No newline at end of file diff --git a/docs/assets/js/be9e6e2d.944ea2f0.js b/docs/assets/js/be9e6e2d.944ea2f0.js new file mode 100644 index 00000000..1c4c7230 --- /dev/null +++ b/docs/assets/js/be9e6e2d.944ea2f0.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[7871],{3405:a=>{a.exports=JSON.parse('{"tag":{"label":"embedding-search","permalink":"/BharatMLStack/blog/tags/embedding-search","allTagsPath":"/BharatMLStack/blog/tags","count":1,"unlisted":false},"listMetadata":{"permalink":"/BharatMLStack/blog/tags/embedding-search","page":1,"postsPerPage":10,"totalPages":1,"totalCount":1,"blogDescription":"Blog","blogTitle":"Blog"}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/bf2864cf.6fc085c5.js b/docs/assets/js/bf2864cf.6fc085c5.js new file mode 100644 index 00000000..04058e29 --- /dev/null +++ b/docs/assets/js/bf2864cf.6fc085c5.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[9688],{3969:(t,e,r)=>{r.r(e),r.d(e,{assets:()=>l,contentTitle:()=>o,default:()=>m,frontMatter:()=>a,metadata:()=>n,toc:()=>u});const n=JSON.parse('{"id":"quick-start/v1.0.0/index","title":"v1.0.0","description":"Quick Start v1.0.0","source":"@site/docs/quick-start/v1.0.0/index.md","sourceDirName":"quick-start/v1.0.0","slug":"/quick-start/v1.0.0","permalink":"/BharatMLStack/quick-start/v1.0.0","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/quick-start/v1.0.0/index.md","tags":[],"version":"current","sidebarPosition":0,"frontMatter":{"title":"v1.0.0","description":"Quick Start v1.0.0","sidebar_position":0,"slug":"/quick-start/v1.0.0"},"sidebar":"tutorialSidebar","previous":{"title":"Quick Start","permalink":"/BharatMLStack/category/quick-start"},"next":{"title":"Quick Start","permalink":"/BharatMLStack/quick-start/v1.0.0/quick-start"}}');var s=r(4848),c=r(8453),i=r(4795);const a={title:"v1.0.0",description:"Quick Start v1.0.0",sidebar_position:0,slug:"/quick-start/v1.0.0"},o="Quick Start v1.0.0",l={},u=[];function d(t){const e={h1:"h1",header:"header",p:"p",...(0,c.R)(),...t.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(e.header,{children:(0,s.jsx)(e.h1,{id:"quick-start-v100",children:"Quick Start v1.0.0"})}),"\n",(0,s.jsx)(e.p,{children:"Get up and running quickly with step-by-step instructions, sample data, and Docker Compose setup for local development and testing."}),"\n",(0,s.jsx)(i.A,{})]})}function m(t={}){const{wrapper:e}={...(0,c.R)(),...t.components};return e?(0,s.jsx)(e,{...t,children:(0,s.jsx)(d,{...t})}):d(t)}},4795:(t,e,r)=>{r.d(e,{A:()=>j});r(6540);var n=r(4164),s=r(6972),c=r(8774),i=r(5846),a=r(6654),o=r(1312),l=r(1107);const u={cardContainer:"cardContainer_fWXF",cardTitle:"cardTitle_rnsV",cardDescription:"cardDescription_PWke"};var d=r(4848);function m({className:t,href:e,children:r}){return(0,d.jsx)(c.A,{href:e,className:(0,n.A)("card padding--lg",u.cardContainer,t),children:r})}function p({className:t,href:e,icon:r,title:s,description:c}){return(0,d.jsxs)(m,{href:e,className:t,children:[(0,d.jsxs)(l.A,{as:"h2",className:(0,n.A)("text--truncate",u.cardTitle),title:s,children:[r," ",s]}),c&&(0,d.jsx)("p",{className:(0,n.A)("text--truncate",u.cardDescription),title:c,children:c})]})}function h({item:t}){const e=(0,s.Nr)(t),r=function(){const{selectMessage:t}=(0,i.W)();return e=>t(e,(0,o.T)({message:"1 item|{count} items",id:"theme.docs.DocCard.categoryDescription.plurals",description:"The default description for a category card in the generated index about how many items this category includes"},{count:e}))}();return e?(0,d.jsx)(p,{className:t.className,href:e,icon:"\ud83d\uddc3\ufe0f",title:t.label,description:t.description??r(t.items.length)}):null}function f({item:t}){const e=(0,a.A)(t.href)?"\ud83d\udcc4\ufe0f":"\ud83d\udd17",r=(0,s.cC)(t.docId??void 0);return(0,d.jsx)(p,{className:t.className,href:t.href,icon:e,title:t.label,description:t.description??r?.description})}function k({item:t}){switch(t.type){case"link":return(0,d.jsx)(f,{item:t});case"category":return(0,d.jsx)(h,{item:t});default:throw new Error(`unknown item type ${JSON.stringify(t)}`)}}const x={docCardListItem:"docCardListItem_W1sv"};function g({className:t}){const e=(0,s.a4)();return(0,d.jsx)(j,{items:e,className:t})}function v({item:t}){return(0,d.jsx)("article",{className:(0,n.A)(x.docCardListItem,"col col--6"),children:(0,d.jsx)(k,{item:t})})}function j(t){const{items:e,className:r}=t;if(!e)return(0,d.jsx)(g,{...t});const c=(0,s.d1)(e);return(0,d.jsx)("section",{className:(0,n.A)("row",r),children:c.map((t,e)=>(0,d.jsx)(v,{item:t},e))})}},5846:(t,e,r)=>{r.d(e,{W:()=>l});var n=r(6540),s=r(4586);const c=["zero","one","two","few","many","other"];function i(t){return c.filter(e=>t.includes(e))}const a={locale:"en",pluralForms:i(["one","other"]),select:t=>1===t?"one":"other"};function o(){const{i18n:{currentLocale:t}}=(0,s.A)();return(0,n.useMemo)(()=>{try{return function(t){const e=new Intl.PluralRules(t);return{locale:t,pluralForms:i(e.resolvedOptions().pluralCategories),select:t=>e.select(t)}}(t)}catch(e){return console.error(`Failed to use Intl.PluralRules for locale "${t}".\nDocusaurus will fallback to the default (English) implementation.\nError: ${e.message}\n`),a}},[t])}function l(){const t=o();return{selectMessage:(e,r)=>function(t,e,r){const n=t.split("|");if(1===n.length)return n[0];n.length>r.pluralForms.length&&console.error(`For locale=${r.locale}, a maximum of ${r.pluralForms.length} plural forms are expected (${r.pluralForms.join(",")}), but the message contains ${n.length}: ${t}`);const s=r.select(e),c=r.pluralForms.indexOf(s);return n[Math.min(c,n.length-1)]}(r,e,t)}}},8453:(t,e,r)=>{r.d(e,{R:()=>i,x:()=>a});var n=r(6540);const s={},c=n.createContext(s);function i(t){const e=n.useContext(c);return n.useMemo(function(){return"function"==typeof t?t(e):{...e,...t}},[e,t])}function a(t){let e;return e=t.disableParentContext?"function"==typeof t.components?t.components(s):t.components||s:i(t.components),n.createElement(c.Provider,{value:e},t.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/c31e69d4.0061b133.js b/docs/assets/js/c31e69d4.0061b133.js new file mode 100644 index 00000000..c2165c80 --- /dev/null +++ b/docs/assets/js/c31e69d4.0061b133.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[3212],{915:a=>{a.exports=JSON.parse('{"tag":{"label":"ai-agents","permalink":"/BharatMLStack/blog/tags/ai-agents","allTagsPath":"/BharatMLStack/blog/tags","count":1,"unlisted":false},"listMetadata":{"permalink":"/BharatMLStack/blog/tags/ai-agents","page":1,"postsPerPage":10,"totalPages":1,"totalCount":1,"blogDescription":"Blog","blogTitle":"Blog"}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/c4822c4f.9fe3ec4e.js b/docs/assets/js/c4822c4f.9fe3ec4e.js deleted file mode 100644 index bf79f6f5..00000000 --- a/docs/assets/js/c4822c4f.9fe3ec4e.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[1915],{3649:(e,n,r)=>{r.r(n),r.d(n,{assets:()=>a,contentTitle:()=>o,default:()=>h,frontMatter:()=>l,metadata:()=>i,toc:()=>c});const i=JSON.parse('{"id":"online-feature-store/v1.0.0/functionalities","title":"Key Functionalities","description":"Overview","source":"@site/docs/online-feature-store/v1.0.0/functionalities.md","sourceDirName":"online-feature-store/v1.0.0","slug":"/online-feature-store/v1.0.0/functionalities","permalink":"/BharatMLStack/online-feature-store/v1.0.0/functionalities","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/online-feature-store/v1.0.0/functionalities.md","tags":[],"version":"current","sidebarPosition":4,"frontMatter":{"title":"Key Functionalities","sidebar_position":4},"sidebar":"tutorialSidebar","previous":{"title":"Benchmarks","permalink":"/BharatMLStack/online-feature-store/v1.0.0/benchmarks"},"next":{"title":"Release Notes","permalink":"/BharatMLStack/online-feature-store/v1.0.0/release-notes"}}');var s=r(4848),t=r(8453);const l={title:"Key Functionalities",sidebar_position:4},o="Online Feature Store - Key Functionalities",a={},c=[{value:"Overview",id:"overview",level:2},{value:"\ud83d\ude80 Core Capabilities",id:"-core-capabilities",level:2},{value:"<strong>Real-Time Feature Serving</strong>",id:"real-time-feature-serving",level:3},{value:"<strong>Multi-Format Data Support</strong>",id:"multi-format-data-support",level:3},{value:"<strong>Multi-Database Backend</strong>",id:"multi-database-backend",level:3},{value:"\ud83c\udfaf Key Features",id:"-key-features",level:2},{value:"<strong>Performance Optimizations</strong>",id:"performance-optimizations",level:3},{value:"<strong>Data Management</strong>",id:"data-management",level:3},{value:"<strong>Developer Experience</strong>",id:"developer-experience",level:3},{value:"<strong>Production Ready</strong>",id:"production-ready",level:3},{value:"\ud83d\udcca Use Cases",id:"-use-cases",level:2},{value:"<strong>Real-Time ML Inference</strong>",id:"real-time-ml-inference",level:3},{value:"<strong>Batch Feature Serving</strong>",id:"batch-feature-serving",level:3},{value:"<strong>A/B Testing Support</strong>",id:"ab-testing-support",level:3},{value:"\ud83c\udf9b\ufe0f Configuration Options",id:"\ufe0f-configuration-options",level:2},{value:"<strong>Performance Tuning</strong>",id:"performance-tuning",level:3},{value:"<strong>Storage Configuration</strong>",id:"storage-configuration",level:3},{value:"<strong>Monitoring & Observability</strong>",id:"monitoring--observability",level:3},{value:"\ud83d\udcc8 Production Deployment",id:"-production-deployment",level:2},{value:"<strong>Recommended Architecture</strong>",id:"recommended-architecture",level:3},{value:"<strong>Scaling Guidelines</strong>",id:"scaling-guidelines",level:3},{value:"Contributing",id:"contributing",level:2},{value:"Community & Support",id:"community--support",level:2},{value:"License",id:"license",level:2}];function d(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",li:"li",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,t.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.header,{children:(0,s.jsx)(n.h1,{id:"online-feature-store---key-functionalities",children:"Online Feature Store - Key Functionalities"})}),"\n",(0,s.jsx)(n.h2,{id:"overview",children:"Overview"}),"\n",(0,s.jsxs)(n.p,{children:["The BharatML Online Feature Store is a high-performance, production-ready system designed to serve machine learning features with ",(0,s.jsx)(n.strong,{children:"sub-10ms P99 latency"})," and ",(0,s.jsx)(n.strong,{children:"1M+ RPS capacity"}),". It bridges the gap between offline feature engineering and real-time model inference."]}),"\n",(0,s.jsx)(n.h2,{id:"-core-capabilities",children:"\ud83d\ude80 Core Capabilities"}),"\n",(0,s.jsx)(n.h3,{id:"real-time-feature-serving",children:(0,s.jsx)(n.strong,{children:"Real-Time Feature Serving"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Ultra-Low Latency"}),": Sub-10ms P99 response times"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"High Throughput"}),": Tested at 1M+ requests per second with 100 IDs per request"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Batch Retrieval"}),": Fetch multiple features for multiple entities in a single request"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Point-in-Time Consistency"}),": Ensure feature consistency across model predictions"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Dependent feature consistency"}),": features part of same feature group."]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"multi-format-data-support",children:(0,s.jsx)(n.strong,{children:"Multi-Format Data Support"})}),"\n",(0,s.jsx)(n.p,{children:"Supports all common ML data types with optimized serialization:"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Data Type"}),(0,s.jsx)(n.th,{children:"Support"}),(0,s.jsx)(n.th,{children:"Use Cases"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"Integers"})}),(0,s.jsx)(n.td,{children:"int8, int16, int32, int64"}),(0,s.jsx)(n.td,{children:"User IDs, counts, categorical encodings"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"Floats"})}),(0,s.jsx)(n.td,{children:"float16, float32, float64"}),(0,s.jsx)(n.td,{children:"Continuous features, embeddings, scores"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"Strings"})}),(0,s.jsx)(n.td,{children:"Variable length"}),(0,s.jsx)(n.td,{children:"Categories, text features, metadata"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"Booleans"})}),(0,s.jsx)(n.td,{children:"Bit-packed"}),(0,s.jsx)(n.td,{children:"Feature flags, binary indicators"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"Vectors"})}),(0,s.jsx)(n.td,{children:"All above types"}),(0,s.jsx)(n.td,{children:"Embeddings, feature arrays, time series"})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"multi-database-backend",children:(0,s.jsx)(n.strong,{children:"Multi-Database Backend"})}),"\n",(0,s.jsx)(n.p,{children:"Flexible storage options for different deployment needs:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"\ud83d\udd25 Scylla DB"}),": Ultra-high performance NoSQL (recommended for production)"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"\u26a1 Dragonfly"}),": Modern Redis alternative with better memory efficiency"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"\ud83d\udcca Redis"}),": Standard in-memory store for development and small-scale deployments"]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"-key-features",children:"\ud83c\udfaf Key Features"}),"\n",(0,s.jsx)(n.h3,{id:"performance-optimizations",children:(0,s.jsx)(n.strong,{children:"Performance Optimizations"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Custom PSDB Format"}),": Proprietary serialization format optimized for ML features"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Object Pooling"}),": Memory-efficient resource reuse"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Connection Pooling"}),": Optimized database connection management"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Compression Support"}),": Multiple algorithms (LZ4, Snappy, ZSTD) with intelligent fallback"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"data-management",children:(0,s.jsx)(n.strong,{children:"Data Management"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"TTL Support"}),": Automatic feature expiration with configurable time-to-live"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Versioning"}),": Multiple feature schema versions with backward compatibility"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Batch Operations"}),": Efficient bulk read/write operations"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Feature Groups"}),": Logical grouping of related features for better organization"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"developer-experience",children:(0,s.jsx)(n.strong,{children:"Developer Experience"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"gRPC API"}),": High-performance, language-agnostic interface"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Go SDK"}),": Native Go client with connection pooling and error handling"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Python SDK"}),": ML-friendly Python bindings for data scientists"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"RESTful Interface"}),": HTTP API for health check"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"production-ready",children:(0,s.jsx)(n.strong,{children:"Production Ready"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Health Checks"}),": Built-in monitoring and health endpoints"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Metrics Integration"}),": DataDog, Prometheus-compatible metrics"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Structured Logging"}),": JSON-formatted logs with configurable levels"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Graceful Shutdown"}),": Clean resource cleanup on termination"]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"-use-cases",children:"\ud83d\udcca Use Cases"}),"\n",(0,s.jsx)(n.h3,{id:"real-time-ml-inference",children:(0,s.jsx)(n.strong,{children:"Real-Time ML Inference"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-go",children:'// Fetch user features for recommendation model\nquery := &onfs.Query{\n EntityLabel: "user",\n FeatureGroups: []onfs.FeatureGroup{\n {\n Label: "demographics",\n FeatureLabels: []string{"age", "location", "income"},\n },\n {\n Label: "behavior", \n FeatureLabels: []string{"click_rate", "purchase_history"},\n },\n },\n KeysSchema: []string{"user_id"},\n Keys: []onfs.Keys{\n {Cols: []string{"user_123"}},\n },\n}\n\nresult, err := client.RetrieveFeatures(ctx, query)\n'})}),"\n",(0,s.jsx)(n.h3,{id:"batch-feature-serving",children:(0,s.jsx)(n.strong,{children:"Batch Feature Serving"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-go",children:'// Bulk feature retrieval for model training\nquery := &onfs.Query{\n EntityLabel: "transaction",\n FeatureGroups: []onfs.FeatureGroup{\n {\n Label: "transaction_history",\n FeatureLabels: []string{"amount", "frequency", "merchant_type"},\n },\n {\n Label: "risk_scores",\n FeatureLabels: []string{"fraud_score", "credit_score"},\n },\n },\n KeysSchema: []string{"transaction_id"},\n Keys: []onfs.Keys{\n {Cols: []string{"txn_001"}},\n {Cols: []string{"txn_002"}},\n // ... 100s of transaction IDs\n },\n}\n\nresult, err := client.RetrieveFeatures(ctx, query)\n'})}),"\n",(0,s.jsx)(n.h3,{id:"ab-testing-support",children:(0,s.jsx)(n.strong,{children:"A/B Testing Support"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-go",children:'// Version-aware feature retrieval with decoded values\nquery := &onfs.Query{\n EntityLabel: "experiment",\n FeatureGroups: []onfs.FeatureGroup{\n {\n Label: "model_features_v2", // Specific version\n FeatureLabels: []string{"feature_a", "feature_b", "feature_c"},\n },\n },\n KeysSchema: []string{"user_id"},\n Keys: []onfs.Keys{\n {Cols: []string{"user_123"}},\n },\n}\n\n// Get string-decoded values for easier debugging/analysis\ndecodedResult, err := client.RetrieveDecodedFeatures(ctx, query)\n'})}),"\n",(0,s.jsx)(n.h2,{id:"\ufe0f-configuration-options",children:"\ud83c\udf9b\ufe0f Configuration Options"}),"\n",(0,s.jsx)(n.h3,{id:"performance-tuning",children:(0,s.jsx)(n.strong,{children:"Performance Tuning"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Cache TTL"}),": Configure feature freshness requirements"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"storage-configuration",children:(0,s.jsx)(n.strong,{children:"Storage Configuration"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Database Selection"}),": Choose backend based on scale and requirements"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Replication Factor"}),": RF=2 for ScyllaDB"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Partition Strategy"}),": DB side control - not handled here"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Backup Frequency"}),": DB side control - not handled here"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"monitoring--observability",children:(0,s.jsx)(n.strong,{children:"Monitoring & Observability"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Metrics Collection"}),": Request rates, latencies, error rates"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Custom Dashboards"}),": Feature-specific monitoring views"]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"-production-deployment",children:"\ud83d\udcc8 Production Deployment"}),"\n",(0,s.jsx)(n.h3,{id:"recommended-architecture",children:(0,s.jsx)(n.strong,{children:"Recommended Architecture"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Load Balancer"}),": Distribute traffic across multiple instances"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Feature Store Cluster"}),": 3+ instances for high availability"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Database Cluster"}),": Replicated backend with automatic failover"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Monitoring Stack"}),": Metrics, logs, and alerting infrastructure"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"scaling-guidelines",children:(0,s.jsx)(n.strong,{children:"Scaling Guidelines"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Horizontal Scaling"}),": Add more feature store instances"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Database Scaling"}),": Increase partition count or upgrade hardware"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Dragonfly/Remote Cache Scaling"}),": Use Cluster or Failover/Sentinal based setup"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Connection Tuning"}),": Optimize pool sizes for your traffic patterns"]}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"contributing",children:"Contributing"}),"\n",(0,s.jsxs)(n.p,{children:["We welcome contributions from the community! Please see our ",(0,s.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTING.md",children:"Contributing Guide"})," for details on how to get started."]}),"\n",(0,s.jsx)(n.h2,{id:"community--support",children:"Community & Support"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["\ud83d\udcac ",(0,s.jsx)(n.strong,{children:"Discord"}),": Join our ",(0,s.jsx)(n.a,{href:"https://discord.gg/XkT7XsV2AU",children:"community chat"})]}),"\n",(0,s.jsxs)(n.li,{children:["\ud83d\udc1b ",(0,s.jsx)(n.strong,{children:"Issues"}),": Report bugs and request features on ",(0,s.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/issues",children:"GitHub Issues"})]}),"\n",(0,s.jsxs)(n.li,{children:["\ud83d\udce7 ",(0,s.jsx)(n.strong,{children:"Email"}),": Contact us at ",(0,s.jsx)(n.a,{href:"mailto:ml-oss@meesho.com",children:"ml-oss@meesho.com"})]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"license",children:"License"}),"\n",(0,s.jsxs)(n.p,{children:["BharatMLStack is open-source software licensed under the ",(0,s.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md",children:"BharatMLStack Business Source License 1.1"}),"."]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)("div",{align:"center",children:(0,s.jsx)("strong",{children:"Built with \u2764\ufe0f for the ML community from Meesho"})}),"\n",(0,s.jsx)("div",{align:"center",children:(0,s.jsx)("strong",{children:"If you find this useful, \u2b50\ufe0f the repo \u2014 your support means the world to us!"})})]})}function h(e={}){const{wrapper:n}={...(0,t.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(d,{...e})}):d(e)}},8453:(e,n,r)=>{r.d(n,{R:()=>l,x:()=>o});var i=r(6540);const s={},t=i.createContext(s);function l(e){const n=i.useContext(t);return i.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:l(e.components),i.createElement(t.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/c4822c4f.c80625fe.js b/docs/assets/js/c4822c4f.c80625fe.js new file mode 100644 index 00000000..8c408293 --- /dev/null +++ b/docs/assets/js/c4822c4f.c80625fe.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[1915],{3649:(e,n,r)=>{r.r(n),r.d(n,{assets:()=>a,contentTitle:()=>o,default:()=>h,frontMatter:()=>l,metadata:()=>i,toc:()=>c});const i=JSON.parse('{"id":"online-feature-store/v1.0.0/functionalities","title":"Key Functionalities","description":"Overview","source":"@site/docs/online-feature-store/v1.0.0/functionalities.md","sourceDirName":"online-feature-store/v1.0.0","slug":"/online-feature-store/v1.0.0/functionalities","permalink":"/BharatMLStack/online-feature-store/v1.0.0/functionalities","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/online-feature-store/v1.0.0/functionalities.md","tags":[],"version":"current","sidebarPosition":4,"frontMatter":{"title":"Key Functionalities","sidebar_position":4},"sidebar":"tutorialSidebar","previous":{"title":"Benchmarks","permalink":"/BharatMLStack/online-feature-store/v1.0.0/benchmarks"},"next":{"title":"Release Notes","permalink":"/BharatMLStack/online-feature-store/v1.0.0/release-notes"}}');var s=r(4848),t=r(8453);const l={title:"Key Functionalities",sidebar_position:4},o="Online Feature Store - Key Functionalities",a={},c=[{value:"Overview",id:"overview",level:2},{value:"\ud83d\ude80 Core Capabilities",id:"-core-capabilities",level:2},{value:"<strong>Real-Time Feature Serving</strong>",id:"real-time-feature-serving",level:3},{value:"<strong>Multi-Format Data Support</strong>",id:"multi-format-data-support",level:3},{value:"<strong>Multi-Database Backend</strong>",id:"multi-database-backend",level:3},{value:"\ud83c\udfaf Key Features",id:"-key-features",level:2},{value:"<strong>Performance Optimizations</strong>",id:"performance-optimizations",level:3},{value:"<strong>Data Management</strong>",id:"data-management",level:3},{value:"<strong>Developer Experience</strong>",id:"developer-experience",level:3},{value:"<strong>Production Ready</strong>",id:"production-ready",level:3},{value:"\ud83d\udcca Use Cases",id:"-use-cases",level:2},{value:"<strong>Real-Time ML Inference</strong>",id:"real-time-ml-inference",level:3},{value:"<strong>Batch Feature Serving</strong>",id:"batch-feature-serving",level:3},{value:"<strong>A/B Testing Support</strong>",id:"ab-testing-support",level:3},{value:"\ud83c\udf9b\ufe0f Configuration Options",id:"\ufe0f-configuration-options",level:2},{value:"<strong>Performance Tuning</strong>",id:"performance-tuning",level:3},{value:"<strong>Storage Configuration</strong>",id:"storage-configuration",level:3},{value:"<strong>Monitoring & Observability</strong>",id:"monitoring--observability",level:3},{value:"\ud83d\udcc8 Production Deployment",id:"-production-deployment",level:2},{value:"<strong>Recommended Architecture</strong>",id:"recommended-architecture",level:3},{value:"<strong>Scaling Guidelines</strong>",id:"scaling-guidelines",level:3},{value:"Contributing",id:"contributing",level:2},{value:"Community & Support",id:"community--support",level:2},{value:"License",id:"license",level:2}];function d(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",li:"li",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,t.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.header,{children:(0,s.jsx)(n.h1,{id:"online-feature-store---key-functionalities",children:"Online Feature Store - Key Functionalities"})}),"\n",(0,s.jsx)(n.h2,{id:"overview",children:"Overview"}),"\n",(0,s.jsxs)(n.p,{children:["The BharatML Online Feature Store is a high-performance, production-ready system designed to serve machine learning features with ",(0,s.jsx)(n.strong,{children:"sub-10ms P99 latency"})," and ",(0,s.jsx)(n.strong,{children:"1M+ RPS capacity"}),". It bridges the gap between offline feature engineering and real-time model inference."]}),"\n",(0,s.jsx)(n.h2,{id:"-core-capabilities",children:"\ud83d\ude80 Core Capabilities"}),"\n",(0,s.jsx)(n.h3,{id:"real-time-feature-serving",children:(0,s.jsx)(n.strong,{children:"Real-Time Feature Serving"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Ultra-Low Latency"}),": Sub-10ms P99 response times"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"High Throughput"}),": Tested at 1M+ requests per second with 100 IDs per request"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Batch Retrieval"}),": Fetch multiple features for multiple entities in a single request"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Point-in-Time Consistency"}),": Ensure feature consistency across model predictions"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Dependent feature consistency"}),": features part of same feature group."]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"multi-format-data-support",children:(0,s.jsx)(n.strong,{children:"Multi-Format Data Support"})}),"\n",(0,s.jsx)(n.p,{children:"Supports all common ML data types with optimized serialization:"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Data Type"}),(0,s.jsx)(n.th,{children:"Support"}),(0,s.jsx)(n.th,{children:"Use Cases"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"Integers"})}),(0,s.jsx)(n.td,{children:"int8, int16, int32, int64"}),(0,s.jsx)(n.td,{children:"User IDs, counts, categorical encodings"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"Floats"})}),(0,s.jsx)(n.td,{children:"float16, float32, float64"}),(0,s.jsx)(n.td,{children:"Continuous features, embeddings, scores"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"Strings"})}),(0,s.jsx)(n.td,{children:"Variable length"}),(0,s.jsx)(n.td,{children:"Categories, text features, metadata"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"Booleans"})}),(0,s.jsx)(n.td,{children:"Bit-packed"}),(0,s.jsx)(n.td,{children:"Feature flags, binary indicators"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"Vectors"})}),(0,s.jsx)(n.td,{children:"All above types"}),(0,s.jsx)(n.td,{children:"Embeddings, feature arrays, time series"})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"multi-database-backend",children:(0,s.jsx)(n.strong,{children:"Multi-Database Backend"})}),"\n",(0,s.jsx)(n.p,{children:"Flexible storage options for different deployment needs:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"\ud83d\udd25 Scylla DB"}),": Ultra-high performance NoSQL (recommended for production)"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"\u26a1 Dragonfly"}),": Modern Redis alternative with better memory efficiency"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"\ud83d\udcca Redis"}),": Standard in-memory store for development and small-scale deployments"]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"-key-features",children:"\ud83c\udfaf Key Features"}),"\n",(0,s.jsx)(n.h3,{id:"performance-optimizations",children:(0,s.jsx)(n.strong,{children:"Performance Optimizations"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Custom PSDB Format"}),": Proprietary serialization format optimized for ML features"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Object Pooling"}),": Memory-efficient resource reuse"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Connection Pooling"}),": Optimized database connection management"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Compression Support"}),": Multiple algorithms (LZ4, Snappy, ZSTD) with intelligent fallback"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"data-management",children:(0,s.jsx)(n.strong,{children:"Data Management"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"TTL Support"}),": Automatic feature expiration with configurable time-to-live"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Versioning"}),": Multiple feature schema versions with backward compatibility"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Batch Operations"}),": Efficient bulk read/write operations"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Feature Groups"}),": Logical grouping of related features for better organization"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"developer-experience",children:(0,s.jsx)(n.strong,{children:"Developer Experience"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"gRPC API"}),": High-performance, language-agnostic interface"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Go SDK"}),": Native Go client with connection pooling and error handling"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Python SDK"}),": ML-friendly Python bindings for data scientists"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"RESTful Interface"}),": HTTP API for health check"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"production-ready",children:(0,s.jsx)(n.strong,{children:"Production Ready"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Health Checks"}),": Built-in monitoring and health endpoints"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Metrics Integration"}),": DataDog, Prometheus-compatible metrics"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Structured Logging"}),": JSON-formatted logs with configurable levels"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Graceful Shutdown"}),": Clean resource cleanup on termination"]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"-use-cases",children:"\ud83d\udcca Use Cases"}),"\n",(0,s.jsx)(n.h3,{id:"real-time-ml-inference",children:(0,s.jsx)(n.strong,{children:"Real-Time ML Inference"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-go",children:'// Fetch user features for recommendation model\nquery := &onfs.Query{\n EntityLabel: "user",\n FeatureGroups: []onfs.FeatureGroup{\n {\n Label: "demographics",\n FeatureLabels: []string{"age", "location", "income"},\n },\n {\n Label: "behavior", \n FeatureLabels: []string{"click_rate", "purchase_history"},\n },\n },\n KeysSchema: []string{"user_id"},\n Keys: []onfs.Keys{\n {Cols: []string{"user_123"}},\n },\n}\n\nresult, err := client.RetrieveFeatures(ctx, query)\n'})}),"\n",(0,s.jsx)(n.h3,{id:"batch-feature-serving",children:(0,s.jsx)(n.strong,{children:"Batch Feature Serving"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-go",children:'// Bulk feature retrieval for model training\nquery := &onfs.Query{\n EntityLabel: "transaction",\n FeatureGroups: []onfs.FeatureGroup{\n {\n Label: "transaction_history",\n FeatureLabels: []string{"amount", "frequency", "merchant_type"},\n },\n {\n Label: "risk_scores",\n FeatureLabels: []string{"fraud_score", "credit_score"},\n },\n },\n KeysSchema: []string{"transaction_id"},\n Keys: []onfs.Keys{\n {Cols: []string{"txn_001"}},\n {Cols: []string{"txn_002"}},\n // ... 100s of transaction IDs\n },\n}\n\nresult, err := client.RetrieveFeatures(ctx, query)\n'})}),"\n",(0,s.jsx)(n.h3,{id:"ab-testing-support",children:(0,s.jsx)(n.strong,{children:"A/B Testing Support"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-go",children:'// Version-aware feature retrieval with decoded values\nquery := &onfs.Query{\n EntityLabel: "experiment",\n FeatureGroups: []onfs.FeatureGroup{\n {\n Label: "model_features_v2", // Specific version\n FeatureLabels: []string{"feature_a", "feature_b", "feature_c"},\n },\n },\n KeysSchema: []string{"user_id"},\n Keys: []onfs.Keys{\n {Cols: []string{"user_123"}},\n },\n}\n\n// Get string-decoded values for easier debugging/analysis\ndecodedResult, err := client.RetrieveDecodedFeatures(ctx, query)\n'})}),"\n",(0,s.jsx)(n.h2,{id:"\ufe0f-configuration-options",children:"\ud83c\udf9b\ufe0f Configuration Options"}),"\n",(0,s.jsx)(n.h3,{id:"performance-tuning",children:(0,s.jsx)(n.strong,{children:"Performance Tuning"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Cache TTL"}),": Configure feature freshness requirements"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"storage-configuration",children:(0,s.jsx)(n.strong,{children:"Storage Configuration"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Database Selection"}),": Choose backend based on scale and requirements"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Replication Factor"}),": RF=2 for ScyllaDB"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Partition Strategy"}),": DB side control - not handled here"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Backup Frequency"}),": DB side control - not handled here"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"monitoring--observability",children:(0,s.jsx)(n.strong,{children:"Monitoring & Observability"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Metrics Collection"}),": Request rates, latencies, error rates"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Custom Dashboards"}),": Feature-specific monitoring views"]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"-production-deployment",children:"\ud83d\udcc8 Production Deployment"}),"\n",(0,s.jsx)(n.h3,{id:"recommended-architecture",children:(0,s.jsx)(n.strong,{children:"Recommended Architecture"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Load Balancer"}),": Distribute traffic across multiple instances"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Feature Store Cluster"}),": 3+ instances for high availability"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Database Cluster"}),": Replicated backend with automatic failover"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Monitoring Stack"}),": Metrics, logs, and alerting infrastructure"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"scaling-guidelines",children:(0,s.jsx)(n.strong,{children:"Scaling Guidelines"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Horizontal Scaling"}),": Add more feature store instances"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Database Scaling"}),": Increase partition count or upgrade hardware"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Dragonfly/Remote Cache Scaling"}),": Use Cluster or Failover/Sentinal based setup"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Connection Tuning"}),": Optimize pool sizes for your traffic patterns"]}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"contributing",children:"Contributing"}),"\n",(0,s.jsxs)(n.p,{children:["We welcome contributions from the community! Please see our ",(0,s.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTING.md",children:"Contributing Guide"})," for details on how to get started."]}),"\n",(0,s.jsx)(n.h2,{id:"community--support",children:"Community & Support"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["\ud83d\udcac ",(0,s.jsx)(n.strong,{children:"Discord"}),": Join our ",(0,s.jsx)(n.a,{href:"https://discord.gg/XkT7XsV2AU",children:"community chat"})]}),"\n",(0,s.jsxs)(n.li,{children:["\ud83d\udc1b ",(0,s.jsx)(n.strong,{children:"Issues"}),": Report bugs and request features on ",(0,s.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/issues",children:"GitHub Issues"})]}),"\n",(0,s.jsxs)(n.li,{children:["\ud83d\udce7 ",(0,s.jsx)(n.strong,{children:"Email"}),": Contact us at ",(0,s.jsx)(n.a,{href:"mailto:ml-oss@meesho.com",children:"ml-oss@meesho.com"})]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"license",children:"License"}),"\n",(0,s.jsxs)(n.p,{children:["BharatMLStack is open-source software licensed under the ",(0,s.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md",children:"BharatMLStack Business Source License 1.1"}),"."]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)("div",{align:"center",children:(0,s.jsx)("strong",{children:"Built with \u2764\ufe0f for the ML community from Meesho"})}),"\n",(0,s.jsx)("div",{align:"center",children:(0,s.jsx)("strong",{children:"If you find this useful, \u2b50\ufe0f the repo \u2014 your support means the world to us!"})})]})}function h(e={}){const{wrapper:n}={...(0,t.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(d,{...e})}):d(e)}},8453:(e,n,r)=>{r.d(n,{R:()=>l,x:()=>o});var i=r(6540);const s={},t=i.createContext(s);function l(e){const n=i.useContext(t);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:l(e.components),i.createElement(t.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/c4f5d8e4.3bbeb6fa.js b/docs/assets/js/c4f5d8e4.3bbeb6fa.js new file mode 100644 index 00000000..7b8ef077 --- /dev/null +++ b/docs/assets/js/c4f5d8e4.3bbeb6fa.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[2634],{1459:(e,t,s)=>{s.r(t),s.d(t,{default:()=>M});var n=s(6540),i=s(1656),r=s(4586),a=s(6025);const o={homepageWrapper:"homepageWrapper_H_rv",customNav:"customNav_xRNg",navContainer:"navContainer_E5Tz",logo:"logo_Ukns",hpGradientShift:"hpGradientShift_w9XB",navLinks:"navLinks_FO3Z",navLink:"navLink_aQaq",btn:"btn_bvfa",btnPrimary:"btnPrimary_hBjO",btnSecondary:"btnSecondary_mRVh",btnWhite:"btnWhite_DoE5",btnOutlineWhite:"btnOutlineWhite_Kzbe",hero:"hero_aEcG",networkCanvas:"networkCanvas_S8Th",heroContent:"heroContent_mKPX",hpFadeInUp:"hpFadeInUp_NspS",heroBadge:"heroBadge_Z6oq",heroTitle:"heroTitle_qg2I",heroSubtitle:"heroSubtitle_jFu1",heroButtons:"heroButtons_r52D",heroImage:"heroImage_xZN7",adoptionBadge:"adoptionBadge_hbYR",section:"section_Q9Zo",container:"container_bfhl",sectionHeader:"sectionHeader_Gahl",sectionSubtitle:"sectionSubtitle_AZuW",sectionTitle:"sectionTitle_Ut5p",sectionDescription:"sectionDescription_cpL1",barriersGrid:"barriersGrid_u0Jf",barrierCard:"barrierCard_tMSq",barrierIcon:"barrierIcon_HTIA",barrierQuestions:"barrierQuestions_jlWA",barrierAnswer:"barrierAnswer_ZtxW",componentsGrid:"componentsGrid_KtT5",componentCard:"componentCard_LlUg",componentCardVisible:"componentCardVisible_hAJc",componentContent:"componentContent_xz2v",componentLink:"componentLink_RzJT",componentIcon:"componentIcon_JDYs",statsSection:"statsSection_GUBq",statsGrid:"statsGrid_wBRk",statCard:"statCard_w2S8",statLabel:"statLabel_I99V",statValue:"statValue_tB6D",statDescription:"statDescription_WIU_",videosGrid:"videosGrid_FXHY",videoCard:"videoCard_jGks",videoWrapper:"videoWrapper_XWWU",videoPlayer:"videoPlayer_Nt7m",videoContent:"videoContent_pd0B",blogGrid:"blogGrid_Qec3",blogCard:"blogCard_hyds",blogCardIcon:"blogCardIcon_JPeR",blogContent:"blogContent_dJxs",blogCategory:"blogCategory_UY54",blogMeta:"blogMeta_skDH",ctaSection:"ctaSection_bmsv",hpRotate:"hpRotate_a55V",ctaTitle:"ctaTitle_arch",ctaDescription:"ctaDescription_HswS",ctaButtons:"ctaButtons_vsp7",customFooter:"customFooter_Ymmc",footerContent:"footerContent_obNo",footerSection:"footerSection__c07",footerList:"footerList_2l2h",footerBottom:"footerBottom_nS2f",footerLinks:"footerLinks_lH9U"};var c=s(4848);const l=[{icon:"\ud83e\udde0",title:"Focus on building intelligence, not infrastructure",questions:["Does every model deployment require a full-stack integration effort?","Do engineers have to rebuild feature retrieval, endpoint integrations, and logging for each new model?","Does changing a simple expression like 0.2\xd7s\u2081 + 0.8\xd7s\u2082 to 0.3\xd7s\u2081 + 0.7\xd7s\u2082 really need code reviews and redeployments?","Why does deploying intelligence require the devops team to provision infra?"],answer:"Machine learning teams should be iterating on models, not systems. Yet today, infrastructure complexity turns simple improvements into weeks of engineering effort, slowing experimentation and innovation."},{icon:"\ud83d\udcb0",title:"Built for scale without exponential cost growth",questions:["Do your infrastructure costs scale faster than your ML impact?","Are you recomputing the same features, reloading the same data, and moving the same bytes across systems repeatedly?","Are expensive GPUs and compute sitting underutilized while workloads wait on data or inefficient pipelines?","Why does scaling ML often mean scaling cost linearly\u2014or worse?"],answer:"A modern ML platform should eliminate redundant computation, reuse features intelligently, and optimize data access across memory, NVMe, and object storage. Compute should be pooled, scheduled efficiently, and fully utilized\u2014ensuring that scale drives impact, not runaway infrastructure costs."},{icon:"\ud83c\udf0d",title:"Freedom to deploy anywhere, without lock-in",questions:["Are your models tied to a single cloud, making migration costly and complex?","Does adopting managed services today limit your ability to optimize cost or move infrastructure tomorrow?","Can you deploy the same ML stack across public cloud, private cloud, or sovereign environments without redesigning everything?","Why should infrastructure choices dictate the future of your ML systems?"],answer:"A modern ML platform should be built on open standards and cloud-neutral abstractions, allowing you to deploy anywhere\u2014public cloud, private infrastructure, or sovereign environments. This ensures complete control over your data, freedom from vendor lock-in, and the ability to optimize for cost, performance, and compliance without architectural constraints."}],d=[{icon:"\u26a1",title:"Online Feature Store",description:"BharatMLStack Online Feature Store delivers sub-10ms, high-throughput access to machine learning features for real-time inference. It seamlessly ingests batch and streaming data, validates schemas, and persists compact, versioned feature groups optimized for low latency and efficiency. With scalable storage backends, gRPC APIs, and binary-optimized formats, it ensures consistent, reliable feature serving across ML pipelines.",cta:"/online-feature-store/v1.0.0"},{icon:"\ud83d\udd00",title:"Inferflow",description:"Inferflow is BharatMLStack's intelligent inference gateway that dynamically retrieves and assembles features required by ML models using a graph-based configuration called Inferpipes. It automatically resolves entity relationships, fetches features from the Online Feature Store, and constructs feature vectors without custom code.",cta:"/inferflow/v1.0.0"},{icon:"\ud83d\udd0d",title:"Skye",description:"Skye enables fast similarity retrieval by representing data as vectors and querying nearest matches in high-dimensional space. It supports pluggable vector databases, ensuring flexibility across infrastructure. The system provides tenant-level index isolation while allowing single embedding ingestion even when shared across tenants, reducing redundancy.",cta:"/skye/v1.0.0"},{icon:"\ud83e\uddee",title:"Numerix",description:"Numerix is a high-performance compute engine designed for ultra-fast element-wise matrix operations. Built in Rust and accelerated using SIMD, it delivers exceptional efficiency and predictable performance. Optimized for real-time inference workloads, it achieves strict sub-5ms p99 latency on matrices up to 1000\xd710.",cta:"/numerix/v1.0.0"},{icon:"\ud83d\ude80",title:"Predator",description:"Predator streamlines infrastructure and model lifecycle management. It enables the creation of deployables with specific Triton Server versions and supports seamless model rollouts. Leveraging Helm charts and Argo CD, Predator automates Kubernetes-based deployments while integrating with KEDA for auto-scaling and performance tuning.",cta:"/predator/v1.0.0"}],h=[{target:4.5,suffix:"M+",decimals:1,label:"Daily Orders",description:"Daily orders processed via ML pipelines"},{target:2.4,suffix:"M",decimals:1,label:"QPS on FS",description:"QPS on Feature Store with batch size of 100 id lookups"},{target:1,suffix:"M+",decimals:0,label:"QPS Inference",description:"QPS on Model Inference"},{target:500,suffix:"K",decimals:0,label:"QPS Embedding",description:"QPS Embedding Search"}],m=[{title:"Feature Store",description:"Learn how to onboard and manage features using the self-serve UI for the Online Feature Store.",url:"https://videos.meesho.com/reels/feature_store.mp4"},{title:"Embedding Platform",description:"Walkthrough of onboarding and managing embedding models via the Skye self-serve UI.",url:"https://videos.meesho.com/reels/embedding_platform.mp4"},{title:"Numerix",description:"Step-by-step guide to configuring and running matrix operations through the Numerix self-serve UI.",url:"https://videos.meesho.com/reels/numerix.mp4"},{title:"Predator",description:"How to deploy and manage ML models on Kubernetes using the Predator self-serve UI.",url:"https://videos.meesho.com/reels/predator.mp4"},{title:"Inferflow",description:"Setting up inferpipes and feature retrieval graphs through the Inferflow self-serve UI.",url:"https://videos.meesho.com/reels/inferflow.mp4"}],u=[{title:"Building Meesho's ML Platform: From Chaos to Cutting-Edge (Part 1)",category:"ML Platform",icon:"\ud83d\ude80",link:"/blog/post-one"},{title:"Building Meesho's ML Platform: Lessons from the First-Gen System (Part 2)",category:"ML Platform",icon:"\ud83e\udde9",link:"/blog/post-two"},{title:"Cracking the Code: Scaling Model Inference & Real-Time Embedding Search",category:"Inference",icon:"\u26a1",link:"/blog/post-three"},{title:"Designing a Production-Grade LLM Inference Platform: From Model Weights to Scalable GPU Serving",category:"LLM",icon:"\ud83e\udde0",link:"/blog/post-four"},{title:"LLM Inference Optimization Techniques: Engineering Sub-Second Latency at Scale",category:"Optimization",icon:"\ud83d\udd2c",link:"/blog/post-five"},{title:"Beyond Vector RAG: Building Agent Memory That Learns From Experience.",category:"AI Agents",icon:"\ud83e\udde0",link:"/blog/episodic-memory-for-agents"}];function p(){const e=(0,a.Ay)("/"),t=(0,a.Ay)("/blog");return(0,c.jsx)("nav",{className:o.customNav,children:(0,c.jsxs)("div",{className:o.navContainer,children:[(0,c.jsx)("a",{href:e,className:o.logo,children:"BharatMLStack"}),(0,c.jsxs)("div",{className:o.navLinks,children:[(0,c.jsx)("a",{href:"#components",className:o.navLink,children:"Components"}),(0,c.jsx)("a",{href:"#stats",className:o.navLink,children:"Scale"}),(0,c.jsx)("a",{href:"#demos",className:o.navLink,children:"Demos"}),(0,c.jsx)("a",{href:t,className:o.navLink,children:"Blog"}),(0,c.jsx)("a",{href:"https://github.com/Meesho/BharatMLStack",className:`${o.btn} ${o.btnPrimary}`,target:"_blank",rel:"noopener noreferrer",children:"GitHub"})]})]})})}function f(){const e=(0,n.useRef)(null);return(0,n.useEffect)(()=>{const t=e.current;if(!t)return;const s=t.getContext("2d");let n,i=[];function r(){const e=t.parentElement;t.width=e.offsetWidth,t.height=e.offsetHeight}r(),function(){i=[];for(let e=0;e<50;e++)i.push({x:Math.random()*t.width,y:Math.random()*t.height,vx:.4*(Math.random()-.5),vy:.4*(Math.random()-.5),radius:2*Math.random()+1})}(),function e(){!function(){for(const e of i)e.x+=e.vx,e.y+=e.vy,(e.x<0||e.x>t.width)&&(e.vx*=-1),(e.y<0||e.y>t.height)&&(e.vy*=-1),e.x=Math.max(0,Math.min(t.width,e.x)),e.y=Math.max(0,Math.min(t.height,e.y))}(),function(){s.clearRect(0,0,t.width,t.height);for(let e=0;e<i.length;e++)for(let t=e+1;t<i.length;t++){const n=i[e].x-i[t].x,r=i[e].y-i[t].y,a=Math.sqrt(n*n+r*r);if(a<150){const n=.25*(1-a/150);s.beginPath(),s.moveTo(i[e].x,i[e].y),s.lineTo(i[t].x,i[t].y),s.strokeStyle=`rgba(99, 102, 241, ${n})`,s.lineWidth=.8,s.stroke()}}for(const e of i)s.beginPath(),s.arc(e.x,e.y,e.radius,0,2*Math.PI),s.fillStyle="rgba(139, 92, 246, 0.5)",s.fill()}(),n=requestAnimationFrame(e)}();const a=new ResizeObserver(()=>{r()});return a.observe(t.parentElement),()=>{cancelAnimationFrame(n),a.disconnect()}},[]),(0,c.jsx)("canvas",{ref:e,className:o.networkCanvas,"aria-hidden":"true"})}function g(){const e=(0,a.Ay)("/intro");return(0,c.jsxs)("section",{className:o.hero,children:[(0,c.jsx)(f,{}),(0,c.jsxs)("div",{className:o.heroContent,children:[(0,c.jsx)("div",{className:o.heroBadge,children:"Open-source, scalable stack for enterprise ML"}),(0,c.jsx)("h1",{className:o.heroTitle,children:"Build production ML pipelines faster"}),(0,c.jsx)("p",{className:o.heroSubtitle,children:"Open source, end-to-end ML infrastructure stack built for scale, speed, and simplicity. Integrate, deploy, and manage robust ML workflows with full reliability and control."}),(0,c.jsxs)("div",{className:o.heroButtons,children:[(0,c.jsx)("a",{href:e,className:`${o.btn} ${o.btnPrimary}`,children:"Get Started"}),(0,c.jsx)("a",{href:"https://github.com/Meesho/BharatMLStack",className:`${o.btn} ${o.btnSecondary}`,target:"_blank",rel:"noopener noreferrer",children:"View on GitHub"})]}),(0,c.jsx)("div",{className:o.adoptionBadge,children:(0,c.jsx)("p",{children:"Adopted by data teams building at scale"})})]}),(0,c.jsx)("div",{className:o.heroImage,children:(0,c.jsx)("img",{src:(0,a.Ay)("/img/bharatml-stack-logo.jpg"),alt:"BharatML Stack Logo",loading:"eager"})})]})}function x(){return(0,c.jsx)("section",{className:o.section,children:(0,c.jsxs)("div",{className:o.container,children:[(0,c.jsxs)("div",{className:o.sectionHeader,children:[(0,c.jsx)("p",{className:o.sectionSubtitle,children:"Why BharatMLStack"}),(0,c.jsx)("h2",{className:o.sectionTitle,children:"The Real Barriers to Scaling Machine Learning"}),(0,c.jsx)("p",{className:o.sectionDescription,children:"ML teams spend more time fighting infrastructure than building intelligence. BharatMLStack removes those barriers."})]}),(0,c.jsx)("div",{className:o.barriersGrid,children:l.map((e,t)=>(0,c.jsxs)("div",{className:o.barrierCard,children:[(0,c.jsx)("div",{className:o.barrierIcon,children:e.icon}),(0,c.jsx)("h3",{children:e.title}),(0,c.jsx)("ul",{className:o.barrierQuestions,children:e.questions.map((e,t)=>(0,c.jsx)("li",{children:e},t))}),(0,c.jsx)("p",{className:o.barrierAnswer,children:e.answer})]},t))})]})})}function b(){const e=(0,n.useRef)([]),t=(0,a.Ay)("/");return(0,n.useEffect)(()=>{const t=new IntersectionObserver(e=>{e.forEach(e=>{e.isIntersecting&&e.target.classList.add(o.componentCardVisible)})},{threshold:.1,rootMargin:"0px 0px -80px 0px"});return e.current.forEach(e=>{e&&t.observe(e)}),()=>t.disconnect()},[]),(0,c.jsx)("section",{className:o.section,id:"components",children:(0,c.jsxs)("div",{className:o.container,children:[(0,c.jsxs)("div",{className:o.sectionHeader,children:[(0,c.jsx)("p",{className:o.sectionSubtitle,children:"Platform Components"}),(0,c.jsx)("h2",{className:o.sectionTitle,children:"BharatMLStack Components"}),(0,c.jsx)("p",{className:o.sectionDescription,children:"Purpose-built components for every stage of the ML lifecycle, from feature serving to model deployment."})]}),(0,c.jsx)("div",{className:o.componentsGrid,children:d.map((s,n)=>(0,c.jsxs)("div",{className:o.componentCard,ref:t=>e.current[n]=t,children:[(0,c.jsx)("div",{className:o.componentIcon,children:s.icon}),(0,c.jsxs)("div",{className:o.componentContent,children:[(0,c.jsx)("h3",{children:s.title}),(0,c.jsx)("p",{children:s.description}),(0,c.jsx)("a",{href:`${t}${s.cta.replace(/^\//,"")}`,className:o.componentLink,children:"Learn more \u2192"})]})]},n))})]})})}function v({target:e,suffix:t,decimals:s,duration:i=1500}){const[r,a]=(0,n.useState)(0),[l,d]=(0,n.useState)(!1),h=(0,n.useRef)(null),m=(0,n.useCallback)(()=>{if(l)return;d(!0);const t=performance.now(),s=n=>{const r=n-t,o=Math.min(r/i,1),c=1-Math.pow(1-o,3);a(c*e),o<1?requestAnimationFrame(s):a(e)};requestAnimationFrame(s)},[e,i,l]);(0,n.useEffect)(()=>{const e=h.current;if(!e)return;const t=new IntersectionObserver(([e])=>{e.isIntersecting&&m()},{threshold:.3});return t.observe(e),()=>t.disconnect()},[m]);const u=s>0?r.toFixed(s):Math.round(r).toLocaleString();return(0,c.jsxs)("div",{className:o.statValue,ref:h,children:[u,t]})}function j(){return(0,c.jsx)("section",{className:`${o.section} ${o.statsSection}`,id:"stats",children:(0,c.jsxs)("div",{className:o.container,children:[(0,c.jsxs)("div",{className:o.sectionHeader,children:[(0,c.jsx)("p",{className:o.sectionSubtitle,children:"Proven at scale"}),(0,c.jsx)("h2",{className:o.sectionTitle,children:"Scaling Numbers"})]}),(0,c.jsx)("div",{className:o.statsGrid,children:h.map((e,t)=>(0,c.jsxs)("div",{className:o.statCard,children:[(0,c.jsx)("p",{className:o.statLabel,children:e.label}),(0,c.jsx)(v,{target:e.target,suffix:e.suffix,decimals:e.decimals}),(0,c.jsx)("p",{className:o.statDescription,children:e.description})]},t))})]})})}function y(){return(0,c.jsx)("section",{className:o.section,id:"demos",children:(0,c.jsxs)("div",{className:o.container,children:[(0,c.jsxs)("div",{className:o.sectionHeader,children:[(0,c.jsx)("p",{className:o.sectionSubtitle,children:"See it in action"}),(0,c.jsx)("h2",{className:o.sectionTitle,children:"Demo Videos"}),(0,c.jsx)("p",{className:o.sectionDescription,children:"Watch short demos of each BharatMLStack component in action."})]}),(0,c.jsx)("div",{className:o.videosGrid,children:m.map((e,t)=>(0,c.jsxs)("div",{className:o.videoCard,children:[(0,c.jsx)("div",{className:o.videoWrapper,children:(0,c.jsxs)("video",{className:o.videoPlayer,controls:!0,preload:"metadata",playsInline:!0,children:[(0,c.jsx)("source",{src:e.url,type:"video/mp4"}),"Your browser does not support the video tag."]})}),(0,c.jsxs)("div",{className:o.videoContent,children:[(0,c.jsx)("h3",{children:e.title}),(0,c.jsx)("p",{children:e.description})]})]},t))})]})})}function N(){const e=(0,a.Ay)("/");return(0,c.jsx)("section",{className:o.section,id:"blog",children:(0,c.jsxs)("div",{className:o.container,children:[(0,c.jsxs)("div",{className:o.sectionHeader,children:[(0,c.jsx)("p",{className:o.sectionSubtitle,children:"From our blog"}),(0,c.jsx)("h2",{className:o.sectionTitle,children:"View Our Blogs"}),(0,c.jsx)("p",{className:o.sectionDescription,children:"Technical articles, architecture deep-dives, and the story behind BharatMLStack."})]}),(0,c.jsx)("div",{className:o.blogGrid,children:u.map((t,s)=>(0,c.jsxs)("a",{href:`${e}${t.link.replace(/^\//,"")}`,className:o.blogCard,children:[(0,c.jsx)("div",{className:o.blogCardIcon,children:t.icon}),(0,c.jsxs)("div",{className:o.blogContent,children:[(0,c.jsx)("span",{className:o.blogCategory,children:t.category}),(0,c.jsx)("h3",{children:t.title}),(0,c.jsx)("div",{className:o.blogMeta,children:(0,c.jsx)("span",{children:"BharatMLStack Team"})})]})]},s))})]})})}function S(){const e=(0,a.Ay)("/intro");return(0,c.jsx)("section",{className:o.section,children:(0,c.jsx)("div",{className:o.container,children:(0,c.jsxs)("div",{className:o.ctaSection,children:[(0,c.jsx)("h2",{className:o.ctaTitle,children:"Deploy ML models with confidence"}),(0,c.jsx)("p",{className:o.ctaDescription,children:"Comprehensive stack for business-ready ML. Integrates seamlessly with enterprise systems. Robust security and regulatory compliance."}),(0,c.jsxs)("div",{className:o.ctaButtons,children:[(0,c.jsx)("a",{href:e,className:`${o.btn} ${o.btnWhite}`,children:"Start Now"}),(0,c.jsx)("a",{href:"https://github.com/Meesho/BharatMLStack",className:`${o.btn} ${o.btnOutlineWhite}`,target:"_blank",rel:"noopener noreferrer",children:"View on GitHub"})]})]})})})}function L(){const e=(0,a.Ay)("/"),t=(0,a.Ay)("/blog");return(0,c.jsxs)("footer",{className:o.customFooter,children:[(0,c.jsxs)("div",{className:o.footerContent,children:[(0,c.jsxs)("div",{className:o.footerSection,children:[(0,c.jsx)("h4",{children:"BharatMLStack"}),(0,c.jsx)("p",{children:"Enterprise-ready open-source ML infrastructure built for scale, speed, and simplicity."})]}),(0,c.jsxs)("div",{className:o.footerSection,children:[(0,c.jsx)("h4",{children:"Platform"}),(0,c.jsxs)("ul",{className:o.footerList,children:[(0,c.jsx)("li",{children:(0,c.jsx)("a",{href:(0,a.Ay)("/online-feature-store/v1.0.0"),children:"Online Feature Store"})}),(0,c.jsx)("li",{children:(0,c.jsx)("a",{href:(0,a.Ay)("/inferflow/v1.0.0"),children:"Inferflow"})}),(0,c.jsx)("li",{children:(0,c.jsx)("a",{href:(0,a.Ay)("/skye/v1.0.0"),children:"Skye"})}),(0,c.jsx)("li",{children:(0,c.jsx)("a",{href:(0,a.Ay)("/numerix/v1.0.0"),children:"Numerix"})}),(0,c.jsx)("li",{children:(0,c.jsx)("a",{href:(0,a.Ay)("/predator/v1.0.0"),children:"Predator"})})]})]}),(0,c.jsxs)("div",{className:o.footerSection,children:[(0,c.jsx)("h4",{children:"Resources"}),(0,c.jsxs)("ul",{className:o.footerList,children:[(0,c.jsx)("li",{children:(0,c.jsx)("a",{href:t,children:"Blog"})}),(0,c.jsx)("li",{children:(0,c.jsx)("a",{href:e,children:"Documentation"})}),(0,c.jsx)("li",{children:(0,c.jsx)("a",{href:"https://github.com/Meesho/BharatMLStack/discussions",children:"Forum"})})]})]}),(0,c.jsxs)("div",{className:o.footerSection,children:[(0,c.jsx)("h4",{children:"Community"}),(0,c.jsxs)("ul",{className:o.footerList,children:[(0,c.jsx)("li",{children:(0,c.jsx)("a",{href:"https://github.com/Meesho/BharatMLStack",children:"GitHub"})}),(0,c.jsx)("li",{children:(0,c.jsx)("a",{href:"https://discord.gg/XkT7XsV2AU",children:"Discord"})}),(0,c.jsx)("li",{children:(0,c.jsx)("a",{href:"https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTING.md",children:"Contributing"})})]})]})]}),(0,c.jsxs)("div",{className:o.footerBottom,children:[(0,c.jsxs)("p",{children:["\xa9 ",(new Date).getFullYear()," Meesho Ltd. All rights reserved. Open Source under Apache 2.0 License."]}),(0,c.jsx)("div",{className:o.footerLinks,children:(0,c.jsx)("a",{href:"https://github.com/Meesho/BharatMLStack",children:"GitHub"})})]})]})}function M(){const{siteConfig:e}=(0,r.A)();return(0,n.useLayoutEffect)(()=>(document.documentElement.classList.add("homepage-active"),()=>{document.documentElement.classList.remove("homepage-active")}),[]),(0,c.jsxs)(i.A,{title:`${e.title} - Open Source ML Infrastructure`,description:"Open source, end-to-end ML infrastructure stack built for scale, speed, and simplicity.",children:[(0,c.jsx)("style",{children:"\n .navbar { display: none !important; }\n .footer { display: none !important; }\n [class*='docMainContainer'], [class*='mainWrapper'] { padding-top: 0 !important; }\n main { margin-top: 0 !important; }\n "}),(0,c.jsxs)("div",{className:o.homepageWrapper,children:[(0,c.jsx)(p,{}),(0,c.jsx)(g,{}),(0,c.jsx)(x,{}),(0,c.jsx)(b,{}),(0,c.jsx)(j,{}),(0,c.jsx)(y,{}),(0,c.jsx)(N,{}),(0,c.jsx)(S,{}),(0,c.jsx)(L,{})]})]})}}}]); \ No newline at end of file diff --git a/docs/assets/js/c4f5d8e4.f5d4db47.js b/docs/assets/js/c4f5d8e4.f5d4db47.js deleted file mode 100644 index 24625a06..00000000 --- a/docs/assets/js/c4f5d8e4.f5d4db47.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[2634],{6467:(e,t,i)=>{i.r(t),i.d(t,{default:()=>w});var s=i(4164),r=i(8774),n=i(4586),a=i(6025),o=i(1656),c=i(1107);const l={features:"features_t9lD",featureSvg:"featureSvg_GfXr",featuresHeader:"featuresHeader_qR2i",featuresSubtitle:"featuresSubtitle_VdGe","bharatml-card":"bharatml-card_xZ6l","bharatml-icon":"bharatml-icon_XBoJ",featureDescription:"featureDescription_sP1D"};var d=i(4848);const h=[{title:"High-Performance Feature Store",icon:"\ud83d\ude80",description:(0,d.jsx)(d.Fragment,{children:"Sub-10ms P99 latency and 1M+ RPS capacity. Built for real-time ML inference with custom PSDB serialization format that outperforms Protocol Buffers and Apache Arrow."})},{title:"Production-Ready ML Infrastructure",icon:"\u26a1",description:(0,d.jsx)(d.Fragment,{children:"Multi-database backends (Scylla, Dragonfly, Redis), comprehensive monitoring, and enterprise-grade features. Deploy with confidence using battle-tested components."})},{title:"Developer-First Experience",icon:"\ud83d\udee0\ufe0f",description:(0,d.jsx)(d.Fragment,{children:"Multi-language SDKs (Go, Python), gRPC APIs, and extensive documentation. From data scientists, ML engineers to backend engineers, everyone gets tools they love."})}],u=[{title:"Feature Catalog & Management",icon:"\ud83d\udccb",description:(0,d.jsx)(d.Fragment,{children:"Comprehensive feature catalog with metadata management, versioning, and governance. Organize and discover features across your ML platform with ease."})},{title:"User Management & Admin Ops",icon:"\ud83d\udc65",description:(0,d.jsx)(d.Fragment,{children:"Role-based access control, user authentication, and administrative operations. Secure your ML platform with enterprise-grade user management capabilities."})},{title:"Modern UI Framework",icon:"\ud83c\udfa8",description:(0,d.jsx)(d.Fragment,{children:"Intuitive, responsive web interface built with modern web technologies. Streamline MLOps workflows with beautiful and functional user experiences."})}],m=[{title:"Multi-Language Support",icon:"\ud83c\udf10",description:(0,d.jsx)(d.Fragment,{children:"Native SDKs for Go and Python with idiomatic APIs. Choose the language that fits your team's expertise and existing infrastructure."})},{title:"gRPC & REST APIs",icon:"\ud83d\udd17",description:(0,d.jsx)(d.Fragment,{children:"High-performance gRPC clients and REST APIs for seamless integration. Built-in support for streaming, batching, and async operations."})},{title:"Spark Integration",icon:"\u26a1",description:(0,d.jsx)(d.Fragment,{children:"Native Apache Spark integration for batch feature processing and ingestion. Scale your feature engineering workflows with distributed computing power."})}];function g({icon:e,title:t,description:i}){return(0,d.jsxs)("div",{className:(0,s.A)("col col--4"),children:[(0,d.jsx)("div",{className:"text--center",children:(0,d.jsx)("div",{className:"bharatml-icon",children:e})}),(0,d.jsxs)("div",{className:"text--center padding-horiz--md bharatml-card",children:[(0,d.jsx)(c.A,{as:"h3",children:t}),(0,d.jsx)("p",{className:l.featureDescription,children:i})]})]})}function p({title:e,subtitle:t,features:i}){return(0,d.jsx)("section",{className:l.features,children:(0,d.jsxs)("div",{className:"container",children:[(0,d.jsxs)("div",{className:"text--center margin-bottom--xl",children:[(0,d.jsx)(c.A,{as:"h2",className:l.featuresHeader,children:e}),(0,d.jsx)("p",{className:l.featuresSubtitle,children:t})]}),(0,d.jsx)("div",{className:"row",children:i.map(((e,t)=>(0,d.jsx)(g,{...e},t)))})]})})}function x(){return(0,d.jsx)(p,{title:"Online Feature Store",subtitle:"High-performance, production-ready feature serving for real-time ML inference",features:h})}function f(){return(0,d.jsx)(p,{title:"Trufflebox UI",subtitle:"Modern, feature-rich UI framework for comprehensive MLOps management",features:u})}function j(){return(0,d.jsx)(p,{title:"SDKs",subtitle:"Developer-friendly client libraries and APIs for seamless platform integration",features:m})}const b={heroBanner:"heroBanner_qdFl",logoContainer:"logoContainer_xdaK",heroLogo:"heroLogo_U6bI",buttons:"buttons_AeoN",statsContainer:"statsContainer_KpvY",statItem:"statItem_bwiZ",aboutSection:"aboutSection_udvw",highlightBox:"highlightBox_Uhe8"};function v(){const{siteConfig:e}=(0,n.A)();return(0,d.jsx)("header",{className:(0,s.A)("hero bharatml-hero",b.heroBanner),children:(0,d.jsxs)("div",{className:"container",children:[(0,d.jsx)("div",{className:b.logoContainer,children:(0,d.jsx)("img",{src:(0,a.Ay)("/img/logo.svg"),alt:"BharatMLStack Logo",className:b.heroLogo})}),(0,d.jsxs)(c.A,{as:"h1",className:"hero__title",children:["Welcome to ",e.title]}),(0,d.jsx)("p",{className:"hero__subtitle",children:"Open source, end-to-end ML infrastructure stack built for scale, speed, and simplicity."}),(0,d.jsxs)("div",{className:b.buttons,children:[(0,d.jsx)(r.A,{className:"button button--secondary button--lg margin-right--md bharatml-button",to:"/category/online-feature-store",children:"\ud83d\udcda Get Started"}),(0,d.jsx)(r.A,{className:"button button--outline button--secondary button--lg",href:"https://github.com/Meesho/BharatMLStack",target:"_blank",children:"\u2b50 Star on GitHub"})]}),(0,d.jsxs)("div",{className:b.statsContainer,children:[(0,d.jsxs)("div",{className:b.statItem,children:[(0,d.jsx)("strong",{children:"Sub-10ms"}),(0,d.jsx)("span",{children:"P99 Latency"})]}),(0,d.jsxs)("div",{className:b.statItem,children:[(0,d.jsx)("strong",{children:"1M+ RPS"}),(0,d.jsx)("span",{children:"Tested Capacity"})]}),(0,d.jsxs)("div",{className:b.statItem,children:[(0,d.jsx)("strong",{children:"Multi-DB"}),(0,d.jsx)("span",{children:"Support"})]})]})]})})}function y(){return(0,d.jsx)("section",{className:b.aboutSection,children:(0,d.jsx)("div",{className:"container",children:(0,d.jsxs)("div",{className:"row",children:[(0,d.jsxs)("div",{className:"col col--6",children:[(0,d.jsx)(c.A,{as:"h2",children:"Built for India's Scale"}),(0,d.jsx)("p",{children:"BharatMLStack is a comprehensive, production-ready machine learning infrastructure platform designed to democratize ML capabilities across India and beyond. Our mission is to provide a robust, scalable, and accessible ML stack that empowers organizations to build, deploy, and manage machine learning solutions at massive scale."}),(0,d.jsx)(r.A,{className:"button button--primary",to:"/category/online-feature-store",children:"Explore Online Feature Store \u2192"})]}),(0,d.jsx)("div",{className:"col col--6",children:(0,d.jsxs)("div",{className:b.highlightBox,children:[(0,d.jsx)("h3",{children:"\ud83c\udfc6 Key Achievements"}),(0,d.jsxs)("ul",{children:[(0,d.jsx)("li",{children:"\u2705 Sub-10ms P99 latency for real-time inference"}),(0,d.jsx)("li",{children:"\u2705 1M+ RPS tested with 100 IDs per request"}),(0,d.jsx)("li",{children:"\u2705 PSDB format outperforms Proto3 & Arrow"}),(0,d.jsx)("li",{children:"\u2705 Multi-database: Scylla, Dragonfly, Redis"}),(0,d.jsx)("li",{children:"\u2705 Production-ready with comprehensive monitoring"})]})]})})]})})})}function S(){return(0,d.jsx)("section",{className:b.aboutSection,children:(0,d.jsx)("div",{className:"container",children:(0,d.jsxs)("div",{className:"row",children:[(0,d.jsxs)("div",{className:"col col--6",children:[(0,d.jsx)(c.A,{as:"h2",children:"Modern MLOps Management"}),(0,d.jsx)("p",{children:"Trufflebox UI provides a comprehensive, modern web interface for managing your entire ML infrastructure. Built with cutting-edge web technologies, it delivers an intuitive experience for feature management, user administration, and operational oversight. Streamline your MLOps workflows with enterprise-grade UI components."}),(0,d.jsx)(r.A,{className:"button button--primary",to:"/category/trufflebox-ui",children:"Explore Trufflebox UI \u2192"})]}),(0,d.jsx)("div",{className:"col col--6",children:(0,d.jsxs)("div",{className:b.highlightBox,children:[(0,d.jsx)("h3",{children:"\ud83c\udfa8 UI Features"}),(0,d.jsxs)("ul",{children:[(0,d.jsx)("li",{children:"\u2705 Comprehensive feature catalog & discovery"}),(0,d.jsx)("li",{children:"\u2705 Role-based access control & user management"}),(0,d.jsx)("li",{children:"\u2705 Job, Store, Admin Ops management"}),(0,d.jsx)("li",{children:"\u2705 Approval flow for everything"}),(0,d.jsx)("li",{children:"\u2705 Responsive design for desktop & mobile"})]})]})})]})})})}function N(){return(0,d.jsx)("section",{className:b.aboutSection,children:(0,d.jsx)("div",{className:"container",children:(0,d.jsxs)("div",{className:"row",children:[(0,d.jsxs)("div",{className:"col col--6",children:[(0,d.jsx)(c.A,{as:"h2",children:"Developer-First Integration"}),(0,d.jsx)("p",{children:"Our SDKs are designed with developers in mind, providing idiomatic APIs for Go and Python that feel natural in your existing codebase. Whether you're building microservices, data pipelines, or ML applications, our SDKs provide the tools you need for seamless integration with BharatMLStack's powerful infrastructure."}),(0,d.jsx)(r.A,{className:"button button--primary",to:"/category/sdks",children:"Explore SDKs \u2192"})]}),(0,d.jsx)("div",{className:"col col--6",children:(0,d.jsxs)("div",{className:b.highlightBox,children:[(0,d.jsx)("h3",{children:"\ud83d\udee0\ufe0f Developer Tools"}),(0,d.jsxs)("ul",{children:[(0,d.jsx)("li",{children:"\u2705 Native Go & Python SDKs with type safety"}),(0,d.jsx)("li",{children:"\u2705 High-performance gRPC"}),(0,d.jsx)("li",{children:"\u2705 Apache Spark integration for publishing features"})]})]})})]})})})}function w(){const{siteConfig:e}=(0,n.A)();return(0,d.jsxs)(o.A,{title:`${e.title} - Open Source ML Infrastructure`,description:"Open source, end-to-end ML infrastructure stack built for scale, speed, and simplicity. Features high-performance Online Feature Store with sub-10ms latency.",children:[(0,d.jsx)(v,{}),(0,d.jsxs)("main",{children:[(0,d.jsx)(x,{}),(0,d.jsx)(y,{}),(0,d.jsx)(f,{}),(0,d.jsx)(S,{}),(0,d.jsx)(j,{}),(0,d.jsx)(N,{})]})]})}}}]); \ No newline at end of file diff --git a/docs/assets/js/c621f852.beef9a06.js b/docs/assets/js/c621f852.beef9a06.js new file mode 100644 index 00000000..ae7e43ad --- /dev/null +++ b/docs/assets/js/c621f852.beef9a06.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[8276],{3338:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>a,default:()=>h,frontMatter:()=>c,metadata:()=>r,toc:()=>d});const r=JSON.parse('{"id":"sdks/python/v1.0.0/index","title":"v1.0.0","description":"Python SDK v1.0.0","source":"@site/docs/sdks/python/v1.0.0/index.md","sourceDirName":"sdks/python/v1.0.0","slug":"/sdks/python/v1.0.0","permalink":"/BharatMLStack/sdks/python/v1.0.0","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/sdks/python/v1.0.0/index.md","tags":[],"version":"current","sidebarPosition":0,"frontMatter":{"title":"v1.0.0","description":"Python SDK v1.0.0","sidebar_position":0,"slug":"/sdks/python/v1.0.0"},"sidebar":"tutorialSidebar","previous":{"title":"Python SDK","permalink":"/BharatMLStack/category/python-sdk"},"next":{"title":"GRPC Feature client","permalink":"/BharatMLStack/sdks/python/v1.0.0/grpc_feature_client"}}');var s=n(4848),o=n(8453),i=n(4795);const c={title:"v1.0.0",description:"Python SDK v1.0.0",sidebar_position:0,slug:"/sdks/python/v1.0.0"},a="Python SDK v1.0.0",l={},d=[];function u(e){const t={h1:"h1",header:"header",p:"p",...(0,o.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(t.header,{children:(0,s.jsx)(t.h1,{id:"python-sdk-v100",children:"Python SDK v1.0.0"})}),"\n",(0,s.jsx)(t.p,{children:"Python client libraries and utilities for interacting with the BharatML Stack online feature store, including gRPC clients, Spark integration, and common utilities."}),"\n",(0,s.jsx)(i.A,{})]})}function h(e={}){const{wrapper:t}={...(0,o.R)(),...e.components};return t?(0,s.jsx)(t,{...e,children:(0,s.jsx)(u,{...e})}):u(e)}},4795:(e,t,n)=>{n.d(t,{A:()=>k});n(6540);var r=n(4164),s=n(6972),o=n(8774),i=n(5846),c=n(6654),a=n(1312),l=n(1107);const d={cardContainer:"cardContainer_fWXF",cardTitle:"cardTitle_rnsV",cardDescription:"cardDescription_PWke"};var u=n(4848);function h({className:e,href:t,children:n}){return(0,u.jsx)(o.A,{href:t,className:(0,r.A)("card padding--lg",d.cardContainer,e),children:n})}function m({className:e,href:t,icon:n,title:s,description:o}){return(0,u.jsxs)(h,{href:t,className:e,children:[(0,u.jsxs)(l.A,{as:"h2",className:(0,r.A)("text--truncate",d.cardTitle),title:s,children:[n," ",s]}),o&&(0,u.jsx)("p",{className:(0,r.A)("text--truncate",d.cardDescription),title:o,children:o})]})}function p({item:e}){const t=(0,s.Nr)(e),n=function(){const{selectMessage:e}=(0,i.W)();return t=>e(t,(0,a.T)({message:"1 item|{count} items",id:"theme.docs.DocCard.categoryDescription.plurals",description:"The default description for a category card in the generated index about how many items this category includes"},{count:t}))}();return t?(0,u.jsx)(m,{className:e.className,href:t,icon:"\ud83d\uddc3\ufe0f",title:e.label,description:e.description??n(e.items.length)}):null}function f({item:e}){const t=(0,c.A)(e.href)?"\ud83d\udcc4\ufe0f":"\ud83d\udd17",n=(0,s.cC)(e.docId??void 0);return(0,u.jsx)(m,{className:e.className,href:e.href,icon:t,title:e.label,description:e.description??n?.description})}function x({item:e}){switch(e.type){case"link":return(0,u.jsx)(f,{item:e});case"category":return(0,u.jsx)(p,{item:e});default:throw new Error(`unknown item type ${JSON.stringify(e)}`)}}const g={docCardListItem:"docCardListItem_W1sv"};function y({className:e}){const t=(0,s.a4)();return(0,u.jsx)(k,{items:t,className:e})}function v({item:e}){return(0,u.jsx)("article",{className:(0,r.A)(g.docCardListItem,"col col--6"),children:(0,u.jsx)(x,{item:e})})}function k(e){const{items:t,className:n}=e;if(!t)return(0,u.jsx)(y,{...e});const o=(0,s.d1)(t);return(0,u.jsx)("section",{className:(0,r.A)("row",n),children:o.map((e,t)=>(0,u.jsx)(v,{item:e},t))})}},5846:(e,t,n)=>{n.d(t,{W:()=>l});var r=n(6540),s=n(4586);const o=["zero","one","two","few","many","other"];function i(e){return o.filter(t=>e.includes(t))}const c={locale:"en",pluralForms:i(["one","other"]),select:e=>1===e?"one":"other"};function a(){const{i18n:{currentLocale:e}}=(0,s.A)();return(0,r.useMemo)(()=>{try{return function(e){const t=new Intl.PluralRules(e);return{locale:e,pluralForms:i(t.resolvedOptions().pluralCategories),select:e=>t.select(e)}}(e)}catch(t){return console.error(`Failed to use Intl.PluralRules for locale "${e}".\nDocusaurus will fallback to the default (English) implementation.\nError: ${t.message}\n`),c}},[e])}function l(){const e=a();return{selectMessage:(t,n)=>function(e,t,n){const r=e.split("|");if(1===r.length)return r[0];r.length>n.pluralForms.length&&console.error(`For locale=${n.locale}, a maximum of ${n.pluralForms.length} plural forms are expected (${n.pluralForms.join(",")}), but the message contains ${r.length}: ${e}`);const s=n.select(t),o=n.pluralForms.indexOf(s);return r[Math.min(o,r.length-1)]}(n,t,e)}}},8453:(e,t,n)=>{n.d(t,{R:()=>i,x:()=>c});var r=n(6540);const s={},o=r.createContext(s);function i(e){const t=r.useContext(o);return r.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function c(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:i(e.components),r.createElement(o.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/c7b64fcc.70fc6828.js b/docs/assets/js/c7b64fcc.70fc6828.js new file mode 100644 index 00000000..17b14bc0 --- /dev/null +++ b/docs/assets/js/c7b64fcc.70fc6828.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[8933],{9997:e=>{e.exports=JSON.parse('{"categoryGeneratedIndex":{"title":"Go SDK","description":"Go SDK for BharatML Stack. Provides Go client libraries and packages for interacting with the online feature store, including gRPC clients and protocol buffer definitions.","slug":"/category/go-sdk","permalink":"/BharatMLStack/category/go-sdk","sidebar":"tutorialSidebar","navigation":{"previous":{"title":"SDKs","permalink":"/BharatMLStack/category/sdks"},"next":{"title":"v1.0.0","permalink":"/BharatMLStack/sdks/go/v1.0.0"}}}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/c7b64fcc.9ff95135.js b/docs/assets/js/c7b64fcc.9ff95135.js deleted file mode 100644 index ad135ec8..00000000 --- a/docs/assets/js/c7b64fcc.9ff95135.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[8933],{9997:e=>{e.exports=JSON.parse('{"categoryGeneratedIndex":{"title":"Go SDK","description":"Go SDK for BharatML Stack. Provides Go client libraries and packages for interacting with the online feature store, including gRPC clients and protocol buffer definitions.","slug":"/category/go-sdk","permalink":"/BharatMLStack/category/go-sdk","sidebar":"tutorialSidebar","navigation":{"previous":{"title":"SDKs","permalink":"/BharatMLStack/category/sdks"},"next":{"title":"GRPC Feature client","permalink":"/BharatMLStack/sdks/go/v1.0.0/feature_client"}}}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/ccc49370.471f68d9.js b/docs/assets/js/ccc49370.471f68d9.js new file mode 100644 index 00000000..e4fd97c5 --- /dev/null +++ b/docs/assets/js/ccc49370.471f68d9.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[3249],{1689:(e,t,n)=>{n.d(t,{A:()=>d});n(6540);var a=n(4164),s=n(4084),i=n(7559),r=n(7293),l=n(4848);function o({className:e}){return(0,l.jsx)(r.A,{type:"caution",title:(0,l.jsx)(s.Yh,{}),className:(0,a.A)(e,i.G.common.draftBanner),children:(0,l.jsx)(s.TT,{})})}var c=n(2234);function d({metadata:e}){const{unlisted:t,frontMatter:n}=e;return(0,l.jsxs)(l.Fragment,{children:[(t||n.unlisted)&&(0,l.jsx)(c.A,{}),n.draft&&(0,l.jsx)(o,{})]})}},2234:(e,t,n)=>{n.d(t,{A:()=>c});n(6540);var a=n(4164),s=n(7559),i=n(4084),r=n(7293),l=n(4848);function o({className:e}){return(0,l.jsx)(r.A,{type:"caution",title:(0,l.jsx)(i.Rc,{}),className:(0,a.A)(e,s.G.common.unlistedBanner),children:(0,l.jsx)(i.Uh,{})})}function c(e){return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(i.AE,{}),(0,l.jsx)(o,{...e})]})}},2907:(e,t,n)=>{n.d(t,{A:()=>O});n(6540);var a=n(4164),s=n(4096),i=n(4848);function r({children:e,className:t}){return(0,i.jsx)("article",{className:t,children:e})}var l=n(8774);const o={title:"title_f1Hy"};function c({className:e}){const{metadata:t,isBlogPostPage:n}=(0,s.e7)(),{permalink:r,title:c}=t,d=n?"h1":"h2";return(0,i.jsx)(d,{className:(0,a.A)(o.title,e),children:n?c:(0,i.jsx)(l.A,{to:r,children:c})})}var d=n(1312),m=n(5846),u=n(6266);const g={container:"container_mt6G"};function h({readingTime:e}){const t=function(){const{selectMessage:e}=(0,m.W)();return t=>{const n=Math.ceil(t);return e(n,(0,d.T)({id:"theme.blog.post.readingTime.plurals",description:'Pluralized label for "{readingTime} min read". Use as much plural forms (separated by "|") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)',message:"One min read|{readingTime} min read"},{readingTime:n}))}}();return(0,i.jsx)(i.Fragment,{children:t(e)})}function x({date:e,formattedDate:t}){return(0,i.jsx)("time",{dateTime:e,children:t})}function f(){return(0,i.jsx)(i.Fragment,{children:" \xb7 "})}function p({className:e}){const{metadata:t}=(0,s.e7)(),{date:n,readingTime:r}=t,l=(0,u.i)({day:"numeric",month:"long",year:"numeric",timeZone:"UTC"});return(0,i.jsxs)("div",{className:(0,a.A)(g.container,"margin-vert--md",e),children:[(0,i.jsx)(x,{date:n,formattedDate:(o=n,l.format(new Date(o)))}),void 0!==r&&(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(f,{}),(0,i.jsx)(h,{readingTime:r})]})]});var o}var v=n(6382);const j={authorCol:"authorCol_Hf19",imageOnlyAuthorRow:"imageOnlyAuthorRow_pa_O",imageOnlyAuthorCol:"imageOnlyAuthorCol_G86a"};function b({className:e}){const{metadata:{authors:t},assets:n}=(0,s.e7)();if(0===t.length)return null;const r=t.every(({name:e})=>!e),l=1===t.length;return(0,i.jsx)("div",{className:(0,a.A)("margin-top--md margin-bottom--sm",r?j.imageOnlyAuthorRow:"row",e),children:t.map((e,t)=>(0,i.jsx)("div",{className:(0,a.A)(!r&&(l?"col col--12":"col col--6"),r?j.imageOnlyAuthorCol:j.authorCol),children:(0,i.jsx)(v.A,{author:{...e,imageURL:n.authorsImageUrls[t]??e.imageURL}})},t))})}function A(){return(0,i.jsxs)("header",{children:[(0,i.jsx)(c,{}),(0,i.jsx)(p,{}),(0,i.jsx)(b,{})]})}var N=n(440),_=n(3253);function L({children:e,className:t}){const{isBlogPostPage:n}=(0,s.e7)();return(0,i.jsx)("div",{id:n?N.LU:void 0,className:(0,a.A)("markdown",t),children:(0,i.jsx)(_.A,{children:e})})}var y=n(7559),C=n(4336),T=n(4434);function k(){return(0,i.jsx)("b",{children:(0,i.jsx)(d.A,{id:"theme.blog.post.readMore",description:"The label used in blog post item excerpts to link to full blog posts",children:"Read more"})})}function H(e){const{blogPostTitle:t,...n}=e;return(0,i.jsx)(l.A,{"aria-label":(0,d.T)({message:"Read more about {title}",id:"theme.blog.post.readMoreLabel",description:"The ARIA label for the link to full blog posts from excerpts"},{title:t}),...n,children:(0,i.jsx)(k,{})})}function w(){const{metadata:e,isBlogPostPage:t}=(0,s.e7)(),{tags:n,title:r,editUrl:l,hasTruncateMarker:o,lastUpdatedBy:c,lastUpdatedAt:d}=e,m=!t&&o,u=n.length>0;if(!(u||m||l))return null;if(t){const e=!!(l||d||c);return(0,i.jsxs)("footer",{className:"docusaurus-mt-lg",children:[u&&(0,i.jsx)("div",{className:(0,a.A)("row","margin-top--sm",y.G.blog.blogFooterEditMetaRow),children:(0,i.jsx)("div",{className:"col",children:(0,i.jsx)(T.A,{tags:n})})}),e&&(0,i.jsx)(C.A,{className:(0,a.A)("margin-top--sm",y.G.blog.blogFooterEditMetaRow),editUrl:l,lastUpdatedAt:d,lastUpdatedBy:c})]})}return(0,i.jsxs)("footer",{className:"row docusaurus-mt-lg",children:[u&&(0,i.jsx)("div",{className:(0,a.A)("col",{"col--9":m}),children:(0,i.jsx)(T.A,{tags:n})}),m&&(0,i.jsx)("div",{className:(0,a.A)("col text--right",{"col--3":u}),children:(0,i.jsx)(H,{blogPostTitle:r,to:e.permalink})})]})}function O({children:e,className:t}){const n=function(){const{isBlogPostPage:e}=(0,s.e7)();return e?void 0:"margin-bottom--xl"}();return(0,i.jsxs)(r,{className:(0,a.A)(n,t),children:[(0,i.jsx)(A,{}),(0,i.jsx)(L,{children:e}),(0,i.jsx)(w,{})]})}},3858:(e,t,n)=>{n.r(t),n.d(t,{default:()=>j});n(6540);var a=n(4164),s=n(5500),i=n(7559),r=n(4096),l=n(8027),o=n(2907),c=n(1312),d=n(9022),m=n(4848);function u(e){const{nextItem:t,prevItem:n}=e;return(0,m.jsxs)("nav",{className:"pagination-nav docusaurus-mt-lg","aria-label":(0,c.T)({id:"theme.blog.post.paginator.navAriaLabel",message:"Blog post page navigation",description:"The ARIA label for the blog posts pagination"}),children:[n&&(0,m.jsx)(d.A,{...n,subLabel:(0,m.jsx)(c.A,{id:"theme.blog.post.paginator.newerPost",description:"The blog post button label to navigate to the newer/previous post",children:"Newer post"})}),t&&(0,m.jsx)(d.A,{...t,subLabel:(0,m.jsx)(c.A,{id:"theme.blog.post.paginator.olderPost",description:"The blog post button label to navigate to the older/next post",children:"Older post"}),isNext:!0})]})}function g(){const{assets:e,metadata:t}=(0,r.e7)(),{title:n,description:a,date:i,tags:l,authors:o,frontMatter:c}=t,{keywords:d}=c,u=e.image??c.image;return(0,m.jsxs)(s.be,{title:c.title_meta??n,description:a,keywords:d,image:u,children:[(0,m.jsx)("meta",{property:"og:type",content:"article"}),(0,m.jsx)("meta",{property:"article:published_time",content:i}),o.some(e=>e.url)&&(0,m.jsx)("meta",{property:"article:author",content:o.map(e=>e.url).filter(Boolean).join(",")}),l.length>0&&(0,m.jsx)("meta",{property:"article:tag",content:l.map(e=>e.label).join(",")})]})}var h=n(5260);function x(){const e=(0,r.J_)();return(0,m.jsx)(h.A,{children:(0,m.jsx)("script",{type:"application/ld+json",children:JSON.stringify(e)})})}var f=n(7763),p=n(1689);function v({sidebar:e,children:t}){const{metadata:n,toc:a}=(0,r.e7)(),{nextItem:s,prevItem:i,frontMatter:c}=n,{hide_table_of_contents:d,toc_min_heading_level:g,toc_max_heading_level:h}=c;return(0,m.jsxs)(l.A,{sidebar:e,toc:!d&&a.length>0?(0,m.jsx)(f.A,{toc:a,minHeadingLevel:g,maxHeadingLevel:h}):void 0,children:[(0,m.jsx)(p.A,{metadata:n}),(0,m.jsx)(o.A,{children:t}),(s||i)&&(0,m.jsx)(u,{nextItem:s,prevItem:i})]})}function j(e){const t=e.content;return(0,m.jsx)(r.in,{content:e.content,isBlogPostPage:!0,children:(0,m.jsxs)(s.e3,{className:(0,a.A)(i.G.wrapper.blogPages,i.G.page.blogPostPage),children:[(0,m.jsx)(g,{}),(0,m.jsx)(x,{}),(0,m.jsx)(v,{sidebar:e.sidebar,children:(0,m.jsx)(t,{})})]})})}},4084:(e,t,n)=>{n.d(t,{AE:()=>o,Rc:()=>r,TT:()=>d,Uh:()=>l,Yh:()=>c});n(6540);var a=n(1312),s=n(5260),i=n(4848);function r(){return(0,i.jsx)(a.A,{id:"theme.contentVisibility.unlistedBanner.title",description:"The unlisted content banner title",children:"Unlisted page"})}function l(){return(0,i.jsx)(a.A,{id:"theme.contentVisibility.unlistedBanner.message",description:"The unlisted content banner message",children:"This page is unlisted. Search engines will not index it, and only users having a direct link can access it."})}function o(){return(0,i.jsx)(s.A,{children:(0,i.jsx)("meta",{name:"robots",content:"noindex, nofollow"})})}function c(){return(0,i.jsx)(a.A,{id:"theme.contentVisibility.draftBanner.title",description:"The draft content banner title",children:"Draft page"})}function d(){return(0,i.jsx)(a.A,{id:"theme.contentVisibility.draftBanner.message",description:"The draft content banner message",children:"This page is a draft. It will only be visible in dev and be excluded from the production build."})}},4434:(e,t,n)=>{n.d(t,{A:()=>o});n(6540);var a=n(4164),s=n(1312),i=n(6133);const r={tags:"tags_jXut",tag:"tag_QGVx"};var l=n(4848);function o({tags:e}){return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)("b",{children:(0,l.jsx)(s.A,{id:"theme.tags.tagsListLabel",description:"The label alongside a tag list",children:"Tags:"})}),(0,l.jsx)("ul",{className:(0,a.A)(r.tags,"padding--none","margin-left--sm"),children:e.map(e=>(0,l.jsx)("li",{className:r.tag,children:(0,l.jsx)(i.A,{...e})},e.permalink))})]})}},5195:(e,t,n)=>{n.d(t,{A:()=>x});var a=n(6540),s=n(6342);function i(e){const t=e.map(e=>({...e,parentIndex:-1,children:[]})),n=Array(7).fill(-1);t.forEach((e,t)=>{const a=n.slice(2,e.level);e.parentIndex=Math.max(...a),n[e.level]=t});const a=[];return t.forEach(e=>{const{parentIndex:n,...s}=e;n>=0?t[n].children.push(s):a.push(s)}),a}function r({toc:e,minHeadingLevel:t,maxHeadingLevel:n}){return e.flatMap(e=>{const a=r({toc:e.children,minHeadingLevel:t,maxHeadingLevel:n});return function(e){return e.level>=t&&e.level<=n}(e)?[{...e,children:a}]:a})}function l(e){const t=e.getBoundingClientRect();return t.top===t.bottom?l(e.parentNode):t}function o(e,{anchorTopOffset:t}){const n=e.find(e=>l(e).top>=t);if(n){return function(e){return e.top>0&&e.bottom<window.innerHeight/2}(l(n))?n:e[e.indexOf(n)-1]??null}return e[e.length-1]??null}function c(){const e=(0,a.useRef)(0),{navbar:{hideOnScroll:t}}=(0,s.p)();return(0,a.useEffect)(()=>{e.current=t?0:document.querySelector(".navbar").clientHeight},[t]),e}function d(e){const t=(0,a.useRef)(void 0),n=c();(0,a.useEffect)(()=>{if(!e)return()=>{};const{linkClassName:a,linkActiveClassName:s,minHeadingLevel:i,maxHeadingLevel:r}=e;function l(){const e=function(e){return Array.from(document.getElementsByClassName(e))}(a),l=function({minHeadingLevel:e,maxHeadingLevel:t}){const n=[];for(let a=e;a<=t;a+=1)n.push(`h${a}.anchor`);return Array.from(document.querySelectorAll(n.join()))}({minHeadingLevel:i,maxHeadingLevel:r}),c=o(l,{anchorTopOffset:n.current}),d=e.find(e=>c&&c.id===function(e){return decodeURIComponent(e.href.substring(e.href.indexOf("#")+1))}(e));e.forEach(e=>{!function(e,n){n?(t.current&&t.current!==e&&t.current.classList.remove(s),e.classList.add(s),t.current=e):e.classList.remove(s)}(e,e===d)})}return document.addEventListener("scroll",l),document.addEventListener("resize",l),l(),()=>{document.removeEventListener("scroll",l),document.removeEventListener("resize",l)}},[e,n])}var m=n(8774),u=n(4848);function g({toc:e,className:t,linkClassName:n,isChild:a}){return e.length?(0,u.jsx)("ul",{className:a?void 0:t,children:e.map(e=>(0,u.jsxs)("li",{children:[(0,u.jsx)(m.A,{to:`#${e.id}`,className:n??void 0,dangerouslySetInnerHTML:{__html:e.value}}),(0,u.jsx)(g,{isChild:!0,toc:e.children,className:t,linkClassName:n})]},e.id))}):null}const h=a.memo(g);function x({toc:e,className:t="table-of-contents table-of-contents__left-border",linkClassName:n="table-of-contents__link",linkActiveClassName:l,minHeadingLevel:o,maxHeadingLevel:c,...m}){const g=(0,s.p)(),x=o??g.tableOfContents.minHeadingLevel,f=c??g.tableOfContents.maxHeadingLevel,p=function({toc:e,minHeadingLevel:t,maxHeadingLevel:n}){return(0,a.useMemo)(()=>r({toc:i(e),minHeadingLevel:t,maxHeadingLevel:n}),[e,t,n])}({toc:e,minHeadingLevel:x,maxHeadingLevel:f});return d((0,a.useMemo)(()=>{if(n&&l)return{linkClassName:n,linkActiveClassName:l,minHeadingLevel:x,maxHeadingLevel:f}},[n,l,x,f])),(0,u.jsx)(h,{toc:p,className:t,linkClassName:n,...m})}},6133:(e,t,n)=>{n.d(t,{A:()=>l});n(6540);var a=n(4164),s=n(8774);const i={tag:"tag_zVej",tagRegular:"tagRegular_sFm0",tagWithCount:"tagWithCount_h2kH"};var r=n(4848);function l({permalink:e,label:t,count:n,description:l}){return(0,r.jsxs)(s.A,{rel:"tag",href:e,title:l,className:(0,a.A)(i.tag,n?i.tagWithCount:i.tagRegular),children:[t,n&&(0,r.jsx)("span",{children:n})]})}},7763:(e,t,n)=>{n.d(t,{A:()=>c});n(6540);var a=n(4164),s=n(5195);const i={tableOfContents:"tableOfContents_bqdL",docItemContainer:"docItemContainer_F8PC"};var r=n(4848);const l="table-of-contents__link toc-highlight",o="table-of-contents__link--active";function c({className:e,...t}){return(0,r.jsx)("div",{className:(0,a.A)(i.tableOfContents,"thin-scrollbar",e),children:(0,r.jsx)(s.A,{...t,linkClassName:l,linkActiveClassName:o})})}},9022:(e,t,n)=>{n.d(t,{A:()=>r});n(6540);var a=n(4164),s=n(8774),i=n(4848);function r(e){const{permalink:t,title:n,subLabel:r,isNext:l}=e;return(0,i.jsxs)(s.A,{className:(0,a.A)("pagination-nav__link",l?"pagination-nav__link--next":"pagination-nav__link--prev"),to:t,children:[r&&(0,i.jsx)("div",{className:"pagination-nav__sublabel",children:r}),(0,i.jsx)("div",{className:"pagination-nav__label",children:n})]})}}}]); \ No newline at end of file diff --git a/docs/assets/js/ccc49370.8f9e0351.js b/docs/assets/js/ccc49370.8f9e0351.js deleted file mode 100644 index 7648bd8a..00000000 --- a/docs/assets/js/ccc49370.8f9e0351.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[3249],{1689:(e,t,n)=>{n.d(t,{A:()=>d});n(6540);var a=n(4164),s=n(4084),i=n(7559),r=n(7293),l=n(4848);function o({className:e}){return(0,l.jsx)(r.A,{type:"caution",title:(0,l.jsx)(s.Yh,{}),className:(0,a.A)(e,i.G.common.draftBanner),children:(0,l.jsx)(s.TT,{})})}var c=n(2234);function d({metadata:e}){const{unlisted:t,frontMatter:n}=e;return(0,l.jsxs)(l.Fragment,{children:[(t||n.unlisted)&&(0,l.jsx)(c.A,{}),n.draft&&(0,l.jsx)(o,{})]})}},2053:(e,t,n)=>{n.d(t,{A:()=>o});n(6540);var a=n(4164),s=n(1312),i=n(6133);const r={tags:"tags_jXut",tag:"tag_QGVx"};var l=n(4848);function o({tags:e}){return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)("b",{children:(0,l.jsx)(s.A,{id:"theme.tags.tagsListLabel",description:"The label alongside a tag list",children:"Tags:"})}),(0,l.jsx)("ul",{className:(0,a.A)(r.tags,"padding--none","margin-left--sm"),children:e.map((e=>(0,l.jsx)("li",{className:r.tag,children:(0,l.jsx)(i.A,{...e})},e.permalink)))})]})}},2234:(e,t,n)=>{n.d(t,{A:()=>c});n(6540);var a=n(4164),s=n(4084),i=n(7559),r=n(7293),l=n(4848);function o({className:e}){return(0,l.jsx)(r.A,{type:"caution",title:(0,l.jsx)(s.Rc,{}),className:(0,a.A)(e,i.G.common.unlistedBanner),children:(0,l.jsx)(s.Uh,{})})}function c(e){return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(s.AE,{}),(0,l.jsx)(o,{...e})]})}},2907:(e,t,n)=>{n.d(t,{A:()=>O});n(6540);var a=n(4164),s=n(4096),i=n(4848);function r({children:e,className:t}){return(0,i.jsx)("article",{className:t,children:e})}var l=n(8774);const o={title:"title_f1Hy"};function c({className:e}){const{metadata:t,isBlogPostPage:n}=(0,s.e7)(),{permalink:r,title:c}=t,d=n?"h1":"h2";return(0,i.jsx)(d,{className:(0,a.A)(o.title,e),children:n?c:(0,i.jsx)(l.A,{to:r,children:c})})}var d=n(1312),m=n(5846),u=n(6266);const g={container:"container_mt6G"};function h({readingTime:e}){const t=function(){const{selectMessage:e}=(0,m.W)();return t=>{const n=Math.ceil(t);return e(n,(0,d.T)({id:"theme.blog.post.readingTime.plurals",description:'Pluralized label for "{readingTime} min read". Use as much plural forms (separated by "|") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)',message:"One min read|{readingTime} min read"},{readingTime:n}))}}();return(0,i.jsx)(i.Fragment,{children:t(e)})}function x({date:e,formattedDate:t}){return(0,i.jsx)("time",{dateTime:e,children:t})}function f(){return(0,i.jsx)(i.Fragment,{children:" \xb7 "})}function p({className:e}){const{metadata:t}=(0,s.e7)(),{date:n,readingTime:r}=t,l=(0,u.i)({day:"numeric",month:"long",year:"numeric",timeZone:"UTC"});return(0,i.jsxs)("div",{className:(0,a.A)(g.container,"margin-vert--md",e),children:[(0,i.jsx)(x,{date:n,formattedDate:(o=n,l.format(new Date(o)))}),void 0!==r&&(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(f,{}),(0,i.jsx)(h,{readingTime:r})]})]});var o}var v=n(6382);const j={authorCol:"authorCol_Hf19",imageOnlyAuthorRow:"imageOnlyAuthorRow_pa_O",imageOnlyAuthorCol:"imageOnlyAuthorCol_G86a"};function b({className:e}){const{metadata:{authors:t},assets:n}=(0,s.e7)();if(0===t.length)return null;const r=t.every((({name:e})=>!e)),l=1===t.length;return(0,i.jsx)("div",{className:(0,a.A)("margin-top--md margin-bottom--sm",r?j.imageOnlyAuthorRow:"row",e),children:t.map(((e,t)=>(0,i.jsx)("div",{className:(0,a.A)(!r&&(l?"col col--12":"col col--6"),r?j.imageOnlyAuthorCol:j.authorCol),children:(0,i.jsx)(v.A,{author:{...e,imageURL:n.authorsImageUrls[t]??e.imageURL}})},t)))})}function A(){return(0,i.jsxs)("header",{children:[(0,i.jsx)(c,{}),(0,i.jsx)(p,{}),(0,i.jsx)(b,{})]})}var N=n(440),_=n(3253);function L({children:e,className:t}){const{isBlogPostPage:n}=(0,s.e7)();return(0,i.jsx)("div",{id:n?N.LU:void 0,className:(0,a.A)("markdown",t),children:(0,i.jsx)(_.A,{children:e})})}var y=n(7559),C=n(4336),T=n(2053);function k(){return(0,i.jsx)("b",{children:(0,i.jsx)(d.A,{id:"theme.blog.post.readMore",description:"The label used in blog post item excerpts to link to full blog posts",children:"Read more"})})}function H(e){const{blogPostTitle:t,...n}=e;return(0,i.jsx)(l.A,{"aria-label":(0,d.T)({message:"Read more about {title}",id:"theme.blog.post.readMoreLabel",description:"The ARIA label for the link to full blog posts from excerpts"},{title:t}),...n,children:(0,i.jsx)(k,{})})}function w(){const{metadata:e,isBlogPostPage:t}=(0,s.e7)(),{tags:n,title:r,editUrl:l,hasTruncateMarker:o,lastUpdatedBy:c,lastUpdatedAt:d}=e,m=!t&&o,u=n.length>0;if(!(u||m||l))return null;if(t){const e=!!(l||d||c);return(0,i.jsxs)("footer",{className:"docusaurus-mt-lg",children:[u&&(0,i.jsx)("div",{className:(0,a.A)("row","margin-top--sm",y.G.blog.blogFooterEditMetaRow),children:(0,i.jsx)("div",{className:"col",children:(0,i.jsx)(T.A,{tags:n})})}),e&&(0,i.jsx)(C.A,{className:(0,a.A)("margin-top--sm",y.G.blog.blogFooterEditMetaRow),editUrl:l,lastUpdatedAt:d,lastUpdatedBy:c})]})}return(0,i.jsxs)("footer",{className:"row docusaurus-mt-lg",children:[u&&(0,i.jsx)("div",{className:(0,a.A)("col",{"col--9":m}),children:(0,i.jsx)(T.A,{tags:n})}),m&&(0,i.jsx)("div",{className:(0,a.A)("col text--right",{"col--3":u}),children:(0,i.jsx)(H,{blogPostTitle:r,to:e.permalink})})]})}function O({children:e,className:t}){const n=function(){const{isBlogPostPage:e}=(0,s.e7)();return e?void 0:"margin-bottom--xl"}();return(0,i.jsxs)(r,{className:(0,a.A)(n,t),children:[(0,i.jsx)(A,{}),(0,i.jsx)(L,{children:e}),(0,i.jsx)(w,{})]})}},3858:(e,t,n)=>{n.r(t),n.d(t,{default:()=>j});n(6540);var a=n(4164),s=n(5500),i=n(7559),r=n(4096),l=n(8027),o=n(2907),c=n(1312),d=n(9022),m=n(4848);function u(e){const{nextItem:t,prevItem:n}=e;return(0,m.jsxs)("nav",{className:"pagination-nav docusaurus-mt-lg","aria-label":(0,c.T)({id:"theme.blog.post.paginator.navAriaLabel",message:"Blog post page navigation",description:"The ARIA label for the blog posts pagination"}),children:[n&&(0,m.jsx)(d.A,{...n,subLabel:(0,m.jsx)(c.A,{id:"theme.blog.post.paginator.newerPost",description:"The blog post button label to navigate to the newer/previous post",children:"Newer post"})}),t&&(0,m.jsx)(d.A,{...t,subLabel:(0,m.jsx)(c.A,{id:"theme.blog.post.paginator.olderPost",description:"The blog post button label to navigate to the older/next post",children:"Older post"}),isNext:!0})]})}function g(){const{assets:e,metadata:t}=(0,r.e7)(),{title:n,description:a,date:i,tags:l,authors:o,frontMatter:c}=t,{keywords:d}=c,u=e.image??c.image;return(0,m.jsxs)(s.be,{title:c.title_meta??n,description:a,keywords:d,image:u,children:[(0,m.jsx)("meta",{property:"og:type",content:"article"}),(0,m.jsx)("meta",{property:"article:published_time",content:i}),o.some((e=>e.url))&&(0,m.jsx)("meta",{property:"article:author",content:o.map((e=>e.url)).filter(Boolean).join(",")}),l.length>0&&(0,m.jsx)("meta",{property:"article:tag",content:l.map((e=>e.label)).join(",")})]})}var h=n(5260);function x(){const e=(0,r.J_)();return(0,m.jsx)(h.A,{children:(0,m.jsx)("script",{type:"application/ld+json",children:JSON.stringify(e)})})}var f=n(7763),p=n(1689);function v({sidebar:e,children:t}){const{metadata:n,toc:a}=(0,r.e7)(),{nextItem:s,prevItem:i,frontMatter:c}=n,{hide_table_of_contents:d,toc_min_heading_level:g,toc_max_heading_level:h}=c;return(0,m.jsxs)(l.A,{sidebar:e,toc:!d&&a.length>0?(0,m.jsx)(f.A,{toc:a,minHeadingLevel:g,maxHeadingLevel:h}):void 0,children:[(0,m.jsx)(p.A,{metadata:n}),(0,m.jsx)(o.A,{children:t}),(s||i)&&(0,m.jsx)(u,{nextItem:s,prevItem:i})]})}function j(e){const t=e.content;return(0,m.jsx)(r.in,{content:e.content,isBlogPostPage:!0,children:(0,m.jsxs)(s.e3,{className:(0,a.A)(i.G.wrapper.blogPages,i.G.page.blogPostPage),children:[(0,m.jsx)(g,{}),(0,m.jsx)(x,{}),(0,m.jsx)(v,{sidebar:e.sidebar,children:(0,m.jsx)(t,{})})]})})}},4084:(e,t,n)=>{n.d(t,{AE:()=>o,Rc:()=>r,TT:()=>d,Uh:()=>l,Yh:()=>c});n(6540);var a=n(1312),s=n(5260),i=n(4848);function r(){return(0,i.jsx)(a.A,{id:"theme.contentVisibility.unlistedBanner.title",description:"The unlisted content banner title",children:"Unlisted page"})}function l(){return(0,i.jsx)(a.A,{id:"theme.contentVisibility.unlistedBanner.message",description:"The unlisted content banner message",children:"This page is unlisted. Search engines will not index it, and only users having a direct link can access it."})}function o(){return(0,i.jsx)(s.A,{children:(0,i.jsx)("meta",{name:"robots",content:"noindex, nofollow"})})}function c(){return(0,i.jsx)(a.A,{id:"theme.contentVisibility.draftBanner.title",description:"The draft content banner title",children:"Draft page"})}function d(){return(0,i.jsx)(a.A,{id:"theme.contentVisibility.draftBanner.message",description:"The draft content banner message",children:"This page is a draft. It will only be visible in dev and be excluded from the production build."})}},5195:(e,t,n)=>{n.d(t,{A:()=>x});var a=n(6540),s=n(6342);function i(e){const t=e.map((e=>({...e,parentIndex:-1,children:[]}))),n=Array(7).fill(-1);t.forEach(((e,t)=>{const a=n.slice(2,e.level);e.parentIndex=Math.max(...a),n[e.level]=t}));const a=[];return t.forEach((e=>{const{parentIndex:n,...s}=e;n>=0?t[n].children.push(s):a.push(s)})),a}function r({toc:e,minHeadingLevel:t,maxHeadingLevel:n}){return e.flatMap((e=>{const a=r({toc:e.children,minHeadingLevel:t,maxHeadingLevel:n});return function(e){return e.level>=t&&e.level<=n}(e)?[{...e,children:a}]:a}))}function l(e){const t=e.getBoundingClientRect();return t.top===t.bottom?l(e.parentNode):t}function o(e,{anchorTopOffset:t}){const n=e.find((e=>l(e).top>=t));if(n){return function(e){return e.top>0&&e.bottom<window.innerHeight/2}(l(n))?n:e[e.indexOf(n)-1]??null}return e[e.length-1]??null}function c(){const e=(0,a.useRef)(0),{navbar:{hideOnScroll:t}}=(0,s.p)();return(0,a.useEffect)((()=>{e.current=t?0:document.querySelector(".navbar").clientHeight}),[t]),e}function d(e){const t=(0,a.useRef)(void 0),n=c();(0,a.useEffect)((()=>{if(!e)return()=>{};const{linkClassName:a,linkActiveClassName:s,minHeadingLevel:i,maxHeadingLevel:r}=e;function l(){const e=function(e){return Array.from(document.getElementsByClassName(e))}(a),l=function({minHeadingLevel:e,maxHeadingLevel:t}){const n=[];for(let a=e;a<=t;a+=1)n.push(`h${a}.anchor`);return Array.from(document.querySelectorAll(n.join()))}({minHeadingLevel:i,maxHeadingLevel:r}),c=o(l,{anchorTopOffset:n.current}),d=e.find((e=>c&&c.id===function(e){return decodeURIComponent(e.href.substring(e.href.indexOf("#")+1))}(e)));e.forEach((e=>{!function(e,n){n?(t.current&&t.current!==e&&t.current.classList.remove(s),e.classList.add(s),t.current=e):e.classList.remove(s)}(e,e===d)}))}return document.addEventListener("scroll",l),document.addEventListener("resize",l),l(),()=>{document.removeEventListener("scroll",l),document.removeEventListener("resize",l)}}),[e,n])}var m=n(8774),u=n(4848);function g({toc:e,className:t,linkClassName:n,isChild:a}){return e.length?(0,u.jsx)("ul",{className:a?void 0:t,children:e.map((e=>(0,u.jsxs)("li",{children:[(0,u.jsx)(m.A,{to:`#${e.id}`,className:n??void 0,dangerouslySetInnerHTML:{__html:e.value}}),(0,u.jsx)(g,{isChild:!0,toc:e.children,className:t,linkClassName:n})]},e.id)))}):null}const h=a.memo(g);function x({toc:e,className:t="table-of-contents table-of-contents__left-border",linkClassName:n="table-of-contents__link",linkActiveClassName:l,minHeadingLevel:o,maxHeadingLevel:c,...m}){const g=(0,s.p)(),x=o??g.tableOfContents.minHeadingLevel,f=c??g.tableOfContents.maxHeadingLevel,p=function({toc:e,minHeadingLevel:t,maxHeadingLevel:n}){return(0,a.useMemo)((()=>r({toc:i(e),minHeadingLevel:t,maxHeadingLevel:n})),[e,t,n])}({toc:e,minHeadingLevel:x,maxHeadingLevel:f});return d((0,a.useMemo)((()=>{if(n&&l)return{linkClassName:n,linkActiveClassName:l,minHeadingLevel:x,maxHeadingLevel:f}}),[n,l,x,f])),(0,u.jsx)(h,{toc:p,className:t,linkClassName:n,...m})}},6133:(e,t,n)=>{n.d(t,{A:()=>l});n(6540);var a=n(4164),s=n(8774);const i={tag:"tag_zVej",tagRegular:"tagRegular_sFm0",tagWithCount:"tagWithCount_h2kH"};var r=n(4848);function l({permalink:e,label:t,count:n,description:l}){return(0,r.jsxs)(s.A,{rel:"tag",href:e,title:l,className:(0,a.A)(i.tag,n?i.tagWithCount:i.tagRegular),children:[t,n&&(0,r.jsx)("span",{children:n})]})}},7763:(e,t,n)=>{n.d(t,{A:()=>c});n(6540);var a=n(4164),s=n(5195);const i={tableOfContents:"tableOfContents_bqdL",docItemContainer:"docItemContainer_F8PC"};var r=n(4848);const l="table-of-contents__link toc-highlight",o="table-of-contents__link--active";function c({className:e,...t}){return(0,r.jsx)("div",{className:(0,a.A)(i.tableOfContents,"thin-scrollbar",e),children:(0,r.jsx)(s.A,{...t,linkClassName:l,linkActiveClassName:o})})}},9022:(e,t,n)=>{n.d(t,{A:()=>r});n(6540);var a=n(4164),s=n(8774),i=n(4848);function r(e){const{permalink:t,title:n,subLabel:r,isNext:l}=e;return(0,i.jsxs)(s.A,{className:(0,a.A)("pagination-nav__link",l?"pagination-nav__link--next":"pagination-nav__link--prev"),to:t,children:[r&&(0,i.jsx)("div",{className:"pagination-nav__sublabel",children:r}),(0,i.jsx)("div",{className:"pagination-nav__label",children:n})]})}}}]); \ No newline at end of file diff --git a/docs/assets/js/d01bc907.3a0113c2.js b/docs/assets/js/d01bc907.3a0113c2.js new file mode 100644 index 00000000..d8833b48 --- /dev/null +++ b/docs/assets/js/d01bc907.3a0113c2.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[8593],{4795:(e,t,r)=>{r.d(t,{A:()=>N});r(6540);var n=r(4164),s=r(6972),o=r(8774),a=r(5846),c=r(6654),i=r(1312),l=r(1107);const d={cardContainer:"cardContainer_fWXF",cardTitle:"cardTitle_rnsV",cardDescription:"cardDescription_PWke"};var u=r(4848);function m({className:e,href:t,children:r}){return(0,u.jsx)(o.A,{href:t,className:(0,n.A)("card padding--lg",d.cardContainer,e),children:r})}function p({className:e,href:t,icon:r,title:s,description:o}){return(0,u.jsxs)(m,{href:t,className:e,children:[(0,u.jsxs)(l.A,{as:"h2",className:(0,n.A)("text--truncate",d.cardTitle),title:s,children:[r," ",s]}),o&&(0,u.jsx)("p",{className:(0,n.A)("text--truncate",d.cardDescription),title:o,children:o})]})}function h({item:e}){const t=(0,s.Nr)(e),r=function(){const{selectMessage:e}=(0,a.W)();return t=>e(t,(0,i.T)({message:"1 item|{count} items",id:"theme.docs.DocCard.categoryDescription.plurals",description:"The default description for a category card in the generated index about how many items this category includes"},{count:t}))}();return t?(0,u.jsx)(p,{className:e.className,href:t,icon:"\ud83d\uddc3\ufe0f",title:e.label,description:e.description??r(e.items.length)}):null}function f({item:e}){const t=(0,c.A)(e.href)?"\ud83d\udcc4\ufe0f":"\ud83d\udd17",r=(0,s.cC)(e.docId??void 0);return(0,u.jsx)(p,{className:e.className,href:e.href,icon:t,title:e.label,description:e.description??r?.description})}function x({item:e}){switch(e.type){case"link":return(0,u.jsx)(f,{item:e});case"category":return(0,u.jsx)(h,{item:e});default:throw new Error(`unknown item type ${JSON.stringify(e)}`)}}const v={docCardListItem:"docCardListItem_W1sv"};function g({className:e}){const t=(0,s.a4)();return(0,u.jsx)(N,{items:t,className:e})}function j({item:e}){return(0,u.jsx)("article",{className:(0,n.A)(v.docCardListItem,"col col--6"),children:(0,u.jsx)(x,{item:e})})}function N(e){const{items:t,className:r}=e;if(!t)return(0,u.jsx)(g,{...e});const o=(0,s.d1)(t);return(0,u.jsx)("section",{className:(0,n.A)("row",r),children:o.map((e,t)=>(0,u.jsx)(j,{item:e},t))})}},5846:(e,t,r)=>{r.d(t,{W:()=>l});var n=r(6540),s=r(4586);const o=["zero","one","two","few","many","other"];function a(e){return o.filter(t=>e.includes(t))}const c={locale:"en",pluralForms:a(["one","other"]),select:e=>1===e?"one":"other"};function i(){const{i18n:{currentLocale:e}}=(0,s.A)();return(0,n.useMemo)(()=>{try{return function(e){const t=new Intl.PluralRules(e);return{locale:e,pluralForms:a(t.resolvedOptions().pluralCategories),select:e=>t.select(e)}}(e)}catch(t){return console.error(`Failed to use Intl.PluralRules for locale "${e}".\nDocusaurus will fallback to the default (English) implementation.\nError: ${t.message}\n`),c}},[e])}function l(){const e=i();return{selectMessage:(t,r)=>function(e,t,r){const n=e.split("|");if(1===n.length)return n[0];n.length>r.pluralForms.length&&console.error(`For locale=${r.locale}, a maximum of ${r.pluralForms.length} plural forms are expected (${r.pluralForms.join(",")}), but the message contains ${n.length}: ${e}`);const s=r.select(t),o=r.pluralForms.indexOf(s);return n[Math.min(o,n.length-1)]}(r,t,e)}}},7383:(e,t,r)=>{r.r(t),r.d(t,{assets:()=>l,contentTitle:()=>i,default:()=>m,frontMatter:()=>c,metadata:()=>n,toc:()=>d});const n=JSON.parse('{"id":"predator/v1.0.0/index","title":"v1.0.0","description":"Predator v1.0.0","source":"@site/docs/predator/v1.0.0/index.md","sourceDirName":"predator/v1.0.0","slug":"/predator/v1.0.0","permalink":"/BharatMLStack/predator/v1.0.0","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/predator/v1.0.0/index.md","tags":[],"version":"current","sidebarPosition":0,"frontMatter":{"title":"v1.0.0","description":"Predator v1.0.0","sidebar_position":0,"slug":"/predator/v1.0.0"},"sidebar":"tutorialSidebar","previous":{"title":"Predator","permalink":"/BharatMLStack/category/predator"},"next":{"title":"Architecture","permalink":"/BharatMLStack/predator/v1.0.0/architecture"}}');var s=r(4848),o=r(8453),a=r(4795);const c={title:"v1.0.0",description:"Predator v1.0.0",sidebar_position:0,slug:"/predator/v1.0.0"},i="Predator v1.0.0",l={},d=[];function u(e){const t={h1:"h1",header:"header",p:"p",...(0,o.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(t.header,{children:(0,s.jsx)(t.h1,{id:"predator-v100",children:"Predator v1.0.0"})}),"\n",(0,s.jsx)(t.p,{children:"Predator is a scalable, high-performance model inference service built as a wrapper around NVIDIA Triton Inference Server, designed to serve ML models with low latency in Kubernetes."}),"\n",(0,s.jsx)(a.A,{})]})}function m(e={}){const{wrapper:t}={...(0,o.R)(),...e.components};return t?(0,s.jsx)(t,{...e,children:(0,s.jsx)(u,{...e})}):u(e)}},8453:(e,t,r)=>{r.d(t,{R:()=>a,x:()=>c});var n=r(6540);const s={},o=n.createContext(s);function a(e){const t=n.useContext(o);return n.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function c(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:a(e.components),n.createElement(o.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/d152284c.0800e671.js b/docs/assets/js/d152284c.0800e671.js new file mode 100644 index 00000000..ff655ac8 --- /dev/null +++ b/docs/assets/js/d152284c.0800e671.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[1606],{5876:(e,n,r)=>{r.r(n),r.d(n,{assets:()=>a,contentTitle:()=>o,default:()=>h,frontMatter:()=>l,metadata:()=>s,toc:()=>c});const s=JSON.parse('{"id":"online-feature-store/v1.0.0/release-notes","title":"Release Notes","description":"Version 1.0.0 \ud83d\ude80","source":"@site/docs/online-feature-store/v1.0.0/release-notes.md","sourceDirName":"online-feature-store/v1.0.0","slug":"/online-feature-store/v1.0.0/release-notes","permalink":"/BharatMLStack/online-feature-store/v1.0.0/release-notes","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/online-feature-store/v1.0.0/release-notes.md","tags":[],"version":"current","sidebarPosition":5,"frontMatter":{"title":"Release Notes","sidebar_position":5},"sidebar":"tutorialSidebar","previous":{"title":"Key Functionalities","permalink":"/BharatMLStack/online-feature-store/v1.0.0/functionalities"},"next":{"title":"Inferflow","permalink":"/BharatMLStack/category/inferflow"}}');var i=r(4848),t=r(8453);const l={title:"Release Notes",sidebar_position:5},o="Online Feature Store - Release Notes",a={},c=[{value:"Version 1.0.0 \ud83d\ude80",id:"version-100-",level:2},{value:"\ud83c\udfaf <strong>What's New</strong>",id:"-whats-new",level:2},{value:"<strong>Core Feature Store Engine</strong>",id:"core-feature-store-engine",level:3},{value:"<strong>Advanced Data Type Support</strong>",id:"advanced-data-type-support",level:3},{value:"<strong>Multi-Database Architecture</strong>",id:"multi-database-architecture",level:3},{value:"\ud83d\ude80 <strong>Performance & Optimization</strong>",id:"-performance--optimization",level:2},{value:"<strong>PSDB v2 Serialization Format without compression</strong>",id:"psdb-v2-serialization-format-without-compression",level:3},{value:"<strong>Memory Management</strong>",id:"memory-management",level:3},{value:"<strong>Compression Support</strong>",id:"compression-support",level:3},{value:"\ud83d\udee0\ufe0f <strong>APIs & SDKs</strong>",id:"\ufe0f-apis--sdks",level:2},{value:"<strong>gRPC API</strong>",id:"grpc-api",level:3},{value:"<strong>Go SDK v1.0.0</strong>",id:"go-sdk-v100",level:3},{value:"<strong>Python SDK Collection v1.0.0</strong>",id:"python-sdk-collection-v100",level:3},{value:"<strong>RESTful Interface</strong>",id:"restful-interface",level:3},{value:"\ud83d\udd27 <strong>Enterprise Features</strong>",id:"-enterprise-features",level:2},{value:"<strong>Production Readiness</strong>",id:"production-readiness",level:3},{value:"<strong>Monitoring & Observability</strong>",id:"monitoring--observability",level:3},{value:"<strong>Data Management</strong>",id:"data-management",level:3},{value:"\ud83c\udfd7\ufe0f <strong>Deployment & Configuration</strong>",id:"\ufe0f-deployment--configuration",level:2},{value:"<strong>Container Support</strong>",id:"container-support",level:3},{value:"\ud83d\udd04 <strong>Compatibility</strong>",id:"-compatibility",level:2},{value:"<strong>Supported Go Versions</strong>",id:"supported-go-versions",level:3},{value:"<strong>Database Compatibility</strong>",id:"database-compatibility",level:3},{value:"\ud83d\udc1b <strong>Known Issues</strong>",id:"-known-issues",level:2},{value:"<strong>Current Limitations</strong>",id:"current-limitations",level:3},{value:"<strong>Workarounds</strong>",id:"workarounds",level:3},{value:"\ud83d\udcbe <strong>Download & Installation</strong>",id:"-download--installation",level:2},{value:"<strong>Container Images</strong>",id:"container-images",level:3},{value:"<strong>Arch Supported</strong>",id:"arch-supported",level:3},{value:"<strong>Source Code</strong>",id:"source-code",level:3},{value:"Contributing",id:"contributing",level:2},{value:"Community & Support",id:"community--support",level:2},{value:"License",id:"license",level:2}];function d(e){const n={a:"a",br:"br",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,t.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.header,{children:(0,i.jsx)(n.h1,{id:"online-feature-store---release-notes",children:"Online Feature Store - Release Notes"})}),"\n",(0,i.jsx)(n.h2,{id:"version-100-",children:"Version 1.0.0 \ud83d\ude80"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Release Date"}),": June 2025",(0,i.jsx)(n.br,{}),"\n",(0,i.jsx)(n.strong,{children:"Status"}),": General Availability (GA)"]}),"\n",(0,i.jsxs)(n.p,{children:["We're excited to announce the first stable release of the ",(0,i.jsx)(n.strong,{children:"BharatML Online Feature Store"})," - a high-performance, production-ready feature serving system designed for machine learning workloads."]}),"\n",(0,i.jsx)(n.hr,{}),"\n",(0,i.jsxs)(n.h2,{id:"-whats-new",children:["\ud83c\udfaf ",(0,i.jsx)(n.strong,{children:"What's New"})]}),"\n",(0,i.jsx)(n.h3,{id:"core-feature-store-engine",children:(0,i.jsx)(n.strong,{children:"Core Feature Store Engine"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Ultra-Low Latency"}),": Achieve sub-10ms P99 response times for real-time inference"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"High Throughput"}),": Tested and validated at 1M+ requests per second with 100 IDs per request"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Multi-Entity Support"}),": Serve features for multiple entity types (users, transactions, products, etc.)"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Batch Retrieval"}),": Efficient bulk feature fetching for real-time inference and incremental/online training workloads"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"advanced-data-type-support",children:(0,i.jsx)(n.strong,{children:"Advanced Data Type Support"})}),"\n",(0,i.jsx)(n.p,{children:"Complete support for all ML-relevant data types:"}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Data Type"}),(0,i.jsx)(n.th,{children:"Variants"}),(0,i.jsx)(n.th,{children:"Optimizations"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"Integers"})}),(0,i.jsx)(n.td,{children:"int8, int16, int32, int64"}),(0,i.jsx)(n.td,{children:"Varint encoding, bit packing"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"Floats"})}),(0,i.jsx)(n.td,{children:"float8, float16, float32, float64"}),(0,i.jsx)(n.td,{children:"IEEE 754 compliant storage"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"Strings"})}),(0,i.jsx)(n.td,{children:"Variable length"}),(0,i.jsx)(n.td,{children:"Pascal string encoding"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"Booleans"})}),(0,i.jsx)(n.td,{children:"Bit-packed"}),(0,i.jsx)(n.td,{children:"8x memory compression"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"Vectors"})}),(0,i.jsx)(n.td,{children:"All above types"}),(0,i.jsx)(n.td,{children:"Contiguous memory layout"})]})]})]}),"\n",(0,i.jsx)(n.h3,{id:"multi-database-architecture",children:(0,i.jsx)(n.strong,{children:"Multi-Database Architecture"})}),"\n",(0,i.jsx)(n.p,{children:"Flexible backend storage with optimized drivers:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"\ud83d\udd25 Scylla DB"}),": Ultra-high performance NoSQL (recommended for production)"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"\u26a1 Dragonfly"}),": Modern Redis alternative with better memory efficiency"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"\ud83d\udcca Redis"}),": Standard in-memory store for development environments"]}),"\n"]}),"\n",(0,i.jsxs)(n.h2,{id:"-performance--optimization",children:["\ud83d\ude80 ",(0,i.jsx)(n.strong,{children:"Performance & Optimization"})]}),"\n",(0,i.jsx)(n.h3,{id:"psdb-v2-serialization-format-without-compression",children:(0,i.jsx)(n.strong,{children:"PSDB v2 Serialization Format without compression"})}),"\n",(0,i.jsxs)(n.p,{children:["Our proprietary ",(0,i.jsx)(n.strong,{children:"Permanent Storage Data Block"})," format delivers:"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"35% faster"})," serialization than Protocol Buffers"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"100.0-102.2%"})," size efficiency (near raw data size)"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"93% fewer allocations"})," than Apache Arrow (4 vs 66 allocs/op)"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"975 MB/s"})," throughput capacity"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"memory-management",children:(0,i.jsx)(n.strong,{children:"Memory Management"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Object Pooling"}),": Zero-allocation feature retrieval with PSDBPool"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Connection Pooling"}),": Optimized database connection reuse"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Buffer Management"}),": Pre-allocated buffers for serialization operations"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Smart Caching"}),": Configurable TTL-based feature caching"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"compression-support",children:(0,i.jsx)(n.strong,{children:"Compression Support"})}),"\n",(0,i.jsx)(n.p,{children:"Intelligent compression with multiple algorithms:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"ZSTD"}),": Maximum compression for bandwidth-constrained environments"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Auto-Fallback"}),": Intelligent selection based on data characteristics"]}),"\n"]}),"\n",(0,i.jsxs)(n.h2,{id:"\ufe0f-apis--sdks",children:["\ud83d\udee0\ufe0f ",(0,i.jsx)(n.strong,{children:"APIs & SDKs"})]}),"\n",(0,i.jsx)(n.h3,{id:"grpc-api",children:(0,i.jsx)(n.strong,{children:"gRPC API"})}),"\n",(0,i.jsx)(n.p,{children:"High-performance, language-agnostic interface:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-protobuf",children:"service FeatureStoreService {\n rpc RetrieveFeatures(Query) returns (QueryResult);\n rpc RetrieveDecodedFeatures(Query) returns (DecodedQueryResult);\n rpc PersistFeatures(PersistFeaturesRequest) returns (Result);\n}\n"})}),"\n",(0,i.jsx)(n.h3,{id:"go-sdk-v100",children:(0,i.jsx)(n.strong,{children:"Go SDK v1.0.0"})}),"\n",(0,i.jsx)(n.p,{children:"Native Go client with enterprise features:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Type-Safe API"}),": Strongly typed interfaces and data structures"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Connection Management"}),": Configurable timeouts, TLS, and pooling"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Batch Processing"}),": Configurable batch sizes for bulk operations"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Metrics Integration"}),": Built-in timing and count metrics"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Authentication"}),": Caller ID and token-based security"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"python-sdk-collection-v100",children:(0,i.jsx)(n.strong,{children:"Python SDK Collection v1.0.0"})}),"\n",(0,i.jsx)(n.p,{children:"Three specialized Python packages for different ML workflows:"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"bharatml_commons"})," - Common utilities and protobuf definitions:"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"HTTP Client"}),": Feature metadata operations"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Protobuf Support"}),": Generated Python definitions for all APIs"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Utility Functions"}),": Column cleaning and feature processing"]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"spark_feature_push_client"})," - Apache Spark-based data pipeline:"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Batch ETL"}),": Large-scale data processing with Spark"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Kafka Integration"}),": Protobuf serialization and Kafka publishing"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Multi-Source Support"}),": Hive, Delta, Parquet, Cloud Storage"]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"grpc_feature_client"})," - High-performance gRPC client:"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Real-time Operations"}),": Direct persist/retrieve API access"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Low Latency"}),": Optimized for model inference workflows"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Type Safety"}),": Strongly typed Python interfaces"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"restful-interface",children:(0,i.jsx)(n.strong,{children:"RESTful Interface"})}),"\n",(0,i.jsx)(n.p,{children:"HTTP API for web applications:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Health Endpoints"}),": Built-in monitoring and status checks"]}),"\n"]}),"\n",(0,i.jsxs)(n.h2,{id:"-enterprise-features",children:["\ud83d\udd27 ",(0,i.jsx)(n.strong,{children:"Enterprise Features"})]}),"\n",(0,i.jsx)(n.h3,{id:"production-readiness",children:(0,i.jsx)(n.strong,{children:"Production Readiness"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Health Checks"}),": ",(0,i.jsx)(n.code,{children:"/health/self"})," endpoints for probing"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Graceful Shutdown"}),": Clean resource cleanup with configurable timeouts"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Structured Logging"}),": Formatted logs with configurable levels"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Signal Handling"}),": SIGTERM/SIGINT support for container environments"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"monitoring--observability",children:(0,i.jsx)(n.strong,{children:"Monitoring & Observability"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"DataDog Integration"}),": Built-in metrics collection and reporting"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Prometheus Compatibility"}),": Standard metrics format support"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Custom Metrics"}),": Request rates, latencies, error rates, and business metrics"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Distributed Tracing [untested]"}),": Request flow visibility across services"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"data-management",children:(0,i.jsx)(n.strong,{children:"Data Management"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"TTL Support"}),": Automatic feature expiration"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Feature Versioning"}),": Schema evolution with backward compatibility"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Bulk Operations"}),": Efficient batch read/write with configurable sizes"]}),"\n"]}),"\n",(0,i.jsxs)(n.h2,{id:"\ufe0f-deployment--configuration",children:["\ud83c\udfd7\ufe0f ",(0,i.jsx)(n.strong,{children:"Deployment & Configuration"})]}),"\n",(0,i.jsx)(n.h3,{id:"container-support",children:(0,i.jsx)(n.strong,{children:"Container Support"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Docker Images"}),": Multi-architecture support (amd64, arm64)"]}),"\n"]}),"\n",(0,i.jsxs)(n.h2,{id:"-compatibility",children:["\ud83d\udd04 ",(0,i.jsx)(n.strong,{children:"Compatibility"})]}),"\n",(0,i.jsx)(n.h3,{id:"supported-go-versions",children:(0,i.jsx)(n.strong,{children:"Supported Go Versions"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Minimum"}),": Go 1.22.0"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Recommended"}),": Go 1.22.8+"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"database-compatibility",children:(0,i.jsx)(n.strong,{children:"Database Compatibility"})}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Database"}),(0,i.jsx)(n.th,{children:"Version"}),(0,i.jsx)(n.th,{children:"Status"}),(0,i.jsx)(n.th,{children:"Notes"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Scylla DB"}),(0,i.jsx)(n.td,{children:"5.0+"}),(0,i.jsx)(n.td,{children:"\u2705 Recommended"}),(0,i.jsx)(n.td,{children:"Optimal performance"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Dragonfly"}),(0,i.jsx)(n.td,{children:"1.0+"}),(0,i.jsx)(n.td,{children:"\u2705 Supported"}),(0,i.jsx)(n.td,{children:"Memory efficient"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Redis"}),(0,i.jsx)(n.td,{children:"6.0+"}),(0,i.jsx)(n.td,{children:"\u2705 Development"}),(0,i.jsx)(n.td,{children:"Limited scale"})]})]})]}),"\n",(0,i.jsxs)(n.h2,{id:"-known-issues",children:["\ud83d\udc1b ",(0,i.jsx)(n.strong,{children:"Known Issues"})]}),"\n",(0,i.jsx)(n.h3,{id:"current-limitations",children:(0,i.jsx)(n.strong,{children:"Current Limitations"})}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Large Vector Support"}),": Vectors >10MB may experience increased latency"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"workarounds",children:(0,i.jsx)(n.strong,{children:"Workarounds"})}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Vector Chunking"}),": Split large vectors into smaller segments"]}),"\n"]}),"\n",(0,i.jsxs)(n.h2,{id:"-download--installation",children:["\ud83d\udcbe ",(0,i.jsx)(n.strong,{children:"Download & Installation"})]}),"\n",(0,i.jsx)(n.h3,{id:"container-images",children:(0,i.jsx)(n.strong,{children:"Container Images"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"# Pull the latest image\ndocker pull ghcr.io/meesho/onfs-api-server:latest\ndocker pull ghcr.io/meesho/onfs-consumer:latest\ndocker pull ghcr.io/meesho/horizon:latest\ndocker pull ghcr.io/meesho/trufflebox-ui:latest\n\n"})}),"\n",(0,i.jsx)(n.h3,{id:"arch-supported",children:(0,i.jsx)(n.strong,{children:"Arch Supported"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:(0,i.jsx)(n.strong,{children:"Linux (amd64)"})}),"\n",(0,i.jsx)(n.li,{children:(0,i.jsx)(n.strong,{children:"Linux (arm64)"})}),"\n",(0,i.jsx)(n.li,{children:(0,i.jsx)(n.strong,{children:"macOS (Intel)"})}),"\n",(0,i.jsx)(n.li,{children:(0,i.jsx)(n.strong,{children:"macOS (Apple Silicon)"})}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["Checkout ",(0,i.jsx)(n.a,{href:"https://github.com/orgs/Meesho/packages?repo_name=BharatMLStack",children:"Packages"})]}),"\n",(0,i.jsx)(n.h3,{id:"source-code",children:(0,i.jsx)(n.strong,{children:"Source Code"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"git clone https://github.com/Meesho/BharatMLStack.git\ncd BharatMLStack/online-feature-store\ngit checkout release/1.0.0\n"})}),"\n",(0,i.jsx)(n.h2,{id:"contributing",children:"Contributing"}),"\n",(0,i.jsxs)(n.p,{children:["We welcome contributions from the community! Please see our ",(0,i.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTING.md",children:"Contributing Guide"})," for details on how to get started."]}),"\n",(0,i.jsx)(n.h2,{id:"community--support",children:"Community & Support"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["\ud83d\udcac ",(0,i.jsx)(n.strong,{children:"Discord"}),": Join our ",(0,i.jsx)(n.a,{href:"https://discord.gg/XkT7XsV2AU",children:"community chat"})]}),"\n",(0,i.jsxs)(n.li,{children:["\ud83d\udc1b ",(0,i.jsx)(n.strong,{children:"Issues"}),": Report bugs and request features on ",(0,i.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/issues",children:"GitHub Issues"})]}),"\n",(0,i.jsxs)(n.li,{children:["\ud83d\udce7 ",(0,i.jsx)(n.strong,{children:"Email"}),": Contact us at ",(0,i.jsx)(n.a,{href:"mailto:ml-oss@meesho.com",children:"ml-oss@meesho.com"})]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"license",children:"License"}),"\n",(0,i.jsxs)(n.p,{children:["BharatMLStack is open-source software licensed under the ",(0,i.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md",children:"BharatMLStack Business Source License 1.1"}),"."]}),"\n",(0,i.jsx)(n.hr,{}),"\n",(0,i.jsx)("div",{align:"center",children:(0,i.jsx)("strong",{children:"Built with \u2764\ufe0f for the ML community from Meesho"})}),"\n",(0,i.jsx)("div",{align:"center",children:(0,i.jsx)("strong",{children:"If you find this useful, \u2b50\ufe0f the repo \u2014 your support means the world to us!"})})]})}function h(e={}){const{wrapper:n}={...(0,t.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(d,{...e})}):d(e)}},8453:(e,n,r)=>{r.d(n,{R:()=>l,x:()=>o});var s=r(6540);const i={},t=s.createContext(i);function l(e){const n=s.useContext(t);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:l(e.components),s.createElement(t.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/d152284c.8adb699d.js b/docs/assets/js/d152284c.8adb699d.js deleted file mode 100644 index b8f92b00..00000000 --- a/docs/assets/js/d152284c.8adb699d.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[1606],{5876:(e,n,r)=>{r.r(n),r.d(n,{assets:()=>a,contentTitle:()=>o,default:()=>h,frontMatter:()=>l,metadata:()=>s,toc:()=>c});const s=JSON.parse('{"id":"online-feature-store/v1.0.0/release-notes","title":"Release Notes","description":"Version 1.0.0 \ud83d\ude80","source":"@site/docs/online-feature-store/v1.0.0/release-notes.md","sourceDirName":"online-feature-store/v1.0.0","slug":"/online-feature-store/v1.0.0/release-notes","permalink":"/BharatMLStack/online-feature-store/v1.0.0/release-notes","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/online-feature-store/v1.0.0/release-notes.md","tags":[],"version":"current","sidebarPosition":5,"frontMatter":{"title":"Release Notes","sidebar_position":5},"sidebar":"tutorialSidebar","previous":{"title":"Key Functionalities","permalink":"/BharatMLStack/online-feature-store/v1.0.0/functionalities"},"next":{"title":"Quick Start","permalink":"/BharatMLStack/category/quick-start"}}');var i=r(4848),t=r(8453);const l={title:"Release Notes",sidebar_position:5},o="Online Feature Store - Release Notes",a={},c=[{value:"Version 1.0.0 \ud83d\ude80",id:"version-100-",level:2},{value:"\ud83c\udfaf <strong>What's New</strong>",id:"-whats-new",level:2},{value:"<strong>Core Feature Store Engine</strong>",id:"core-feature-store-engine",level:3},{value:"<strong>Advanced Data Type Support</strong>",id:"advanced-data-type-support",level:3},{value:"<strong>Multi-Database Architecture</strong>",id:"multi-database-architecture",level:3},{value:"\ud83d\ude80 <strong>Performance & Optimization</strong>",id:"-performance--optimization",level:2},{value:"<strong>PSDB v2 Serialization Format without compression</strong>",id:"psdb-v2-serialization-format-without-compression",level:3},{value:"<strong>Memory Management</strong>",id:"memory-management",level:3},{value:"<strong>Compression Support</strong>",id:"compression-support",level:3},{value:"\ud83d\udee0\ufe0f <strong>APIs & SDKs</strong>",id:"\ufe0f-apis--sdks",level:2},{value:"<strong>gRPC API</strong>",id:"grpc-api",level:3},{value:"<strong>Go SDK v1.0.0</strong>",id:"go-sdk-v100",level:3},{value:"<strong>Python SDK Collection v1.0.0</strong>",id:"python-sdk-collection-v100",level:3},{value:"<strong>RESTful Interface</strong>",id:"restful-interface",level:3},{value:"\ud83d\udd27 <strong>Enterprise Features</strong>",id:"-enterprise-features",level:2},{value:"<strong>Production Readiness</strong>",id:"production-readiness",level:3},{value:"<strong>Monitoring & Observability</strong>",id:"monitoring--observability",level:3},{value:"<strong>Data Management</strong>",id:"data-management",level:3},{value:"\ud83c\udfd7\ufe0f <strong>Deployment & Configuration</strong>",id:"\ufe0f-deployment--configuration",level:2},{value:"<strong>Container Support</strong>",id:"container-support",level:3},{value:"\ud83d\udd04 <strong>Compatibility</strong>",id:"-compatibility",level:2},{value:"<strong>Supported Go Versions</strong>",id:"supported-go-versions",level:3},{value:"<strong>Database Compatibility</strong>",id:"database-compatibility",level:3},{value:"\ud83d\udc1b <strong>Known Issues</strong>",id:"-known-issues",level:2},{value:"<strong>Current Limitations</strong>",id:"current-limitations",level:3},{value:"<strong>Workarounds</strong>",id:"workarounds",level:3},{value:"\ud83d\udcbe <strong>Download & Installation</strong>",id:"-download--installation",level:2},{value:"<strong>Container Images</strong>",id:"container-images",level:3},{value:"<strong>Arch Supported</strong>",id:"arch-supported",level:3},{value:"<strong>Source Code</strong>",id:"source-code",level:3},{value:"Contributing",id:"contributing",level:2},{value:"Community & Support",id:"community--support",level:2},{value:"License",id:"license",level:2}];function d(e){const n={a:"a",br:"br",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,t.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.header,{children:(0,i.jsx)(n.h1,{id:"online-feature-store---release-notes",children:"Online Feature Store - Release Notes"})}),"\n",(0,i.jsx)(n.h2,{id:"version-100-",children:"Version 1.0.0 \ud83d\ude80"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Release Date"}),": June 2025",(0,i.jsx)(n.br,{}),"\n",(0,i.jsx)(n.strong,{children:"Status"}),": General Availability (GA)"]}),"\n",(0,i.jsxs)(n.p,{children:["We're excited to announce the first stable release of the ",(0,i.jsx)(n.strong,{children:"BharatML Online Feature Store"})," - a high-performance, production-ready feature serving system designed for machine learning workloads."]}),"\n",(0,i.jsx)(n.hr,{}),"\n",(0,i.jsxs)(n.h2,{id:"-whats-new",children:["\ud83c\udfaf ",(0,i.jsx)(n.strong,{children:"What's New"})]}),"\n",(0,i.jsx)(n.h3,{id:"core-feature-store-engine",children:(0,i.jsx)(n.strong,{children:"Core Feature Store Engine"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Ultra-Low Latency"}),": Achieve sub-10ms P99 response times for real-time inference"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"High Throughput"}),": Tested and validated at 1M+ requests per second with 100 IDs per request"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Multi-Entity Support"}),": Serve features for multiple entity types (users, transactions, products, etc.)"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Batch Retrieval"}),": Efficient bulk feature fetching for real-time inference and incremental/online training workloads"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"advanced-data-type-support",children:(0,i.jsx)(n.strong,{children:"Advanced Data Type Support"})}),"\n",(0,i.jsx)(n.p,{children:"Complete support for all ML-relevant data types:"}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Data Type"}),(0,i.jsx)(n.th,{children:"Variants"}),(0,i.jsx)(n.th,{children:"Optimizations"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"Integers"})}),(0,i.jsx)(n.td,{children:"int8, int16, int32, int64"}),(0,i.jsx)(n.td,{children:"Varint encoding, bit packing"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"Floats"})}),(0,i.jsx)(n.td,{children:"float8, float16, float32, float64"}),(0,i.jsx)(n.td,{children:"IEEE 754 compliant storage"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"Strings"})}),(0,i.jsx)(n.td,{children:"Variable length"}),(0,i.jsx)(n.td,{children:"Pascal string encoding"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"Booleans"})}),(0,i.jsx)(n.td,{children:"Bit-packed"}),(0,i.jsx)(n.td,{children:"8x memory compression"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"Vectors"})}),(0,i.jsx)(n.td,{children:"All above types"}),(0,i.jsx)(n.td,{children:"Contiguous memory layout"})]})]})]}),"\n",(0,i.jsx)(n.h3,{id:"multi-database-architecture",children:(0,i.jsx)(n.strong,{children:"Multi-Database Architecture"})}),"\n",(0,i.jsx)(n.p,{children:"Flexible backend storage with optimized drivers:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"\ud83d\udd25 Scylla DB"}),": Ultra-high performance NoSQL (recommended for production)"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"\u26a1 Dragonfly"}),": Modern Redis alternative with better memory efficiency"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"\ud83d\udcca Redis"}),": Standard in-memory store for development environments"]}),"\n"]}),"\n",(0,i.jsxs)(n.h2,{id:"-performance--optimization",children:["\ud83d\ude80 ",(0,i.jsx)(n.strong,{children:"Performance & Optimization"})]}),"\n",(0,i.jsx)(n.h3,{id:"psdb-v2-serialization-format-without-compression",children:(0,i.jsx)(n.strong,{children:"PSDB v2 Serialization Format without compression"})}),"\n",(0,i.jsxs)(n.p,{children:["Our proprietary ",(0,i.jsx)(n.strong,{children:"Permanent Storage Data Block"})," format delivers:"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"35% faster"})," serialization than Protocol Buffers"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"100.0-102.2%"})," size efficiency (near raw data size)"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"93% fewer allocations"})," than Apache Arrow (4 vs 66 allocs/op)"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"975 MB/s"})," throughput capacity"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"memory-management",children:(0,i.jsx)(n.strong,{children:"Memory Management"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Object Pooling"}),": Zero-allocation feature retrieval with PSDBPool"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Connection Pooling"}),": Optimized database connection reuse"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Buffer Management"}),": Pre-allocated buffers for serialization operations"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Smart Caching"}),": Configurable TTL-based feature caching"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"compression-support",children:(0,i.jsx)(n.strong,{children:"Compression Support"})}),"\n",(0,i.jsx)(n.p,{children:"Intelligent compression with multiple algorithms:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"ZSTD"}),": Maximum compression for bandwidth-constrained environments"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Auto-Fallback"}),": Intelligent selection based on data characteristics"]}),"\n"]}),"\n",(0,i.jsxs)(n.h2,{id:"\ufe0f-apis--sdks",children:["\ud83d\udee0\ufe0f ",(0,i.jsx)(n.strong,{children:"APIs & SDKs"})]}),"\n",(0,i.jsx)(n.h3,{id:"grpc-api",children:(0,i.jsx)(n.strong,{children:"gRPC API"})}),"\n",(0,i.jsx)(n.p,{children:"High-performance, language-agnostic interface:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-protobuf",children:"service FeatureStoreService {\n rpc RetrieveFeatures(Query) returns (QueryResult);\n rpc RetrieveDecodedFeatures(Query) returns (DecodedQueryResult);\n rpc PersistFeatures(PersistFeaturesRequest) returns (Result);\n}\n"})}),"\n",(0,i.jsx)(n.h3,{id:"go-sdk-v100",children:(0,i.jsx)(n.strong,{children:"Go SDK v1.0.0"})}),"\n",(0,i.jsx)(n.p,{children:"Native Go client with enterprise features:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Type-Safe API"}),": Strongly typed interfaces and data structures"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Connection Management"}),": Configurable timeouts, TLS, and pooling"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Batch Processing"}),": Configurable batch sizes for bulk operations"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Metrics Integration"}),": Built-in timing and count metrics"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Authentication"}),": Caller ID and token-based security"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"python-sdk-collection-v100",children:(0,i.jsx)(n.strong,{children:"Python SDK Collection v1.0.0"})}),"\n",(0,i.jsx)(n.p,{children:"Three specialized Python packages for different ML workflows:"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"bharatml_commons"})," - Common utilities and protobuf definitions:"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"HTTP Client"}),": Feature metadata operations"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Protobuf Support"}),": Generated Python definitions for all APIs"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Utility Functions"}),": Column cleaning and feature processing"]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"spark_feature_push_client"})," - Apache Spark-based data pipeline:"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Batch ETL"}),": Large-scale data processing with Spark"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Kafka Integration"}),": Protobuf serialization and Kafka publishing"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Multi-Source Support"}),": Hive, Delta, Parquet, Cloud Storage"]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"grpc_feature_client"})," - High-performance gRPC client:"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Real-time Operations"}),": Direct persist/retrieve API access"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Low Latency"}),": Optimized for model inference workflows"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Type Safety"}),": Strongly typed Python interfaces"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"restful-interface",children:(0,i.jsx)(n.strong,{children:"RESTful Interface"})}),"\n",(0,i.jsx)(n.p,{children:"HTTP API for web applications:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Health Endpoints"}),": Built-in monitoring and status checks"]}),"\n"]}),"\n",(0,i.jsxs)(n.h2,{id:"-enterprise-features",children:["\ud83d\udd27 ",(0,i.jsx)(n.strong,{children:"Enterprise Features"})]}),"\n",(0,i.jsx)(n.h3,{id:"production-readiness",children:(0,i.jsx)(n.strong,{children:"Production Readiness"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Health Checks"}),": ",(0,i.jsx)(n.code,{children:"/health/self"})," endpoints for probing"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Graceful Shutdown"}),": Clean resource cleanup with configurable timeouts"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Structured Logging"}),": Formatted logs with configurable levels"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Signal Handling"}),": SIGTERM/SIGINT support for container environments"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"monitoring--observability",children:(0,i.jsx)(n.strong,{children:"Monitoring & Observability"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"DataDog Integration"}),": Built-in metrics collection and reporting"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Prometheus Compatibility"}),": Standard metrics format support"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Custom Metrics"}),": Request rates, latencies, error rates, and business metrics"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Distributed Tracing [untested]"}),": Request flow visibility across services"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"data-management",children:(0,i.jsx)(n.strong,{children:"Data Management"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"TTL Support"}),": Automatic feature expiration"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Feature Versioning"}),": Schema evolution with backward compatibility"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Bulk Operations"}),": Efficient batch read/write with configurable sizes"]}),"\n"]}),"\n",(0,i.jsxs)(n.h2,{id:"\ufe0f-deployment--configuration",children:["\ud83c\udfd7\ufe0f ",(0,i.jsx)(n.strong,{children:"Deployment & Configuration"})]}),"\n",(0,i.jsx)(n.h3,{id:"container-support",children:(0,i.jsx)(n.strong,{children:"Container Support"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Docker Images"}),": Multi-architecture support (amd64, arm64)"]}),"\n"]}),"\n",(0,i.jsxs)(n.h2,{id:"-compatibility",children:["\ud83d\udd04 ",(0,i.jsx)(n.strong,{children:"Compatibility"})]}),"\n",(0,i.jsx)(n.h3,{id:"supported-go-versions",children:(0,i.jsx)(n.strong,{children:"Supported Go Versions"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Minimum"}),": Go 1.22.0"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Recommended"}),": Go 1.22.8+"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"database-compatibility",children:(0,i.jsx)(n.strong,{children:"Database Compatibility"})}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Database"}),(0,i.jsx)(n.th,{children:"Version"}),(0,i.jsx)(n.th,{children:"Status"}),(0,i.jsx)(n.th,{children:"Notes"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Scylla DB"}),(0,i.jsx)(n.td,{children:"5.0+"}),(0,i.jsx)(n.td,{children:"\u2705 Recommended"}),(0,i.jsx)(n.td,{children:"Optimal performance"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Dragonfly"}),(0,i.jsx)(n.td,{children:"1.0+"}),(0,i.jsx)(n.td,{children:"\u2705 Supported"}),(0,i.jsx)(n.td,{children:"Memory efficient"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Redis"}),(0,i.jsx)(n.td,{children:"6.0+"}),(0,i.jsx)(n.td,{children:"\u2705 Development"}),(0,i.jsx)(n.td,{children:"Limited scale"})]})]})]}),"\n",(0,i.jsxs)(n.h2,{id:"-known-issues",children:["\ud83d\udc1b ",(0,i.jsx)(n.strong,{children:"Known Issues"})]}),"\n",(0,i.jsx)(n.h3,{id:"current-limitations",children:(0,i.jsx)(n.strong,{children:"Current Limitations"})}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Large Vector Support"}),": Vectors >10MB may experience increased latency"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"workarounds",children:(0,i.jsx)(n.strong,{children:"Workarounds"})}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Vector Chunking"}),": Split large vectors into smaller segments"]}),"\n"]}),"\n",(0,i.jsxs)(n.h2,{id:"-download--installation",children:["\ud83d\udcbe ",(0,i.jsx)(n.strong,{children:"Download & Installation"})]}),"\n",(0,i.jsx)(n.h3,{id:"container-images",children:(0,i.jsx)(n.strong,{children:"Container Images"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"# Pull the latest image\ndocker pull ghcr.io/meesho/onfs-api-server:latest\ndocker pull ghcr.io/meesho/onfs-consumer:latest\ndocker pull ghcr.io/meesho/horizon:latest\ndocker pull ghcr.io/meesho/trufflebox-ui:latest\n\n"})}),"\n",(0,i.jsx)(n.h3,{id:"arch-supported",children:(0,i.jsx)(n.strong,{children:"Arch Supported"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:(0,i.jsx)(n.strong,{children:"Linux (amd64)"})}),"\n",(0,i.jsx)(n.li,{children:(0,i.jsx)(n.strong,{children:"Linux (arm64)"})}),"\n",(0,i.jsx)(n.li,{children:(0,i.jsx)(n.strong,{children:"macOS (Intel)"})}),"\n",(0,i.jsx)(n.li,{children:(0,i.jsx)(n.strong,{children:"macOS (Apple Silicon)"})}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["Checkout ",(0,i.jsx)(n.a,{href:"https://github.com/orgs/Meesho/packages?repo_name=BharatMLStack",children:"Packages"})]}),"\n",(0,i.jsx)(n.h3,{id:"source-code",children:(0,i.jsx)(n.strong,{children:"Source Code"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"git clone https://github.com/Meesho/BharatMLStack.git\ncd BharatMLStack/online-feature-store\ngit checkout release/1.0.0\n"})}),"\n",(0,i.jsx)(n.h2,{id:"contributing",children:"Contributing"}),"\n",(0,i.jsxs)(n.p,{children:["We welcome contributions from the community! Please see our ",(0,i.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTING.md",children:"Contributing Guide"})," for details on how to get started."]}),"\n",(0,i.jsx)(n.h2,{id:"community--support",children:"Community & Support"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["\ud83d\udcac ",(0,i.jsx)(n.strong,{children:"Discord"}),": Join our ",(0,i.jsx)(n.a,{href:"https://discord.gg/XkT7XsV2AU",children:"community chat"})]}),"\n",(0,i.jsxs)(n.li,{children:["\ud83d\udc1b ",(0,i.jsx)(n.strong,{children:"Issues"}),": Report bugs and request features on ",(0,i.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/issues",children:"GitHub Issues"})]}),"\n",(0,i.jsxs)(n.li,{children:["\ud83d\udce7 ",(0,i.jsx)(n.strong,{children:"Email"}),": Contact us at ",(0,i.jsx)(n.a,{href:"mailto:ml-oss@meesho.com",children:"ml-oss@meesho.com"})]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"license",children:"License"}),"\n",(0,i.jsxs)(n.p,{children:["BharatMLStack is open-source software licensed under the ",(0,i.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md",children:"BharatMLStack Business Source License 1.1"}),"."]}),"\n",(0,i.jsx)(n.hr,{}),"\n",(0,i.jsx)("div",{align:"center",children:(0,i.jsx)("strong",{children:"Built with \u2764\ufe0f for the ML community from Meesho"})}),"\n",(0,i.jsx)("div",{align:"center",children:(0,i.jsx)("strong",{children:"If you find this useful, \u2b50\ufe0f the repo \u2014 your support means the world to us!"})})]})}function h(e={}){const{wrapper:n}={...(0,t.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(d,{...e})}):d(e)}},8453:(e,n,r)=>{r.d(n,{R:()=>l,x:()=>o});var s=r(6540);const i={},t=s.createContext(i);function l(e){const n=s.useContext(t);return s.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:l(e.components),s.createElement(t.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/d853e668.e1234cf0.js b/docs/assets/js/d853e668.e1234cf0.js new file mode 100644 index 00000000..a0f79ef8 --- /dev/null +++ b/docs/assets/js/d853e668.e1234cf0.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[3068],{1187:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/bms-7399e8796d2cd24617c432518ce3f312.png"},2516:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/llm-plat-9ac69c0ffd8c387d177e582611b8c775.png"},3562:e=>{e.exports=JSON.parse('{"permalink":"/BharatMLStack/blog/multi-engine-llm-inferencing-platform","editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/blog/bharatmlstack-history/llm-inferencing-platform/index.md","source":"@site/blog/bharatmlstack-history/llm-inferencing-platform/index.md","title":"Designing a Production-Grade LLM Inference Platform: From Model Weights to Scalable GPU Serving","description":"A deep dive into building a production-grade LLM inference platform\u2014covering the full LLMOps lifecycle from model onboarding and automated compilation to multi-engine serving with TensorRT-LLM, vLLM, and Dynamo, along with cold-start mitigation and LLM-specific observability.","date":"2025-03-29T00:00:00.000Z","tags":[{"inline":true,"label":"llm","permalink":"/BharatMLStack/blog/tags/llm"},{"inline":true,"label":"vllm","permalink":"/BharatMLStack/blog/tags/vllm"},{"inline":true,"label":"tensorrt-llm","permalink":"/BharatMLStack/blog/tags/tensorrt-llm"},{"inline":true,"label":"mlplatform","permalink":"/BharatMLStack/blog/tags/mlplatform"},{"inline":true,"label":"meesho","permalink":"/BharatMLStack/blog/tags/meesho"},{"inline":true,"label":"bharatmlstack","permalink":"/BharatMLStack/blog/tags/bharatmlstack"}],"readingTime":13.31,"hasTruncateMarker":false,"authors":[{"name":"Jaya Kumar","title":"Lead ML Engineer @ Meesho","url":"https://github.com/jayakommuru","imageURL":"https://github.com/jayakommuru.png","key":"jaya","page":null}],"frontMatter":{"title":"Designing a Production-Grade LLM Inference Platform: From Model Weights to Scalable GPU Serving","description":"A deep dive into building a production-grade LLM inference platform\u2014covering the full LLMOps lifecycle from model onboarding and automated compilation to multi-engine serving with TensorRT-LLM, vLLM, and Dynamo, along with cold-start mitigation and LLM-specific observability.","authors":["jaya"],"slug":"multi-engine-llm-inferencing-platform","date":"2025-3-29","tags":["llm","vllm","tensorrt-llm","mlplatform","meesho","bharatmlstack"]},"unlisted":false,"prevItem":{"title":"LLM Inference Optimization Techniques: Engineering Sub-Second Latency at Scale","permalink":"/BharatMLStack/blog/llm-inference-optimization-sub-sec-latency"},"nextItem":{"title":"Cracking the Code: Scaling Model Inference & Real-Time Embedding Search","permalink":"/BharatMLStack/blog/scaling-model-inference-and-embedding-search"}}')},7062:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>l,contentTitle:()=>o,default:()=>h,frontMatter:()=>s,metadata:()=>t,toc:()=>c});var t=i(3562),r=i(4848),a=i(8453);const s={title:"Designing a Production-Grade LLM Inference Platform: From Model Weights to Scalable GPU Serving",description:"A deep dive into building a production-grade LLM inference platform\u2014covering the full LLMOps lifecycle from model onboarding and automated compilation to multi-engine serving with TensorRT-LLM, vLLM, and Dynamo, along with cold-start mitigation and LLM-specific observability.",authors:["jaya"],slug:"multi-engine-llm-inferencing-platform",date:"2025-3-29",tags:["llm","vllm","tensorrt-llm","mlplatform","meesho","bharatmlstack"]},o=void 0,l={authorsImageUrls:[void 0]},c=[{value:"Why LLM Inference Is not just bigger ML model serving",id:"why-llm-inference-is-not-just-bigger-ml-model-serving",level:2},{value:"Autoregressive Generation and Sequential Computation:",id:"autoregressive-generation-and-sequential-computation",level:3},{value:"Prefill and Decode Phases:",id:"prefill-and-decode-phases",level:3},{value:"Context Management and KV Caching:",id:"context-management-and-kv-caching",level:3},{value:"Dynamic and Irregular Workloads:",id:"dynamic-and-irregular-workloads",level:3},{value:"Streaming and User Experience Constraints:",id:"streaming-and-user-experience-constraints",level:3},{value:"LLMOps: High-Level Architecture",id:"llmops-high-level-architecture",level:2},{value:"Supported Inference backends (TensorRT LLM, Dynamo & vLLM)",id:"supported-inference-backends-tensorrt-llm--dynamo--vllm",level:2},{value:"Conclusion",id:"conclusion",level:2},{value:"Future Explorations",id:"future-explorations",level:2}];function d(e){const n={h2:"h2",h3:"h3",img:"img",li:"li",ol:"ol",p:"p",ul:"ul",...(0,a.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.img,{alt:"BharatMLStack",src:i(1187).A+"",width:"1396",height:"460"}),"\nServing large language models in production introduces new challenges across infrastructure, performance optimization, and operational lifecycle management. The LLM Inference Platform addresses these challenges by providing a unified system for deploying and managing open-source and fine-tuned LLMs at scale."]}),"\n",(0,r.jsx)(n.p,{children:"The platform implements a complete LLMOps lifecycle \u2014 from model registration and automated compilation to deployment, runtime optimization, and monitoring. Designed as a self-service environment, users can onboard models directly from open repositories such as Hugging Face or upload custom fine-tuned models, and deploy them using a single-click workflow with no manual infrastructure or configuration steps required."}),"\n",(0,r.jsx)(n.p,{children:"In addition to fully automated deployment, the platform allows users to select and apply custom inference optimization techniques \u2014 such as quantization strategies, batching configurations, and runtime-specific performance enhancements \u2014 enabling teams to balance latency, throughput, and cost based on their use case. The goal is to reduce operational friction while enabling high-performance, production-grade LLM inference."}),"\n",(0,r.jsx)(n.h2,{id:"why-llm-inference-is-not-just-bigger-ml-model-serving",children:"Why LLM Inference Is not just bigger ML model serving"}),"\n",(0,r.jsx)(n.p,{children:"Large language model (LLM) inference introduces a fundamentally different set of challenges compared to traditional machine learning inference. While classical ML models typically perform a single forward pass to produce a fixed prediction, LLMs operate as autoregressive systems, generating outputs token by token based on previously generated context. This difference dramatically changes how inference systems must be designed, optimized, and scaled."}),"\n",(0,r.jsx)(n.h3,{id:"autoregressive-generation-and-sequential-computation",children:"Autoregressive Generation and Sequential Computation:"}),"\n",(0,r.jsx)(n.p,{children:"Unlike traditional models such as classifiers or recommenders \u2014 where inference cost is relatively constant \u2014 LLMs generate responses incrementally. Each new token depends on all previously generated tokens, making inference inherently sequential and dynamic. This means latency and compute requirements vary significantly depending on prompt length and output size, introducing complexity in scheduling and resource allocation.\nBecause tokens cannot be generated fully in parallel during decoding, GPUs may become underutilized without specialized batching and scheduling strategies. This has led to the development of dedicated LLM inference engines optimized for token-level execution."}),"\n",(0,r.jsx)(n.h3,{id:"prefill-and-decode-phases",children:"Prefill and Decode Phases:"}),"\n",(0,r.jsx)(n.p,{children:"LLM inference typically consists of two distinct stages:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Prefill phase \u2014 the model processes the input prompt and builds internal representations. This stage is compute-heavy and highly parallelizable."}),"\n",(0,r.jsx)(n.li,{children:"Decode phase \u2014 the model generates tokens sequentially, predicting one token at a time using previously generated context."}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"The decode stage often becomes memory-bound rather than compute-bound, which creates new performance bottlenecks compared to traditional ML workloads."}),"\n",(0,r.jsx)(n.h3,{id:"context-management-and-kv-caching",children:"Context Management and KV Caching:"}),"\n",(0,r.jsx)(n.p,{children:"Another fundamental difference lies in how LLMs maintain context. Transformer-based models rely on attention mechanisms that require access to past token representations. To avoid recomputing these representations repeatedly, inference engines use key-value (KV) caching, which stores intermediate activations from previous tokens.\nKV caching significantly improves performance by eliminating redundant computation, but it introduces new challenges:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Memory consumption grows with sequence length and batch size"}),"\n",(0,r.jsx)(n.li,{children:"GPU memory becomes a critical bottleneck"}),"\n",(0,r.jsx)(n.li,{children:"Efficient memory management becomes essential for scaling concurrent requests"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"This tradeoff between compute efficiency and memory usage is unique to LLM inference workloads."}),"\n",(0,r.jsx)(n.h3,{id:"dynamic-and-irregular-workloads",children:"Dynamic and Irregular Workloads:"}),"\n",(0,r.jsx)(n.p,{children:"Traditional ML inference typically operates on fixed-size inputs with predictable latency. In contrast, LLM requests vary widely in prompt length, output length, and runtime behavior. As a result:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Batch sizes must be dynamic rather than static"}),"\n",(0,r.jsx)(n.li,{children:"Requests may enter and leave batches asynchronously"}),"\n",(0,r.jsx)(n.li,{children:"Scheduling systems must continuously rebalance workloads to maximize GPU utilization"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"These characteristics require specialized serving architectures that differ significantly from standard ML serving pipelines."}),"\n",(0,r.jsx)(n.h3,{id:"streaming-and-user-experience-constraints",children:"Streaming and User Experience Constraints:"}),"\n",(0,r.jsx)(n.p,{children:"Another distinguishing factor is the expectation of real-time streaming responses. Instead of returning a single output, LLM systems often stream tokens to users as they are generated.\nBecause of these differences \u2014 sequential generation, growing memory requirements, dynamic workloads, and streaming constraints \u2014 LLM inference cannot be treated as a simple extension of existing ML serving systems. Production platforms must incorporate specialized runtime engines, advanced optimization techniques, and observability tailored specifically to LLM workloads."}),"\n",(0,r.jsx)(n.h2,{id:"llmops-high-level-architecture",children:"LLMOps: High-Level Architecture"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.img,{alt:"LLM Architecture",src:i(2516).A+"",width:"1302",height:"830"})}),"\n",(0,r.jsx)(n.p,{children:"The LLM Inference Framework is designed as a fully automated, end-to-end system for deploying and operating open-source and fine-tuned large language models at scale. The architecture abstracts the complexity of model optimization, hardware selection, deployment, and runtime management into a unified workflow that enables users to move from raw model weights to production-ready inference endpoints with minimal manual intervention."}),"\n",(0,r.jsx)(n.p,{children:"Our LLM Inference Framework is architected not just as a serving engine, but as a complete lifecycle management system. As illustrated in the high-level design below, the platform automates the journey of a model through seven distinct stages, ensuring reproducibility, performance, and scalability."}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"Onboarding & Registration (The Source of Truth)"}),"\n",(0,r.jsx)(n.p,{children:"The lifecycle begins with the Data Scientist or engineer."}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Model Ingestion: Users onboard models\u2014whether open-source (Hugging Face, NeMo) or internally fine-tuned\u2014via the Truffle Box SDK/UI."}),"\n",(0,r.jsx)(n.li,{children:'LLM + Prompt Registry: Unlike traditional systems that only track model weights, our registry is a unified control plane. It stores both the Model Artifacts and the Prompt Templates. This allows Data Scientists to register and version-control prompts (e.g., "customer_support_v2") independently of the application code.'}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:'The "Black Box" Build Engine'}),"\n",(0,r.jsx)(n.p,{children:"Once a model is registered, the Automated LLM Compiler + Quantizer Module kicks off a background job on ephemeral GPU resources."}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Transformation: The raw model is converted into a TRT-LLM Checkpoint."}),"\n",(0,r.jsx)(n.li,{children:"Quantization: The system automatically applies quantization algorithms (like INT4 AWQ or FP8) to reduce memory footprint."}),"\n",(0,r.jsx)(n.li,{children:"Engine Building: Finally, it compiles a highly optimized TRT Engine specifically tuned for the target hardware."}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"Intelligent Profiling & Validation"}),"\n",(0,r.jsx)(n.p,{children:"Before deployment, the new engine passes through the Hardware & Inference Runtime Profiler."}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Benchmarking: This module empirically tests the engine against various hardware configurations (L4 vs. A100) and runtimes (TRT-LLM vs. vLLM)."}),"\n",(0,r.jsx)(n.li,{children:"Optimization: It recommends the optimal configuration that meets latency SLAs (Time-To-First-Token) while minimizing cost."}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"Smart Artifact Generation & Distribution"}),"\n",(0,r.jsx)(n.p,{children:'To solve the Kubernetes "Cold Start" problem, the LLM Serving Artifacts Generation module packages the model using a bifurcated strategy:'}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Standard Models: Artifacts are uploaded to Cloud Storage (GCS) and downloaded by pods at startup."}),"\n",(0,r.jsx)(n.li,{children:"Very Large Models: For massive models (>8GB) where network downloads are too slow, the system pre-caches the model onto Secondary Boot Disks. These disks are attached directly to new GPU nodes during autoscaling, eliminating download wait times."}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"Image Streaming & Deployment"}),"\n",(0,r.jsx)(n.p,{children:"Simultaneously, the inference runtime container images are pulled from the Artifact Registry."}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Image Streaming: We utilize container image streaming to allow pods to start initializing while the massive Triton/Dynamo container layers are still downloading, further shaving seconds off the startup time. link"}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"The Inference Runtime (Kubernetes)"}),"\n",(0,r.jsx)(n.p,{children:"The workload lands on Kubernetes with Autoscaling."}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Dynamic Backends: Depending on the profile generated in Stage 3, the pod initializes either TensorRT-LLM (for throughput) or vLLM (for flexibility), or spins up a Dynamo worker for distributed inference."}),"\n",(0,r.jsx)(n.li,{children:'Data Loading: The pod either downloads the model from Cloud Storage or mounts the pre-warmed Secondary Boot Disk ("Pull from Disk").'}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"Client Interaction & Observability"}),"\n",(0,r.jsx)(n.p,{children:"Finally, the LLM Inference Client executes the request."}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Prompt Injection: The client pulls the specific prompt template ID from the Registry, ensuring the exact versioned instructions are used."}),"\n",(0,r.jsx)(n.li,{children:"Streaming Response: The request is sent via gRPC, and tokens are streamed back to the user in real-time."}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"Observability: Monitoring the Pulse of GenAI"}),"\n",(0,r.jsx)(n.p,{children:"In traditional microservices, success is measured by CPU utilization and request latency (p99). For Large Language Models, these metrics are insufficient. A user doesn't care if the GPU is at 80% utilization; they care about how fast the first word appears and how smoothly the rest of the sentence follows."}),"\n",(0,r.jsx)(n.p,{children:"To capture the true user experience, our platform instrumentation focuses on three critical LLM-specific metrics:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"Time to First Token (TTFT)"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Definition: TTFT measures the time elapsed from the moment a request is received until the very first token is generated and streamed back to the user."}),"\n",(0,r.jsx)(n.li,{children:'Why it matters: This represents the "Prefill Phase" latency\u2014the time the model takes to process the input prompt and load weights. A high TTFT makes the application feel unresponsive or "hung."'}),"\n",(0,r.jsx)(n.li,{children:"Optimization: We closely monitor TTFT to ensure our Prefix Caching is effective (aiming for high cache hitrates), which drastically lowers this metric by skipping redundant prompt processing."}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"Inter-Token Latency (ITL)"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:'Definition: ITL measures the average time interval between the generation of consecutive tokens during the "Decode Phase".'}),"\n",(0,r.jsx)(n.li,{children:'Why it matters: This defines the "perceived speed" of reading. Even if the first token is fast (low TTFT), high ITL makes the text generation look "jerky" or slow to the user.'}),"\n",(0,r.jsx)(n.li,{children:"Benchmarks: In our testing with Llama 3.1, we track p99 ITL to ensure it stays below human reading speeds to maintain a natural conversational flow."}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"Token Throughput vs. Request Throughput"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"We distinguish between two types of throughput to balance system efficiency with user load:"}),"\n",(0,r.jsx)(n.li,{children:"Token Throughput (tokens/sec): The total number of tokens generated across all concurrent requests. This measures the raw compute efficiency of the GPU and the effectiveness of batching."}),"\n",(0,r.jsx)(n.li,{children:"Request Throughput (req/sec): The number of distinct user queries served per second. We use this to determine autoscaling thresholds, ensuring we scale out before the queue depth impacts ITL."}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"The Monitoring Stack"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:'Real-time Dashboards: We utilize Grafana to visualize these streaming metrics in real-time, allowing on-call engineers to spot "slow generation" incidents that generic "500 error" alerts would miss.'}),"\n",(0,r.jsx)(n.li,{children:'Request Tracing: Since Triton Inference Server does not log request payloads by default, we integrate a Helix Client to asynchronously publish request logs to Log Tables. This allows us to trace a specific "slow" request back to its prompt to understand if a complex input caused the latency spike.'}),"\n"]}),"\n"]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"supported-inference-backends-tensorrt-llm--dynamo--vllm",children:"Supported Inference backends (TensorRT LLM, Dynamo & vLLM)"}),"\n",(0,r.jsx)(n.p,{children:'Tailored for the Use Case: We do not believe in a "one-size-fits-all" approach to inference. Different use cases\u2014whether a real-time voice bot requiring ultra-lowsub-second latency or a massive reasoning task requiring huge context windows\u2014demand different runtime characteristics. Our platform is designed to be runtime-agnostic, allowing us to automatically select and tailor the best engine based on the specific requirements of the application:'}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"TensorRT-LLM: The High-Performance Standard"}),"\n",(0,r.jsx)(n.p,{children:"Suitable for: High-throughput production workloads where latency is critical (e.g., customer support chat, real-time voice bots)."}),"\n",(0,r.jsx)(n.p,{children:"TensorRT-LLM serves as our default backend for these scenarios. Our internal benchmarks on Llama 3.1 and 3.2 models demonstrated that a tuned TensorRT-LLM engine significantly outperforms standard runtimes, especially when utilizing INT4 AWQ and FP8 quantization ."}),"\n",(0,r.jsx)(n.p,{children:"Key optimizations we tailor for these high-load cases include:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Optimized execution via TensorRT engine compilation"}),"\n",(0,r.jsx)(n.li,{children:"Quantization-aware execution for reduced memory usage and improved throughput"}),"\n",(0,r.jsx)(n.li,{children:"Inflight Batching: Allowing requests to be processed continuously without waiting for the entire batch to finish, drastically improving GPU utilization ."}),"\n",(0,r.jsx)(n.li,{children:"Custom Plugins: Enabling specific NVIDIA plugins like the GEMM plugin and GPT Attention plugin to accelerate matrix multiplications and attention mechanisms ."}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"Dynamo: Distributed Inference for Reasoning Models"}),"\n",(0,r.jsx)(n.p,{children:'Suitable for: Very large "reasoning" models (70B+) or scenarios requiring massive context windows where a single GPU\'s memory is insufficient.'}),"\n",(0,r.jsx)(n.p,{children:"For these memory-bound tasks, we utilize Dynamo, a low-latency distributed inference framework . Unlike monolithic servers, Dynamo disaggregates the inference process to scale resources horizontally:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"KV Aware Routing: A specialized router directs requests to workers that already hold the relevant Key-Value (KV) cache, minimizing redundant computation ."}),"\n",(0,r.jsx)(n.li,{children:'Prefill vs. Decode Split: The workload is divided into Prefill Workers (processing the prompt) and Decode Workers (generating tokens), allowing us to scale the compute-heavy "reading" phase independently from the memory-heavy "writing" phase .'}),"\n",(0,r.jsx)(n.li,{children:"Distributed execution across multiple GPU resources"}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"vLLM: The Flexible Baseline"}),"\n",(0,r.jsx)(n.p,{children:"Suitable for: Rapid prototyping, testing new model architectures, or low-traffic internal tools where ease of deployment outweighs raw throughput."}),"\n",(0,r.jsx)(n.p,{children:"While TensorRT-LLM is optimized for maximum speed, vLLM provides a robust and flexible baseline ."}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"High throughput through dynamic batching and efficient memory utilization"}),"\n",(0,r.jsx)(n.li,{children:"Paged KV cache management for handling long contexts and concurrent requests"}),"\n",(0,r.jsx)(n.li,{children:"Strong support for open-source model ecosystems"}),"\n",(0,r.jsx)(n.li,{children:"Rapid Adoption: It allows us to onboard new model architectures immediately without waiting for a custom TensorRT build."}),"\n",(0,r.jsx)(n.li,{children:"Benchmarking Insight: In our internal tests, vLLM provided a strong baseline but often lacked the specific max-token optimizations present in our custom TRT engines . We use it strategically for initial testing before committing to a full TensorRT optimization pipeline."}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"conclusion",children:"Conclusion"}),"\n",(0,r.jsx)(n.p,{children:"Large language model inference introduces a fundamentally new class of infrastructure challenges\u2014where performance is governed not just by raw compute, but by memory efficiency, intelligent scheduling, runtime specialization, and lifecycle automation. Unlike traditional ML serving, LLM inference requires systems that understand token-level execution, manage rapidly growing context state, and continuously balance latency, throughput, and cost under highly dynamic workloads."}),"\n",(0,r.jsx)(n.p,{children:"The LLM Inference Framework addresses these challenges by transforming inference into a fully automated, reproducible lifecycle\u2014from model onboarding and compilation to deployment, optimization, and observability. By integrating automated quantization and engine compilation, intelligent runtime selection, cold-start mitigation strategies, and LLM-specific observability metrics such as Time-to-First-Token and Inter-Token Latency, the platform ensures both high performance and operational simplicity."}),"\n",(0,r.jsx)(n.p,{children:"Equally important, the framework is designed with flexibility and future evolution in mind. Its runtime-agnostic architecture enables seamless adoption of emerging inference engines, hardware accelerators, and optimization techniques without requiring platform redesign. This ensures that teams can continuously leverage advancements in the rapidly evolving LLM ecosystem while maintaining consistent operational workflows."}),"\n",(0,r.jsx)(n.p,{children:"Ultimately, the goal of the platform is to make production-scale LLM deployment as seamless and reliable as traditional software deployment\u2014allowing teams to focus on building intelligent applications rather than managing infrastructure complexity. By combining lifecycle automation, runtime optimization, and deep observability, the LLM Inference Framework provides a scalable foundation for delivering fast, cost-efficient, and production-ready LLM experiences."}),"\n",(0,r.jsx)(n.h2,{id:"future-explorations",children:"Future Explorations"}),"\n",(0,r.jsx)(n.p,{children:"While we have achieved significant milestones in latency and throughput, the landscape of GenAI is evolving rapidly. Our roadmap focuses on increasing flexibility, reducing costs, and enhancing reliability for enterprise-grade workloads. Here is what we are building next:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"TPU Support: To diversify our hardware supply chain and further optimize cost-per-token, we are evaluating Google Cloud TPUs to bake it into our platform. By leveraging the JAX and PyTorch/XLA ecosystems, we aim to unlock the massive throughput potential of TPU v5e chips, particularly for our open-source Llama models. This will allow the hardware profiler to dynamically choose between NVIDIA GPUs and Google TPUs based on real-time availability and price-performance metrics."}),"\n",(0,r.jsx)(n.li,{children:'Multi-LoRA Serving (Serverless Experience): Currently, deploying a fine-tuned model requires a dedicated GPU. We are building support for Multi-LoRA serving, which will allow us to serve hundreds of unique, fine-tuned adapters on top of a single frozen base model. This will drastically reduce costs for multi-tenant applications, enabling a "serverless" experience where specific fine-tunes are hot-swapped instantly per request.'}),"\n",(0,r.jsx)(n.li,{children:"Spot Instance Orchestration: To further optimize cloud costs, we are developing fault-tolerant mechanisms to run inference workloads on Spot Instances. By implementing aggressive checkpointing and seamless request draining, we aim to leverage cheaper, preemptible compute capacity without interrupting the user's streaming experience."}),"\n",(0,r.jsx)(n.li,{children:'Semantic Caching Layer: We plan to move beyond standard Prefix Caching to implement Semantic Caching. By using a vector database to fetch responses for semantically similar queries (e.g., "How do I reset my password?" vs. "Password reset steps"), we can bypass the GPU entirely for repetitive queries, reducing latency to near-zero.'}),"\n",(0,r.jsx)(n.li,{children:"Context-Aware Autoscaling: Standard CPU/GPU utilization metrics are often insufficient signals for scaling LLMs. We are working on KV-cache pressure metrics for autoscaling. This ensures that we scale out before the memory fills up, preventing eviction-based slowdowns during traffic spikes."}),"\n",(0,r.jsx)(n.li,{children:'Online Evaluation & Guardrails: We are integrating a lightweight "Trust Layer" into the proxy. This will allow for low-latency input/output filtering (Guardrails) and asynchronous "LLM-as-a-Judge" evaluation pipelines to monitor response quality in production, not just system health.'}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,a.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(d,{...e})}):d(e)}},8453:(e,n,i)=>{i.d(n,{R:()=>s,x:()=>o});var t=i(6540);const r={},a=t.createContext(r);function s(e){const n=t.useContext(a);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:s(e.components),t.createElement(a.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/d9861b0f.449cd5bc.js b/docs/assets/js/d9861b0f.449cd5bc.js new file mode 100644 index 00000000..5e34c69e --- /dev/null +++ b/docs/assets/js/d9861b0f.449cd5bc.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[9795],{1822:(e,t,i)=>{i.d(t,{A:()=>n});const n=i.p+"assets/images/bms-7399e8796d2cd24617c432518ce3f312.png"},2437:e=>{e.exports=JSON.parse('{"permalink":"/BharatMLStack/blog/llm-inference-optimization-sub-sec-latency","editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/blog/bharatmlstack-history/llm-inference-optimization/index.md","source":"@site/blog/bharatmlstack-history/llm-inference-optimization/index.md","title":"LLM Inference Optimization Techniques: Engineering Sub-Second Latency at Scale","description":"A practical guide to the optimization techniques behind sub-second LLM inference\u2014covering paged KV caching, INT4 AWQ and FP8 quantization, kernel fusion, inflight batching, parallelism strategies, and speculative decoding, with production benchmarks on L4 and A100 GPUs.","date":"2025-06-02T00:00:00.000Z","tags":[{"inline":true,"label":"llm","permalink":"/BharatMLStack/blog/tags/llm"},{"inline":true,"label":"vllm","permalink":"/BharatMLStack/blog/tags/vllm"},{"inline":true,"label":"tensorrt-llm","permalink":"/BharatMLStack/blog/tags/tensorrt-llm"},{"inline":true,"label":"mlplatform","permalink":"/BharatMLStack/blog/tags/mlplatform"},{"inline":true,"label":"meesho","permalink":"/BharatMLStack/blog/tags/meesho"},{"inline":true,"label":"bharatmlstack","permalink":"/BharatMLStack/blog/tags/bharatmlstack"}],"readingTime":4.88,"hasTruncateMarker":false,"authors":[{"name":"Jaya Kumar","title":"Lead ML Engineer @ Meesho","url":"https://github.com/jayakommuru","imageURL":"https://github.com/jayakommuru.png","key":"jaya","page":null}],"frontMatter":{"title":"LLM Inference Optimization Techniques: Engineering Sub-Second Latency at Scale","description":"A practical guide to the optimization techniques behind sub-second LLM inference\u2014covering paged KV caching, INT4 AWQ and FP8 quantization, kernel fusion, inflight batching, parallelism strategies, and speculative decoding, with production benchmarks on L4 and A100 GPUs.","authors":["jaya"],"slug":"llm-inference-optimization-sub-sec-latency","date":"2025-6-2","tags":["llm","vllm","tensorrt-llm","mlplatform","meesho","bharatmlstack"]},"unlisted":false,"prevItem":{"title":"Beyond Vector RAG: Building Agent Memory That Learns From Experience.","permalink":"/BharatMLStack/blog/episodic-memory-for-agents"},"nextItem":{"title":"Designing a Production-Grade LLM Inference Platform: From Model Weights to Scalable GPU Serving","permalink":"/BharatMLStack/blog/multi-engine-llm-inferencing-platform"}}')},8453:(e,t,i)=>{i.d(t,{R:()=>r,x:()=>d});var n=i(6540);const s={},l=n.createContext(s);function r(e){const t=n.useContext(l);return n.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function d(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:r(e.components),n.createElement(l.Provider,{value:t},e.children)}},9957:(e,t,i)=>{i.r(t),i.d(t,{assets:()=>a,contentTitle:()=>d,default:()=>o,frontMatter:()=>r,metadata:()=>n,toc:()=>c});var n=i(2437),s=i(4848),l=i(8453);const r={title:"LLM Inference Optimization Techniques: Engineering Sub-Second Latency at Scale",description:"A practical guide to the optimization techniques behind sub-second LLM inference\u2014covering paged KV caching, INT4 AWQ and FP8 quantization, kernel fusion, inflight batching, parallelism strategies, and speculative decoding, with production benchmarks on L4 and A100 GPUs.",authors:["jaya"],slug:"llm-inference-optimization-sub-sec-latency",date:"2025-6-2",tags:["llm","vllm","tensorrt-llm","mlplatform","meesho","bharatmlstack"]},d=void 0,a={authorsImageUrls:[void 0]},c=[{value:"1. Advanced Memory Management: Paged & Prefix KV Caching",id:"1-advanced-memory-management-paged--prefix-kv-caching",level:2},{value:"Paged KV caching",id:"paged-kv-caching",level:3},{value:"KV cache quantization",id:"kv-cache-quantization",level:3},{value:"Prefix caching (the "voice bot" optimizer)",id:"prefix-caching-the-voice-bot-optimizer",level:3},{value:"2. Aggressive Quantization (INT4 AWQ & FP8)",id:"2-aggressive-quantization-int4-awq--fp8",level:2},{value:"INT4 AWQ (Activation-aware Weight Quantization)",id:"int4-awq-activation-aware-weight-quantization",level:3},{value:"FP8 precision",id:"fp8-precision",level:3},{value:"3. Kernel Fusion & Custom Plugins",id:"3-kernel-fusion--custom-plugins",level:2},{value:"4. Inflight (Continuous) Batching",id:"4-inflight-continuous-batching",level:2},{value:"5. Parallelism Strategies: Scaling Beyond One GPU",id:"5-parallelism-strategies-scaling-beyond-one-gpu",level:2},{value:"6. Speculative Decoding",id:"6-speculative-decoding",level:2},{value:"Few Benchmarks",id:"few-benchmarks",level:2},{value:"Search query rewriting",id:"search-query-rewriting",level:3},{value:"Voice bot query",id:"voice-bot-query",level:3},{value:"Conclusion",id:"conclusion",level:2}];function h(e){const t={h2:"h2",h3:"h3",img:"img",li:"li",p:"p",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,l.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.img,{alt:"BharatMLStack",src:i(1822).A+"",width:"1396",height:"460"}),"\nRaw execution of Large Language Models is inherently expensive and memory-intensive. To achieve sub-second latency and high throughput, we implement a multi-layered optimization strategy that targets the entire inference stack\u2014from memory management to kernel execution."]}),"\n",(0,s.jsx)(t.h2,{id:"1-advanced-memory-management-paged--prefix-kv-caching",children:"1. Advanced Memory Management: Paged & Prefix KV Caching"}),"\n",(0,s.jsx)(t.p,{children:"The most significant bottleneck in LLM inference is not always compute, but memory bandwidth\u2014specifically managing the Key-Value (KV) cache."}),"\n",(0,s.jsx)(t.h3,{id:"paged-kv-caching",children:"Paged KV caching"}),"\n",(0,s.jsxs)(t.p,{children:["Standard caching suffers from fragmentation. We use ",(0,s.jsx)(t.strong,{children:"Paged KV caching"}),", which operates similarly to an operating system's virtual memory: the KV cache is divided into non-contiguous blocks. This lets us serve larger batch sizes without running out of memory."]}),"\n",(0,s.jsx)(t.h3,{id:"kv-cache-quantization",children:"KV cache quantization"}),"\n",(0,s.jsxs)(t.p,{children:["To further maximize available memory, we implement ",(0,s.jsx)(t.strong,{children:"KV cache quantization"})," (e.g., FP8). By compressing stored attention keys and values from 16-bit to 8-bit, we nearly double the effective context window capacity of the GPU, allowing longer conversations or larger batches without materially degrading quality."]}),"\n",(0,s.jsx)(t.h3,{id:"prefix-caching-the-voice-bot-optimizer",children:'Prefix caching (the "voice bot" optimizer)'}),"\n",(0,s.jsxs)(t.p,{children:['For use cases like GenAI voice bots where the system prompt (e.g., "You are a helpful assistant...") is static across thousands of requests, we enable ',(0,s.jsx)(t.strong,{children:"prefix caching"}),"."]}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"Impact"}),": By reusing pre-computed KV states for common prefixes, we achieve a cache hit rate of ~90%. This reduces ",(0,s.jsx)(t.strong,{children:"Time To First Token (TTFT)"})," by skipping redundant computation of the system prompt."]}),"\n"]}),"\n",(0,s.jsx)(t.h2,{id:"2-aggressive-quantization-int4-awq--fp8",children:"2. Aggressive Quantization (INT4 AWQ & FP8)"}),"\n",(0,s.jsx)(t.p,{children:"Running models in their native 16-bit precision (BF16) restricts maximum batch size and throughput. We use quantization to shrink model weights without sacrificing accuracy."}),"\n",(0,s.jsx)(t.h3,{id:"int4-awq-activation-aware-weight-quantization",children:"INT4 AWQ (Activation-aware Weight Quantization)"}),"\n",(0,s.jsxs)(t.p,{children:["For the Llama 3 family, we use ",(0,s.jsx)(t.strong,{children:"AWQ"})," to compress weights to 4 bits. This reduces model size by ~75%, allowing larger models to fit into L4 GPU memory and significantly improving token generation speed."]}),"\n",(0,s.jsx)(t.h3,{id:"fp8-precision",children:"FP8 precision"}),"\n",(0,s.jsxs)(t.p,{children:["For NVIDIA Hopper (H100) architectures, we are exploring ",(0,s.jsx)(t.strong,{children:"FP8 quantization"}),", leveraging native FP8 tensor cores to accelerate matrix multiplications while maintaining a higher dynamic range than integer quantization."]}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"Verification"}),": We validate quantized models by comparing dot-product similarity of embeddings against the FP16 baseline, consistently achieving ",(0,s.jsx)(t.strong,{children:">99% similarity"}),"."]}),"\n"]}),"\n",(0,s.jsx)(t.h2,{id:"3-kernel-fusion--custom-plugins",children:"3. Kernel Fusion & Custom Plugins"}),"\n",(0,s.jsx)(t.p,{children:"To minimize overhead from launching thousands of small GPU operations, we fuse them into monolithic kernels using NVIDIA TensorRT plugins."}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"Flash attention & FMHA"}),": We enable ",(0,s.jsx)(t.strong,{children:"Fused Multi-Head Attention (FMHA)"})," combined with flash attention to reduce memory reads/writes."]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"GEMM plugins"}),": We use specialized ",(0,s.jsx)(t.strong,{children:"GEMM"})," plugins to accelerate transformer linear layers."]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"Removing input padding"}),": Instead of padding short sequences to match the longest, we remove input padding so the GPU processes only valid tokens."]}),"\n"]}),"\n",(0,s.jsx)(t.h2,{id:"4-inflight-continuous-batching",children:"4. Inflight (Continuous) Batching"}),"\n",(0,s.jsx)(t.p,{children:"Traditional static batching waits for all requests in a batch to finish before returning results\u2014so one long response delays everyone else."}),"\n",(0,s.jsxs)(t.p,{children:["We implement ",(0,s.jsx)(t.strong,{children:"inflight batching"}),": as soon as one request completes, its slot is freed and filled by a new request from the queue. This keeps GPUs saturated and decouples latency of short queries from long ones."]}),"\n",(0,s.jsx)(t.h2,{id:"5-parallelism-strategies-scaling-beyond-one-gpu",children:"5. Parallelism Strategies: Scaling Beyond One GPU"}),"\n",(0,s.jsx)(t.p,{children:"For large models (e.g., 70B+ parameters) that cannot fit into the VRAM of a single GPU, we use parallelism strategies."}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"Tensor parallelism (TP)"}),": Split weight matrices across multiple GPUs (e.g., 4\xd7 L4 or 8\xd7 A100). Each GPU computes a shard and outputs are reduced at every layer."]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"Pipeline parallelism (PP)"}),": Split model layers across GPUs to pipeline compute (e.g., while one GPU computes later layers for Request A, another starts early layers for Request B)."]}),"\n"]}),"\n",(0,s.jsx)(t.h2,{id:"6-speculative-decoding",children:"6. Speculative Decoding"}),"\n",(0,s.jsxs)(t.p,{children:["To reduce inter-token latency (ITL), we explore ",(0,s.jsx)(t.strong,{children:"speculative decoding"}),"."]}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"Mechanism"}),': A smaller, faster "draft" model speculatively generates a short token sequence (e.g., 5 tokens).']}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"Verification"}),": The larger target model verifies those tokens in one parallel forward pass. If correct, we effectively generate multiple tokens per large-model step; if not, we discard and regenerate. This is effective for predictable text, improving perceived generation speed."]}),"\n"]}),"\n",(0,s.jsx)(t.h2,{id:"few-benchmarks",children:"Few Benchmarks"}),"\n",(0,s.jsx)(t.p,{children:"Below are a couple of representative use cases and performance numbers."}),"\n",(0,s.jsx)(t.h3,{id:"search-query-rewriting",children:"Search query rewriting"}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"LLM"}),": Fine-tuned llama-3.2-1B"]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"Input & output token length"}),": ~10\u201320"]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"Response type"}),": Non-streaming"]}),"\n"]}),"\n",(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Inference runtime"}),(0,s.jsx)(t.th,{children:"Hardware"}),(0,s.jsx)(t.th,{style:{textAlign:"right"},children:"Max requests/sec"}),(0,s.jsx)(t.th,{style:{textAlign:"right"},children:"Max p99 latency"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{children:"4 \xd7 L4 GPUs (multi-GPU)"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"1000"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"95 ms"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{children:"1 \xd7 A100 40 GB GPU"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"1000"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"69 ms"})]})]})]}),"\n",(0,s.jsx)(t.h3,{id:"voice-bot-query",children:"Voice bot query"}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"LLM"}),": Llama-3.1-8B"]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"Input token length"}),": ~1900\u20132000"]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"Output token length"}),": ~200"]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"Response type"}),": Streaming"]}),"\n"]}),"\n",(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Inference runtime"}),(0,s.jsx)(t.th,{style:{textAlign:"right"},children:"Concurrency"}),(0,s.jsx)(t.th,{style:{textAlign:"right"},children:"p99 TTFT (ms)"}),(0,s.jsx)(t.th,{style:{textAlign:"right"},children:"p99 ITL (ms)"}),(0,s.jsx)(t.th,{style:{textAlign:"right"},children:"Token throughput (tokens/sec)"}),(0,s.jsx)(t.th,{style:{textAlign:"right"},children:"Request throughput (req/sec)"}),(0,s.jsx)(t.th,{children:"Hardware"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"1"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"36.27"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"22.78"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"45.66"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"0.23"}),(0,s.jsx)(t.td,{children:"L4"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"2"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"49.81"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"23.21"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"89.37"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"0.45"}),(0,s.jsx)(t.td,{children:"L4"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"4"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"55.33"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"36.62"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"153.39"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"0.78"}),(0,s.jsx)(t.td,{children:"L4"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"8"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"66.5"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"39.11"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"279.88"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"1.47"}),(0,s.jsx)(t.td,{children:"L4"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"16"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"131.8"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"30.39"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"547.8"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"2.77"}),(0,s.jsx)(t.td,{children:"L4"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"32"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"277.22"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"48.02"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"925.7"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"4.78"}),(0,s.jsx)(t.td,{children:"L4"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"64"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"498.52"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"71.62"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"1,164.40"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"6.2"}),(0,s.jsx)(t.td,{children:"L4"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"128"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"677.31"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"120.37"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"1,445.18"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"7.69"}),(0,s.jsx)(t.td,{children:"L4"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"256"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"1,926.31"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"216.88"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"1,600.81"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"8.52"}),(0,s.jsx)(t.td,{children:"L4"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"1"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"21.17"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"9.24"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"130.05"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"0.68"}),(0,s.jsx)(t.td,{children:"A100"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"2"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"25.78"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"9.21"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"264.5"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"1.35"}),(0,s.jsx)(t.td,{children:"A100"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"4"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"28.52"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"10.99"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"437.69"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"2.27"}),(0,s.jsx)(t.td,{children:"A100"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"8"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"34.4"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"12.61"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"760.49"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"3.96"}),(0,s.jsx)(t.td,{children:"A100"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"16"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"68.03"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"14.32"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"1,343.80"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"7.01"}),(0,s.jsx)(t.td,{children:"A100"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"32"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"185.96"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"16.82"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"2,287.30"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"11.92"}),(0,s.jsx)(t.td,{children:"A100"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"64"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"136.87"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"21.17"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"3,625.22"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"18.89"}),(0,s.jsx)(t.td,{children:"A100"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"128"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"463.78"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"34.15"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"4,456.51"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"23.24"}),(0,s.jsx)(t.td,{children:"A100"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"TensorRT-LLM"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"256"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"890.12"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"59.18"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"5,188.24"}),(0,s.jsx)(t.td,{style:{textAlign:"right"},children:"27.05"}),(0,s.jsx)(t.td,{children:"A100"})]})]})]}),"\n",(0,s.jsx)(t.h2,{id:"conclusion",children:"Conclusion"}),"\n",(0,s.jsx)(t.p,{children:"High-performance LLM inference is fundamentally a systems engineering problem: memory efficiency, kernel execution, batching strategy, and parallelism determine real-world latency and throughput. Techniques such as paged KV caching, aggressive quantization, kernel fusion, and inflight batching improve GPU utilization while reducing latency and memory pressure."}),"\n",(0,s.jsx)(t.p,{children:"These optimizations enable the platform to deliver sub-second responses, sustain high concurrency, and efficiently serve both lightweight and long-context workloads. By continuously optimizing across the full inference stack, we keep LLM serving scalable, cost-efficient, and production-ready for real-time AI applications."})]})}function o(e={}){const{wrapper:t}={...(0,l.R)(),...e.components};return t?(0,s.jsx)(t,{...e,children:(0,s.jsx)(h,{...e})}):h(e)}}}]); \ No newline at end of file diff --git a/docs/assets/js/df502808.a61c45a0.js b/docs/assets/js/df502808.a61c45a0.js new file mode 100644 index 00000000..784b7367 --- /dev/null +++ b/docs/assets/js/df502808.a61c45a0.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[6088],{4795:(e,t,r)=>{r.d(t,{A:()=>j});r(6540);var n=r(4164),s=r(6972),o=r(8774),i=r(5846),a=r(6654),c=r(1312),l=r(1107);const u={cardContainer:"cardContainer_fWXF",cardTitle:"cardTitle_rnsV",cardDescription:"cardDescription_PWke"};var d=r(4848);function f({className:e,href:t,children:r}){return(0,d.jsx)(o.A,{href:t,className:(0,n.A)("card padding--lg",u.cardContainer,e),children:r})}function m({className:e,href:t,icon:r,title:s,description:o}){return(0,d.jsxs)(f,{href:t,className:e,children:[(0,d.jsxs)(l.A,{as:"h2",className:(0,n.A)("text--truncate",u.cardTitle),title:s,children:[r," ",s]}),o&&(0,d.jsx)("p",{className:(0,n.A)("text--truncate",u.cardDescription),title:o,children:o})]})}function p({item:e}){const t=(0,s.Nr)(e),r=function(){const{selectMessage:e}=(0,i.W)();return t=>e(t,(0,c.T)({message:"1 item|{count} items",id:"theme.docs.DocCard.categoryDescription.plurals",description:"The default description for a category card in the generated index about how many items this category includes"},{count:t}))}();return t?(0,d.jsx)(m,{className:e.className,href:t,icon:"\ud83d\uddc3\ufe0f",title:e.label,description:e.description??r(e.items.length)}):null}function h({item:e}){const t=(0,a.A)(e.href)?"\ud83d\udcc4\ufe0f":"\ud83d\udd17",r=(0,s.cC)(e.docId??void 0);return(0,d.jsx)(m,{className:e.className,href:e.href,icon:t,title:e.label,description:e.description??r?.description})}function x({item:e}){switch(e.type){case"link":return(0,d.jsx)(h,{item:e});case"category":return(0,d.jsx)(p,{item:e});default:throw new Error(`unknown item type ${JSON.stringify(e)}`)}}const g={docCardListItem:"docCardListItem_W1sv"};function b({className:e}){const t=(0,s.a4)();return(0,d.jsx)(j,{items:t,className:e})}function v({item:e}){return(0,d.jsx)("article",{className:(0,n.A)(g.docCardListItem,"col col--6"),children:(0,d.jsx)(x,{item:e})})}function j(e){const{items:t,className:r}=e;if(!t)return(0,d.jsx)(b,{...e});const o=(0,s.d1)(t);return(0,d.jsx)("section",{className:(0,n.A)("row",r),children:o.map((e,t)=>(0,d.jsx)(v,{item:e},t))})}},5074:(e,t,r)=>{r.r(t),r.d(t,{assets:()=>l,contentTitle:()=>c,default:()=>f,frontMatter:()=>a,metadata:()=>n,toc:()=>u});const n=JSON.parse('{"id":"trufflebox-ui/v1.0.0/index","title":"v1.0.0","description":"Trufflebox UI v1.0.0","source":"@site/docs/trufflebox-ui/v1.0.0/index.md","sourceDirName":"trufflebox-ui/v1.0.0","slug":"/trufflebox-ui/v1.0.0","permalink":"/BharatMLStack/trufflebox-ui/v1.0.0","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/trufflebox-ui/v1.0.0/index.md","tags":[],"version":"current","sidebarPosition":0,"frontMatter":{"title":"v1.0.0","description":"Trufflebox UI v1.0.0","sidebar_position":0,"slug":"/trufflebox-ui/v1.0.0"},"sidebar":"tutorialSidebar","previous":{"title":"Trufflebox UI","permalink":"/BharatMLStack/category/trufflebox-ui"},"next":{"title":"User Manual","permalink":"/BharatMLStack/trufflebox-ui/v1.0.0/userguide"}}');var s=r(4848),o=r(8453),i=r(4795);const a={title:"v1.0.0",description:"Trufflebox UI v1.0.0",sidebar_position:0,slug:"/trufflebox-ui/v1.0.0"},c="Trufflebox UI v1.0.0",l={},u=[];function d(e){const t={h1:"h1",header:"header",p:"p",...(0,o.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(t.header,{children:(0,s.jsx)(t.h1,{id:"trufflebox-ui-v100",children:"Trufflebox UI v1.0.0"})}),"\n",(0,s.jsx)(t.p,{children:"Trufflebox UI is a modern, feature-rich UI framework for supporting MLOps. It supports feature catalog, management, user management, and other admin operations."}),"\n",(0,s.jsx)(i.A,{})]})}function f(e={}){const{wrapper:t}={...(0,o.R)(),...e.components};return t?(0,s.jsx)(t,{...e,children:(0,s.jsx)(d,{...e})}):d(e)}},5846:(e,t,r)=>{r.d(t,{W:()=>l});var n=r(6540),s=r(4586);const o=["zero","one","two","few","many","other"];function i(e){return o.filter(t=>e.includes(t))}const a={locale:"en",pluralForms:i(["one","other"]),select:e=>1===e?"one":"other"};function c(){const{i18n:{currentLocale:e}}=(0,s.A)();return(0,n.useMemo)(()=>{try{return function(e){const t=new Intl.PluralRules(e);return{locale:e,pluralForms:i(t.resolvedOptions().pluralCategories),select:e=>t.select(e)}}(e)}catch(t){return console.error(`Failed to use Intl.PluralRules for locale "${e}".\nDocusaurus will fallback to the default (English) implementation.\nError: ${t.message}\n`),a}},[e])}function l(){const e=c();return{selectMessage:(t,r)=>function(e,t,r){const n=e.split("|");if(1===n.length)return n[0];n.length>r.pluralForms.length&&console.error(`For locale=${r.locale}, a maximum of ${r.pluralForms.length} plural forms are expected (${r.pluralForms.join(",")}), but the message contains ${n.length}: ${e}`);const s=r.select(t),o=r.pluralForms.indexOf(s);return n[Math.min(o,n.length-1)]}(r,t,e)}}},8453:(e,t,r)=>{r.d(t,{R:()=>i,x:()=>a});var n=r(6540);const s={},o=n.createContext(s);function i(e){const t=n.useContext(o);return n.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function a(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:i(e.components),n.createElement(o.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/e66382f6.aaa6c9c6.js b/docs/assets/js/e66382f6.aaa6c9c6.js deleted file mode 100644 index b5cedec5..00000000 --- a/docs/assets/js/e66382f6.aaa6c9c6.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[1405],{287:(e,n,s)=>{s.d(n,{A:()=>r});const r=s.p+"assets/images/v1.0.0-onfs-arch-7b3e91a84b2a24a378d13db769995c08.png"},8453:(e,n,s)=>{s.d(n,{R:()=>l,x:()=>o});var r=s(6540);const t={},i=r.createContext(t);function l(e){const n=r.useContext(i);return r.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:l(e.components),r.createElement(i.Provider,{value:n},e.children)}},9563:(e,n,s)=>{s.r(n),s.d(n,{assets:()=>a,contentTitle:()=>o,default:()=>h,frontMatter:()=>l,metadata:()=>r,toc:()=>c});const r=JSON.parse('{"id":"online-feature-store/v1.0.0/architecture","title":"Architecture","description":"The Online Feature Store (OnFS) is part of BharatMLStack, designed to support real-time ML workloads through low-latency feature retrieval and flexible feature ingestion pipelines. It ensures that features generated offline or online are immediately accessible for inference.","source":"@site/docs/online-feature-store/v1.0.0/architecture.md","sourceDirName":"online-feature-store/v1.0.0","slug":"/online-feature-store/v1.0.0/architecture","permalink":"/BharatMLStack/online-feature-store/v1.0.0/architecture","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/online-feature-store/v1.0.0/architecture.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"Architecture","sidebar_position":1},"sidebar":"tutorialSidebar","previous":{"title":"v1.0.0","permalink":"/BharatMLStack/online-feature-store/v1.0.0"},"next":{"title":"Data Formats","permalink":"/BharatMLStack/online-feature-store/v1.0.0/data-formats"}}');var t=s(4848),i=s(8453);const l={title:"Architecture",sidebar_position:1},o="BharatMLStack - Online Feature Store (OnFS)",a={},c=[{value:"\ud83e\udde9 Key Components",id:"-key-components",level:2},{value:"1. <strong>Data Ingestion Paths</strong>",id:"1-data-ingestion-paths",level:3},{value:"a. <strong>Direct Push from Feature Engineering Jobs</strong>",id:"a-direct-push-from-feature-engineering-jobs",level:4},{value:"b. <strong>Push from Offline Feature Store</strong>",id:"b-push-from-offline-feature-store",level:4},{value:"c. <strong>Streaming Push via Apache Flink</strong>",id:"c-streaming-push-via-apache-flink",level:4},{value:"2. <strong>Message Queue: Kafka</strong>",id:"2-message-queue-kafka",level:3},{value:"3. <strong>Core Components</strong>",id:"3-core-components",level:3},{value:"\ud83e\udde0 <strong>Horizon Control Plane</strong>",id:"-horizon-control-plane",level:4},{value:"\ud83d\udd0d <strong>Trufflebox UI</strong>",id:"-trufflebox-ui",level:4},{value:"\u2699\ufe0f <strong>OnFS-Consumers</strong>",id:"\ufe0f-onfs-consumers",level:4},{value:"\ud83d\ude80 <strong>OnFS API Server</strong>",id:"-onfs-api-server",level:4},{value:"4. <strong>Online Databases</strong>",id:"4-online-databases",level:3},{value:"5. <strong>Clients for Serving</strong>",id:"5-clients-for-serving",level:3},{value:"6. <strong>Observability</strong>",id:"6-observability",level:3},{value:"\ud83d\udcbb Supported Environments",id:"-supported-environments",level:2},{value:"\ud83d\udc65 Target Users",id:"-target-users",level:2},{value:"\u2705 Benefits",id:"-benefits",level:2},{value:"Contributing",id:"contributing",level:2},{value:"Community & Support",id:"community--support",level:2},{value:"License",id:"license",level:2}];function d(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",hr:"hr",img:"img",li:"li",p:"p",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,i.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.header,{children:(0,t.jsx)(n.h1,{id:"bharatmlstack---online-feature-store-onfs",children:"BharatMLStack - Online Feature Store (OnFS)"})}),"\n",(0,t.jsxs)(n.p,{children:["The Online Feature Store (OnFS) is part of ",(0,t.jsx)(n.strong,{children:"BharatMLStack"}),", designed to support real-time ML workloads through low-latency feature retrieval and flexible feature ingestion pipelines. It ensures that features generated offline or online are immediately accessible for inference."]}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"BharatMLStack's Online-feature-store Architecture",src:s(287).A+"",width:"2174",height:"1208"})}),"\n",(0,t.jsx)(n.h2,{id:"-key-components",children:"\ud83e\udde9 Key Components"}),"\n",(0,t.jsxs)(n.h3,{id:"1-data-ingestion-paths",children:["1. ",(0,t.jsx)(n.strong,{children:"Data Ingestion Paths"})]}),"\n",(0,t.jsxs)(n.h4,{id:"a-direct-push-from-feature-engineering-jobs",children:["a. ",(0,t.jsx)(n.strong,{children:"Direct Push from Feature Engineering Jobs"})]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Source:"})," Apache Spark"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Client:"})," ",(0,t.jsx)(n.code,{children:"spark_feature_push_client"})]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Flow:"})," Features are pushed directly to Kafka."]}),"\n"]}),"\n",(0,t.jsxs)(n.h4,{id:"b-push-from-offline-feature-store",children:["b. ",(0,t.jsx)(n.strong,{children:"Push from Offline Feature Store"})]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Source:"})," Delta Lake, GCS, or S3"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Flow:"})," Scheduled notebooks (",(0,t.jsx)(n.code,{children:"push_features_to_online-feature-stores.ipynb"}),") push to Kafka using the same ",(0,t.jsx)(n.code,{children:"spark_feature_push_client"}),"."]}),"\n"]}),"\n",(0,t.jsxs)(n.h4,{id:"c-streaming-push-via-apache-flink",children:["c. ",(0,t.jsx)(n.strong,{children:"Streaming Push via Apache Flink"})]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Source:"})," Flink streaming jobs"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Client:"})," ",(0,t.jsx)(n.code,{children:"custom-producer"})]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Flow:"})," Real-time features sent to Kafka."]}),"\n"]}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsxs)(n.h3,{id:"2-message-queue-kafka",children:["2. ",(0,t.jsx)(n.strong,{children:"Message Queue: Kafka"})]}),"\n",(0,t.jsx)(n.p,{children:"Kafka serves as a decoupled buffer between producers (push clients) and consumers (OnFS ingestion workers), ensuring durability and backpressure handling."}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsxs)(n.h3,{id:"3-core-components",children:["3. ",(0,t.jsx)(n.strong,{children:"Core Components"})]}),"\n",(0,t.jsxs)(n.h4,{id:"-horizon-control-plane",children:["\ud83e\udde0 ",(0,t.jsx)(n.strong,{children:"Horizon Control Plane"})]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"Manages config distribution and metadata orchestration."}),"\n",(0,t.jsxs)(n.li,{children:["Stores schemas, feature group mappings, job configurations in ",(0,t.jsx)(n.code,{children:"etcd"}),"."]}),"\n"]}),"\n",(0,t.jsxs)(n.h4,{id:"-trufflebox-ui",children:["\ud83d\udd0d ",(0,t.jsx)(n.strong,{children:"Trufflebox UI"})]}),"\n",(0,t.jsx)(n.p,{children:"Frontend interface for managing the ML Feature Store ecosystem:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Feature Catalog"})," \u2013 Browse, search, and inspect registered features and groups."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Store and Job Registry"})," \u2013 View and manage ingestion jobs, feature store states, and lineage."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Admin Ops"})," \u2013 Approve or reject feature group pushes and schema edits."]}),"\n",(0,t.jsxs)(n.li,{children:["Designed for use by ",(0,t.jsx)(n.strong,{children:"Data Scientists, MLEs"}),", and ",(0,t.jsx)(n.strong,{children:"Platform Admins"}),"."]}),"\n"]}),"\n",(0,t.jsxs)(n.h4,{id:"\ufe0f-onfs-consumers",children:["\u2699\ufe0f ",(0,t.jsx)(n.strong,{children:"OnFS-Consumers"})]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"Kafka consumers that read and validate feature messages."}),"\n",(0,t.jsx)(n.li,{children:"Responsible for persisting features to online databases (Redis, ScyllaDB, DragonflyDB)."}),"\n"]}),"\n",(0,t.jsxs)(n.h4,{id:"-onfs-api-server",children:["\ud83d\ude80 ",(0,t.jsx)(n.strong,{children:"OnFS API Server"})]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["gRPC server exposing interfaces for:","\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"Real-time feature persistence."}),"\n",(0,t.jsx)(n.li,{children:"Low-latency feature retrieval."}),"\n"]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["Access controlled and schema-validated via ",(0,t.jsx)(n.code,{children:"etcd"}),"."]}),"\n"]}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsxs)(n.h3,{id:"4-online-databases",children:["4. ",(0,t.jsx)(n.strong,{children:"Online Databases"})]}),"\n",(0,t.jsx)(n.p,{children:"Stores real-time features for high-performance retrieval:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.strong,{children:"DragonflyDB"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.strong,{children:"Redis"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.strong,{children:"ScyllaDB"})}),"\n"]}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsxs)(n.h3,{id:"5-clients-for-serving",children:["5. ",(0,t.jsx)(n.strong,{children:"Clients for Serving"})]}),"\n",(0,t.jsx)(n.p,{children:"Applications use client SDKs to fetch features:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Go SDK"}),": ",(0,t.jsx)(n.code,{children:"go-sdk"})]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Python SDK"}),": ",(0,t.jsx)(n.code,{children:"grpc-feature-client"})]}),"\n",(0,t.jsx)(n.li,{children:"Used in backend inference apps to request features using entity keys."}),"\n"]}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsxs)(n.h3,{id:"6-observability",children:["6. ",(0,t.jsx)(n.strong,{children:"Observability"})]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Prometheus"})," \u2013 Metrics collection (e.g., ingest lag, QPS, latency)."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Grafana"})," \u2013 Dashboard for platform health, feature access, ingestion success/failure."]}),"\n"]}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsx)(n.h2,{id:"-supported-environments",children:"\ud83d\udcbb Supported Environments"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"Kubernetes (K8s)"}),"\n",(0,t.jsx)(n.li,{children:"Google Kubernetes Engine (GKE)"}),"\n",(0,t.jsx)(n.li,{children:"Amazon EKS"}),"\n"]}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsx)(n.h2,{id:"-target-users",children:"\ud83d\udc65 Target Users"}),"\n",(0,t.jsxs)(n.table,{children:[(0,t.jsx)(n.thead,{children:(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.th,{children:"User"}),(0,t.jsx)(n.th,{children:"Role"})]})}),(0,t.jsxs)(n.tbody,{children:[(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:"Data Scientists"}),(0,t.jsx)(n.td,{children:"Browse features, define jobs, approve/reject changes via Trufflebox UI"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:"MLEs"}),(0,t.jsx)(n.td,{children:"Develop and push features using Spark/Flink/notebooks"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:"Infra Admins"}),(0,t.jsx)(n.td,{children:"Manage store lifecycle, metadata, and approvals"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:"Backend Devs"}),(0,t.jsx)(n.td,{children:"Use SDKs to retrieve features in Go/Python inference services"})]})]})]}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsx)(n.h2,{id:"-benefits",children:"\u2705 Benefits"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"Unified real-time and offline ingestion."}),"\n",(0,t.jsx)(n.li,{children:"Low-latency inference-ready features."}),"\n",(0,t.jsx)(n.li,{children:"Config-driven orchestration."}),"\n",(0,t.jsx)(n.li,{children:"Built-in approval workflows via Trufflebox."}),"\n",(0,t.jsx)(n.li,{children:"Scalable across thousands of entities and feature groups."}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"contributing",children:"Contributing"}),"\n",(0,t.jsxs)(n.p,{children:["We welcome contributions from the community! Please see our ",(0,t.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTING.md",children:"Contributing Guide"})," for details on how to get started."]}),"\n",(0,t.jsx)(n.h2,{id:"community--support",children:"Community & Support"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["\ud83d\udcac ",(0,t.jsx)(n.strong,{children:"Discord"}),": Join our ",(0,t.jsx)(n.a,{href:"https://discord.gg/XkT7XsV2AU",children:"community chat"})]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83d\udc1b ",(0,t.jsx)(n.strong,{children:"Issues"}),": Report bugs and request features on ",(0,t.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/issues",children:"GitHub Issues"})]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83d\udce7 ",(0,t.jsx)(n.strong,{children:"Email"}),": Contact us at ",(0,t.jsx)(n.a,{href:"mailto:ml-oss@meesho.com",children:"ml-oss@meesho.com"})]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"license",children:"License"}),"\n",(0,t.jsxs)(n.p,{children:["BharatMLStack is open-source software licensed under the ",(0,t.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md",children:"BharatMLStack Business Source License 1.1"}),"."]}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsx)("div",{align:"center",children:(0,t.jsx)("strong",{children:"Built with \u2764\ufe0f for the ML community from Meesho"})}),"\n",(0,t.jsx)("div",{align:"center",children:(0,t.jsx)("strong",{children:"If you find this useful, \u2b50\ufe0f the repo \u2014 your support means the world to us!"})})]})}function h(e={}){const{wrapper:n}={...(0,i.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(d,{...e})}):d(e)}}}]); \ No newline at end of file diff --git a/docs/assets/js/e66382f6.ad26fd04.js b/docs/assets/js/e66382f6.ad26fd04.js new file mode 100644 index 00000000..23fcfec6 --- /dev/null +++ b/docs/assets/js/e66382f6.ad26fd04.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[1405],{8453:(e,n,s)=>{s.d(n,{R:()=>l,x:()=>o});var r=s(6540);const t={},i=r.createContext(t);function l(e){const n=r.useContext(i);return r.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:l(e.components),r.createElement(i.Provider,{value:n},e.children)}},9326:(e,n,s)=>{s.d(n,{A:()=>r});const r=s.p+"assets/images/v1.0.0-onfs-arch-7b3e91a84b2a24a378d13db769995c08.png"},9563:(e,n,s)=>{s.r(n),s.d(n,{assets:()=>a,contentTitle:()=>o,default:()=>h,frontMatter:()=>l,metadata:()=>r,toc:()=>c});const r=JSON.parse('{"id":"online-feature-store/v1.0.0/architecture","title":"Architecture","description":"The Online Feature Store (OnFS) is part of BharatMLStack, designed to support real-time ML workloads through low-latency feature retrieval and flexible feature ingestion pipelines. It ensures that features generated offline or online are immediately accessible for inference.","source":"@site/docs/online-feature-store/v1.0.0/architecture.md","sourceDirName":"online-feature-store/v1.0.0","slug":"/online-feature-store/v1.0.0/architecture","permalink":"/BharatMLStack/online-feature-store/v1.0.0/architecture","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/online-feature-store/v1.0.0/architecture.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"Architecture","sidebar_position":1},"sidebar":"tutorialSidebar","previous":{"title":"v1.0.0","permalink":"/BharatMLStack/online-feature-store/v1.0.0"},"next":{"title":"Data Formats","permalink":"/BharatMLStack/online-feature-store/v1.0.0/data-formats"}}');var t=s(4848),i=s(8453);const l={title:"Architecture",sidebar_position:1},o="BharatMLStack - Online Feature Store (OnFS)",a={},c=[{value:"\ud83e\udde9 Key Components",id:"-key-components",level:2},{value:"1. <strong>Data Ingestion Paths</strong>",id:"1-data-ingestion-paths",level:3},{value:"a. <strong>Direct Push from Feature Engineering Jobs</strong>",id:"a-direct-push-from-feature-engineering-jobs",level:4},{value:"b. <strong>Push from Offline Feature Store</strong>",id:"b-push-from-offline-feature-store",level:4},{value:"c. <strong>Streaming Push via Apache Flink</strong>",id:"c-streaming-push-via-apache-flink",level:4},{value:"2. <strong>Message Queue: Kafka</strong>",id:"2-message-queue-kafka",level:3},{value:"3. <strong>Core Components</strong>",id:"3-core-components",level:3},{value:"\ud83e\udde0 <strong>Horizon Control Plane</strong>",id:"-horizon-control-plane",level:4},{value:"\ud83d\udd0d <strong>Trufflebox UI</strong>",id:"-trufflebox-ui",level:4},{value:"\u2699\ufe0f <strong>OnFS-Consumers</strong>",id:"\ufe0f-onfs-consumers",level:4},{value:"\ud83d\ude80 <strong>OnFS API Server</strong>",id:"-onfs-api-server",level:4},{value:"4. <strong>Online Databases</strong>",id:"4-online-databases",level:3},{value:"5. <strong>Clients for Serving</strong>",id:"5-clients-for-serving",level:3},{value:"6. <strong>Observability</strong>",id:"6-observability",level:3},{value:"\ud83d\udcbb Supported Environments",id:"-supported-environments",level:2},{value:"\ud83d\udc65 Target Users",id:"-target-users",level:2},{value:"\u2705 Benefits",id:"-benefits",level:2},{value:"Contributing",id:"contributing",level:2},{value:"Community & Support",id:"community--support",level:2},{value:"License",id:"license",level:2}];function d(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",hr:"hr",img:"img",li:"li",p:"p",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,i.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.header,{children:(0,t.jsx)(n.h1,{id:"bharatmlstack---online-feature-store-onfs",children:"BharatMLStack - Online Feature Store (OnFS)"})}),"\n",(0,t.jsxs)(n.p,{children:["The Online Feature Store (OnFS) is part of ",(0,t.jsx)(n.strong,{children:"BharatMLStack"}),", designed to support real-time ML workloads through low-latency feature retrieval and flexible feature ingestion pipelines. It ensures that features generated offline or online are immediately accessible for inference."]}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"BharatMLStack's Online-feature-store Architecture",src:s(9326).A+"",width:"2174",height:"1208"})}),"\n",(0,t.jsx)(n.h2,{id:"-key-components",children:"\ud83e\udde9 Key Components"}),"\n",(0,t.jsxs)(n.h3,{id:"1-data-ingestion-paths",children:["1. ",(0,t.jsx)(n.strong,{children:"Data Ingestion Paths"})]}),"\n",(0,t.jsxs)(n.h4,{id:"a-direct-push-from-feature-engineering-jobs",children:["a. ",(0,t.jsx)(n.strong,{children:"Direct Push from Feature Engineering Jobs"})]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Source:"})," Apache Spark"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Client:"})," ",(0,t.jsx)(n.code,{children:"spark_feature_push_client"})]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Flow:"})," Features are pushed directly to Kafka."]}),"\n"]}),"\n",(0,t.jsxs)(n.h4,{id:"b-push-from-offline-feature-store",children:["b. ",(0,t.jsx)(n.strong,{children:"Push from Offline Feature Store"})]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Source:"})," Delta Lake, GCS, or S3"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Flow:"})," Scheduled notebooks (",(0,t.jsx)(n.code,{children:"push_features_to_online-feature-stores.ipynb"}),") push to Kafka using the same ",(0,t.jsx)(n.code,{children:"spark_feature_push_client"}),"."]}),"\n"]}),"\n",(0,t.jsxs)(n.h4,{id:"c-streaming-push-via-apache-flink",children:["c. ",(0,t.jsx)(n.strong,{children:"Streaming Push via Apache Flink"})]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Source:"})," Flink streaming jobs"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Client:"})," ",(0,t.jsx)(n.code,{children:"custom-producer"})]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Flow:"})," Real-time features sent to Kafka."]}),"\n"]}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsxs)(n.h3,{id:"2-message-queue-kafka",children:["2. ",(0,t.jsx)(n.strong,{children:"Message Queue: Kafka"})]}),"\n",(0,t.jsx)(n.p,{children:"Kafka serves as a decoupled buffer between producers (push clients) and consumers (OnFS ingestion workers), ensuring durability and backpressure handling."}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsxs)(n.h3,{id:"3-core-components",children:["3. ",(0,t.jsx)(n.strong,{children:"Core Components"})]}),"\n",(0,t.jsxs)(n.h4,{id:"-horizon-control-plane",children:["\ud83e\udde0 ",(0,t.jsx)(n.strong,{children:"Horizon Control Plane"})]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"Manages config distribution and metadata orchestration."}),"\n",(0,t.jsxs)(n.li,{children:["Stores schemas, feature group mappings, job configurations in ",(0,t.jsx)(n.code,{children:"etcd"}),"."]}),"\n"]}),"\n",(0,t.jsxs)(n.h4,{id:"-trufflebox-ui",children:["\ud83d\udd0d ",(0,t.jsx)(n.strong,{children:"Trufflebox UI"})]}),"\n",(0,t.jsx)(n.p,{children:"Frontend interface for managing the ML Feature Store ecosystem:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Feature Catalog"})," \u2013 Browse, search, and inspect registered features and groups."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Store and Job Registry"})," \u2013 View and manage ingestion jobs, feature store states, and lineage."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Admin Ops"})," \u2013 Approve or reject feature group pushes and schema edits."]}),"\n",(0,t.jsxs)(n.li,{children:["Designed for use by ",(0,t.jsx)(n.strong,{children:"Data Scientists, MLEs"}),", and ",(0,t.jsx)(n.strong,{children:"Platform Admins"}),"."]}),"\n"]}),"\n",(0,t.jsxs)(n.h4,{id:"\ufe0f-onfs-consumers",children:["\u2699\ufe0f ",(0,t.jsx)(n.strong,{children:"OnFS-Consumers"})]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"Kafka consumers that read and validate feature messages."}),"\n",(0,t.jsx)(n.li,{children:"Responsible for persisting features to online databases (Redis, ScyllaDB, DragonflyDB)."}),"\n"]}),"\n",(0,t.jsxs)(n.h4,{id:"-onfs-api-server",children:["\ud83d\ude80 ",(0,t.jsx)(n.strong,{children:"OnFS API Server"})]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["gRPC server exposing interfaces for:","\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"Real-time feature persistence."}),"\n",(0,t.jsx)(n.li,{children:"Low-latency feature retrieval."}),"\n"]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["Access controlled and schema-validated via ",(0,t.jsx)(n.code,{children:"etcd"}),"."]}),"\n"]}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsxs)(n.h3,{id:"4-online-databases",children:["4. ",(0,t.jsx)(n.strong,{children:"Online Databases"})]}),"\n",(0,t.jsx)(n.p,{children:"Stores real-time features for high-performance retrieval:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.strong,{children:"DragonflyDB"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.strong,{children:"Redis"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.strong,{children:"ScyllaDB"})}),"\n"]}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsxs)(n.h3,{id:"5-clients-for-serving",children:["5. ",(0,t.jsx)(n.strong,{children:"Clients for Serving"})]}),"\n",(0,t.jsx)(n.p,{children:"Applications use client SDKs to fetch features:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Go SDK"}),": ",(0,t.jsx)(n.code,{children:"go-sdk"})]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Python SDK"}),": ",(0,t.jsx)(n.code,{children:"grpc-feature-client"})]}),"\n",(0,t.jsx)(n.li,{children:"Used in backend inference apps to request features using entity keys."}),"\n"]}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsxs)(n.h3,{id:"6-observability",children:["6. ",(0,t.jsx)(n.strong,{children:"Observability"})]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Prometheus"})," \u2013 Metrics collection (e.g., ingest lag, QPS, latency)."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Grafana"})," \u2013 Dashboard for platform health, feature access, ingestion success/failure."]}),"\n"]}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsx)(n.h2,{id:"-supported-environments",children:"\ud83d\udcbb Supported Environments"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"Kubernetes (K8s)"}),"\n",(0,t.jsx)(n.li,{children:"Google Kubernetes Engine (GKE)"}),"\n",(0,t.jsx)(n.li,{children:"Amazon EKS"}),"\n"]}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsx)(n.h2,{id:"-target-users",children:"\ud83d\udc65 Target Users"}),"\n",(0,t.jsxs)(n.table,{children:[(0,t.jsx)(n.thead,{children:(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.th,{children:"User"}),(0,t.jsx)(n.th,{children:"Role"})]})}),(0,t.jsxs)(n.tbody,{children:[(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:"Data Scientists"}),(0,t.jsx)(n.td,{children:"Browse features, define jobs, approve/reject changes via Trufflebox UI"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:"MLEs"}),(0,t.jsx)(n.td,{children:"Develop and push features using Spark/Flink/notebooks"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:"Infra Admins"}),(0,t.jsx)(n.td,{children:"Manage store lifecycle, metadata, and approvals"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:"Backend Devs"}),(0,t.jsx)(n.td,{children:"Use SDKs to retrieve features in Go/Python inference services"})]})]})]}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsx)(n.h2,{id:"-benefits",children:"\u2705 Benefits"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"Unified real-time and offline ingestion."}),"\n",(0,t.jsx)(n.li,{children:"Low-latency inference-ready features."}),"\n",(0,t.jsx)(n.li,{children:"Config-driven orchestration."}),"\n",(0,t.jsx)(n.li,{children:"Built-in approval workflows via Trufflebox."}),"\n",(0,t.jsx)(n.li,{children:"Scalable across thousands of entities and feature groups."}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"contributing",children:"Contributing"}),"\n",(0,t.jsxs)(n.p,{children:["We welcome contributions from the community! Please see our ",(0,t.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTING.md",children:"Contributing Guide"})," for details on how to get started."]}),"\n",(0,t.jsx)(n.h2,{id:"community--support",children:"Community & Support"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["\ud83d\udcac ",(0,t.jsx)(n.strong,{children:"Discord"}),": Join our ",(0,t.jsx)(n.a,{href:"https://discord.gg/XkT7XsV2AU",children:"community chat"})]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83d\udc1b ",(0,t.jsx)(n.strong,{children:"Issues"}),": Report bugs and request features on ",(0,t.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/issues",children:"GitHub Issues"})]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83d\udce7 ",(0,t.jsx)(n.strong,{children:"Email"}),": Contact us at ",(0,t.jsx)(n.a,{href:"mailto:ml-oss@meesho.com",children:"ml-oss@meesho.com"})]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"license",children:"License"}),"\n",(0,t.jsxs)(n.p,{children:["BharatMLStack is open-source software licensed under the ",(0,t.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md",children:"BharatMLStack Business Source License 1.1"}),"."]}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsx)("div",{align:"center",children:(0,t.jsx)("strong",{children:"Built with \u2764\ufe0f for the ML community from Meesho"})}),"\n",(0,t.jsx)("div",{align:"center",children:(0,t.jsx)("strong",{children:"If you find this useful, \u2b50\ufe0f the repo \u2014 your support means the world to us!"})})]})}function h(e={}){const{wrapper:n}={...(0,i.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(d,{...e})}):d(e)}}}]); \ No newline at end of file diff --git a/docs/assets/js/e8202a51.49541ad2.js b/docs/assets/js/e8202a51.49541ad2.js new file mode 100644 index 00000000..03d054d6 --- /dev/null +++ b/docs/assets/js/e8202a51.49541ad2.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[2771],{1185:(t,e,i)=>{i.r(e),i.d(e,{assets:()=>d,contentTitle:()=>h,default:()=>g,frontMatter:()=>l,metadata:()=>s,toc:()=>x});const s=JSON.parse('{"id":"numerix/v1.0.0/benchmarks","title":"Benchmarks","description":"This PoC measures the performance of vector addition in Rust with and without compiler SIMD optimizations. Requests consist of repeated fixed-size vector addition operations processed in parallel by the CPU. These results provide perspective on how much faster SIMD makes vectorized computations, and similar improvements are expected for other vectorized operations in Numerix.","source":"@site/docs/numerix/v1.0.0/benchmarks.md","sourceDirName":"numerix/v1.0.0","slug":"/numerix/v1.0.0/benchmarks","permalink":"/BharatMLStack/numerix/v1.0.0/benchmarks","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/numerix/v1.0.0/benchmarks.md","tags":[],"version":"current","sidebarPosition":2,"frontMatter":{"title":"Benchmarks","sidebar_position":2},"sidebar":"tutorialSidebar","previous":{"title":"Architecture","permalink":"/BharatMLStack/numerix/v1.0.0/architecture"},"next":{"title":"Key Functionalities","permalink":"/BharatMLStack/numerix/v1.0.0/functionalities"}}');var r=i(4848),n=i(8453);const l={title:"Benchmarks",sidebar_position:2},h="Benchmarks (PoC)",d={},x=[{value:"System Configuration",id:"system-configuration",level:2},{value:"Vector Addition Performance",id:"vector-addition-performance",level:2},{value:"With SIMD",id:"with-simd",level:3},{value:"Without SIMD",id:"without-simd",level:3},{value:"Observations",id:"observations",level:2}];function c(t){const e={blockquote:"blockquote",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",p:"p",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,n.R)(),...t.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(e.header,{children:(0,r.jsx)(e.h1,{id:"benchmarks-poc",children:"Benchmarks (PoC)"})}),"\n",(0,r.jsxs)(e.p,{children:["This PoC measures the performance of ",(0,r.jsx)(e.strong,{children:"vector addition"})," in Rust ",(0,r.jsx)(e.strong,{children:"with and without compiler SIMD optimizations"}),". Requests consist of repeated fixed-size vector addition operations processed in parallel by the CPU. These results provide perspective on ",(0,r.jsx)(e.strong,{children:"how much faster SIMD makes vectorized computations"}),", and similar improvements are expected for other vectorized operations in Numerix."]}),"\n",(0,r.jsx)(e.h2,{id:"system-configuration",children:"System Configuration"}),"\n",(0,r.jsxs)(e.ul,{children:["\n",(0,r.jsxs)(e.li,{children:[(0,r.jsx)(e.strong,{children:"Instance Type"}),": c4a-highcpu-16"]}),"\n",(0,r.jsxs)(e.li,{children:[(0,r.jsx)(e.strong,{children:"Processor"}),": Google Axion (ARMv9, 64-bit)"]}),"\n",(0,r.jsxs)(e.li,{children:[(0,r.jsx)(e.strong,{children:"SIMD Extension"}),": SVE2"]}),"\n",(0,r.jsxs)(e.li,{children:[(0,r.jsx)(e.strong,{children:"OS"}),": Linux (Ubuntu 22.04)"]}),"\n",(0,r.jsxs)(e.li,{children:[(0,r.jsx)(e.strong,{children:"Rust Version"}),": rustc 1.80.0"]}),"\n",(0,r.jsxs)(e.li,{children:[(0,r.jsx)(e.strong,{children:"Target Triple"}),": aarch64-unknown-linux-gnu"]}),"\n"]}),"\n",(0,r.jsx)(e.h2,{id:"vector-addition-performance",children:"Vector Addition Performance"}),"\n",(0,r.jsx)(e.h3,{id:"with-simd",children:"With SIMD"}),"\n",(0,r.jsxs)(e.table,{children:[(0,r.jsx)(e.thead,{children:(0,r.jsxs)(e.tr,{children:[(0,r.jsx)(e.th,{style:{textAlign:"right"},children:"Vector Dim"}),(0,r.jsx)(e.th,{style:{textAlign:"right"},children:"ns per op"}),(0,r.jsx)(e.th,{style:{textAlign:"right"},children:"Iterations"}),(0,r.jsx)(e.th,{style:{textAlign:"right"},children:"Throughput (GiB/s)"}),(0,r.jsx)(e.th,{style:{textAlign:"right"},children:"Total CPU (raw)"}),(0,r.jsx)(e.th,{style:{textAlign:"right"},children:"Total CPU (normalized)"})]})}),(0,r.jsxs)(e.tbody,{children:[(0,r.jsxs)(e.tr,{children:[(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"10"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"0.39626 ns"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"170,057,457,941"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"376.04"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1564%"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"97.75%"})]}),(0,r.jsxs)(e.tr,{children:[(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"50"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"0.6641 ns"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"94,342,709,095"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1121.9"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1590%"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"99.38%"})]}),(0,r.jsxs)(e.tr,{children:[(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"100"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1.1522 ns"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"51,705,835,397"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1286.9"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1560%"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"97.50%"})]}),(0,r.jsxs)(e.tr,{children:[(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"500"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"5.0649 ns"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"12,061,753,661"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1471"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1538%"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"96.12%"})]}),(0,r.jsxs)(e.tr,{children:[(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1000"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"9.648 ns"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"6,488,848,705"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1544.5"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1570%"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"98.12%"})]}),(0,r.jsxs)(e.tr,{children:[(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"5000"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"52.925 ns"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1,169,316,813"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1407.8"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1590%"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"99.38%"})]}),(0,r.jsxs)(e.tr,{children:[(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"10000"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"114.68 ns"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"555,779,981"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1299.4"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1592%"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"99.50%"})]}),(0,r.jsxs)(e.tr,{children:[(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"50000"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"644.60 ns"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"94,372,153"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1155.9"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1560%"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"97.50%"})]}),(0,r.jsxs)(e.tr,{children:[(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"100000"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1.4530 \xb5s"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"42,502,201"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1025.5"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1526%"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"95.38%"})]})]})]}),"\n",(0,r.jsx)(e.h3,{id:"without-simd",children:"Without SIMD"}),"\n",(0,r.jsxs)(e.table,{children:[(0,r.jsx)(e.thead,{children:(0,r.jsxs)(e.tr,{children:[(0,r.jsx)(e.th,{style:{textAlign:"right"},children:"Vector Dim"}),(0,r.jsx)(e.th,{style:{textAlign:"right"},children:"ns per op"}),(0,r.jsx)(e.th,{style:{textAlign:"right"},children:"Iterations"}),(0,r.jsx)(e.th,{style:{textAlign:"right"},children:"Throughput (GiB/s)"}),(0,r.jsx)(e.th,{style:{textAlign:"right"},children:"Total CPU (raw)"}),(0,r.jsx)(e.th,{style:{textAlign:"right"},children:"Total CPU (normalized)"})]})}),(0,r.jsxs)(e.tbody,{children:[(0,r.jsxs)(e.tr,{children:[(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"10"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"3.196 ns"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1,000,000,000"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"25.03"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1313%"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"82.06%"})]}),(0,r.jsxs)(e.tr,{children:[(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"50"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"3.866 ns"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1,000,000,000"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"103.46"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1417%"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"88.56%"})]}),(0,r.jsxs)(e.tr,{children:[(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"100"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"5.867 ns"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1,000,000,000"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"136.35"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1495%"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"93.44%"})]}),(0,r.jsxs)(e.tr,{children:[(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"500"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"19.25 ns"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1,000,000,000"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"207.81"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1600%"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"100.00%"})]}),(0,r.jsxs)(e.tr,{children:[(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1000"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"33.91 ns"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1,000,000,000"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"235.92"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1600%"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"100.00%"})]}),(0,r.jsxs)(e.tr,{children:[(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"5000"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"162.1 ns"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"448,785,386"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"246.71"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1600%"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"100.00%"})]}),(0,r.jsxs)(e.tr,{children:[(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"10000"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"332.0 ns"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"208,428,151"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"240.94"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1600%"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"100.00%"})]}),(0,r.jsxs)(e.tr,{children:[(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"50000"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1,740 ns"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"39,247,646"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"229.93"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1600%"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"100.00%"})]}),(0,r.jsxs)(e.tr,{children:[(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"100000"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"3,401 ns"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"19,598,293"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"235.24"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"1600%"}),(0,r.jsx)(e.td,{style:{textAlign:"right"},children:"100.00%"})]})]})]}),"\n",(0,r.jsxs)(e.blockquote,{children:["\n",(0,r.jsx)(e.p,{children:"Normalization: Total CPU (normalized) = Total CPU (raw) / 16, since 1600% equals full utilization on a 16\u2011core machine."}),"\n"]}),"\n",(0,r.jsx)(e.h2,{id:"observations",children:"Observations"}),"\n",(0,r.jsxs)(e.ul,{children:["\n",(0,r.jsxs)(e.li,{children:[(0,r.jsx)(e.strong,{children:"SIMD provides large speedups across all vector sizes"}),": overall throughput improvements range from roughly ",(0,r.jsx)(e.strong,{children:"4\u201315\xd7"})," versus Without SIMD."]}),"\n",(0,r.jsxs)(e.li,{children:["For small vectors (10\u2013100), throughput gains are about ",(0,r.jsx)(e.strong,{children:"9\u201315\xd7"}),", with ns/op reduced proportionally."]}),"\n",(0,r.jsxs)(e.li,{children:["For larger vectors (500\u2013100000), speedups stabilize around ",(0,r.jsx)(e.strong,{children:"~4\u20137\xd7"})," as memory bandwidth pressure increases."]}),"\n",(0,r.jsxs)(e.li,{children:[(0,r.jsx)(e.strong,{children:"CPU saturation"}),": Without SIMD reaches 100% normalized CPU at and beyond 500 elements, whereas With SIMD typically operates at ~95\u201399% normalized CPU yet delivers substantially higher throughput at similar CPU."]}),"\n",(0,r.jsxs)(e.li,{children:[(0,r.jsx)(e.strong,{children:"Per\u2011CPU efficiency"}),": With SIMD, throughput per unit of CPU is much higher, reflecting better vector unit utilization and fewer instructions per element."]}),"\n",(0,r.jsx)(e.li,{children:"Absolute values depend on hardware and load; the relative differential reflects the benefit of compiler SIMD optimizations."}),"\n"]}),"\n",(0,r.jsxs)(e.blockquote,{children:["\n",(0,r.jsx)(e.p,{children:"\u26a0 Note: Absolute numbers depend on CPU frequency, memory locality, and system load. These results are meant\nto show relative SIMD benefits."}),"\n"]})]})}function g(t={}){const{wrapper:e}={...(0,n.R)(),...t.components};return e?(0,r.jsx)(e,{...t,children:(0,r.jsx)(c,{...t})}):c(t)}},8453:(t,e,i)=>{i.d(e,{R:()=>l,x:()=>h});var s=i(6540);const r={},n=s.createContext(r);function l(t){const e=s.useContext(n);return s.useMemo(function(){return"function"==typeof t?t(e):{...e,...t}},[e,t])}function h(t){let e;return e=t.disableParentContext?"function"==typeof t.components?t.components(r):t.components||r:l(t.components),s.createElement(n.Provider,{value:e},t.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/e8321834.dbcc9814.js b/docs/assets/js/e8321834.dbcc9814.js new file mode 100644 index 00000000..2756e845 --- /dev/null +++ b/docs/assets/js/e8321834.dbcc9814.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[149],{5842:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>d,contentTitle:()=>o,default:()=>h,frontMatter:()=>l,metadata:()=>s,toc:()=>c});const s=JSON.parse('{"id":"predator/v1.0.0/functionalities","title":"Key Functionalities","description":"Overview","source":"@site/docs/predator/v1.0.0/functionalities.md","sourceDirName":"predator/v1.0.0","slug":"/predator/v1.0.0/functionalities","permalink":"/BharatMLStack/predator/v1.0.0/functionalities","draft":false,"unlisted":false,"editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/docs/predator/v1.0.0/functionalities.md","tags":[],"version":"current","sidebarPosition":2,"frontMatter":{"title":"Key Functionalities","sidebar_position":2},"sidebar":"tutorialSidebar","previous":{"title":"Architecture","permalink":"/BharatMLStack/predator/v1.0.0/architecture"},"next":{"title":"Release Notes","permalink":"/BharatMLStack/predator/v1.0.0/release-notes"}}');var r=i(4848),t=i(8453);const l={title:"Key Functionalities",sidebar_position:2},o="Predator - Key Functionalities",d={},c=[{value:"Overview",id:"overview",level:2},{value:"Core Capabilities",id:"core-capabilities",level:2},{value:"Multi-Backend Inference",id:"multi-backend-inference",level:3},{value:"Dynamic Batching",id:"dynamic-batching",level:3},{value:"Concurrent Model Execution",id:"concurrent-model-execution",level:3},{value:"Model Versioning & Ensembles",id:"model-versioning--ensembles",level:3},{value:"Model Instance Scaling",id:"model-instance-scaling",level:3},{value:"Inference & API",id:"inference--api",level:2},{value:"gRPC via Helix Client",id:"grpc-via-helix-client",level:3},{value:"Model Repository",id:"model-repository",level:3},{value:"Deployment & Operational Features",id:"deployment--operational-features",level:2},{value:"Custom Triton Images",id:"custom-triton-images",level:3},{value:"Image Distribution",id:"image-distribution",level:3},{value:"Health Probes",id:"health-probes",level:3},{value:"Autoscaling",id:"autoscaling",level:3},{value:"Observability",id:"observability",level:2},{value:"Contributing",id:"contributing",level:2},{value:"Community & Support",id:"community--support",level:2},{value:"License",id:"license",level:2}];function a(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",li:"li",p:"p",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,t.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"predator---key-functionalities",children:"Predator - Key Functionalities"})}),"\n",(0,r.jsx)(n.h2,{id:"overview",children:"Overview"}),"\n",(0,r.jsxs)(n.p,{children:["Predator is a scalable, high-performance model inference service built as a wrapper around ",(0,r.jsx)(n.strong,{children:"NVIDIA Triton Inference Server"}),". It serves Deep Learning and tree-based models with low latency in ",(0,r.jsx)(n.strong,{children:"Kubernetes"}),", integrates with the ",(0,r.jsx)(n.strong,{children:"Online Feature Store (OnFS)"})," and uses ",(0,r.jsx)(n.strong,{children:"Interflow"})," for orchestration between clients, feature store, and inference engine. Clients send inference requests via the ",(0,r.jsx)(n.strong,{children:"Helix client"})," over gRPC."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"core-capabilities",children:"Core Capabilities"}),"\n",(0,r.jsx)(n.h3,{id:"multi-backend-inference",children:"Multi-Backend Inference"}),"\n",(0,r.jsx)(n.p,{children:"Predator leverages Triton's pluggable backends so you can serve a variety of model types from a single deployment:"}),"\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Backend"}),(0,r.jsx)(n.th,{children:"Use Case"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"TensorRT"})}),(0,r.jsx)(n.td,{children:"GPU-optimized DL; serialized engines (FP16/INT8)"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"PyTorch"})}),(0,r.jsx)(n.td,{children:"Native PyTorch via LibTorch"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"ONNX Runtime"})}),(0,r.jsx)(n.td,{children:"Framework-agnostic ONNX with TensorRT/GPU"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"TensorFlow"})}),(0,r.jsx)(n.td,{children:"SavedModel format"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Python"})}),(0,r.jsx)(n.td,{children:"Custom preprocessing, postprocessing, or unsupported models"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"FIL"})}),(0,r.jsx)(n.td,{children:"Tree-based models (XGBoost, LightGBM, Random Forest) on GPU"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"DALI"})}),(0,r.jsx)(n.td,{children:"GPU-accelerated data preprocessing (image, audio, video)"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Custom"})}),(0,r.jsx)(n.td,{children:"C++/Python backends for proprietary or specialized runtimes"})]})]})]}),"\n",(0,r.jsx)(n.h3,{id:"dynamic-batching",children:"Dynamic Batching"}),"\n",(0,r.jsx)(n.p,{children:"Triton combines multiple incoming requests into a single batch at runtime."}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Higher GPU utilization and improved throughput"}),"\n",(0,r.jsx)(n.li,{children:"Reduced latency variance"}),"\n",(0,r.jsxs)(n.li,{children:["Configurable ",(0,r.jsx)(n.code,{children:"preferred_batch_size"})," and ",(0,r.jsx)(n.code,{children:"max_queue_delay_microseconds"})," in ",(0,r.jsx)(n.code,{children:"config.pbtxt"})]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"concurrent-model-execution",children:"Concurrent Model Execution"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Run multiple models simultaneously"}),"\n",(0,r.jsx)(n.li,{children:"Run multiple instances of the same model"}),"\n",(0,r.jsxs)(n.li,{children:["Distribute load across GPUs via ",(0,r.jsx)(n.code,{children:"instance_group"})," in model config"]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"model-versioning--ensembles",children:"Model Versioning & Ensembles"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Versioning"}),": Multiple versions per model (e.g. ",(0,r.jsx)(n.code,{children:"1/"}),", ",(0,r.jsx)(n.code,{children:"2/"})," in the model repository)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Ensembles"}),": Define a pipeline of models as an ensemble; eliminates intermediate network hops and reduces latency"]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"model-instance-scaling",children:"Model Instance Scaling"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Deploy multiple copies of a model for parallel inference and load isolation"}),"\n",(0,r.jsxs)(n.li,{children:["Configured via ",(0,r.jsx)(n.code,{children:"instance_group"})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"inference--api",children:"Inference & API"}),"\n",(0,r.jsx)(n.h3,{id:"grpc-via-helix-client",children:"gRPC via Helix Client"}),"\n",(0,r.jsxs)(n.p,{children:["Predator uses ",(0,r.jsx)(n.strong,{children:"gRPC"})," for efficient request/response handling. Client applications (e.g. Realestate, IOP) send inference requests through the ",(0,r.jsx)(n.strong,{children:"Helix client"}),", which talks to the Triton Inference Server inside the Predator pod."]}),"\n",(0,r.jsx)(n.h3,{id:"model-repository",children:"Model Repository"}),"\n",(0,r.jsxs)(n.p,{children:["Models are stored in a local model repository. Predator materializes this via an ",(0,r.jsx)(n.strong,{children:"Init Container"})," that downloads artifacts from cloud storage (e.g. GCS) so Triton has no runtime dependency on remote storage during inference."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"deployment--operational-features",children:"Deployment & Operational Features"}),"\n",(0,r.jsx)(n.h3,{id:"custom-triton-images",children:"Custom Triton Images"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Production uses ",(0,r.jsx)(n.strong,{children:"custom-built"})," Triton images (only required backends) for smaller size and faster startup"]}),"\n",(0,r.jsxs)(n.li,{children:["Images built on GCP VM, pushed to ",(0,r.jsx)(n.strong,{children:"Artifact Registry"}),", and referenced in Helm deployments"]}),"\n",(0,r.jsxs)(n.li,{children:["Optional ",(0,r.jsx)(n.strong,{children:"response caching"})," via custom cache plugins added at image build time"]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"image-distribution",children:"Image Distribution"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Secondary boot disk caching"}),": Triton image pre-cached on GPU node pool to reduce pod startup and scale-up latency"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Image streaming"}),": Optionally used for faster time-to-readiness during scaling"]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"health-probes",children:"Health Probes"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Readiness and liveness use ",(0,r.jsx)(n.code,{children:"/v2/health/ready"})]}),"\n",(0,r.jsx)(n.li,{children:"Triton receives traffic only after models are loaded; failed instances are restarted automatically"}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"autoscaling",children:"Autoscaling"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"CPU-based scaling for generic load"}),"\n",(0,r.jsxs)(n.li,{children:["GPU-based scaling using ",(0,r.jsx)(n.strong,{children:"DCGM"})," metrics (utilization, memory, power); custom queries drive scale-up/scale-down"]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"observability",children:"Observability"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Prometheus metrics"}),": Latency, throughput, GPU utilization, and more"]}),"\n",(0,r.jsxs)(n.li,{children:["Metrics emitted from the Triton Inference Container and visualized in ",(0,r.jsx)(n.strong,{children:"Grafana"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Warmup requests"}),": Configurable to preload kernels and avoid cold-start latency"]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"contributing",children:"Contributing"}),"\n",(0,r.jsxs)(n.p,{children:["We welcome contributions! See the ",(0,r.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/CONTRIBUTING.md",children:"Contributing Guide"}),"."]}),"\n",(0,r.jsx)(n.h2,{id:"community--support",children:"Community & Support"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Discord"}),": ",(0,r.jsx)(n.a,{href:"https://discord.gg/XkT7XsV2AU",children:"community chat"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Issues"}),": ",(0,r.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/issues",children:"GitHub Issues"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Email"}),": ",(0,r.jsx)(n.a,{href:"mailto:ml-oss@meesho.com",children:"ml-oss@meesho.com"})]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"license",children:"License"}),"\n",(0,r.jsxs)(n.p,{children:["BharatMLStack is open-source under the ",(0,r.jsx)(n.a,{href:"https://github.com/Meesho/BharatMLStack/blob/main/LICENSE.md",children:"BharatMLStack Business Source License 1.1"}),"."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)("div",{align:"center",children:(0,r.jsx)("strong",{children:"Built with \u2764\ufe0f for the ML community from Meesho"})}),"\n",(0,r.jsx)("div",{align:"center",children:(0,r.jsx)("strong",{children:"If you find this useful, \u2b50\ufe0f the repo \u2014 your support means the world to us!"})})]})}function h(e={}){const{wrapper:n}={...(0,t.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(a,{...e})}):a(e)}},8453:(e,n,i)=>{i.d(n,{R:()=>l,x:()=>o});var s=i(6540);const r={},t=s.createContext(r);function l(e){const n=s.useContext(t);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:l(e.components),s.createElement(t.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/f2c141e4.e3b70339.js b/docs/assets/js/f2c141e4.e3b70339.js deleted file mode 100644 index 6ac4e93c..00000000 --- a/docs/assets/js/f2c141e4.e3b70339.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[1909],{161:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>l,contentTitle:()=>o,default:()=>c,frontMatter:()=>a,metadata:()=>t,toc:()=>d});var t=i(3983),s=i(4848),r=i(8453);const a={slug:"post-one",title:"Building Meesho\u2019s ML Platform: From Chaos to Cutting-Edge (Part 1)",authors:["adarsha","aditya","bhawani","jigar"],date:new Date("2022-11-15T00:00:00.000Z"),tags:["online-feature-store","interaction-store","mlplatform","meesho"]},o=void 0,l={authorsImageUrls:[void 0,void 0,void 0,void 0]},d=[{value:"The Genesis: How a Friday Night Roast Sparked Meesho\u2019s ML Platform",id:"the-genesis-how-a-friday-night-roast-sparked-meeshos-ml-platform",level:2},{value:"The Turning Point: From Batch to Real-Time",id:"the-turning-point-from-batch-to-real-time",level:2},{value:"First Generation Design",id:"first-generation-design",level:2},{value:"1. IOP Framework: A Real-Time DAG Executor",id:"1-iop-framework-a-real-time-dag-executor",level:3},{value:"2. Online Feature Store - 0th Version",id:"2-online-feature-store---0th-version",level:3},{value:"3. Interaction Store - 0th Version",id:"3-interaction-store---0th-version",level:3},{value:"Building the Online Feature Store - 0th Version",id:"building-the-online-feature-store---0th-version",level:2},{value:"Choosing the Right Tech Stack",id:"choosing-the-right-tech-stack",level:3},{value:"Streamlining the Data Flow",id:"streamlining-the-data-flow",level:3},{value:"The Challenges: Data Format and Storage",id:"the-challenges-data-format-and-storage",level:2},{value:"Feature Consistency",id:"feature-consistency",level:3},{value:"TTL Granularity",id:"ttl-granularity",level:3},{value:"Extensibility Across Databases",id:"extensibility-across-databases",level:3},{value:"Overcoming Technical Constraints",id:"overcoming-technical-constraints",level:2},{value:"The Solution: Schema Separation",id:"the-solution-schema-separation",level:2},{value:"Tracking Changes in Feature Groups",id:"tracking-changes-in-feature-groups",level:2},{value:"Common Real-World Scenarios:",id:"common-real-world-scenarios",level:3},{value:"The Solution: Schema Versioning",id:"the-solution-schema-versioning",level:2},{value:"Backward Compatibility",id:"backward-compatibility",level:3},{value:"Partial Availability Handling",id:"partial-availability-handling",level:3},{value:"Safe Writes Without Pipeline Pauses",id:"safe-writes-without-pipeline-pauses",level:3},{value:"Interaction Store - 0th Version",id:"interaction-store---0th-version",level:2},{value:"Event Ingestion",id:"event-ingestion",level:2},{value:"Storage Design",id:"storage-design",level:2},{value:"Why Redis?",id:"why-redis",level:3},{value:"Storage Structure",id:"storage-structure",level:3},{value:"Built-in Guardrails",id:"built-in-guardrails",level:3},{value:"Conclusion: Laying the Foundation for Real-Time ML",id:"conclusion-laying-the-foundation-for-real-time-ml",level:2}];function h(e){const n={a:"a",br:"br",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",hr:"hr",img:"img",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,r.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"BharatMLStack",src:i(9930).A+"",width:"1472",height:"892"})}),"\n",(0,s.jsx)(n.h2,{id:"the-genesis-how-a-friday-night-roast-sparked-meeshos-ml-platform",children:"The Genesis: How a Friday Night Roast Sparked Meesho\u2019s ML Platform"}),"\n",(0,s.jsx)(n.p,{children:"It all started in early 2022, over a casual Friday evening catch-up. Like many great origin stories, this one began with friendly banter between a group of backend engineers and data scientists. As the conversations unfolded, so did the roasting\u2014until one remark hit a little too close to home:"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.em,{children:'"Why are we still crunching data for Monthly Active Users (MAU) when the next day it\u2019s all about Daily Active Users (DAU)?"'})}),"\n",(0,s.jsx)(n.p,{children:"The laughter died down, and the question lingered. When we regrouped on Monday\u2014clear-headed and slightly reflective\u2014we decided to dig into the numbers. What they discovered was quite revealing: a large portion of compute resources wasn\u2019t being put to good use.\nMuch of the system\u2019s effort was spent supporting users who weren\u2019t actively engaging, and even for new users, the experience wasn\u2019t optimized to make a meaningful impact."}),"\n",(0,s.jsxs)(n.p,{children:["At the same time, Meesho had just launched a company-wide initiative to reduce costs\u2014and every team had to contribute. This realization sparked the journey that would eventually lead to the ",(0,s.jsx)(n.strong,{children:"Meesho ML Platform"}),", known today as ",(0,s.jsx)(n.strong,{children:"BharatMLStack"}),"."]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Alt Text",src:i(3518).A+"",width:"1600",height:"1078"})}),"\n",(0,s.jsx)(n.p,{children:"Before the ML Platform, our recommendation and ranking pipelines followed a batch processing approach:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Data Ingestion"}),": The Data Platform team executed ETL jobs to ingest raw user data\u2014including user profiles, interaction logs, and product impressions\u2014into designated S3 buckets."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Layer 1"}),": Embedding Generation: On the Data Science side, Spark jobs pulled data from multiple S3 sources, cleaned and preprocessed it, and applied matrix factorization to generate user and item embeddings. The processed data and embeddings were then stored back in S3 in a structured format."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Layer 2"}),": Candidate Generation (CG): In this stage, Spark jobs leveraged embeddings and historical interaction data to generate candidate recommendations for users. These candidate lists were subsequently written to S3."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Layer 3"}),": Ranking and Merging \u2013 A final round of processing ranked the generated candidates using ML models, combined different candidate lists, and stored the final ranked recommendations in a caching system."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Serving"}),': A microservice retrieved ranked recommendations from an in-memory data store via exposed APIs, delivering personalized listings across key surfaces such as "For You" and Category Landing Pages (CLP).']}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"This approach held up well\u2014until Meesho started seeing a significant surge in traffic."}),"\n",(0,s.jsx)(n.h2,{id:"the-turning-point-from-batch-to-real-time",children:"The Turning Point: From Batch to Real-Time"}),"\n",(0,s.jsxs)(n.p,{children:["At this time, the team was iterating on new ",(0,s.jsx)(n.strong,{children:"Ranker models"}),", and real-time inference seemed like the next logical step. But Rankers needed ",(0,s.jsx)(n.strong,{children:"real-time feature retrieval"}),", which meant an ",(0,s.jsx)(n.strong,{children:"online feature store"})," had to be built first."]}),"\n",(0,s.jsxs)(n.p,{children:["Exploring open-source options led to ",(0,s.jsx)(n.strong,{children:"cost vs. performance trade-offs"}),", but Meesho\u2019s surging traffic meant that ",(0,s.jsx)(n.strong,{children:"latency and stability were non-negotiable"}),". After multiple debates and stakeholder discussions, a bold decision was made:"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.em,{children:"We would build our own feature store."})}),"\n",(0,s.jsxs)(n.p,{children:["Meanwhile, efforts began to bring ",(0,s.jsx)(n.strong,{children:"Candidate Generators (CGs)"})," to real-time. The challenge? ",(0,s.jsx)(n.strong,{children:"Storing and retrieving user interactions quickly enough"})," to power real-time recommendations."]}),"\n",(0,s.jsxs)(n.p,{children:["As the team dove deeper, a new roadblock emerged:",(0,s.jsx)(n.br,{}),"\n","Our ML jobs were orchestrated using ",(0,s.jsx)(n.strong,{children:"Airflow DAGs"}),", giving data scientists flexibility in experimentation. But transitioning to real-time execution threatened this agility. Every change would now require backend engineering support, ",(0,s.jsx)(n.strong,{children:"slowing down iteration cycles"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["That\u2019s when the idea struck:",(0,s.jsx)(n.br,{}),"\n","We needed a ",(0,s.jsx)(n.strong,{children:"framework for real-time DAG execution"}),"\u2014one that preserved the same flexibility as Airflow but worked for ",(0,s.jsx)(n.strong,{children:"streaming data"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["This moment shaped the ",(0,s.jsx)(n.strong,{children:"next phase of our journey"}),"."]}),"\n",(0,s.jsx)(n.h2,{id:"first-generation-design",children:"First Generation Design"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Alt Text",src:i(7131).A+"",width:"1600",height:"1006"})}),"\n",(0,s.jsx)(n.h1,{id:"laying-the-groundwork-the-first-gen-ml-platform",children:"Laying the Groundwork: The First-Gen ML Platform"}),"\n",(0,s.jsx)(n.p,{children:"To solve these challenges, the team built three foundational components:"}),"\n",(0,s.jsx)(n.h3,{id:"1-iop-framework-a-real-time-dag-executor",children:"1. IOP Framework: A Real-Time DAG Executor"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Reusable Nodes"}),": Each DAG node (e.g., an invocation to a CG service, a ranker, or a filter) had to be implemented only once. After that, it could be reused across any workflow by referencing it in config."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Config-driven Dynamic Graphs"}),": Execution graphs were defined as adjacency lists stored in ",(0,s.jsx)(n.strong,{children:"ZooKeeper"}),", allowing teams to modify the sequence or structure of operations without touching application code."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Plug-and-play CGs"}),": The Candidate Generator interface was preserved, so a single CG node could call any CG service by passing ",(0,s.jsx)(n.code,{children:"cg_name"})," in the request. This drastically reduced the code surface area and improved maintainability."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Production-Grade DAGs"}),": DAGs were designed to execute in ",(0,s.jsx)(n.strong,{children:"low-latency real-time environments"}),", with support for ",(0,s.jsx)(n.strong,{children:"parallel execution, retries, and branching"}),"."]}),"\n"]}),"\n",(0,s.jsx)("u",{children:(0,s.jsx)(n.a,{href:"https://www.meesho.io/blog/rebuilding-meeshos-ranking-platform",children:"More about IOP DAG"})}),"\n",(0,s.jsx)(n.h3,{id:"2-online-feature-store---0th-version",children:"2. Online Feature Store - 0th Version"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Used ",(0,s.jsx)(n.strong,{children:"Cassandra"})," and ",(0,s.jsx)(n.strong,{children:"Redis"})," for low-latency feature serving."]}),"\n",(0,s.jsxs)(n.li,{children:["Maintained feature consistency using ",(0,s.jsx)(n.strong,{children:"Feature Groups"})," with TTL-based expiry."]}),"\n",(0,s.jsxs)(n.li,{children:["A hybrid schema was used: feature keys stored in ",(0,s.jsx)(n.strong,{children:"ZooKeeper"}),", data stored in ",(0,s.jsx)(n.strong,{children:"compact arrays"}),"."]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"3-interaction-store---0th-version",children:"3. Interaction Store - 0th Version"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Captured real-time user interactions like clicks, orders, and add-to-cart events."}),"\n",(0,s.jsxs)(n.li,{children:["Stored event data in ",(0,s.jsx)(n.strong,{children:"Redis ZSETs (sorted sets)"})," to enable fast lookups for recommendation engines."]}),"\n",(0,s.jsxs)(n.li,{children:["Provided an API to fetch a user's ",(0,s.jsxs)(n.strong,{children:["last ",(0,s.jsx)(n.em,{children:"k"})," interactions"]})," or ",(0,s.jsx)(n.strong,{children:"interactions within a time window"}),"."]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["With these components in place, ",(0,s.jsx)(n.strong,{children:"real-time ML at Meesho became a reality"}),"."]}),"\n",(0,s.jsx)(n.p,{children:"This was just the beginning."}),"\n",(0,s.jsx)(n.h2,{id:"building-the-online-feature-store---0th-version",children:"Building the Online Feature Store - 0th Version"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Alt text",src:i(5728).A+"",width:"1574",height:"562"})}),"\n",(0,s.jsx)(n.h3,{id:"choosing-the-right-tech-stack",children:"Choosing the Right Tech Stack"}),"\n",(0,s.jsxs)(n.p,{children:["We spent considerable time evaluating various databases, caches, and communication protocols for our ",(0,s.jsx)(n.strong,{children:"online feature store"}),". After carefully weighing ",(0,s.jsx)(n.strong,{children:"cost, latency, throughput"}),", and ",(0,s.jsx)(n.strong,{children:"operational stability"}),", we settled on a combination of:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Cassandra"})," and ",(0,s.jsx)(n.strong,{children:"Redis"})," for storage"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"gRPC + Proto3"})," as our communication layer"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"streamlining-the-data-flow",children:"Streamlining the Data Flow"}),"\n",(0,s.jsx)(n.p,{children:"To keep things simple in the initial version:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Feature engineering jobs"})," wrote raw outputs to an ",(0,s.jsx)(n.strong,{children:"S3 bucket"})]}),"\n",(0,s.jsxs)(n.li,{children:["A ",(0,s.jsx)(n.strong,{children:"daily feature push job"}),":","\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Read from S3"}),"\n",(0,s.jsxs)(n.li,{children:["Grouped related features into ",(0,s.jsx)(n.strong,{children:"Feature Groups"})," (ensuring consistency)"]}),"\n",(0,s.jsxs)(n.li,{children:["Pushed them to ",(0,s.jsx)(n.strong,{children:"Kafka"})]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"For features requiring frequent updates:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Ad-hoc jobs"})," computed features in higher frequency"]}),"\n",(0,s.jsxs)(n.li,{children:["These jobs pushed to both ",(0,s.jsx)(n.strong,{children:"Kafka"})," and ",(0,s.jsx)(n.strong,{children:"S3"})," (S3 preserved historical data for future model training)"]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"the-challenges-data-format-and-storage",children:"The Challenges: Data Format and Storage"}),"\n",(0,s.jsxs)(n.p,{children:["One of the most critical design challenges was how to store feature data ",(0,s.jsx)(n.strong,{children:"efficiently and consistently"}),", especially in databases like ",(0,s.jsx)(n.strong,{children:"Cassandra"})," and ",(0,s.jsx)(n.strong,{children:"Redis"}),", which come with unique storage constraints."]}),"\n",(0,s.jsx)(n.p,{children:"We had to solve for three key requirements:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.h3,{id:"feature-consistency",children:"Feature Consistency"}),"\n",(0,s.jsxs)(n.p,{children:["When a feature group contains features like ",(0,s.jsx)(n.code,{children:"order_count_1h"})," and ",(0,s.jsx)(n.code,{children:"click_count_1h"}),", both must reflect the ",(0,s.jsx)(n.strong,{children:"same time window"}),". Inconsistent updates would lead to ",(0,s.jsx)(n.strong,{children:"unreliable model predictions"}),"."]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.h3,{id:"ttl-granularity",children:"TTL Granularity"}),"\n",(0,s.jsxs)(n.p,{children:["Each feature group required an ",(0,s.jsx)(n.strong,{children:"expiry timestamp"}),", so that ",(0,s.jsx)(n.strong,{children:"all features within it expired together"}),"\u2014preserving consistency during reads."]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.h3,{id:"extensibility-across-databases",children:"Extensibility Across Databases"}),"\n",(0,s.jsxs)(n.p,{children:["We anticipated that infra needs would evolve. To future-proof our system, the data format was designed to be ",(0,s.jsx)(n.strong,{children:"decoupled from DB-specific layouts"}),", enabling portability to systems like ",(0,s.jsx)(n.strong,{children:"ScyllaDB"}),", ",(0,s.jsx)(n.strong,{children:"DynamoDB"}),", ",(0,s.jsx)(n.strong,{children:"HBase"}),", or ",(0,s.jsx)(n.strong,{children:"BigTable"}),"."]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h2,{id:"overcoming-technical-constraints",children:"Overcoming Technical Constraints"}),"\n",(0,s.jsx)(n.p,{children:'At the time, we were using Cassandra, which not only imposed a soft limit of 75 columns per row, but also exhibited significant performance degradation as the number of columns increased further, particularly in memory constrained machines. Wide rows caused high memory usage during reads, unpredictable latencies due to heavy deserialization overhead, and inefficiencies during compactions and repairs. This ruled out the naive "one column per feature" approach. We needed a format that was compact, minimized the number of columns, and remained efficient and portable across different storage systems.'}),"\n",(0,s.jsx)(n.h2,{id:"the-solution-schema-separation",children:"The Solution: Schema Separation"}),"\n",(0,s.jsx)(n.p,{children:"We introduced the concept of Feature Groups\u2014logical groupings of features that must remain consistent with one another.\nTo represent these groups efficiently, we adopted a layered storage approach:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Feature Labels (Keys)"})," were stored in ZooKeeper, serving as the schema."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Feature Values"})," were stored as a comma-separated string array in Cassandra or Redis."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Expiry Timestamp and Schema Version"})," were appended using a semi-colon delimiter at the end of the string."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Example:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"feature_1_value,feature_2_value,feature_3_value;expiry_ts\n"})}),"\n",(0,s.jsx)(n.p,{children:"This format allowed:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Consistent writes and reads at the group level"}),"\n",(0,s.jsx)(n.li,{children:"Easy parsing of feature values using the schema lookup from ZooKeeper"}),"\n",(0,s.jsx)(n.li,{children:"Efficient storage with minimal DB column usage"}),"\n",(0,s.jsx)(n.li,{children:"Support for per-group TTLs and schema evolution"}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"tracking-changes-in-feature-groups",children:"Tracking Changes in Feature Groups"}),"\n",(0,s.jsx)(n.p,{children:"Feature groups don\u2019t stay static. As models evolve, features get added, renamed, or removed. But schema changes often go live before the data is ready\u2014and stopping ingestion just to wait for everything to align isn't feasible."}),"\n",(0,s.jsx)(n.h3,{id:"common-real-world-scenarios",children:"Common Real-World Scenarios:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"A new feature is added to the schema, but ingestion jobs still use the older schema version."}),"\n",(0,s.jsx)(n.li,{children:"Ongoing writes don\u2019t include the newly added feature, and stopping ingestion would break freshness for existing features."}),"\n",(0,s.jsx)(n.li,{children:"During serving, models request a mix of old and new features, depending on rollout stages."}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"the-solution-schema-versioning",children:"The Solution: Schema Versioning"}),"\n",(0,s.jsx)(n.p,{children:"We solved this with versioned feature group schemas, which unlocked several capabilities:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.h3,{id:"backward-compatibility",children:"Backward Compatibility"}),"\n","Older ingestion jobs can continue writing using older schema versions. During reads, the system uses the schema version embedded in the value to interpret the data correctly."]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.h3,{id:"partial-availability-handling",children:"Partial Availability Handling"}),"\n","During inference, if some features in the request aren\u2019t available (due to rollout delays or missing data), the system serves default values, ensuring the inference call doesn\u2019t fail."]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.h3,{id:"safe-writes-without-pipeline-pauses",children:"Safe Writes Without Pipeline Pauses"}),"\n","With schema versioning, we no longer had to stop ingestion pipelines for schema updates. Writes using previous versions can continue safely, and downstream consumers evolve independently.\nThis design gave us the flexibility to move fast without breaking things\u2014preserving data quality, enabling experimentation, and ensuring reliability at scale."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Alt Text",src:i(3190).A+"",width:"1600",height:"599"})}),"\n",(0,s.jsx)(n.h2,{id:"interaction-store---0th-version",children:"Interaction Store - 0th Version"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Alt Text",src:i(1012).A+"",width:"1600",height:"518"})}),"\n",(0,s.jsxs)(n.p,{children:["To power real-time Candidate Generators (CGs), we needed fast access to user behavior signals\u2014like what a user recently clicked, ordered, or added to their cart. These interactions form the basis for many real-time recommendations, such as ",(0,s.jsx)(n.strong,{children:"Similar Products"}),", ",(0,s.jsx)(n.strong,{children:"People Also Viewed"}),", or ",(0,s.jsx)(n.strong,{children:"Recently Ordered Again"}),".\nFor the ",(0,s.jsx)(n.strong,{children:"0th version"})," of the Interaction Store, we focused on a design that was ",(0,s.jsx)(n.strong,{children:"simple, fast, and reliable"})," \u2014 optimized for high-throughput ingestion and low-latency lookups."]}),"\n",(0,s.jsx)(n.h2,{id:"event-ingestion",children:"Event Ingestion"}),"\n",(0,s.jsx)(n.p,{children:"We instrumented our backend services to emit key user interaction events to Kafka in real time. These included:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Click"}),"\n",(0,s.jsx)(n.li,{children:"Order"}),"\n",(0,s.jsx)(n.li,{children:"Add to Cart"}),"\n",(0,s.jsx)(n.li,{children:"Wishlist"}),"\n",(0,s.jsx)(n.li,{children:"Share"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Each event carried essential metadata:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"userId \u2014 uniquely identifies the user"}),"\n",(0,s.jsx)(n.li,{children:"productId \u2014 the item being interacted with"}),"\n",(0,s.jsx)(n.li,{children:"timestamp \u2014 the moment the interaction occurred"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"This decoupled the interaction logging from storage, allowing ingestion and consumption to scale independently."}),"\n",(0,s.jsx)(n.h2,{id:"storage-design",children:"Storage Design"}),"\n",(0,s.jsx)(n.p,{children:"To store these events, we built Kafka consumers that processed the incoming streams and wrote the data into Redis, using sorted sets (ZSETs) as the primary data structure."}),"\n",(0,s.jsx)(n.h3,{id:"why-redis",children:"Why Redis?"}),"\n",(0,s.jsx)(n.p,{children:"Redis gave us:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Low-latency"})," reads and writes"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Time-ordered data"})," using ZSETs (via score = timestamp)"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Native TTL support"}),", if needed in later versions"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"In-memory performance"})," \u2014ideal for real-time CGs"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"storage-structure",children:"Storage Structure"}),"\n",(0,s.jsx)(n.p,{children:"Each user\u2019s interactions were stored using a composite key format, uniquely identifying the user and interaction type. This structure allowed efficient organization and quick retrieval of recent activity for recommendation generation:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"userId_eventType \u2192 ZSET[...(pid, ts)...]\n"})}),"\n",(0,s.jsx)(n.p,{children:"Within each ZSET:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["The ",(0,s.jsx)(n.strong,{children:"timestamp"})," served as the score, maintaining temporal order"]}),"\n",(0,s.jsxs)(n.li,{children:["The ",(0,s.jsx)(n.strong,{children:"productId"})," (optionally with metadata) was the ",(0,s.jsx)(n.strong,{children:"value"})]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"This allowed us to efficiently retrieve the interactions with HTTP-based API server with two query modes:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Fetch the ",(0,s.jsx)(n.strong,{children:"last k interactions"})," of a specific type for a given user with ",(0,s.jsx)(n.code,{children:"ZREVRANGE(userId_eventType, count)"})]}),"\n",(0,s.jsxs)(n.li,{children:["Retrieve ",(0,s.jsx)(n.strong,{children:"all interactions within a time range"})," (e.g., last 24 hours) with ",(0,s.jsx)(n.code,{children:"ZREVRANGEBYSCORE(userId_eventType, timeRange)"})]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"built-in-guardrails",children:"Built-in Guardrails"}),"\n",(0,s.jsx)(n.p,{children:"Since Redis was the sole store, we implemented High Availability (HA) to prevent data loss. To optimize memory usage, we also enforced size limits per event type\u2014only storing the last k interactions per user, with older entries getting truncated."}),"\n",(0,s.jsx)(n.h2,{id:"conclusion-laying-the-foundation-for-real-time-ml",children:"Conclusion: Laying the Foundation for Real-Time ML"}),"\n",(0,s.jsxs)(n.p,{children:["In this first phase, we tackled the ",(0,s.jsx)(n.strong,{children:"fundamentals"}),"\u2014shifting from batch-based recommendations to a ",(0,s.jsx)(n.strong,{children:"real-time Recommendation"})," using ML platform that could keep up with Meesho\u2019s growth."]}),"\n",(0,s.jsxs)(n.p,{children:["With the ",(0,s.jsx)(n.strong,{children:"IOP Framework"}),", ",(0,s.jsx)(n.strong,{children:"Online Feature Store"}),", and ",(0,s.jsx)(n.strong,{children:"Interaction Store"}),", we built the core infrastructure to support real-time personalization at scale. These wins have already unlocked:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"\u2705 Faster, more dynamic recommendations for millions of users."}),"\n",(0,s.jsx)(n.li,{children:"\u2705 Better infrastructure efficiency, reducing wasted compute power."}),"\n",(0,s.jsx)(n.li,{children:"\u2705 A flexible, modular system that allows for further experimentation."}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["But this is just the beginning. While we've solved key challenges, ",(0,s.jsx)(n.strong,{children:"certain roadblocks remain"})," \u2014from optimizing ",(0,s.jsx)(n.strong,{children:"cost-performance trade-offs"})," to ",(0,s.jsx)(n.strong,{children:"seamlessly evolving schemas"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["This foundational work laid the path for a reliable and scalable ",(0,s.jsx)(n.strong,{children:"real-time feature serving layer"}),"."]})]})}function c(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(h,{...e})}):h(e)}},1012:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/interaction-store-v0-68167b64c6e462ef2f177f0f86d55bda.png"},3190:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/schema-d699efc400ed0f83bba421c1f55ab211.png"},3518:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/old-batch-arch-bc2cedbc1fed0fc6f08479ba8fe52996.png"},3983:e=>{e.exports=JSON.parse('{"permalink":"/BharatMLStack/blog/post-one","editUrl":"https://github.com/Meesho/BharatMLStack/tree/main/docs/blog/bharatmlstack-history/post-one/index.md","source":"@site/blog/bharatmlstack-history/post-one/index.md","title":"Building Meesho\u2019s ML Platform: From Chaos to Cutting-Edge (Part 1)","description":"BharatMLStack","date":"2022-11-15T00:00:00.000Z","tags":[{"inline":true,"label":"online-feature-store","permalink":"/BharatMLStack/blog/tags/online-feature-store"},{"inline":true,"label":"interaction-store","permalink":"/BharatMLStack/blog/tags/interaction-store"},{"inline":true,"label":"mlplatform","permalink":"/BharatMLStack/blog/tags/mlplatform"},{"inline":true,"label":"meesho","permalink":"/BharatMLStack/blog/tags/meesho"}],"readingTime":10.25,"hasTruncateMarker":false,"authors":[{"name":"Adarsha Das","title":"Senior Architect @ Meesho","url":"https://github.com/a0d00kc","imageURL":"https://github.com/a0d00kc.png","key":"adarsha","page":null},{"name":"Aditya Kumar","title":"SDE-III @ Meesho","url":"https://github.com/Adit2607","imageURL":"https://github.com/Adit2607.png","key":"aditya","page":null},{"name":"Bhawani Singh","title":"SDE-IV @ Meesho","url":"https://github.com/singh-bhawani","imageURL":"https://github.com/singh-bhawani.png","key":"bhawani","page":null},{"name":"Jigar Dave","title":"SDE-IV @ Meesho","url":"https://github.com/jigarpatel26","imageURL":"https://github.com/jigarpatel26.png","key":"jigar","page":null}],"frontMatter":{"slug":"post-one","title":"Building Meesho\u2019s ML Platform: From Chaos to Cutting-Edge (Part 1)","authors":["adarsha","aditya","bhawani","jigar"],"date":"2022-11-15T00:00:00.000Z","tags":["online-feature-store","interaction-store","mlplatform","meesho"]},"unlisted":false}')},5728:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/online-feature-store-v0-86ec0010947ae24621f39ebd0d1729ca.png"},7131:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/first-gen-arch-7c0b286810aecb7eff42b48f51caee1f.png"},8453:(e,n,i)=>{i.d(n,{R:()=>a,x:()=>o});var t=i(6540);const s={},r=t.createContext(s);function a(e){const n=t.useContext(r);return t.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:a(e.components),t.createElement(r.Provider,{value:n},e.children)}},9930:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/bharatmlstack-72e1796337bfa224dee2a0f59ec4e2da.png"}}]); \ No newline at end of file diff --git a/docs/assets/js/f994c8da.90063f83.js b/docs/assets/js/f994c8da.90063f83.js deleted file mode 100644 index d977a51a..00000000 --- a/docs/assets/js/f994c8da.90063f83.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[1999],{38:a=>{a.exports=JSON.parse('{"metadata":{"permalink":"/BharatMLStack/blog","page":1,"postsPerPage":10,"totalPages":1,"totalCount":1,"blogDescription":"Blog","blogTitle":"Blog"}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/f994c8da.ba88b6c0.js b/docs/assets/js/f994c8da.ba88b6c0.js new file mode 100644 index 00000000..59aa8f64 --- /dev/null +++ b/docs/assets/js/f994c8da.ba88b6c0.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[1999],{38:a=>{a.exports=JSON.parse('{"metadata":{"permalink":"/BharatMLStack/blog","page":1,"postsPerPage":10,"totalPages":1,"totalCount":6,"blogDescription":"Blog","blogTitle":"Blog"}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/fa31f022.c62034f4.js b/docs/assets/js/fa31f022.c62034f4.js deleted file mode 100644 index 7ca34e55..00000000 --- a/docs/assets/js/fa31f022.c62034f4.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[6062],{6096:e=>{e.exports=JSON.parse('{"categoryGeneratedIndex":{"title":"v1.0.0","description":"Python SDK v1.0.0 documentation for BharatML Stack. Contains API reference, usage guides, and examples for the Python client libraries including gRPC feature client, Spark feature push client, and common utilities.","slug":"/category/v100","permalink":"/BharatMLStack/category/v100","sidebar":"tutorialSidebar","navigation":{"previous":{"title":"Python SDK","permalink":"/BharatMLStack/category/python-sdk"},"next":{"title":"GRPC Feature client","permalink":"/BharatMLStack/sdks/python/v1.0.0/grpc_feature_client"}}}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/fcf4f6ca.8b12d88e.js b/docs/assets/js/fcf4f6ca.8b12d88e.js new file mode 100644 index 00000000..98232945 --- /dev/null +++ b/docs/assets/js/fcf4f6ca.8b12d88e.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[7720],{4041:e=>{e.exports=JSON.parse('{"categoryGeneratedIndex":{"title":"Trufflebox UI","description":"Trufflebox UI is a modern, feature rich UI framework for supporting MLOps. It supports Feature catalog, management, user managemnet and other adminops","slug":"/category/trufflebox-ui","permalink":"/BharatMLStack/category/trufflebox-ui","sidebar":"tutorialSidebar","navigation":{"previous":{"title":"Quick Start","permalink":"/BharatMLStack/quick-start/v1.0.0/quick-start"},"next":{"title":"v1.0.0","permalink":"/BharatMLStack/trufflebox-ui/v1.0.0"}}}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/fcf4f6ca.d9bac5e5.js b/docs/assets/js/fcf4f6ca.d9bac5e5.js deleted file mode 100644 index c8bf1a6b..00000000 --- a/docs/assets/js/fcf4f6ca.d9bac5e5.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[7720],{4041:e=>{e.exports=JSON.parse('{"categoryGeneratedIndex":{"title":"Trufflebox UI","description":"Trufflebox UI is a modern, feature rich UI framework for supporting MLOps. It supports Feature catalog, management, user managemnet and other adminops","slug":"/category/trufflebox-ui","permalink":"/BharatMLStack/category/trufflebox-ui","sidebar":"tutorialSidebar","navigation":{"previous":{"title":"Quick Start","permalink":"/BharatMLStack/quick-start/v1.0.0/quick-start"},"next":{"title":"User Manual","permalink":"/BharatMLStack/trufflebox-ui/v1.0.0/userguide"}}}}')}}]); \ No newline at end of file diff --git a/docs/assets/js/main.6f8db0ca.js b/docs/assets/js/main.6f8db0ca.js new file mode 100644 index 00000000..fa43f95c --- /dev/null +++ b/docs/assets/js/main.6f8db0ca.js @@ -0,0 +1,2 @@ +/*! For license information please see main.6f8db0ca.js.LICENSE.txt */ +(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[8792],{115:e=>{var t="undefined"!=typeof Element,n="function"==typeof Map,r="function"==typeof Set,a="function"==typeof ArrayBuffer&&!!ArrayBuffer.isView;function o(e,i){if(e===i)return!0;if(e&&i&&"object"==typeof e&&"object"==typeof i){if(e.constructor!==i.constructor)return!1;var l,s,c,u;if(Array.isArray(e)){if((l=e.length)!=i.length)return!1;for(s=l;0!==s--;)if(!o(e[s],i[s]))return!1;return!0}if(n&&e instanceof Map&&i instanceof Map){if(e.size!==i.size)return!1;for(u=e.entries();!(s=u.next()).done;)if(!i.has(s.value[0]))return!1;for(u=e.entries();!(s=u.next()).done;)if(!o(s.value[1],i.get(s.value[0])))return!1;return!0}if(r&&e instanceof Set&&i instanceof Set){if(e.size!==i.size)return!1;for(u=e.entries();!(s=u.next()).done;)if(!i.has(s.value[0]))return!1;return!0}if(a&&ArrayBuffer.isView(e)&&ArrayBuffer.isView(i)){if((l=e.length)!=i.length)return!1;for(s=l;0!==s--;)if(e[s]!==i[s])return!1;return!0}if(e.constructor===RegExp)return e.source===i.source&&e.flags===i.flags;if(e.valueOf!==Object.prototype.valueOf&&"function"==typeof e.valueOf&&"function"==typeof i.valueOf)return e.valueOf()===i.valueOf();if(e.toString!==Object.prototype.toString&&"function"==typeof e.toString&&"function"==typeof i.toString)return e.toString()===i.toString();if((l=(c=Object.keys(e)).length)!==Object.keys(i).length)return!1;for(s=l;0!==s--;)if(!Object.prototype.hasOwnProperty.call(i,c[s]))return!1;if(t&&e instanceof Element)return!1;for(s=l;0!==s--;)if(("_owner"!==c[s]&&"__v"!==c[s]&&"__o"!==c[s]||!e.$$typeof)&&!o(e[c[s]],i[c[s]]))return!1;return!0}return e!=e&&i!=i}e.exports=function(e,t){try{return o(e,t)}catch(n){if((n.message||"").match(/stack|recursion/i))return console.warn("react-fast-compare cannot handle circular refs"),!1;throw n}}},119:(e,t,n)=>{"use strict";n.r(t)},205:(e,t,n)=>{"use strict";n.d(t,{A:()=>a});var r=n(6540);const a=n(8193).A.canUseDOM?r.useLayoutEffect:r.useEffect},253:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getErrorCausalChain=function e(t){if(t.cause)return[t,...e(t.cause)];return[t]}},311:e=>{"use strict";e.exports=function(e,t,n,r,a,o,i,l){if(!e){var s;if(void 0===t)s=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var c=[n,r,a,o,i,l],u=0;(s=new Error(t.replace(/%s/g,function(){return c[u++]}))).name="Invariant Violation"}throw s.framesToPop=1,s}}},418:(e,t,n)=>{"use strict";n.d(t,{A:()=>r});const r=()=>null},440:(e,t,n)=>{"use strict";t.rA=t.Ks=t.LU=void 0;const r=n(1635);t.LU="__blog-post-container";var a=n(2983);Object.defineProperty(t,"Ks",{enumerable:!0,get:function(){return r.__importDefault(a).default}});var o=n(2566);var i=n(253);Object.defineProperty(t,"rA",{enumerable:!0,get:function(){return i.getErrorCausalChain}})},545:(e,t,n)=>{"use strict";n.d(t,{mg:()=>J,vd:()=>G});var r=n(6540),a=n(5556),o=n.n(a),i=n(115),l=n.n(i),s=n(311),c=n.n(s),u=n(2833),d=n.n(u);function f(){return f=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},f.apply(this,arguments)}function p(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,m(e,t)}function m(e,t){return m=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e},m(e,t)}function h(e,t){if(null==e)return{};var n,r,a={},o=Object.keys(e);for(r=0;r<o.length;r++)t.indexOf(n=o[r])>=0||(a[n]=e[n]);return a}var g={BASE:"base",BODY:"body",HEAD:"head",HTML:"html",LINK:"link",META:"meta",NOSCRIPT:"noscript",SCRIPT:"script",STYLE:"style",TITLE:"title",FRAGMENT:"Symbol(react.fragment)"},b={rel:["amphtml","canonical","alternate"]},y={type:["application/ld+json"]},v={charset:"",name:["robots","description"],property:["og:type","og:title","og:url","og:image","og:image:alt","og:description","twitter:url","twitter:title","twitter:description","twitter:image","twitter:image:alt","twitter:card","twitter:site"]},k=Object.keys(g).map(function(e){return g[e]}),w={accesskey:"accessKey",charset:"charSet",class:"className",contenteditable:"contentEditable",contextmenu:"contextMenu","http-equiv":"httpEquiv",itemprop:"itemProp",tabindex:"tabIndex"},S=Object.keys(w).reduce(function(e,t){return e[w[t]]=t,e},{}),x=function(e,t){for(var n=e.length-1;n>=0;n-=1){var r=e[n];if(Object.prototype.hasOwnProperty.call(r,t))return r[t]}return null},_=function(e){var t=x(e,g.TITLE),n=x(e,"titleTemplate");if(Array.isArray(t)&&(t=t.join("")),n&&t)return n.replace(/%s/g,function(){return t});var r=x(e,"defaultTitle");return t||r||void 0},E=function(e){return x(e,"onChangeClientState")||function(){}},C=function(e,t){return t.filter(function(t){return void 0!==t[e]}).map(function(t){return t[e]}).reduce(function(e,t){return f({},e,t)},{})},L=function(e,t){return t.filter(function(e){return void 0!==e[g.BASE]}).map(function(e){return e[g.BASE]}).reverse().reduce(function(t,n){if(!t.length)for(var r=Object.keys(n),a=0;a<r.length;a+=1){var o=r[a].toLowerCase();if(-1!==e.indexOf(o)&&n[o])return t.concat(n)}return t},[])},A=function(e,t,n){var r={};return n.filter(function(t){return!!Array.isArray(t[e])||(void 0!==t[e]&&console&&"function"==typeof console.warn&&console.warn("Helmet: "+e+' should be of type "Array". Instead found type "'+typeof t[e]+'"'),!1)}).map(function(t){return t[e]}).reverse().reduce(function(e,n){var a={};n.filter(function(e){for(var n,o=Object.keys(e),i=0;i<o.length;i+=1){var l=o[i],s=l.toLowerCase();-1===t.indexOf(s)||"rel"===n&&"canonical"===e[n].toLowerCase()||"rel"===s&&"stylesheet"===e[s].toLowerCase()||(n=s),-1===t.indexOf(l)||"innerHTML"!==l&&"cssText"!==l&&"itemprop"!==l||(n=l)}if(!n||!e[n])return!1;var c=e[n].toLowerCase();return r[n]||(r[n]={}),a[n]||(a[n]={}),!r[n][c]&&(a[n][c]=!0,!0)}).reverse().forEach(function(t){return e.push(t)});for(var o=Object.keys(a),i=0;i<o.length;i+=1){var l=o[i],s=f({},r[l],a[l]);r[l]=s}return e},[]).reverse()},T=function(e,t){if(Array.isArray(e)&&e.length)for(var n=0;n<e.length;n+=1)if(e[n][t])return!0;return!1},j=function(e){return Array.isArray(e)?e.join(""):e},M=function(e,t){return Array.isArray(e)?e.reduce(function(e,n){return function(e,t){for(var n=Object.keys(e),r=0;r<n.length;r+=1)if(t[n[r]]&&t[n[r]].includes(e[n[r]]))return!0;return!1}(n,t)?e.priority.push(n):e.default.push(n),e},{priority:[],default:[]}):{default:e}},P=function(e,t){var n;return f({},e,((n={})[t]=void 0,n))},N=[g.NOSCRIPT,g.SCRIPT,g.STYLE],O=function(e,t){return void 0===t&&(t=!0),!1===t?String(e):String(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")},R=function(e){return Object.keys(e).reduce(function(t,n){var r=void 0!==e[n]?n+'="'+e[n]+'"':""+n;return t?t+" "+r:r},"")},B=function(e,t){return void 0===t&&(t={}),Object.keys(e).reduce(function(t,n){return t[w[n]||n]=e[n],t},t)},D=function(e,t){return t.map(function(t,n){var a,o=((a={key:n})["data-rh"]=!0,a);return Object.keys(t).forEach(function(e){var n=w[e]||e;"innerHTML"===n||"cssText"===n?o.dangerouslySetInnerHTML={__html:t.innerHTML||t.cssText}:o[n]=t[e]}),r.createElement(e,o)})},F=function(e,t,n){switch(e){case g.TITLE:return{toComponent:function(){return n=t.titleAttributes,(a={key:e=t.title})["data-rh"]=!0,o=B(n,a),[r.createElement(g.TITLE,o,e)];var e,n,a,o},toString:function(){return function(e,t,n,r){var a=R(n),o=j(t);return a?"<"+e+' data-rh="true" '+a+">"+O(o,r)+"</"+e+">":"<"+e+' data-rh="true">'+O(o,r)+"</"+e+">"}(e,t.title,t.titleAttributes,n)}};case"bodyAttributes":case"htmlAttributes":return{toComponent:function(){return B(t)},toString:function(){return R(t)}};default:return{toComponent:function(){return D(e,t)},toString:function(){return function(e,t,n){return t.reduce(function(t,r){var a=Object.keys(r).filter(function(e){return!("innerHTML"===e||"cssText"===e)}).reduce(function(e,t){var a=void 0===r[t]?t:t+'="'+O(r[t],n)+'"';return e?e+" "+a:a},""),o=r.innerHTML||r.cssText||"",i=-1===N.indexOf(e);return t+"<"+e+' data-rh="true" '+a+(i?"/>":">"+o+"</"+e+">")},"")}(e,t,n)}}}},I=function(e){var t=e.baseTag,n=e.bodyAttributes,r=e.encode,a=e.htmlAttributes,o=e.noscriptTags,i=e.styleTags,l=e.title,s=void 0===l?"":l,c=e.titleAttributes,u=e.linkTags,d=e.metaTags,f=e.scriptTags,p={toComponent:function(){},toString:function(){return""}};if(e.prioritizeSeoTags){var m=function(e){var t=e.linkTags,n=e.scriptTags,r=e.encode,a=M(e.metaTags,v),o=M(t,b),i=M(n,y);return{priorityMethods:{toComponent:function(){return[].concat(D(g.META,a.priority),D(g.LINK,o.priority),D(g.SCRIPT,i.priority))},toString:function(){return F(g.META,a.priority,r)+" "+F(g.LINK,o.priority,r)+" "+F(g.SCRIPT,i.priority,r)}},metaTags:a.default,linkTags:o.default,scriptTags:i.default}}(e);p=m.priorityMethods,u=m.linkTags,d=m.metaTags,f=m.scriptTags}return{priority:p,base:F(g.BASE,t,r),bodyAttributes:F("bodyAttributes",n,r),htmlAttributes:F("htmlAttributes",a,r),link:F(g.LINK,u,r),meta:F(g.META,d,r),noscript:F(g.NOSCRIPT,o,r),script:F(g.SCRIPT,f,r),style:F(g.STYLE,i,r),title:F(g.TITLE,{title:s,titleAttributes:c},r)}},z=[],$=function(e,t){var n=this;void 0===t&&(t="undefined"!=typeof document),this.instances=[],this.value={setHelmet:function(e){n.context.helmet=e},helmetInstances:{get:function(){return n.canUseDOM?z:n.instances},add:function(e){(n.canUseDOM?z:n.instances).push(e)},remove:function(e){var t=(n.canUseDOM?z:n.instances).indexOf(e);(n.canUseDOM?z:n.instances).splice(t,1)}}},this.context=e,this.canUseDOM=t,t||(e.helmet=I({baseTag:[],bodyAttributes:{},encodeSpecialCharacters:!0,htmlAttributes:{},linkTags:[],metaTags:[],noscriptTags:[],scriptTags:[],styleTags:[],title:"",titleAttributes:{}}))},U=r.createContext({}),q=o().shape({setHelmet:o().func,helmetInstances:o().shape({get:o().func,add:o().func,remove:o().func})}),H="undefined"!=typeof document,G=function(e){function t(n){var r;return(r=e.call(this,n)||this).helmetData=new $(r.props.context,t.canUseDOM),r}return p(t,e),t.prototype.render=function(){return r.createElement(U.Provider,{value:this.helmetData.value},this.props.children)},t}(r.Component);G.canUseDOM=H,G.propTypes={context:o().shape({helmet:o().shape()}),children:o().node.isRequired},G.defaultProps={context:{}},G.displayName="HelmetProvider";var V=function(e,t){var n,r=document.head||document.querySelector(g.HEAD),a=r.querySelectorAll(e+"[data-rh]"),o=[].slice.call(a),i=[];return t&&t.length&&t.forEach(function(t){var r=document.createElement(e);for(var a in t)Object.prototype.hasOwnProperty.call(t,a)&&("innerHTML"===a?r.innerHTML=t.innerHTML:"cssText"===a?r.styleSheet?r.styleSheet.cssText=t.cssText:r.appendChild(document.createTextNode(t.cssText)):r.setAttribute(a,void 0===t[a]?"":t[a]));r.setAttribute("data-rh","true"),o.some(function(e,t){return n=t,r.isEqualNode(e)})?o.splice(n,1):i.push(r)}),o.forEach(function(e){return e.parentNode.removeChild(e)}),i.forEach(function(e){return r.appendChild(e)}),{oldTags:o,newTags:i}},W=function(e,t){var n=document.getElementsByTagName(e)[0];if(n){for(var r=n.getAttribute("data-rh"),a=r?r.split(","):[],o=[].concat(a),i=Object.keys(t),l=0;l<i.length;l+=1){var s=i[l],c=t[s]||"";n.getAttribute(s)!==c&&n.setAttribute(s,c),-1===a.indexOf(s)&&a.push(s);var u=o.indexOf(s);-1!==u&&o.splice(u,1)}for(var d=o.length-1;d>=0;d-=1)n.removeAttribute(o[d]);a.length===o.length?n.removeAttribute("data-rh"):n.getAttribute("data-rh")!==i.join(",")&&n.setAttribute("data-rh",i.join(","))}},Q=function(e,t){var n=e.baseTag,r=e.htmlAttributes,a=e.linkTags,o=e.metaTags,i=e.noscriptTags,l=e.onChangeClientState,s=e.scriptTags,c=e.styleTags,u=e.title,d=e.titleAttributes;W(g.BODY,e.bodyAttributes),W(g.HTML,r),function(e,t){void 0!==e&&document.title!==e&&(document.title=j(e)),W(g.TITLE,t)}(u,d);var f={baseTag:V(g.BASE,n),linkTags:V(g.LINK,a),metaTags:V(g.META,o),noscriptTags:V(g.NOSCRIPT,i),scriptTags:V(g.SCRIPT,s),styleTags:V(g.STYLE,c)},p={},m={};Object.keys(f).forEach(function(e){var t=f[e],n=t.newTags,r=t.oldTags;n.length&&(p[e]=n),r.length&&(m[e]=f[e].oldTags)}),t&&t(),l(e,p,m)},K=null,Y=function(e){function t(){for(var t,n=arguments.length,r=new Array(n),a=0;a<n;a++)r[a]=arguments[a];return(t=e.call.apply(e,[this].concat(r))||this).rendered=!1,t}p(t,e);var n=t.prototype;return n.shouldComponentUpdate=function(e){return!d()(e,this.props)},n.componentDidUpdate=function(){this.emitChange()},n.componentWillUnmount=function(){this.props.context.helmetInstances.remove(this),this.emitChange()},n.emitChange=function(){var e,t,n=this.props.context,r=n.setHelmet,a=null,o=(e=n.helmetInstances.get().map(function(e){var t=f({},e.props);return delete t.context,t}),{baseTag:L(["href"],e),bodyAttributes:C("bodyAttributes",e),defer:x(e,"defer"),encode:x(e,"encodeSpecialCharacters"),htmlAttributes:C("htmlAttributes",e),linkTags:A(g.LINK,["rel","href"],e),metaTags:A(g.META,["name","charset","http-equiv","property","itemprop"],e),noscriptTags:A(g.NOSCRIPT,["innerHTML"],e),onChangeClientState:E(e),scriptTags:A(g.SCRIPT,["src","innerHTML"],e),styleTags:A(g.STYLE,["cssText"],e),title:_(e),titleAttributes:C("titleAttributes",e),prioritizeSeoTags:T(e,"prioritizeSeoTags")});G.canUseDOM?(t=o,K&&cancelAnimationFrame(K),t.defer?K=requestAnimationFrame(function(){Q(t,function(){K=null})}):(Q(t),K=null)):I&&(a=I(o)),r(a)},n.init=function(){this.rendered||(this.rendered=!0,this.props.context.helmetInstances.add(this),this.emitChange())},n.render=function(){return this.init(),null},t}(r.Component);Y.propTypes={context:q.isRequired},Y.displayName="HelmetDispatcher";var X=["children"],Z=["children"],J=function(e){function t(){return e.apply(this,arguments)||this}p(t,e);var n=t.prototype;return n.shouldComponentUpdate=function(e){return!l()(P(this.props,"helmetData"),P(e,"helmetData"))},n.mapNestedChildrenToProps=function(e,t){if(!t)return null;switch(e.type){case g.SCRIPT:case g.NOSCRIPT:return{innerHTML:t};case g.STYLE:return{cssText:t};default:throw new Error("<"+e.type+" /> elements are self-closing and can not contain children. Refer to our API for more information.")}},n.flattenArrayTypeChildren=function(e){var t,n=e.child,r=e.arrayTypeChildren;return f({},r,((t={})[n.type]=[].concat(r[n.type]||[],[f({},e.newChildProps,this.mapNestedChildrenToProps(n,e.nestedChildren))]),t))},n.mapObjectTypeChildren=function(e){var t,n,r=e.child,a=e.newProps,o=e.newChildProps,i=e.nestedChildren;switch(r.type){case g.TITLE:return f({},a,((t={})[r.type]=i,t.titleAttributes=f({},o),t));case g.BODY:return f({},a,{bodyAttributes:f({},o)});case g.HTML:return f({},a,{htmlAttributes:f({},o)});default:return f({},a,((n={})[r.type]=f({},o),n))}},n.mapArrayTypeChildrenToProps=function(e,t){var n=f({},t);return Object.keys(e).forEach(function(t){var r;n=f({},n,((r={})[t]=e[t],r))}),n},n.warnOnInvalidChildren=function(e,t){return c()(k.some(function(t){return e.type===t}),"function"==typeof e.type?"You may be attempting to nest <Helmet> components within each other, which is not allowed. Refer to our API for more information.":"Only elements types "+k.join(", ")+" are allowed. Helmet does not support rendering <"+e.type+"> elements. Refer to our API for more information."),c()(!t||"string"==typeof t||Array.isArray(t)&&!t.some(function(e){return"string"!=typeof e}),"Helmet expects a string as a child of <"+e.type+">. Did you forget to wrap your children in braces? ( <"+e.type+">{``}</"+e.type+"> ) Refer to our API for more information."),!0},n.mapChildrenToProps=function(e,t){var n=this,a={};return r.Children.forEach(e,function(e){if(e&&e.props){var r=e.props,o=r.children,i=h(r,X),l=Object.keys(i).reduce(function(e,t){return e[S[t]||t]=i[t],e},{}),s=e.type;switch("symbol"==typeof s?s=s.toString():n.warnOnInvalidChildren(e,o),s){case g.FRAGMENT:t=n.mapChildrenToProps(o,t);break;case g.LINK:case g.META:case g.NOSCRIPT:case g.SCRIPT:case g.STYLE:a=n.flattenArrayTypeChildren({child:e,arrayTypeChildren:a,newChildProps:l,nestedChildren:o});break;default:t=n.mapObjectTypeChildren({child:e,newProps:t,newChildProps:l,nestedChildren:o})}}}),this.mapArrayTypeChildrenToProps(a,t)},n.render=function(){var e=this.props,t=e.children,n=h(e,Z),a=f({},n),o=n.helmetData;return t&&(a=this.mapChildrenToProps(t,a)),!o||o instanceof $||(o=new $(o.context,o.instances)),o?r.createElement(Y,f({},a,{context:o.value,helmetData:void 0})):r.createElement(U.Consumer,null,function(e){return r.createElement(Y,f({},a,{context:e}))})},t}(r.Component);J.propTypes={base:o().object,bodyAttributes:o().object,children:o().oneOfType([o().arrayOf(o().node),o().node]),defaultTitle:o().string,defer:o().bool,encodeSpecialCharacters:o().bool,htmlAttributes:o().object,link:o().arrayOf(o().object),meta:o().arrayOf(o().object),noscript:o().arrayOf(o().object),onChangeClientState:o().func,script:o().arrayOf(o().object),style:o().arrayOf(o().object),title:o().string,titleAttributes:o().object,titleTemplate:o().string,prioritizeSeoTags:o().bool,helmetData:o().object},J.defaultProps={defer:!0,encodeSpecialCharacters:!0,prioritizeSeoTags:!1},J.displayName="Helmet"},609:(e,t,n)=>{"use strict";n.d(t,{V:()=>s,t:()=>c});var r=n(6540),a=n(9532),o=n(4848);const i=Symbol("EmptyContext"),l=r.createContext(i);function s({children:e,name:t,items:n}){const a=(0,r.useMemo)(()=>t&&n?{name:t,items:n}:null,[t,n]);return(0,o.jsx)(l.Provider,{value:a,children:e})}function c(){const e=(0,r.useContext)(l);if(e===i)throw new a.dV("DocsSidebarProvider");return e}},679:(e,t,n)=>{"use strict";n.d(t,{Wf:()=>c});n(6540);const r=JSON.parse('{"N":"localStorage","M":""}'),a=r.N;function o({key:e,oldValue:t,newValue:n,storage:r}){if(t===n)return;const a=document.createEvent("StorageEvent");a.initStorageEvent("storage",!1,!1,e,t,n,window.location.href,r),window.dispatchEvent(a)}function i(e=a){if("undefined"==typeof window)throw new Error("Browser storage is not available on Node.js/Docusaurus SSR process.");if("none"===e)return null;try{return window[e]}catch(n){return t=n,l||(console.warn("Docusaurus browser storage is not available.\nPossible reasons: running Docusaurus in an iframe, in an incognito browser session, or using too strict browser privacy settings.",t),l=!0),null}var t}let l=!1;const s={get:()=>null,set:()=>{},del:()=>{},listen:()=>()=>{}};function c(e,t){const n=`${e}${r.M}`;if("undefined"==typeof window)return function(e){function t(){throw new Error(`Illegal storage API usage for storage key "${e}".\nDocusaurus storage APIs are not supposed to be called on the server-rendering process.\nPlease only call storage APIs in effects and event handlers.`)}return{get:t,set:t,del:t,listen:t}}(n);const a=i(t?.persistence);return null===a?s:{get:()=>{try{return a.getItem(n)}catch(e){return console.error(`Docusaurus storage error, can't get key=${n}`,e),null}},set:e=>{try{const t=a.getItem(n);a.setItem(n,e),o({key:n,oldValue:t,newValue:e,storage:a})}catch(t){console.error(`Docusaurus storage error, can't set ${n}=${e}`,t)}},del:()=>{try{const e=a.getItem(n);a.removeItem(n),o({key:n,oldValue:e,newValue:null,storage:a})}catch(e){console.error(`Docusaurus storage error, can't delete key=${n}`,e)}},listen:e=>{try{const t=t=>{t.storageArea===a&&t.key===n&&e(t)};return window.addEventListener("storage",t),()=>window.removeEventListener("storage",t)}catch(t){return console.error(`Docusaurus storage error, can't listen for changes of key=${n}`,t),()=>{}}}}}},961:(e,t,n)=>{"use strict";!function e(){if("undefined"!=typeof __REACT_DEVTOOLS_GLOBAL_HOOK__&&"function"==typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE)try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(t){console.error(t)}}(),e.exports=n(6221)},1043:(e,t,n)=>{"use strict";n.r(t)},1107:(e,t,n)=>{"use strict";n.d(t,{A:()=>u});n(6540);var r=n(4164),a=n(1312),o=n(6342),i=n(8774),l=n(3427);const s={anchorWithStickyNavbar:"anchorWithStickyNavbar_LWe7",anchorWithHideOnScrollNavbar:"anchorWithHideOnScrollNavbar_WYt5"};var c=n(4848);function u({as:e,id:t,...n}){const u=(0,l.A)(),{navbar:{hideOnScroll:d}}=(0,o.p)();if("h1"===e||!t)return(0,c.jsx)(e,{...n,id:void 0});u.collectAnchor(t);const f=(0,a.T)({id:"theme.common.headingLinkTitle",message:"Direct link to {heading}",description:"Title for link to heading"},{heading:"string"==typeof n.children?n.children:t});return(0,c.jsxs)(e,{...n,className:(0,r.A)("anchor",d?s.anchorWithHideOnScrollNavbar:s.anchorWithStickyNavbar,n.className),id:t,children:[n.children,(0,c.jsx)(i.A,{className:"hash-link",to:`#${t}`,"aria-label":f,title:f,children:"\u200b"})]})}},1122:(e,t,n)=>{"use strict";n.d(t,{A:()=>u});var r=n(6540),a=n(4164),o=n(2303),i=n(5293);const l={themedComponent:"themedComponent_mlkZ","themedComponent--light":"themedComponent--light_NVdE","themedComponent--dark":"themedComponent--dark_xIcU"};var s=n(4848);function c({className:e,children:t}){const n=(0,o.A)(),{colorMode:c}=(0,i.G)();return(0,s.jsx)(s.Fragment,{children:(n?"dark"===c?["dark"]:["light"]:["light","dark"]).map(n=>{const o=t({theme:n,className:(0,a.A)(e,l.themedComponent,l[`themedComponent--${n}`])});return(0,s.jsx)(r.Fragment,{children:o},n)})})}function u(e){const{sources:t,className:n,alt:r,...a}=e;return(0,s.jsx)(c,{className:n,children:({theme:e,className:n})=>(0,s.jsx)("img",{src:t[e],alt:r,className:n,...a})})}},1247:(e,t,n)=>{"use strict";var r=n(9982),a=n(6540),o=n(961);function i(e){var t="https://react.dev/errors/"+e;if(1<arguments.length){t+="?args[]="+encodeURIComponent(arguments[1]);for(var n=2;n<arguments.length;n++)t+="&args[]="+encodeURIComponent(arguments[n])}return"Minified React error #"+e+"; visit "+t+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}function l(e){return!(!e||1!==e.nodeType&&9!==e.nodeType&&11!==e.nodeType)}function s(e){var t=e,n=e;if(e.alternate)for(;t.return;)t=t.return;else{e=t;do{!!(4098&(t=e).flags)&&(n=t.return),e=t.return}while(e)}return 3===t.tag?n:null}function c(e){if(13===e.tag){var t=e.memoizedState;if(null===t&&(null!==(e=e.alternate)&&(t=e.memoizedState)),null!==t)return t.dehydrated}return null}function u(e){if(s(e)!==e)throw Error(i(188))}function d(e){var t=e.tag;if(5===t||26===t||27===t||6===t)return e;for(e=e.child;null!==e;){if(null!==(t=d(e)))return t;e=e.sibling}return null}var f=Object.assign,p=Symbol.for("react.element"),m=Symbol.for("react.transitional.element"),h=Symbol.for("react.portal"),g=Symbol.for("react.fragment"),b=Symbol.for("react.strict_mode"),y=Symbol.for("react.profiler"),v=Symbol.for("react.provider"),k=Symbol.for("react.consumer"),w=Symbol.for("react.context"),S=Symbol.for("react.forward_ref"),x=Symbol.for("react.suspense"),_=Symbol.for("react.suspense_list"),E=Symbol.for("react.memo"),C=Symbol.for("react.lazy");Symbol.for("react.scope");var L=Symbol.for("react.activity");Symbol.for("react.legacy_hidden"),Symbol.for("react.tracing_marker");var A=Symbol.for("react.memo_cache_sentinel");Symbol.for("react.view_transition");var T=Symbol.iterator;function j(e){return null===e||"object"!=typeof e?null:"function"==typeof(e=T&&e[T]||e["@@iterator"])?e:null}var M=Symbol.for("react.client.reference");function P(e){if(null==e)return null;if("function"==typeof e)return e.$$typeof===M?null:e.displayName||e.name||null;if("string"==typeof e)return e;switch(e){case g:return"Fragment";case y:return"Profiler";case b:return"StrictMode";case x:return"Suspense";case _:return"SuspenseList";case L:return"Activity"}if("object"==typeof e)switch(e.$$typeof){case h:return"Portal";case w:return(e.displayName||"Context")+".Provider";case k:return(e._context.displayName||"Context")+".Consumer";case S:var t=e.render;return(e=e.displayName)||(e=""!==(e=t.displayName||t.name||"")?"ForwardRef("+e+")":"ForwardRef"),e;case E:return null!==(t=e.displayName||null)?t:P(e.type)||"Memo";case C:t=e._payload,e=e._init;try{return P(e(t))}catch(n){}}return null}var N=Array.isArray,O=a.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,R=o.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,B={pending:!1,data:null,method:null,action:null},D=[],F=-1;function I(e){return{current:e}}function z(e){0>F||(e.current=D[F],D[F]=null,F--)}function $(e,t){F++,D[F]=e.current,e.current=t}var U=I(null),q=I(null),H=I(null),G=I(null);function V(e,t){switch($(H,t),$(q,e),$(U,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?ad(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)e=od(t=ad(t),e);else switch(e){case"svg":e=1;break;case"math":e=2;break;default:e=0}}z(U),$(U,e)}function W(){z(U),z(q),z(H)}function Q(e){null!==e.memoizedState&&$(G,e);var t=U.current,n=od(t,e.type);t!==n&&($(q,e),$(U,n))}function K(e){q.current===e&&(z(U),z(q)),G.current===e&&(z(G),Qd._currentValue=B)}var Y=Object.prototype.hasOwnProperty,X=r.unstable_scheduleCallback,Z=r.unstable_cancelCallback,J=r.unstable_shouldYield,ee=r.unstable_requestPaint,te=r.unstable_now,ne=r.unstable_getCurrentPriorityLevel,re=r.unstable_ImmediatePriority,ae=r.unstable_UserBlockingPriority,oe=r.unstable_NormalPriority,ie=r.unstable_LowPriority,le=r.unstable_IdlePriority,se=r.log,ce=r.unstable_setDisableYieldValue,ue=null,de=null;function fe(e){if("function"==typeof se&&ce(e),de&&"function"==typeof de.setStrictMode)try{de.setStrictMode(ue,e)}catch(t){}}var pe=Math.clz32?Math.clz32:function(e){return 0===(e>>>=0)?32:31-(me(e)/he|0)|0},me=Math.log,he=Math.LN2;var ge=256,be=4194304;function ye(e){var t=42&e;if(0!==t)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return 4194048&e;case 4194304:case 8388608:case 16777216:case 33554432:return 62914560&e;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function ve(e,t,n){var r=e.pendingLanes;if(0===r)return 0;var a=0,o=e.suspendedLanes,i=e.pingedLanes;e=e.warmLanes;var l=134217727&r;return 0!==l?0!==(r=l&~o)?a=ye(r):0!==(i&=l)?a=ye(i):n||0!==(n=l&~e)&&(a=ye(n)):0!==(l=r&~o)?a=ye(l):0!==i?a=ye(i):n||0!==(n=r&~e)&&(a=ye(n)),0===a?0:0!==t&&t!==a&&0===(t&o)&&((o=a&-a)>=(n=t&-t)||32===o&&4194048&n)?t:a}function ke(e,t){return 0===(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)}function we(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;default:return-1}}function Se(){var e=ge;return!(4194048&(ge<<=1))&&(ge=256),e}function xe(){var e=be;return!(62914560&(be<<=1))&&(be=4194304),e}function _e(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function Ee(e,t){e.pendingLanes|=t,268435456!==t&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function Ce(e,t,n){e.pendingLanes|=t,e.suspendedLanes&=~t;var r=31-pe(t);e.entangledLanes|=t,e.entanglements[r]=1073741824|e.entanglements[r]|4194090&n}function Le(e,t){var n=e.entangledLanes|=t;for(e=e.entanglements;n;){var r=31-pe(n),a=1<<r;a&t|e[r]&t&&(e[r]|=t),n&=~a}}function Ae(e){switch(e){case 2:e=1;break;case 8:e=4;break;case 32:e=16;break;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:e=128;break;case 268435456:e=134217728;break;default:e=0}return e}function Te(e){return 2<(e&=-e)?8<e?134217727&e?32:268435456:8:2}function je(){var e=R.p;return 0!==e?e:void 0===(e=window.event)?32:uf(e.type)}var Me=Math.random().toString(36).slice(2),Pe="__reactFiber$"+Me,Ne="__reactProps$"+Me,Oe="__reactContainer$"+Me,Re="__reactEvents$"+Me,Be="__reactListeners$"+Me,De="__reactHandles$"+Me,Fe="__reactResources$"+Me,Ie="__reactMarker$"+Me;function ze(e){delete e[Pe],delete e[Ne],delete e[Re],delete e[Be],delete e[De]}function $e(e){var t=e[Pe];if(t)return t;for(var n=e.parentNode;n;){if(t=n[Oe]||n[Pe]){if(n=t.alternate,null!==t.child||null!==n&&null!==n.child)for(e=vd(e);null!==e;){if(n=e[Pe])return n;e=vd(e)}return t}n=(e=n).parentNode}return null}function Ue(e){if(e=e[Pe]||e[Oe]){var t=e.tag;if(5===t||6===t||13===t||26===t||27===t||3===t)return e}return null}function qe(e){var t=e.tag;if(5===t||26===t||27===t||6===t)return e.stateNode;throw Error(i(33))}function He(e){var t=e[Fe];return t||(t=e[Fe]={hoistableStyles:new Map,hoistableScripts:new Map}),t}function Ge(e){e[Ie]=!0}var Ve=new Set,We={};function Qe(e,t){Ke(e,t),Ke(e+"Capture",t)}function Ke(e,t){for(We[e]=t,e=0;e<t.length;e++)Ve.add(t[e])}var Ye,Xe,Ze=RegExp("^[:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD][:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD\\-.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040]*$"),Je={},et={};function tt(e,t,n){if(a=t,Y.call(et,a)||!Y.call(Je,a)&&(Ze.test(a)?et[a]=!0:(Je[a]=!0,0)))if(null===n)e.removeAttribute(t);else{switch(typeof n){case"undefined":case"function":case"symbol":return void e.removeAttribute(t);case"boolean":var r=t.toLowerCase().slice(0,5);if("data-"!==r&&"aria-"!==r)return void e.removeAttribute(t)}e.setAttribute(t,""+n)}var a}function nt(e,t,n){if(null===n)e.removeAttribute(t);else{switch(typeof n){case"undefined":case"function":case"symbol":case"boolean":return void e.removeAttribute(t)}e.setAttribute(t,""+n)}}function rt(e,t,n,r){if(null===r)e.removeAttribute(n);else{switch(typeof r){case"undefined":case"function":case"symbol":case"boolean":return void e.removeAttribute(n)}e.setAttributeNS(t,n,""+r)}}function at(e){if(void 0===Ye)try{throw Error()}catch(n){var t=n.stack.trim().match(/\n( *(at )?)/);Ye=t&&t[1]||"",Xe=-1<n.stack.indexOf("\n at")?" (<anonymous>)":-1<n.stack.indexOf("@")?"@unknown:0:0":""}return"\n"+Ye+e+Xe}var ot=!1;function it(e,t){if(!e||ot)return"";ot=!0;var n=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var r={DetermineComponentFrameRoot:function(){try{if(t){var n=function(){throw Error()};if(Object.defineProperty(n.prototype,"props",{set:function(){throw Error()}}),"object"==typeof Reflect&&Reflect.construct){try{Reflect.construct(n,[])}catch(a){var r=a}Reflect.construct(e,[],n)}else{try{n.call()}catch(o){r=o}e.call(n.prototype)}}else{try{throw Error()}catch(i){r=i}(n=e())&&"function"==typeof n.catch&&n.catch(function(){})}}catch(l){if(l&&r&&"string"==typeof l.stack)return[l.stack,r.stack]}return[null,null]}};r.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var a=Object.getOwnPropertyDescriptor(r.DetermineComponentFrameRoot,"name");a&&a.configurable&&Object.defineProperty(r.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var o=r.DetermineComponentFrameRoot(),i=o[0],l=o[1];if(i&&l){var s=i.split("\n"),c=l.split("\n");for(a=r=0;r<s.length&&!s[r].includes("DetermineComponentFrameRoot");)r++;for(;a<c.length&&!c[a].includes("DetermineComponentFrameRoot");)a++;if(r===s.length||a===c.length)for(r=s.length-1,a=c.length-1;1<=r&&0<=a&&s[r]!==c[a];)a--;for(;1<=r&&0<=a;r--,a--)if(s[r]!==c[a]){if(1!==r||1!==a)do{if(r--,0>--a||s[r]!==c[a]){var u="\n"+s[r].replace(" at new "," at ");return e.displayName&&u.includes("<anonymous>")&&(u=u.replace("<anonymous>",e.displayName)),u}}while(1<=r&&0<=a);break}}}finally{ot=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:"")?at(n):""}function lt(e){switch(e.tag){case 26:case 27:case 5:return at(e.type);case 16:return at("Lazy");case 13:return at("Suspense");case 19:return at("SuspenseList");case 0:case 15:return it(e.type,!1);case 11:return it(e.type.render,!1);case 1:return it(e.type,!0);case 31:return at("Activity");default:return""}}function st(e){try{var t="";do{t+=lt(e),e=e.return}while(e);return t}catch(n){return"\nError generating stack: "+n.message+"\n"+n.stack}}function ct(e){switch(typeof e){case"bigint":case"boolean":case"number":case"string":case"undefined":case"object":return e;default:return""}}function ut(e){var t=e.type;return(e=e.nodeName)&&"input"===e.toLowerCase()&&("checkbox"===t||"radio"===t)}function dt(e){e._valueTracker||(e._valueTracker=function(e){var t=ut(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&void 0!==n&&"function"==typeof n.get&&"function"==typeof n.set){var a=n.get,o=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return a.call(this)},set:function(e){r=""+e,o.call(this,e)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(e){r=""+e},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}(e))}function ft(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=ut(e)?e.checked?"true":"false":e.value),(e=r)!==n&&(t.setValue(e),!0)}function pt(e){if(void 0===(e=e||("undefined"!=typeof document?document:void 0)))return null;try{return e.activeElement||e.body}catch(t){return e.body}}var mt=/[\n"\\]/g;function ht(e){return e.replace(mt,function(e){return"\\"+e.charCodeAt(0).toString(16)+" "})}function gt(e,t,n,r,a,o,i,l){e.name="",null!=i&&"function"!=typeof i&&"symbol"!=typeof i&&"boolean"!=typeof i?e.type=i:e.removeAttribute("type"),null!=t?"number"===i?(0===t&&""===e.value||e.value!=t)&&(e.value=""+ct(t)):e.value!==""+ct(t)&&(e.value=""+ct(t)):"submit"!==i&&"reset"!==i||e.removeAttribute("value"),null!=t?yt(e,i,ct(t)):null!=n?yt(e,i,ct(n)):null!=r&&e.removeAttribute("value"),null==a&&null!=o&&(e.defaultChecked=!!o),null!=a&&(e.checked=a&&"function"!=typeof a&&"symbol"!=typeof a),null!=l&&"function"!=typeof l&&"symbol"!=typeof l&&"boolean"!=typeof l?e.name=""+ct(l):e.removeAttribute("name")}function bt(e,t,n,r,a,o,i,l){if(null!=o&&"function"!=typeof o&&"symbol"!=typeof o&&"boolean"!=typeof o&&(e.type=o),null!=t||null!=n){if(("submit"===o||"reset"===o)&&null==t)return;n=null!=n?""+ct(n):"",t=null!=t?""+ct(t):n,l||t===e.value||(e.value=t),e.defaultValue=t}r="function"!=typeof(r=null!=r?r:a)&&"symbol"!=typeof r&&!!r,e.checked=l?e.checked:!!r,e.defaultChecked=!!r,null!=i&&"function"!=typeof i&&"symbol"!=typeof i&&"boolean"!=typeof i&&(e.name=i)}function yt(e,t,n){"number"===t&&pt(e.ownerDocument)===e||e.defaultValue===""+n||(e.defaultValue=""+n)}function vt(e,t,n,r){if(e=e.options,t){t={};for(var a=0;a<n.length;a++)t["$"+n[a]]=!0;for(n=0;n<e.length;n++)a=t.hasOwnProperty("$"+e[n].value),e[n].selected!==a&&(e[n].selected=a),a&&r&&(e[n].defaultSelected=!0)}else{for(n=""+ct(n),t=null,a=0;a<e.length;a++){if(e[a].value===n)return e[a].selected=!0,void(r&&(e[a].defaultSelected=!0));null!==t||e[a].disabled||(t=e[a])}null!==t&&(t.selected=!0)}}function kt(e,t,n){null==t||((t=""+ct(t))!==e.value&&(e.value=t),null!=n)?e.defaultValue=null!=n?""+ct(n):"":e.defaultValue!==t&&(e.defaultValue=t)}function wt(e,t,n,r){if(null==t){if(null!=r){if(null!=n)throw Error(i(92));if(N(r)){if(1<r.length)throw Error(i(93));r=r[0]}n=r}null==n&&(n=""),t=n}n=ct(t),e.defaultValue=n,(r=e.textContent)===n&&""!==r&&null!==r&&(e.value=r)}function St(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&3===n.nodeType)return void(n.nodeValue=t)}e.textContent=t}var xt=new Set("animationIterationCount aspectRatio borderImageOutset borderImageSlice borderImageWidth boxFlex boxFlexGroup boxOrdinalGroup columnCount columns flex flexGrow flexPositive flexShrink flexNegative flexOrder gridArea gridRow gridRowEnd gridRowSpan gridRowStart gridColumn gridColumnEnd gridColumnSpan gridColumnStart fontWeight lineClamp lineHeight opacity order orphans scale tabSize widows zIndex zoom fillOpacity floodOpacity stopOpacity strokeDasharray strokeDashoffset strokeMiterlimit strokeOpacity strokeWidth MozAnimationIterationCount MozBoxFlex MozBoxFlexGroup MozLineClamp msAnimationIterationCount msFlex msZoom msFlexGrow msFlexNegative msFlexOrder msFlexPositive msFlexShrink msGridColumn msGridColumnSpan msGridRow msGridRowSpan WebkitAnimationIterationCount WebkitBoxFlex WebKitBoxFlexGroup WebkitBoxOrdinalGroup WebkitColumnCount WebkitColumns WebkitFlex WebkitFlexGrow WebkitFlexPositive WebkitFlexShrink WebkitLineClamp".split(" "));function _t(e,t,n){var r=0===t.indexOf("--");null==n||"boolean"==typeof n||""===n?r?e.setProperty(t,""):"float"===t?e.cssFloat="":e[t]="":r?e.setProperty(t,n):"number"!=typeof n||0===n||xt.has(t)?"float"===t?e.cssFloat=n:e[t]=(""+n).trim():e[t]=n+"px"}function Et(e,t,n){if(null!=t&&"object"!=typeof t)throw Error(i(62));if(e=e.style,null!=n){for(var r in n)!n.hasOwnProperty(r)||null!=t&&t.hasOwnProperty(r)||(0===r.indexOf("--")?e.setProperty(r,""):"float"===r?e.cssFloat="":e[r]="");for(var a in t)r=t[a],t.hasOwnProperty(a)&&n[a]!==r&&_t(e,a,r)}else for(var o in t)t.hasOwnProperty(o)&&_t(e,o,t[o])}function Ct(e){if(-1===e.indexOf("-"))return!1;switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var Lt=new Map([["acceptCharset","accept-charset"],["htmlFor","for"],["httpEquiv","http-equiv"],["crossOrigin","crossorigin"],["accentHeight","accent-height"],["alignmentBaseline","alignment-baseline"],["arabicForm","arabic-form"],["baselineShift","baseline-shift"],["capHeight","cap-height"],["clipPath","clip-path"],["clipRule","clip-rule"],["colorInterpolation","color-interpolation"],["colorInterpolationFilters","color-interpolation-filters"],["colorProfile","color-profile"],["colorRendering","color-rendering"],["dominantBaseline","dominant-baseline"],["enableBackground","enable-background"],["fillOpacity","fill-opacity"],["fillRule","fill-rule"],["floodColor","flood-color"],["floodOpacity","flood-opacity"],["fontFamily","font-family"],["fontSize","font-size"],["fontSizeAdjust","font-size-adjust"],["fontStretch","font-stretch"],["fontStyle","font-style"],["fontVariant","font-variant"],["fontWeight","font-weight"],["glyphName","glyph-name"],["glyphOrientationHorizontal","glyph-orientation-horizontal"],["glyphOrientationVertical","glyph-orientation-vertical"],["horizAdvX","horiz-adv-x"],["horizOriginX","horiz-origin-x"],["imageRendering","image-rendering"],["letterSpacing","letter-spacing"],["lightingColor","lighting-color"],["markerEnd","marker-end"],["markerMid","marker-mid"],["markerStart","marker-start"],["overlinePosition","overline-position"],["overlineThickness","overline-thickness"],["paintOrder","paint-order"],["panose-1","panose-1"],["pointerEvents","pointer-events"],["renderingIntent","rendering-intent"],["shapeRendering","shape-rendering"],["stopColor","stop-color"],["stopOpacity","stop-opacity"],["strikethroughPosition","strikethrough-position"],["strikethroughThickness","strikethrough-thickness"],["strokeDasharray","stroke-dasharray"],["strokeDashoffset","stroke-dashoffset"],["strokeLinecap","stroke-linecap"],["strokeLinejoin","stroke-linejoin"],["strokeMiterlimit","stroke-miterlimit"],["strokeOpacity","stroke-opacity"],["strokeWidth","stroke-width"],["textAnchor","text-anchor"],["textDecoration","text-decoration"],["textRendering","text-rendering"],["transformOrigin","transform-origin"],["underlinePosition","underline-position"],["underlineThickness","underline-thickness"],["unicodeBidi","unicode-bidi"],["unicodeRange","unicode-range"],["unitsPerEm","units-per-em"],["vAlphabetic","v-alphabetic"],["vHanging","v-hanging"],["vIdeographic","v-ideographic"],["vMathematical","v-mathematical"],["vectorEffect","vector-effect"],["vertAdvY","vert-adv-y"],["vertOriginX","vert-origin-x"],["vertOriginY","vert-origin-y"],["wordSpacing","word-spacing"],["writingMode","writing-mode"],["xmlnsXlink","xmlns:xlink"],["xHeight","x-height"]]),At=/^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*:/i;function Tt(e){return At.test(""+e)?"javascript:throw new Error('React has blocked a javascript: URL as a security precaution.')":e}var jt=null;function Mt(e){return(e=e.target||e.srcElement||window).correspondingUseElement&&(e=e.correspondingUseElement),3===e.nodeType?e.parentNode:e}var Pt=null,Nt=null;function Ot(e){var t=Ue(e);if(t&&(e=t.stateNode)){var n=e[Ne]||null;e:switch(e=t.stateNode,t.type){case"input":if(gt(e,n.value,n.defaultValue,n.defaultValue,n.checked,n.defaultChecked,n.type,n.name),t=n.name,"radio"===n.type&&null!=t){for(n=e;n.parentNode;)n=n.parentNode;for(n=n.querySelectorAll('input[name="'+ht(""+t)+'"][type="radio"]'),t=0;t<n.length;t++){var r=n[t];if(r!==e&&r.form===e.form){var a=r[Ne]||null;if(!a)throw Error(i(90));gt(r,a.value,a.defaultValue,a.defaultValue,a.checked,a.defaultChecked,a.type,a.name)}}for(t=0;t<n.length;t++)(r=n[t]).form===e.form&&ft(r)}break e;case"textarea":kt(e,n.value,n.defaultValue);break e;case"select":null!=(t=n.value)&&vt(e,!!n.multiple,t,!1)}}}var Rt=!1;function Bt(e,t,n){if(Rt)return e(t,n);Rt=!0;try{return e(t)}finally{if(Rt=!1,(null!==Pt||null!==Nt)&&($c(),Pt&&(t=Pt,e=Nt,Nt=Pt=null,Ot(t),e)))for(t=0;t<e.length;t++)Ot(e[t])}}function Dt(e,t){var n=e.stateNode;if(null===n)return null;var r=n[Ne]||null;if(null===r)return null;n=r[t];e:switch(t){case"onClick":case"onClickCapture":case"onDoubleClick":case"onDoubleClickCapture":case"onMouseDown":case"onMouseDownCapture":case"onMouseMove":case"onMouseMoveCapture":case"onMouseUp":case"onMouseUpCapture":case"onMouseEnter":(r=!r.disabled)||(r=!("button"===(e=e.type)||"input"===e||"select"===e||"textarea"===e)),e=!r;break e;default:e=!1}if(e)return null;if(n&&"function"!=typeof n)throw Error(i(231,t,typeof n));return n}var Ft=!("undefined"==typeof window||void 0===window.document||void 0===window.document.createElement),It=!1;if(Ft)try{var zt={};Object.defineProperty(zt,"passive",{get:function(){It=!0}}),window.addEventListener("test",zt,zt),window.removeEventListener("test",zt,zt)}catch(Of){It=!1}var $t=null,Ut=null,qt=null;function Ht(){if(qt)return qt;var e,t,n=Ut,r=n.length,a="value"in $t?$t.value:$t.textContent,o=a.length;for(e=0;e<r&&n[e]===a[e];e++);var i=r-e;for(t=1;t<=i&&n[r-t]===a[o-t];t++);return qt=a.slice(e,1<t?1-t:void 0)}function Gt(e){var t=e.keyCode;return"charCode"in e?0===(e=e.charCode)&&13===t&&(e=13):e=t,10===e&&(e=13),32<=e||13===e?e:0}function Vt(){return!0}function Wt(){return!1}function Qt(e){function t(t,n,r,a,o){for(var i in this._reactName=t,this._targetInst=r,this.type=n,this.nativeEvent=a,this.target=o,this.currentTarget=null,e)e.hasOwnProperty(i)&&(t=e[i],this[i]=t?t(a):a[i]);return this.isDefaultPrevented=(null!=a.defaultPrevented?a.defaultPrevented:!1===a.returnValue)?Vt:Wt,this.isPropagationStopped=Wt,this}return f(t.prototype,{preventDefault:function(){this.defaultPrevented=!0;var e=this.nativeEvent;e&&(e.preventDefault?e.preventDefault():"unknown"!=typeof e.returnValue&&(e.returnValue=!1),this.isDefaultPrevented=Vt)},stopPropagation:function(){var e=this.nativeEvent;e&&(e.stopPropagation?e.stopPropagation():"unknown"!=typeof e.cancelBubble&&(e.cancelBubble=!0),this.isPropagationStopped=Vt)},persist:function(){},isPersistent:Vt}),t}var Kt,Yt,Xt,Zt={eventPhase:0,bubbles:0,cancelable:0,timeStamp:function(e){return e.timeStamp||Date.now()},defaultPrevented:0,isTrusted:0},Jt=Qt(Zt),en=f({},Zt,{view:0,detail:0}),tn=Qt(en),nn=f({},en,{screenX:0,screenY:0,clientX:0,clientY:0,pageX:0,pageY:0,ctrlKey:0,shiftKey:0,altKey:0,metaKey:0,getModifierState:mn,button:0,buttons:0,relatedTarget:function(e){return void 0===e.relatedTarget?e.fromElement===e.srcElement?e.toElement:e.fromElement:e.relatedTarget},movementX:function(e){return"movementX"in e?e.movementX:(e!==Xt&&(Xt&&"mousemove"===e.type?(Kt=e.screenX-Xt.screenX,Yt=e.screenY-Xt.screenY):Yt=Kt=0,Xt=e),Kt)},movementY:function(e){return"movementY"in e?e.movementY:Yt}}),rn=Qt(nn),an=Qt(f({},nn,{dataTransfer:0})),on=Qt(f({},en,{relatedTarget:0})),ln=Qt(f({},Zt,{animationName:0,elapsedTime:0,pseudoElement:0})),sn=Qt(f({},Zt,{clipboardData:function(e){return"clipboardData"in e?e.clipboardData:window.clipboardData}})),cn=Qt(f({},Zt,{data:0})),un={Esc:"Escape",Spacebar:" ",Left:"ArrowLeft",Up:"ArrowUp",Right:"ArrowRight",Down:"ArrowDown",Del:"Delete",Win:"OS",Menu:"ContextMenu",Apps:"ContextMenu",Scroll:"ScrollLock",MozPrintableKey:"Unidentified"},dn={8:"Backspace",9:"Tab",12:"Clear",13:"Enter",16:"Shift",17:"Control",18:"Alt",19:"Pause",20:"CapsLock",27:"Escape",32:" ",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",45:"Insert",46:"Delete",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"NumLock",145:"ScrollLock",224:"Meta"},fn={Alt:"altKey",Control:"ctrlKey",Meta:"metaKey",Shift:"shiftKey"};function pn(e){var t=this.nativeEvent;return t.getModifierState?t.getModifierState(e):!!(e=fn[e])&&!!t[e]}function mn(){return pn}var hn=Qt(f({},en,{key:function(e){if(e.key){var t=un[e.key]||e.key;if("Unidentified"!==t)return t}return"keypress"===e.type?13===(e=Gt(e))?"Enter":String.fromCharCode(e):"keydown"===e.type||"keyup"===e.type?dn[e.keyCode]||"Unidentified":""},code:0,location:0,ctrlKey:0,shiftKey:0,altKey:0,metaKey:0,repeat:0,locale:0,getModifierState:mn,charCode:function(e){return"keypress"===e.type?Gt(e):0},keyCode:function(e){return"keydown"===e.type||"keyup"===e.type?e.keyCode:0},which:function(e){return"keypress"===e.type?Gt(e):"keydown"===e.type||"keyup"===e.type?e.keyCode:0}})),gn=Qt(f({},nn,{pointerId:0,width:0,height:0,pressure:0,tangentialPressure:0,tiltX:0,tiltY:0,twist:0,pointerType:0,isPrimary:0})),bn=Qt(f({},en,{touches:0,targetTouches:0,changedTouches:0,altKey:0,metaKey:0,ctrlKey:0,shiftKey:0,getModifierState:mn})),yn=Qt(f({},Zt,{propertyName:0,elapsedTime:0,pseudoElement:0})),vn=Qt(f({},nn,{deltaX:function(e){return"deltaX"in e?e.deltaX:"wheelDeltaX"in e?-e.wheelDeltaX:0},deltaY:function(e){return"deltaY"in e?e.deltaY:"wheelDeltaY"in e?-e.wheelDeltaY:"wheelDelta"in e?-e.wheelDelta:0},deltaZ:0,deltaMode:0})),kn=Qt(f({},Zt,{newState:0,oldState:0})),wn=[9,13,27,32],Sn=Ft&&"CompositionEvent"in window,xn=null;Ft&&"documentMode"in document&&(xn=document.documentMode);var _n=Ft&&"TextEvent"in window&&!xn,En=Ft&&(!Sn||xn&&8<xn&&11>=xn),Cn=String.fromCharCode(32),Ln=!1;function An(e,t){switch(e){case"keyup":return-1!==wn.indexOf(t.keyCode);case"keydown":return 229!==t.keyCode;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Tn(e){return"object"==typeof(e=e.detail)&&"data"in e?e.data:null}var jn=!1;var Mn={color:!0,date:!0,datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0};function Pn(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return"input"===t?!!Mn[e.type]:"textarea"===t}function Nn(e,t,n,r){Pt?Nt?Nt.push(r):Nt=[r]:Pt=r,0<(t=Hu(t,"onChange")).length&&(n=new Jt("onChange","change",null,n,r),e.push({event:n,listeners:t}))}var On=null,Rn=null;function Bn(e){Bu(e,0)}function Dn(e){if(ft(qe(e)))return e}function Fn(e,t){if("change"===e)return t}var In=!1;if(Ft){var zn;if(Ft){var $n="oninput"in document;if(!$n){var Un=document.createElement("div");Un.setAttribute("oninput","return;"),$n="function"==typeof Un.oninput}zn=$n}else zn=!1;In=zn&&(!document.documentMode||9<document.documentMode)}function qn(){On&&(On.detachEvent("onpropertychange",Hn),Rn=On=null)}function Hn(e){if("value"===e.propertyName&&Dn(Rn)){var t=[];Nn(t,Rn,e,Mt(e)),Bt(Bn,t)}}function Gn(e,t,n){"focusin"===e?(qn(),Rn=n,(On=t).attachEvent("onpropertychange",Hn)):"focusout"===e&&qn()}function Vn(e){if("selectionchange"===e||"keyup"===e||"keydown"===e)return Dn(Rn)}function Wn(e,t){if("click"===e)return Dn(t)}function Qn(e,t){if("input"===e||"change"===e)return Dn(t)}var Kn="function"==typeof Object.is?Object.is:function(e,t){return e===t&&(0!==e||1/e==1/t)||e!=e&&t!=t};function Yn(e,t){if(Kn(e,t))return!0;if("object"!=typeof e||null===e||"object"!=typeof t||null===t)return!1;var n=Object.keys(e),r=Object.keys(t);if(n.length!==r.length)return!1;for(r=0;r<n.length;r++){var a=n[r];if(!Y.call(t,a)||!Kn(e[a],t[a]))return!1}return!0}function Xn(e){for(;e&&e.firstChild;)e=e.firstChild;return e}function Zn(e,t){var n,r=Xn(e);for(e=0;r;){if(3===r.nodeType){if(n=e+r.textContent.length,e<=t&&n>=t)return{node:r,offset:t-e};e=n}e:{for(;r;){if(r.nextSibling){r=r.nextSibling;break e}r=r.parentNode}r=void 0}r=Xn(r)}}function Jn(e,t){return!(!e||!t)&&(e===t||(!e||3!==e.nodeType)&&(t&&3===t.nodeType?Jn(e,t.parentNode):"contains"in e?e.contains(t):!!e.compareDocumentPosition&&!!(16&e.compareDocumentPosition(t))))}function er(e){for(var t=pt((e=null!=e&&null!=e.ownerDocument&&null!=e.ownerDocument.defaultView?e.ownerDocument.defaultView:window).document);t instanceof e.HTMLIFrameElement;){try{var n="string"==typeof t.contentWindow.location.href}catch(r){n=!1}if(!n)break;t=pt((e=t.contentWindow).document)}return t}function tr(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&("input"===t&&("text"===e.type||"search"===e.type||"tel"===e.type||"url"===e.type||"password"===e.type)||"textarea"===t||"true"===e.contentEditable)}var nr=Ft&&"documentMode"in document&&11>=document.documentMode,rr=null,ar=null,or=null,ir=!1;function lr(e,t,n){var r=n.window===n?n.document:9===n.nodeType?n:n.ownerDocument;ir||null==rr||rr!==pt(r)||("selectionStart"in(r=rr)&&tr(r)?r={start:r.selectionStart,end:r.selectionEnd}:r={anchorNode:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection()).anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset},or&&Yn(or,r)||(or=r,0<(r=Hu(ar,"onSelect")).length&&(t=new Jt("onSelect","select",null,t,n),e.push({event:t,listeners:r}),t.target=rr)))}function sr(e,t){var n={};return n[e.toLowerCase()]=t.toLowerCase(),n["Webkit"+e]="webkit"+t,n["Moz"+e]="moz"+t,n}var cr={animationend:sr("Animation","AnimationEnd"),animationiteration:sr("Animation","AnimationIteration"),animationstart:sr("Animation","AnimationStart"),transitionrun:sr("Transition","TransitionRun"),transitionstart:sr("Transition","TransitionStart"),transitioncancel:sr("Transition","TransitionCancel"),transitionend:sr("Transition","TransitionEnd")},ur={},dr={};function fr(e){if(ur[e])return ur[e];if(!cr[e])return e;var t,n=cr[e];for(t in n)if(n.hasOwnProperty(t)&&t in dr)return ur[e]=n[t];return e}Ft&&(dr=document.createElement("div").style,"AnimationEvent"in window||(delete cr.animationend.animation,delete cr.animationiteration.animation,delete cr.animationstart.animation),"TransitionEvent"in window||delete cr.transitionend.transition);var pr=fr("animationend"),mr=fr("animationiteration"),hr=fr("animationstart"),gr=fr("transitionrun"),br=fr("transitionstart"),yr=fr("transitioncancel"),vr=fr("transitionend"),kr=new Map,wr="abort auxClick beforeToggle cancel canPlay canPlayThrough click close contextMenu copy cut drag dragEnd dragEnter dragExit dragLeave dragOver dragStart drop durationChange emptied encrypted ended error gotPointerCapture input invalid keyDown keyPress keyUp load loadedData loadedMetadata loadStart lostPointerCapture mouseDown mouseMove mouseOut mouseOver mouseUp paste pause play playing pointerCancel pointerDown pointerMove pointerOut pointerOver pointerUp progress rateChange reset resize seeked seeking stalled submit suspend timeUpdate touchCancel touchEnd touchStart volumeChange scroll toggle touchMove waiting wheel".split(" ");function Sr(e,t){kr.set(e,t),Qe(t,[e])}wr.push("scrollEnd");var xr=new WeakMap;function _r(e,t){if("object"==typeof e&&null!==e){var n=xr.get(e);return void 0!==n?n:(t={value:e,source:t,stack:st(t)},xr.set(e,t),t)}return{value:e,source:t,stack:st(t)}}var Er=[],Cr=0,Lr=0;function Ar(){for(var e=Cr,t=Lr=Cr=0;t<e;){var n=Er[t];Er[t++]=null;var r=Er[t];Er[t++]=null;var a=Er[t];Er[t++]=null;var o=Er[t];if(Er[t++]=null,null!==r&&null!==a){var i=r.pending;null===i?a.next=a:(a.next=i.next,i.next=a),r.pending=a}0!==o&&Pr(n,a,o)}}function Tr(e,t,n,r){Er[Cr++]=e,Er[Cr++]=t,Er[Cr++]=n,Er[Cr++]=r,Lr|=r,e.lanes|=r,null!==(e=e.alternate)&&(e.lanes|=r)}function jr(e,t,n,r){return Tr(e,t,n,r),Nr(e)}function Mr(e,t){return Tr(e,null,null,t),Nr(e)}function Pr(e,t,n){e.lanes|=n;var r=e.alternate;null!==r&&(r.lanes|=n);for(var a=!1,o=e.return;null!==o;)o.childLanes|=n,null!==(r=o.alternate)&&(r.childLanes|=n),22===o.tag&&(null===(e=o.stateNode)||1&e._visibility||(a=!0)),e=o,o=o.return;return 3===e.tag?(o=e.stateNode,a&&null!==t&&(a=31-pe(n),null===(r=(e=o.hiddenUpdates)[a])?e[a]=[t]:r.push(t),t.lane=536870912|n),o):null}function Nr(e){if(50<Pc)throw Pc=0,Nc=null,Error(i(185));for(var t=e.return;null!==t;)t=(e=t).return;return 3===e.tag?e.stateNode:null}var Or={};function Rr(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.refCleanup=this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function Br(e,t,n,r){return new Rr(e,t,n,r)}function Dr(e){return!(!(e=e.prototype)||!e.isReactComponent)}function Fr(e,t){var n=e.alternate;return null===n?((n=Br(e.tag,t,e.key,e.mode)).elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=65011712&e.flags,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=null===t?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n.refCleanup=e.refCleanup,n}function Ir(e,t){e.flags&=65011714;var n=e.alternate;return null===n?(e.childLanes=0,e.lanes=t,e.child=null,e.subtreeFlags=0,e.memoizedProps=null,e.memoizedState=null,e.updateQueue=null,e.dependencies=null,e.stateNode=null):(e.childLanes=n.childLanes,e.lanes=n.lanes,e.child=n.child,e.subtreeFlags=0,e.deletions=null,e.memoizedProps=n.memoizedProps,e.memoizedState=n.memoizedState,e.updateQueue=n.updateQueue,e.type=n.type,t=n.dependencies,e.dependencies=null===t?null:{lanes:t.lanes,firstContext:t.firstContext}),e}function zr(e,t,n,r,a,o){var l=0;if(r=e,"function"==typeof e)Dr(e)&&(l=1);else if("string"==typeof e)l=function(e,t,n){if(1===n||null!=t.itemProp)return!1;switch(e){case"meta":case"title":return!0;case"style":if("string"!=typeof t.precedence||"string"!=typeof t.href||""===t.href)break;return!0;case"link":if("string"!=typeof t.rel||"string"!=typeof t.href||""===t.href||t.onLoad||t.onError)break;return"stylesheet"!==t.rel||(e=t.disabled,"string"==typeof t.precedence&&null==e);case"script":if(t.async&&"function"!=typeof t.async&&"symbol"!=typeof t.async&&!t.onLoad&&!t.onError&&t.src&&"string"==typeof t.src)return!0}return!1}(e,n,U.current)?26:"html"===e||"head"===e||"body"===e?27:5;else e:switch(e){case L:return(e=Br(31,n,t,a)).elementType=L,e.lanes=o,e;case g:return $r(n.children,a,o,t);case b:l=8,a|=24;break;case y:return(e=Br(12,n,t,2|a)).elementType=y,e.lanes=o,e;case x:return(e=Br(13,n,t,a)).elementType=x,e.lanes=o,e;case _:return(e=Br(19,n,t,a)).elementType=_,e.lanes=o,e;default:if("object"==typeof e&&null!==e)switch(e.$$typeof){case v:case w:l=10;break e;case k:l=9;break e;case S:l=11;break e;case E:l=14;break e;case C:l=16,r=null;break e}l=29,n=Error(i(130,null===e?"null":typeof e,"")),r=null}return(t=Br(l,n,t,a)).elementType=e,t.type=r,t.lanes=o,t}function $r(e,t,n,r){return(e=Br(7,e,r,t)).lanes=n,e}function Ur(e,t,n){return(e=Br(6,e,null,t)).lanes=n,e}function qr(e,t,n){return(t=Br(4,null!==e.children?e.children:[],e.key,t)).lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}var Hr=[],Gr=0,Vr=null,Wr=0,Qr=[],Kr=0,Yr=null,Xr=1,Zr="";function Jr(e,t){Hr[Gr++]=Wr,Hr[Gr++]=Vr,Vr=e,Wr=t}function ea(e,t,n){Qr[Kr++]=Xr,Qr[Kr++]=Zr,Qr[Kr++]=Yr,Yr=e;var r=Xr;e=Zr;var a=32-pe(r)-1;r&=~(1<<a),n+=1;var o=32-pe(t)+a;if(30<o){var i=a-a%5;o=(r&(1<<i)-1).toString(32),r>>=i,a-=i,Xr=1<<32-pe(t)+a|n<<a|r,Zr=o+e}else Xr=1<<o|n<<a|r,Zr=e}function ta(e){null!==e.return&&(Jr(e,1),ea(e,1,0))}function na(e){for(;e===Vr;)Vr=Hr[--Gr],Hr[Gr]=null,Wr=Hr[--Gr],Hr[Gr]=null;for(;e===Yr;)Yr=Qr[--Kr],Qr[Kr]=null,Zr=Qr[--Kr],Qr[Kr]=null,Xr=Qr[--Kr],Qr[Kr]=null}var ra=null,aa=null,oa=!1,ia=null,la=!1,sa=Error(i(519));function ca(e){throw ha(_r(Error(i(418,"")),e)),sa}function ua(e){var t=e.stateNode,n=e.type,r=e.memoizedProps;switch(t[Pe]=e,t[Ne]=r,n){case"dialog":Du("cancel",t),Du("close",t);break;case"iframe":case"object":case"embed":Du("load",t);break;case"video":case"audio":for(n=0;n<Ou.length;n++)Du(Ou[n],t);break;case"source":Du("error",t);break;case"img":case"image":case"link":Du("error",t),Du("load",t);break;case"details":Du("toggle",t);break;case"input":Du("invalid",t),bt(t,r.value,r.defaultValue,r.checked,r.defaultChecked,r.type,r.name,!0),dt(t);break;case"select":Du("invalid",t);break;case"textarea":Du("invalid",t),wt(t,r.value,r.defaultValue,r.children),dt(t)}"string"!=typeof(n=r.children)&&"number"!=typeof n&&"bigint"!=typeof n||t.textContent===""+n||!0===r.suppressHydrationWarning||Yu(t.textContent,n)?(null!=r.popover&&(Du("beforetoggle",t),Du("toggle",t)),null!=r.onScroll&&Du("scroll",t),null!=r.onScrollEnd&&Du("scrollend",t),null!=r.onClick&&(t.onclick=Xu),t=!0):t=!1,t||ca(e)}function da(e){for(ra=e.return;ra;)switch(ra.tag){case 5:case 13:return void(la=!1);case 27:case 3:return void(la=!0);default:ra=ra.return}}function fa(e){if(e!==ra)return!1;if(!oa)return da(e),oa=!0,!1;var t,n=e.tag;if((t=3!==n&&27!==n)&&((t=5===n)&&(t=!("form"!==(t=e.type)&&"button"!==t)||id(e.type,e.memoizedProps)),t=!t),t&&aa&&ca(e),da(e),13===n){if(!(e=null!==(e=e.memoizedState)?e.dehydrated:null))throw Error(i(317));e:{for(e=e.nextSibling,n=0;e;){if(8===e.nodeType)if("/$"===(t=e.data)){if(0===n){aa=bd(e.nextSibling);break e}n--}else"$"!==t&&"$!"!==t&&"$?"!==t||n++;e=e.nextSibling}aa=null}}else 27===n?(n=aa,pd(e.type)?(e=yd,yd=null,aa=e):aa=n):aa=ra?bd(e.stateNode.nextSibling):null;return!0}function pa(){aa=ra=null,oa=!1}function ma(){var e=ia;return null!==e&&(null===vc?vc=e:vc.push.apply(vc,e),ia=null),e}function ha(e){null===ia?ia=[e]:ia.push(e)}var ga=I(null),ba=null,ya=null;function va(e,t,n){$(ga,t._currentValue),t._currentValue=n}function ka(e){e._currentValue=ga.current,z(ga)}function wa(e,t,n){for(;null!==e;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,null!==r&&(r.childLanes|=t)):null!==r&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function Sa(e,t,n,r){var a=e.child;for(null!==a&&(a.return=e);null!==a;){var o=a.dependencies;if(null!==o){var l=a.child;o=o.firstContext;e:for(;null!==o;){var s=o;o=a;for(var c=0;c<t.length;c++)if(s.context===t[c]){o.lanes|=n,null!==(s=o.alternate)&&(s.lanes|=n),wa(o.return,n,e),r||(l=null);break e}o=s.next}}else if(18===a.tag){if(null===(l=a.return))throw Error(i(341));l.lanes|=n,null!==(o=l.alternate)&&(o.lanes|=n),wa(l,n,e),l=null}else l=a.child;if(null!==l)l.return=a;else for(l=a;null!==l;){if(l===e){l=null;break}if(null!==(a=l.sibling)){a.return=l.return,l=a;break}l=l.return}a=l}}function xa(e,t,n,r){e=null;for(var a=t,o=!1;null!==a;){if(!o)if(524288&a.flags)o=!0;else if(262144&a.flags)break;if(10===a.tag){var l=a.alternate;if(null===l)throw Error(i(387));if(null!==(l=l.memoizedProps)){var s=a.type;Kn(a.pendingProps.value,l.value)||(null!==e?e.push(s):e=[s])}}else if(a===G.current){if(null===(l=a.alternate))throw Error(i(387));l.memoizedState.memoizedState!==a.memoizedState.memoizedState&&(null!==e?e.push(Qd):e=[Qd])}a=a.return}null!==e&&Sa(t,e,n,r),t.flags|=262144}function _a(e){for(e=e.firstContext;null!==e;){if(!Kn(e.context._currentValue,e.memoizedValue))return!0;e=e.next}return!1}function Ea(e){ba=e,ya=null,null!==(e=e.dependencies)&&(e.firstContext=null)}function Ca(e){return Aa(ba,e)}function La(e,t){return null===ba&&Ea(e),Aa(e,t)}function Aa(e,t){var n=t._currentValue;if(t={context:t,memoizedValue:n,next:null},null===ya){if(null===e)throw Error(i(308));ya=t,e.dependencies={lanes:0,firstContext:t},e.flags|=524288}else ya=ya.next=t;return n}var Ta="undefined"!=typeof AbortController?AbortController:function(){var e=[],t=this.signal={aborted:!1,addEventListener:function(t,n){e.push(n)}};this.abort=function(){t.aborted=!0,e.forEach(function(e){return e()})}},ja=r.unstable_scheduleCallback,Ma=r.unstable_NormalPriority,Pa={$$typeof:w,Consumer:null,Provider:null,_currentValue:null,_currentValue2:null,_threadCount:0};function Na(){return{controller:new Ta,data:new Map,refCount:0}}function Oa(e){e.refCount--,0===e.refCount&&ja(Ma,function(){e.controller.abort()})}var Ra=null,Ba=0,Da=0,Fa=null;function Ia(){if(0===--Ba&&null!==Ra){null!==Fa&&(Fa.status="fulfilled");var e=Ra;Ra=null,Da=0,Fa=null;for(var t=0;t<e.length;t++)(0,e[t])()}}var za=O.S;O.S=function(e,t){"object"==typeof t&&null!==t&&"function"==typeof t.then&&function(e,t){if(null===Ra){var n=Ra=[];Ba=0,Da=Tu(),Fa={status:"pending",value:void 0,then:function(e){n.push(e)}}}Ba++,t.then(Ia,Ia)}(0,t),null!==za&&za(e,t)};var $a=I(null);function Ua(){var e=$a.current;return null!==e?e:rc.pooledCache}function qa(e,t){$($a,null===t?$a.current:t.pool)}function Ha(){var e=Ua();return null===e?null:{parent:Pa._currentValue,pool:e}}var Ga=Error(i(460)),Va=Error(i(474)),Wa=Error(i(542)),Qa={then:function(){}};function Ka(e){return"fulfilled"===(e=e.status)||"rejected"===e}function Ya(){}function Xa(e,t,n){switch(void 0===(n=e[n])?e.push(t):n!==t&&(t.then(Ya,Ya),t=n),t.status){case"fulfilled":return t.value;case"rejected":throw eo(e=t.reason),e;default:if("string"==typeof t.status)t.then(Ya,Ya);else{if(null!==(e=rc)&&100<e.shellSuspendCounter)throw Error(i(482));(e=t).status="pending",e.then(function(e){if("pending"===t.status){var n=t;n.status="fulfilled",n.value=e}},function(e){if("pending"===t.status){var n=t;n.status="rejected",n.reason=e}})}switch(t.status){case"fulfilled":return t.value;case"rejected":throw eo(e=t.reason),e}throw Za=t,Ga}}var Za=null;function Ja(){if(null===Za)throw Error(i(459));var e=Za;return Za=null,e}function eo(e){if(e===Ga||e===Wa)throw Error(i(483))}var to=!1;function no(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function ro(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function ao(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function oo(e,t,n){var r=e.updateQueue;if(null===r)return null;if(r=r.shared,2&nc){var a=r.pending;return null===a?t.next=t:(t.next=a.next,a.next=t),r.pending=t,t=Nr(e),Pr(e,null,n),t}return Tr(e,r,t,n),Nr(e)}function io(e,t,n){if(null!==(t=t.updateQueue)&&(t=t.shared,4194048&n)){var r=t.lanes;n|=r&=e.pendingLanes,t.lanes=n,Le(e,n)}}function lo(e,t){var n=e.updateQueue,r=e.alternate;if(null!==r&&n===(r=r.updateQueue)){var a=null,o=null;if(null!==(n=n.firstBaseUpdate)){do{var i={lane:n.lane,tag:n.tag,payload:n.payload,callback:null,next:null};null===o?a=o=i:o=o.next=i,n=n.next}while(null!==n);null===o?a=o=t:o=o.next=t}else a=o=t;return n={baseState:r.baseState,firstBaseUpdate:a,lastBaseUpdate:o,shared:r.shared,callbacks:r.callbacks},void(e.updateQueue=n)}null===(e=n.lastBaseUpdate)?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}var so=!1;function co(){if(so){if(null!==Fa)throw Fa}}function uo(e,t,n,r){so=!1;var a=e.updateQueue;to=!1;var o=a.firstBaseUpdate,i=a.lastBaseUpdate,l=a.shared.pending;if(null!==l){a.shared.pending=null;var s=l,c=s.next;s.next=null,null===i?o=c:i.next=c,i=s;var u=e.alternate;null!==u&&((l=(u=u.updateQueue).lastBaseUpdate)!==i&&(null===l?u.firstBaseUpdate=c:l.next=c,u.lastBaseUpdate=s))}if(null!==o){var d=a.baseState;for(i=0,u=c=s=null,l=o;;){var p=-536870913&l.lane,m=p!==l.lane;if(m?(oc&p)===p:(r&p)===p){0!==p&&p===Da&&(so=!0),null!==u&&(u=u.next={lane:0,tag:l.tag,payload:l.payload,callback:null,next:null});e:{var h=e,g=l;p=t;var b=n;switch(g.tag){case 1:if("function"==typeof(h=g.payload)){d=h.call(b,d,p);break e}d=h;break e;case 3:h.flags=-65537&h.flags|128;case 0:if(null==(p="function"==typeof(h=g.payload)?h.call(b,d,p):h))break e;d=f({},d,p);break e;case 2:to=!0}}null!==(p=l.callback)&&(e.flags|=64,m&&(e.flags|=8192),null===(m=a.callbacks)?a.callbacks=[p]:m.push(p))}else m={lane:p,tag:l.tag,payload:l.payload,callback:l.callback,next:null},null===u?(c=u=m,s=d):u=u.next=m,i|=p;if(null===(l=l.next)){if(null===(l=a.shared.pending))break;l=(m=l).next,m.next=null,a.lastBaseUpdate=m,a.shared.pending=null}}null===u&&(s=d),a.baseState=s,a.firstBaseUpdate=c,a.lastBaseUpdate=u,null===o&&(a.shared.lanes=0),pc|=i,e.lanes=i,e.memoizedState=d}}function fo(e,t){if("function"!=typeof e)throw Error(i(191,e));e.call(t)}function po(e,t){var n=e.callbacks;if(null!==n)for(e.callbacks=null,e=0;e<n.length;e++)fo(n[e],t)}var mo=I(null),ho=I(0);function go(e,t){$(ho,e=dc),$(mo,t),dc=e|t.baseLanes}function bo(){$(ho,dc),$(mo,mo.current)}function yo(){dc=ho.current,z(mo),z(ho)}var vo=0,ko=null,wo=null,So=null,xo=!1,_o=!1,Eo=!1,Co=0,Lo=0,Ao=null,To=0;function jo(){throw Error(i(321))}function Mo(e,t){if(null===t)return!1;for(var n=0;n<t.length&&n<e.length;n++)if(!Kn(e[n],t[n]))return!1;return!0}function Po(e,t,n,r,a,o){return vo=o,ko=t,t.memoizedState=null,t.updateQueue=null,t.lanes=0,O.H=null===e||null===e.memoizedState?Vi:Wi,Eo=!1,o=n(r,a),Eo=!1,_o&&(o=Oo(t,n,r,a)),No(e),o}function No(e){O.H=Gi;var t=null!==wo&&null!==wo.next;if(vo=0,So=wo=ko=null,xo=!1,Lo=0,Ao=null,t)throw Error(i(300));null===e||Ll||null!==(e=e.dependencies)&&_a(e)&&(Ll=!0)}function Oo(e,t,n,r){ko=e;var a=0;do{if(_o&&(Ao=null),Lo=0,_o=!1,25<=a)throw Error(i(301));if(a+=1,So=wo=null,null!=e.updateQueue){var o=e.updateQueue;o.lastEffect=null,o.events=null,o.stores=null,null!=o.memoCache&&(o.memoCache.index=0)}O.H=Qi,o=t(n,r)}while(_o);return o}function Ro(){var e=O.H,t=e.useState()[0];return t="function"==typeof t.then?$o(t):t,e=e.useState()[0],(null!==wo?wo.memoizedState:null)!==e&&(ko.flags|=1024),t}function Bo(){var e=0!==Co;return Co=0,e}function Do(e,t,n){t.updateQueue=e.updateQueue,t.flags&=-2053,e.lanes&=~n}function Fo(e){if(xo){for(e=e.memoizedState;null!==e;){var t=e.queue;null!==t&&(t.pending=null),e=e.next}xo=!1}vo=0,So=wo=ko=null,_o=!1,Lo=Co=0,Ao=null}function Io(){var e={memoizedState:null,baseState:null,baseQueue:null,queue:null,next:null};return null===So?ko.memoizedState=So=e:So=So.next=e,So}function zo(){if(null===wo){var e=ko.alternate;e=null!==e?e.memoizedState:null}else e=wo.next;var t=null===So?ko.memoizedState:So.next;if(null!==t)So=t,wo=e;else{if(null===e){if(null===ko.alternate)throw Error(i(467));throw Error(i(310))}e={memoizedState:(wo=e).memoizedState,baseState:wo.baseState,baseQueue:wo.baseQueue,queue:wo.queue,next:null},null===So?ko.memoizedState=So=e:So=So.next=e}return So}function $o(e){var t=Lo;return Lo+=1,null===Ao&&(Ao=[]),e=Xa(Ao,e,t),t=ko,null===(null===So?t.memoizedState:So.next)&&(t=t.alternate,O.H=null===t||null===t.memoizedState?Vi:Wi),e}function Uo(e){if(null!==e&&"object"==typeof e){if("function"==typeof e.then)return $o(e);if(e.$$typeof===w)return Ca(e)}throw Error(i(438,String(e)))}function qo(e){var t=null,n=ko.updateQueue;if(null!==n&&(t=n.memoCache),null==t){var r=ko.alternate;null!==r&&(null!==(r=r.updateQueue)&&(null!=(r=r.memoCache)&&(t={data:r.data.map(function(e){return e.slice()}),index:0})))}if(null==t&&(t={data:[],index:0}),null===n&&(n={lastEffect:null,events:null,stores:null,memoCache:null},ko.updateQueue=n),n.memoCache=t,void 0===(n=t.data[t.index]))for(n=t.data[t.index]=Array(e),r=0;r<e;r++)n[r]=A;return t.index++,n}function Ho(e,t){return"function"==typeof t?t(e):t}function Go(e){return Vo(zo(),wo,e)}function Vo(e,t,n){var r=e.queue;if(null===r)throw Error(i(311));r.lastRenderedReducer=n;var a=e.baseQueue,o=r.pending;if(null!==o){if(null!==a){var l=a.next;a.next=o.next,o.next=l}t.baseQueue=a=o,r.pending=null}if(o=e.baseState,null===a)e.memoizedState=o;else{var s=l=null,c=null,u=t=a.next,d=!1;do{var f=-536870913&u.lane;if(f!==u.lane?(oc&f)===f:(vo&f)===f){var p=u.revertLane;if(0===p)null!==c&&(c=c.next={lane:0,revertLane:0,action:u.action,hasEagerState:u.hasEagerState,eagerState:u.eagerState,next:null}),f===Da&&(d=!0);else{if((vo&p)===p){u=u.next,p===Da&&(d=!0);continue}f={lane:0,revertLane:u.revertLane,action:u.action,hasEagerState:u.hasEagerState,eagerState:u.eagerState,next:null},null===c?(s=c=f,l=o):c=c.next=f,ko.lanes|=p,pc|=p}f=u.action,Eo&&n(o,f),o=u.hasEagerState?u.eagerState:n(o,f)}else p={lane:f,revertLane:u.revertLane,action:u.action,hasEagerState:u.hasEagerState,eagerState:u.eagerState,next:null},null===c?(s=c=p,l=o):c=c.next=p,ko.lanes|=f,pc|=f;u=u.next}while(null!==u&&u!==t);if(null===c?l=o:c.next=s,!Kn(o,e.memoizedState)&&(Ll=!0,d&&null!==(n=Fa)))throw n;e.memoizedState=o,e.baseState=l,e.baseQueue=c,r.lastRenderedState=o}return null===a&&(r.lanes=0),[e.memoizedState,r.dispatch]}function Wo(e){var t=zo(),n=t.queue;if(null===n)throw Error(i(311));n.lastRenderedReducer=e;var r=n.dispatch,a=n.pending,o=t.memoizedState;if(null!==a){n.pending=null;var l=a=a.next;do{o=e(o,l.action),l=l.next}while(l!==a);Kn(o,t.memoizedState)||(Ll=!0),t.memoizedState=o,null===t.baseQueue&&(t.baseState=o),n.lastRenderedState=o}return[o,r]}function Qo(e,t,n){var r=ko,a=zo(),o=oa;if(o){if(void 0===n)throw Error(i(407));n=n()}else n=t();var l=!Kn((wo||a).memoizedState,n);if(l&&(a.memoizedState=n,Ll=!0),a=a.queue,bi(2048,8,Xo.bind(null,r,a,e),[e]),a.getSnapshot!==t||l||null!==So&&1&So.memoizedState.tag){if(r.flags|=2048,mi(9,{destroy:void 0,resource:void 0},Yo.bind(null,r,a,n,t),null),null===rc)throw Error(i(349));o||124&vo||Ko(r,t,n)}return n}function Ko(e,t,n){e.flags|=16384,e={getSnapshot:t,value:n},null===(t=ko.updateQueue)?(t={lastEffect:null,events:null,stores:null,memoCache:null},ko.updateQueue=t,t.stores=[e]):null===(n=t.stores)?t.stores=[e]:n.push(e)}function Yo(e,t,n,r){t.value=n,t.getSnapshot=r,Zo(t)&&Jo(e)}function Xo(e,t,n){return n(function(){Zo(t)&&Jo(e)})}function Zo(e){var t=e.getSnapshot;e=e.value;try{var n=t();return!Kn(e,n)}catch(r){return!0}}function Jo(e){var t=Mr(e,2);null!==t&&Bc(t,e,2)}function ei(e){var t=Io();if("function"==typeof e){var n=e;if(e=n(),Eo){fe(!0);try{n()}finally{fe(!1)}}}return t.memoizedState=t.baseState=e,t.queue={pending:null,lanes:0,dispatch:null,lastRenderedReducer:Ho,lastRenderedState:e},t}function ti(e,t,n,r){return e.baseState=n,Vo(e,wo,"function"==typeof r?r:Ho)}function ni(e,t,n,r,a){if(Ui(e))throw Error(i(485));if(null!==(e=t.action)){var o={payload:a,action:e,next:null,isTransition:!0,status:"pending",value:null,reason:null,listeners:[],then:function(e){o.listeners.push(e)}};null!==O.T?n(!0):o.isTransition=!1,r(o),null===(n=t.pending)?(o.next=t.pending=o,ri(t,o)):(o.next=n.next,t.pending=n.next=o)}}function ri(e,t){var n=t.action,r=t.payload,a=e.state;if(t.isTransition){var o=O.T,i={};O.T=i;try{var l=n(a,r),s=O.S;null!==s&&s(i,l),ai(e,t,l)}catch(c){ii(e,t,c)}finally{O.T=o}}else try{ai(e,t,o=n(a,r))}catch(u){ii(e,t,u)}}function ai(e,t,n){null!==n&&"object"==typeof n&&"function"==typeof n.then?n.then(function(n){oi(e,t,n)},function(n){return ii(e,t,n)}):oi(e,t,n)}function oi(e,t,n){t.status="fulfilled",t.value=n,li(t),e.state=n,null!==(t=e.pending)&&((n=t.next)===t?e.pending=null:(n=n.next,t.next=n,ri(e,n)))}function ii(e,t,n){var r=e.pending;if(e.pending=null,null!==r){r=r.next;do{t.status="rejected",t.reason=n,li(t),t=t.next}while(t!==r)}e.action=null}function li(e){e=e.listeners;for(var t=0;t<e.length;t++)(0,e[t])()}function si(e,t){return t}function ci(e,t){if(oa){var n=rc.formState;if(null!==n){e:{var r=ko;if(oa){if(aa){t:{for(var a=aa,o=la;8!==a.nodeType;){if(!o){a=null;break t}if(null===(a=bd(a.nextSibling))){a=null;break t}}a="F!"===(o=a.data)||"F"===o?a:null}if(a){aa=bd(a.nextSibling),r="F!"===a.data;break e}}ca(r)}r=!1}r&&(t=n[0])}}return(n=Io()).memoizedState=n.baseState=t,r={pending:null,lanes:0,dispatch:null,lastRenderedReducer:si,lastRenderedState:t},n.queue=r,n=Ii.bind(null,ko,r),r.dispatch=n,r=ei(!1),o=$i.bind(null,ko,!1,r.queue),a={state:t,dispatch:null,action:e,pending:null},(r=Io()).queue=a,n=ni.bind(null,ko,a,o,n),a.dispatch=n,r.memoizedState=e,[t,n,!1]}function ui(e){return di(zo(),wo,e)}function di(e,t,n){if(t=Vo(e,t,si)[0],e=Go(Ho)[0],"object"==typeof t&&null!==t&&"function"==typeof t.then)try{var r=$o(t)}catch(i){if(i===Ga)throw Wa;throw i}else r=t;var a=(t=zo()).queue,o=a.dispatch;return n!==t.memoizedState&&(ko.flags|=2048,mi(9,{destroy:void 0,resource:void 0},fi.bind(null,a,n),null)),[r,o,e]}function fi(e,t){e.action=t}function pi(e){var t=zo(),n=wo;if(null!==n)return di(t,n,e);zo(),t=t.memoizedState;var r=(n=zo()).queue.dispatch;return n.memoizedState=e,[t,r,!1]}function mi(e,t,n,r){return e={tag:e,create:n,deps:r,inst:t,next:null},null===(t=ko.updateQueue)&&(t={lastEffect:null,events:null,stores:null,memoCache:null},ko.updateQueue=t),null===(n=t.lastEffect)?t.lastEffect=e.next=e:(r=n.next,n.next=e,e.next=r,t.lastEffect=e),e}function hi(){return zo().memoizedState}function gi(e,t,n,r){var a=Io();r=void 0===r?null:r,ko.flags|=e,a.memoizedState=mi(1|t,{destroy:void 0,resource:void 0},n,r)}function bi(e,t,n,r){var a=zo();r=void 0===r?null:r;var o=a.memoizedState.inst;null!==wo&&null!==r&&Mo(r,wo.memoizedState.deps)?a.memoizedState=mi(t,o,n,r):(ko.flags|=e,a.memoizedState=mi(1|t,o,n,r))}function yi(e,t){gi(8390656,8,e,t)}function vi(e,t){bi(2048,8,e,t)}function ki(e,t){return bi(4,2,e,t)}function wi(e,t){return bi(4,4,e,t)}function Si(e,t){if("function"==typeof t){e=e();var n=t(e);return function(){"function"==typeof n?n():t(null)}}if(null!=t)return e=e(),t.current=e,function(){t.current=null}}function xi(e,t,n){n=null!=n?n.concat([e]):null,bi(4,4,Si.bind(null,t,e),n)}function _i(){}function Ei(e,t){var n=zo();t=void 0===t?null:t;var r=n.memoizedState;return null!==t&&Mo(t,r[1])?r[0]:(n.memoizedState=[e,t],e)}function Ci(e,t){var n=zo();t=void 0===t?null:t;var r=n.memoizedState;if(null!==t&&Mo(t,r[1]))return r[0];if(r=e(),Eo){fe(!0);try{e()}finally{fe(!1)}}return n.memoizedState=[r,t],r}function Li(e,t,n){return void 0===n||1073741824&vo?e.memoizedState=t:(e.memoizedState=n,e=Rc(),ko.lanes|=e,pc|=e,n)}function Ai(e,t,n,r){return Kn(n,t)?n:null!==mo.current?(e=Li(e,n,r),Kn(e,t)||(Ll=!0),e):42&vo?(e=Rc(),ko.lanes|=e,pc|=e,t):(Ll=!0,e.memoizedState=n)}function Ti(e,t,n,r,a){var o=R.p;R.p=0!==o&&8>o?o:8;var i,l,s,c=O.T,u={};O.T=u,$i(e,!1,t,n);try{var d=a(),f=O.S;if(null!==f&&f(u,d),null!==d&&"object"==typeof d&&"function"==typeof d.then)zi(e,t,(i=r,l=[],s={status:"pending",value:null,reason:null,then:function(e){l.push(e)}},d.then(function(){s.status="fulfilled",s.value=i;for(var e=0;e<l.length;e++)(0,l[e])(i)},function(e){for(s.status="rejected",s.reason=e,e=0;e<l.length;e++)(0,l[e])(void 0)}),s),Oc());else zi(e,t,r,Oc())}catch(p){zi(e,t,{then:function(){},status:"rejected",reason:p},Oc())}finally{R.p=o,O.T=c}}function ji(){}function Mi(e,t,n,r){if(5!==e.tag)throw Error(i(476));var a=Pi(e).queue;Ti(e,a,t,B,null===n?ji:function(){return Ni(e),n(r)})}function Pi(e){var t=e.memoizedState;if(null!==t)return t;var n={};return(t={memoizedState:B,baseState:B,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Ho,lastRenderedState:B},next:null}).next={memoizedState:n,baseState:n,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Ho,lastRenderedState:n},next:null},e.memoizedState=t,null!==(e=e.alternate)&&(e.memoizedState=t),t}function Ni(e){zi(e,Pi(e).next.queue,{},Oc())}function Oi(){return Ca(Qd)}function Ri(){return zo().memoizedState}function Bi(){return zo().memoizedState}function Di(e){for(var t=e.return;null!==t;){switch(t.tag){case 24:case 3:var n=Oc(),r=oo(t,e=ao(n),n);return null!==r&&(Bc(r,t,n),io(r,t,n)),t={cache:Na()},void(e.payload=t)}t=t.return}}function Fi(e,t,n){var r=Oc();n={lane:r,revertLane:0,action:n,hasEagerState:!1,eagerState:null,next:null},Ui(e)?qi(t,n):null!==(n=jr(e,t,n,r))&&(Bc(n,e,r),Hi(n,t,r))}function Ii(e,t,n){zi(e,t,n,Oc())}function zi(e,t,n,r){var a={lane:r,revertLane:0,action:n,hasEagerState:!1,eagerState:null,next:null};if(Ui(e))qi(t,a);else{var o=e.alternate;if(0===e.lanes&&(null===o||0===o.lanes)&&null!==(o=t.lastRenderedReducer))try{var i=t.lastRenderedState,l=o(i,n);if(a.hasEagerState=!0,a.eagerState=l,Kn(l,i))return Tr(e,t,a,0),null===rc&&Ar(),!1}catch(s){}if(null!==(n=jr(e,t,a,r)))return Bc(n,e,r),Hi(n,t,r),!0}return!1}function $i(e,t,n,r){if(r={lane:2,revertLane:Tu(),action:r,hasEagerState:!1,eagerState:null,next:null},Ui(e)){if(t)throw Error(i(479))}else null!==(t=jr(e,n,r,2))&&Bc(t,e,2)}function Ui(e){var t=e.alternate;return e===ko||null!==t&&t===ko}function qi(e,t){_o=xo=!0;var n=e.pending;null===n?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Hi(e,t,n){if(4194048&n){var r=t.lanes;n|=r&=e.pendingLanes,t.lanes=n,Le(e,n)}}var Gi={readContext:Ca,use:Uo,useCallback:jo,useContext:jo,useEffect:jo,useImperativeHandle:jo,useLayoutEffect:jo,useInsertionEffect:jo,useMemo:jo,useReducer:jo,useRef:jo,useState:jo,useDebugValue:jo,useDeferredValue:jo,useTransition:jo,useSyncExternalStore:jo,useId:jo,useHostTransitionStatus:jo,useFormState:jo,useActionState:jo,useOptimistic:jo,useMemoCache:jo,useCacheRefresh:jo},Vi={readContext:Ca,use:Uo,useCallback:function(e,t){return Io().memoizedState=[e,void 0===t?null:t],e},useContext:Ca,useEffect:yi,useImperativeHandle:function(e,t,n){n=null!=n?n.concat([e]):null,gi(4194308,4,Si.bind(null,t,e),n)},useLayoutEffect:function(e,t){return gi(4194308,4,e,t)},useInsertionEffect:function(e,t){gi(4,2,e,t)},useMemo:function(e,t){var n=Io();t=void 0===t?null:t;var r=e();if(Eo){fe(!0);try{e()}finally{fe(!1)}}return n.memoizedState=[r,t],r},useReducer:function(e,t,n){var r=Io();if(void 0!==n){var a=n(t);if(Eo){fe(!0);try{n(t)}finally{fe(!1)}}}else a=t;return r.memoizedState=r.baseState=a,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:a},r.queue=e,e=e.dispatch=Fi.bind(null,ko,e),[r.memoizedState,e]},useRef:function(e){return e={current:e},Io().memoizedState=e},useState:function(e){var t=(e=ei(e)).queue,n=Ii.bind(null,ko,t);return t.dispatch=n,[e.memoizedState,n]},useDebugValue:_i,useDeferredValue:function(e,t){return Li(Io(),e,t)},useTransition:function(){var e=ei(!1);return e=Ti.bind(null,ko,e.queue,!0,!1),Io().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,n){var r=ko,a=Io();if(oa){if(void 0===n)throw Error(i(407));n=n()}else{if(n=t(),null===rc)throw Error(i(349));124&oc||Ko(r,t,n)}a.memoizedState=n;var o={value:n,getSnapshot:t};return a.queue=o,yi(Xo.bind(null,r,o,e),[e]),r.flags|=2048,mi(9,{destroy:void 0,resource:void 0},Yo.bind(null,r,o,n,t),null),n},useId:function(){var e=Io(),t=rc.identifierPrefix;if(oa){var n=Zr;t="\xab"+t+"R"+(n=(Xr&~(1<<32-pe(Xr)-1)).toString(32)+n),0<(n=Co++)&&(t+="H"+n.toString(32)),t+="\xbb"}else t="\xab"+t+"r"+(n=To++).toString(32)+"\xbb";return e.memoizedState=t},useHostTransitionStatus:Oi,useFormState:ci,useActionState:ci,useOptimistic:function(e){var t=Io();t.memoizedState=t.baseState=e;var n={pending:null,lanes:0,dispatch:null,lastRenderedReducer:null,lastRenderedState:null};return t.queue=n,t=$i.bind(null,ko,!0,n),n.dispatch=t,[e,t]},useMemoCache:qo,useCacheRefresh:function(){return Io().memoizedState=Di.bind(null,ko)}},Wi={readContext:Ca,use:Uo,useCallback:Ei,useContext:Ca,useEffect:vi,useImperativeHandle:xi,useInsertionEffect:ki,useLayoutEffect:wi,useMemo:Ci,useReducer:Go,useRef:hi,useState:function(){return Go(Ho)},useDebugValue:_i,useDeferredValue:function(e,t){return Ai(zo(),wo.memoizedState,e,t)},useTransition:function(){var e=Go(Ho)[0],t=zo().memoizedState;return["boolean"==typeof e?e:$o(e),t]},useSyncExternalStore:Qo,useId:Ri,useHostTransitionStatus:Oi,useFormState:ui,useActionState:ui,useOptimistic:function(e,t){return ti(zo(),0,e,t)},useMemoCache:qo,useCacheRefresh:Bi},Qi={readContext:Ca,use:Uo,useCallback:Ei,useContext:Ca,useEffect:vi,useImperativeHandle:xi,useInsertionEffect:ki,useLayoutEffect:wi,useMemo:Ci,useReducer:Wo,useRef:hi,useState:function(){return Wo(Ho)},useDebugValue:_i,useDeferredValue:function(e,t){var n=zo();return null===wo?Li(n,e,t):Ai(n,wo.memoizedState,e,t)},useTransition:function(){var e=Wo(Ho)[0],t=zo().memoizedState;return["boolean"==typeof e?e:$o(e),t]},useSyncExternalStore:Qo,useId:Ri,useHostTransitionStatus:Oi,useFormState:pi,useActionState:pi,useOptimistic:function(e,t){var n=zo();return null!==wo?ti(n,0,e,t):(n.baseState=e,[e,n.queue.dispatch])},useMemoCache:qo,useCacheRefresh:Bi},Ki=null,Yi=0;function Xi(e){var t=Yi;return Yi+=1,null===Ki&&(Ki=[]),Xa(Ki,e,t)}function Zi(e,t){t=t.props.ref,e.ref=void 0!==t?t:null}function Ji(e,t){if(t.$$typeof===p)throw Error(i(525));throw e=Object.prototype.toString.call(t),Error(i(31,"[object Object]"===e?"object with keys {"+Object.keys(t).join(", ")+"}":e))}function el(e){return(0,e._init)(e._payload)}function tl(e){function t(t,n){if(e){var r=t.deletions;null===r?(t.deletions=[n],t.flags|=16):r.push(n)}}function n(n,r){if(!e)return null;for(;null!==r;)t(n,r),r=r.sibling;return null}function r(e){for(var t=new Map;null!==e;)null!==e.key?t.set(e.key,e):t.set(e.index,e),e=e.sibling;return t}function a(e,t){return(e=Fr(e,t)).index=0,e.sibling=null,e}function o(t,n,r){return t.index=r,e?null!==(r=t.alternate)?(r=r.index)<n?(t.flags|=67108866,n):r:(t.flags|=67108866,n):(t.flags|=1048576,n)}function l(t){return e&&null===t.alternate&&(t.flags|=67108866),t}function s(e,t,n,r){return null===t||6!==t.tag?((t=Ur(n,e.mode,r)).return=e,t):((t=a(t,n)).return=e,t)}function c(e,t,n,r){var o=n.type;return o===g?d(e,t,n.props.children,r,n.key):null!==t&&(t.elementType===o||"object"==typeof o&&null!==o&&o.$$typeof===C&&el(o)===t.type)?(Zi(t=a(t,n.props),n),t.return=e,t):(Zi(t=zr(n.type,n.key,n.props,null,e.mode,r),n),t.return=e,t)}function u(e,t,n,r){return null===t||4!==t.tag||t.stateNode.containerInfo!==n.containerInfo||t.stateNode.implementation!==n.implementation?((t=qr(n,e.mode,r)).return=e,t):((t=a(t,n.children||[])).return=e,t)}function d(e,t,n,r,o){return null===t||7!==t.tag?((t=$r(n,e.mode,r,o)).return=e,t):((t=a(t,n)).return=e,t)}function f(e,t,n){if("string"==typeof t&&""!==t||"number"==typeof t||"bigint"==typeof t)return(t=Ur(""+t,e.mode,n)).return=e,t;if("object"==typeof t&&null!==t){switch(t.$$typeof){case m:return Zi(n=zr(t.type,t.key,t.props,null,e.mode,n),t),n.return=e,n;case h:return(t=qr(t,e.mode,n)).return=e,t;case C:return f(e,t=(0,t._init)(t._payload),n)}if(N(t)||j(t))return(t=$r(t,e.mode,n,null)).return=e,t;if("function"==typeof t.then)return f(e,Xi(t),n);if(t.$$typeof===w)return f(e,La(e,t),n);Ji(e,t)}return null}function p(e,t,n,r){var a=null!==t?t.key:null;if("string"==typeof n&&""!==n||"number"==typeof n||"bigint"==typeof n)return null!==a?null:s(e,t,""+n,r);if("object"==typeof n&&null!==n){switch(n.$$typeof){case m:return n.key===a?c(e,t,n,r):null;case h:return n.key===a?u(e,t,n,r):null;case C:return p(e,t,n=(a=n._init)(n._payload),r)}if(N(n)||j(n))return null!==a?null:d(e,t,n,r,null);if("function"==typeof n.then)return p(e,t,Xi(n),r);if(n.$$typeof===w)return p(e,t,La(e,n),r);Ji(e,n)}return null}function b(e,t,n,r,a){if("string"==typeof r&&""!==r||"number"==typeof r||"bigint"==typeof r)return s(t,e=e.get(n)||null,""+r,a);if("object"==typeof r&&null!==r){switch(r.$$typeof){case m:return c(t,e=e.get(null===r.key?n:r.key)||null,r,a);case h:return u(t,e=e.get(null===r.key?n:r.key)||null,r,a);case C:return b(e,t,n,r=(0,r._init)(r._payload),a)}if(N(r)||j(r))return d(t,e=e.get(n)||null,r,a,null);if("function"==typeof r.then)return b(e,t,n,Xi(r),a);if(r.$$typeof===w)return b(e,t,n,La(t,r),a);Ji(t,r)}return null}function y(s,c,u,d){if("object"==typeof u&&null!==u&&u.type===g&&null===u.key&&(u=u.props.children),"object"==typeof u&&null!==u){switch(u.$$typeof){case m:e:{for(var v=u.key;null!==c;){if(c.key===v){if((v=u.type)===g){if(7===c.tag){n(s,c.sibling),(d=a(c,u.props.children)).return=s,s=d;break e}}else if(c.elementType===v||"object"==typeof v&&null!==v&&v.$$typeof===C&&el(v)===c.type){n(s,c.sibling),Zi(d=a(c,u.props),u),d.return=s,s=d;break e}n(s,c);break}t(s,c),c=c.sibling}u.type===g?((d=$r(u.props.children,s.mode,d,u.key)).return=s,s=d):(Zi(d=zr(u.type,u.key,u.props,null,s.mode,d),u),d.return=s,s=d)}return l(s);case h:e:{for(v=u.key;null!==c;){if(c.key===v){if(4===c.tag&&c.stateNode.containerInfo===u.containerInfo&&c.stateNode.implementation===u.implementation){n(s,c.sibling),(d=a(c,u.children||[])).return=s,s=d;break e}n(s,c);break}t(s,c),c=c.sibling}(d=qr(u,s.mode,d)).return=s,s=d}return l(s);case C:return y(s,c,u=(v=u._init)(u._payload),d)}if(N(u))return function(a,i,l,s){for(var c=null,u=null,d=i,m=i=0,h=null;null!==d&&m<l.length;m++){d.index>m?(h=d,d=null):h=d.sibling;var g=p(a,d,l[m],s);if(null===g){null===d&&(d=h);break}e&&d&&null===g.alternate&&t(a,d),i=o(g,i,m),null===u?c=g:u.sibling=g,u=g,d=h}if(m===l.length)return n(a,d),oa&&Jr(a,m),c;if(null===d){for(;m<l.length;m++)null!==(d=f(a,l[m],s))&&(i=o(d,i,m),null===u?c=d:u.sibling=d,u=d);return oa&&Jr(a,m),c}for(d=r(d);m<l.length;m++)null!==(h=b(d,a,m,l[m],s))&&(e&&null!==h.alternate&&d.delete(null===h.key?m:h.key),i=o(h,i,m),null===u?c=h:u.sibling=h,u=h);return e&&d.forEach(function(e){return t(a,e)}),oa&&Jr(a,m),c}(s,c,u,d);if(j(u)){if("function"!=typeof(v=j(u)))throw Error(i(150));return function(a,l,s,c){if(null==s)throw Error(i(151));for(var u=null,d=null,m=l,h=l=0,g=null,y=s.next();null!==m&&!y.done;h++,y=s.next()){m.index>h?(g=m,m=null):g=m.sibling;var v=p(a,m,y.value,c);if(null===v){null===m&&(m=g);break}e&&m&&null===v.alternate&&t(a,m),l=o(v,l,h),null===d?u=v:d.sibling=v,d=v,m=g}if(y.done)return n(a,m),oa&&Jr(a,h),u;if(null===m){for(;!y.done;h++,y=s.next())null!==(y=f(a,y.value,c))&&(l=o(y,l,h),null===d?u=y:d.sibling=y,d=y);return oa&&Jr(a,h),u}for(m=r(m);!y.done;h++,y=s.next())null!==(y=b(m,a,h,y.value,c))&&(e&&null!==y.alternate&&m.delete(null===y.key?h:y.key),l=o(y,l,h),null===d?u=y:d.sibling=y,d=y);return e&&m.forEach(function(e){return t(a,e)}),oa&&Jr(a,h),u}(s,c,u=v.call(u),d)}if("function"==typeof u.then)return y(s,c,Xi(u),d);if(u.$$typeof===w)return y(s,c,La(s,u),d);Ji(s,u)}return"string"==typeof u&&""!==u||"number"==typeof u||"bigint"==typeof u?(u=""+u,null!==c&&6===c.tag?(n(s,c.sibling),(d=a(c,u)).return=s,s=d):(n(s,c),(d=Ur(u,s.mode,d)).return=s,s=d),l(s)):n(s,c)}return function(e,t,n,r){try{Yi=0;var a=y(e,t,n,r);return Ki=null,a}catch(i){if(i===Ga||i===Wa)throw i;var o=Br(29,i,null,e.mode);return o.lanes=r,o.return=e,o}}}var nl=tl(!0),rl=tl(!1),al=I(null),ol=null;function il(e){var t=e.alternate;$(ul,1&ul.current),$(al,e),null===ol&&(null===t||null!==mo.current||null!==t.memoizedState)&&(ol=e)}function ll(e){if(22===e.tag){if($(ul,ul.current),$(al,e),null===ol){var t=e.alternate;null!==t&&null!==t.memoizedState&&(ol=e)}}else sl()}function sl(){$(ul,ul.current),$(al,al.current)}function cl(e){z(al),ol===e&&(ol=null),z(ul)}var ul=I(0);function dl(e){for(var t=e;null!==t;){if(13===t.tag){var n=t.memoizedState;if(null!==n&&(null===(n=n.dehydrated)||"$?"===n.data||gd(n)))return t}else if(19===t.tag&&void 0!==t.memoizedProps.revealOrder){if(128&t.flags)return t}else if(null!==t.child){t.child.return=t,t=t.child;continue}if(t===e)break;for(;null===t.sibling;){if(null===t.return||t.return===e)return null;t=t.return}t.sibling.return=t.return,t=t.sibling}return null}function fl(e,t,n,r){n=null==(n=n(r,t=e.memoizedState))?t:f({},t,n),e.memoizedState=n,0===e.lanes&&(e.updateQueue.baseState=n)}var pl={enqueueSetState:function(e,t,n){e=e._reactInternals;var r=Oc(),a=ao(r);a.payload=t,null!=n&&(a.callback=n),null!==(t=oo(e,a,r))&&(Bc(t,e,r),io(t,e,r))},enqueueReplaceState:function(e,t,n){e=e._reactInternals;var r=Oc(),a=ao(r);a.tag=1,a.payload=t,null!=n&&(a.callback=n),null!==(t=oo(e,a,r))&&(Bc(t,e,r),io(t,e,r))},enqueueForceUpdate:function(e,t){e=e._reactInternals;var n=Oc(),r=ao(n);r.tag=2,null!=t&&(r.callback=t),null!==(t=oo(e,r,n))&&(Bc(t,e,n),io(t,e,n))}};function ml(e,t,n,r,a,o,i){return"function"==typeof(e=e.stateNode).shouldComponentUpdate?e.shouldComponentUpdate(r,o,i):!t.prototype||!t.prototype.isPureReactComponent||(!Yn(n,r)||!Yn(a,o))}function hl(e,t,n,r){e=t.state,"function"==typeof t.componentWillReceiveProps&&t.componentWillReceiveProps(n,r),"function"==typeof t.UNSAFE_componentWillReceiveProps&&t.UNSAFE_componentWillReceiveProps(n,r),t.state!==e&&pl.enqueueReplaceState(t,t.state,null)}function gl(e,t){var n=t;if("ref"in t)for(var r in n={},t)"ref"!==r&&(n[r]=t[r]);if(e=e.defaultProps)for(var a in n===t&&(n=f({},n)),e)void 0===n[a]&&(n[a]=e[a]);return n}var bl="function"==typeof reportError?reportError:function(e){if("object"==typeof window&&"function"==typeof window.ErrorEvent){var t=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:"object"==typeof e&&null!==e&&"string"==typeof e.message?String(e.message):String(e),error:e});if(!window.dispatchEvent(t))return}else if("object"==typeof process&&"function"==typeof process.emit)return void process.emit("uncaughtException",e);console.error(e)};function yl(e){bl(e)}function vl(e){console.error(e)}function kl(e){bl(e)}function wl(e,t){try{(0,e.onUncaughtError)(t.value,{componentStack:t.stack})}catch(n){setTimeout(function(){throw n})}}function Sl(e,t,n){try{(0,e.onCaughtError)(n.value,{componentStack:n.stack,errorBoundary:1===t.tag?t.stateNode:null})}catch(r){setTimeout(function(){throw r})}}function xl(e,t,n){return(n=ao(n)).tag=3,n.payload={element:null},n.callback=function(){wl(e,t)},n}function _l(e){return(e=ao(e)).tag=3,e}function El(e,t,n,r){var a=n.type.getDerivedStateFromError;if("function"==typeof a){var o=r.value;e.payload=function(){return a(o)},e.callback=function(){Sl(t,n,r)}}var i=n.stateNode;null!==i&&"function"==typeof i.componentDidCatch&&(e.callback=function(){Sl(t,n,r),"function"!=typeof a&&(null===_c?_c=new Set([this]):_c.add(this));var e=r.stack;this.componentDidCatch(r.value,{componentStack:null!==e?e:""})})}var Cl=Error(i(461)),Ll=!1;function Al(e,t,n,r){t.child=null===e?rl(t,null,n,r):nl(t,e.child,n,r)}function Tl(e,t,n,r,a){n=n.render;var o=t.ref;if("ref"in r){var i={};for(var l in r)"ref"!==l&&(i[l]=r[l])}else i=r;return Ea(t),r=Po(e,t,n,i,o,a),l=Bo(),null===e||Ll?(oa&&l&&ta(t),t.flags|=1,Al(e,t,r,a),t.child):(Do(e,t,a),Kl(e,t,a))}function jl(e,t,n,r,a){if(null===e){var o=n.type;return"function"!=typeof o||Dr(o)||void 0!==o.defaultProps||null!==n.compare?((e=zr(n.type,null,r,t,t.mode,a)).ref=t.ref,e.return=t,t.child=e):(t.tag=15,t.type=o,Ml(e,t,o,r,a))}if(o=e.child,!Yl(e,a)){var i=o.memoizedProps;if((n=null!==(n=n.compare)?n:Yn)(i,r)&&e.ref===t.ref)return Kl(e,t,a)}return t.flags|=1,(e=Fr(o,r)).ref=t.ref,e.return=t,t.child=e}function Ml(e,t,n,r,a){if(null!==e){var o=e.memoizedProps;if(Yn(o,r)&&e.ref===t.ref){if(Ll=!1,t.pendingProps=r=o,!Yl(e,a))return t.lanes=e.lanes,Kl(e,t,a);131072&e.flags&&(Ll=!0)}}return Rl(e,t,n,r,a)}function Pl(e,t,n){var r=t.pendingProps,a=r.children,o=null!==e?e.memoizedState:null;if("hidden"===r.mode){if(128&t.flags){if(r=null!==o?o.baseLanes|n:n,null!==e){for(a=t.child=e.child,o=0;null!==a;)o=o|a.lanes|a.childLanes,a=a.sibling;t.childLanes=o&~r}else t.childLanes=0,t.child=null;return Nl(e,t,r,n)}if(!(536870912&n))return t.lanes=t.childLanes=536870912,Nl(e,t,null!==o?o.baseLanes|n:n,n);t.memoizedState={baseLanes:0,cachePool:null},null!==e&&qa(0,null!==o?o.cachePool:null),null!==o?go(t,o):bo(),ll(t)}else null!==o?(qa(0,o.cachePool),go(t,o),sl(),t.memoizedState=null):(null!==e&&qa(0,null),bo(),sl());return Al(e,t,a,n),t.child}function Nl(e,t,n,r){var a=Ua();return a=null===a?null:{parent:Pa._currentValue,pool:a},t.memoizedState={baseLanes:n,cachePool:a},null!==e&&qa(0,null),bo(),ll(t),null!==e&&xa(e,t,r,!0),null}function Ol(e,t){var n=t.ref;if(null===n)null!==e&&null!==e.ref&&(t.flags|=4194816);else{if("function"!=typeof n&&"object"!=typeof n)throw Error(i(284));null!==e&&e.ref===n||(t.flags|=4194816)}}function Rl(e,t,n,r,a){return Ea(t),n=Po(e,t,n,r,void 0,a),r=Bo(),null===e||Ll?(oa&&r&&ta(t),t.flags|=1,Al(e,t,n,a),t.child):(Do(e,t,a),Kl(e,t,a))}function Bl(e,t,n,r,a,o){return Ea(t),t.updateQueue=null,n=Oo(t,r,n,a),No(e),r=Bo(),null===e||Ll?(oa&&r&&ta(t),t.flags|=1,Al(e,t,n,o),t.child):(Do(e,t,o),Kl(e,t,o))}function Dl(e,t,n,r,a){if(Ea(t),null===t.stateNode){var o=Or,i=n.contextType;"object"==typeof i&&null!==i&&(o=Ca(i)),o=new n(r,o),t.memoizedState=null!==o.state&&void 0!==o.state?o.state:null,o.updater=pl,t.stateNode=o,o._reactInternals=t,(o=t.stateNode).props=r,o.state=t.memoizedState,o.refs={},no(t),i=n.contextType,o.context="object"==typeof i&&null!==i?Ca(i):Or,o.state=t.memoizedState,"function"==typeof(i=n.getDerivedStateFromProps)&&(fl(t,n,i,r),o.state=t.memoizedState),"function"==typeof n.getDerivedStateFromProps||"function"==typeof o.getSnapshotBeforeUpdate||"function"!=typeof o.UNSAFE_componentWillMount&&"function"!=typeof o.componentWillMount||(i=o.state,"function"==typeof o.componentWillMount&&o.componentWillMount(),"function"==typeof o.UNSAFE_componentWillMount&&o.UNSAFE_componentWillMount(),i!==o.state&&pl.enqueueReplaceState(o,o.state,null),uo(t,r,o,a),co(),o.state=t.memoizedState),"function"==typeof o.componentDidMount&&(t.flags|=4194308),r=!0}else if(null===e){o=t.stateNode;var l=t.memoizedProps,s=gl(n,l);o.props=s;var c=o.context,u=n.contextType;i=Or,"object"==typeof u&&null!==u&&(i=Ca(u));var d=n.getDerivedStateFromProps;u="function"==typeof d||"function"==typeof o.getSnapshotBeforeUpdate,l=t.pendingProps!==l,u||"function"!=typeof o.UNSAFE_componentWillReceiveProps&&"function"!=typeof o.componentWillReceiveProps||(l||c!==i)&&hl(t,o,r,i),to=!1;var f=t.memoizedState;o.state=f,uo(t,r,o,a),co(),c=t.memoizedState,l||f!==c||to?("function"==typeof d&&(fl(t,n,d,r),c=t.memoizedState),(s=to||ml(t,n,s,r,f,c,i))?(u||"function"!=typeof o.UNSAFE_componentWillMount&&"function"!=typeof o.componentWillMount||("function"==typeof o.componentWillMount&&o.componentWillMount(),"function"==typeof o.UNSAFE_componentWillMount&&o.UNSAFE_componentWillMount()),"function"==typeof o.componentDidMount&&(t.flags|=4194308)):("function"==typeof o.componentDidMount&&(t.flags|=4194308),t.memoizedProps=r,t.memoizedState=c),o.props=r,o.state=c,o.context=i,r=s):("function"==typeof o.componentDidMount&&(t.flags|=4194308),r=!1)}else{o=t.stateNode,ro(e,t),u=gl(n,i=t.memoizedProps),o.props=u,d=t.pendingProps,f=o.context,c=n.contextType,s=Or,"object"==typeof c&&null!==c&&(s=Ca(c)),(c="function"==typeof(l=n.getDerivedStateFromProps)||"function"==typeof o.getSnapshotBeforeUpdate)||"function"!=typeof o.UNSAFE_componentWillReceiveProps&&"function"!=typeof o.componentWillReceiveProps||(i!==d||f!==s)&&hl(t,o,r,s),to=!1,f=t.memoizedState,o.state=f,uo(t,r,o,a),co();var p=t.memoizedState;i!==d||f!==p||to||null!==e&&null!==e.dependencies&&_a(e.dependencies)?("function"==typeof l&&(fl(t,n,l,r),p=t.memoizedState),(u=to||ml(t,n,u,r,f,p,s)||null!==e&&null!==e.dependencies&&_a(e.dependencies))?(c||"function"!=typeof o.UNSAFE_componentWillUpdate&&"function"!=typeof o.componentWillUpdate||("function"==typeof o.componentWillUpdate&&o.componentWillUpdate(r,p,s),"function"==typeof o.UNSAFE_componentWillUpdate&&o.UNSAFE_componentWillUpdate(r,p,s)),"function"==typeof o.componentDidUpdate&&(t.flags|=4),"function"==typeof o.getSnapshotBeforeUpdate&&(t.flags|=1024)):("function"!=typeof o.componentDidUpdate||i===e.memoizedProps&&f===e.memoizedState||(t.flags|=4),"function"!=typeof o.getSnapshotBeforeUpdate||i===e.memoizedProps&&f===e.memoizedState||(t.flags|=1024),t.memoizedProps=r,t.memoizedState=p),o.props=r,o.state=p,o.context=s,r=u):("function"!=typeof o.componentDidUpdate||i===e.memoizedProps&&f===e.memoizedState||(t.flags|=4),"function"!=typeof o.getSnapshotBeforeUpdate||i===e.memoizedProps&&f===e.memoizedState||(t.flags|=1024),r=!1)}return o=r,Ol(e,t),r=!!(128&t.flags),o||r?(o=t.stateNode,n=r&&"function"!=typeof n.getDerivedStateFromError?null:o.render(),t.flags|=1,null!==e&&r?(t.child=nl(t,e.child,null,a),t.child=nl(t,null,n,a)):Al(e,t,n,a),t.memoizedState=o.state,e=t.child):e=Kl(e,t,a),e}function Fl(e,t,n,r){return pa(),t.flags|=256,Al(e,t,n,r),t.child}var Il={dehydrated:null,treeContext:null,retryLane:0,hydrationErrors:null};function zl(e){return{baseLanes:e,cachePool:Ha()}}function $l(e,t,n){return e=null!==e?e.childLanes&~n:0,t&&(e|=gc),e}function Ul(e,t,n){var r,a=t.pendingProps,o=!1,l=!!(128&t.flags);if((r=l)||(r=(null===e||null!==e.memoizedState)&&!!(2&ul.current)),r&&(o=!0,t.flags&=-129),r=!!(32&t.flags),t.flags&=-33,null===e){if(oa){if(o?il(t):sl(),oa){var s,c=aa;if(s=c){e:{for(s=c,c=la;8!==s.nodeType;){if(!c){c=null;break e}if(null===(s=bd(s.nextSibling))){c=null;break e}}c=s}null!==c?(t.memoizedState={dehydrated:c,treeContext:null!==Yr?{id:Xr,overflow:Zr}:null,retryLane:536870912,hydrationErrors:null},(s=Br(18,null,null,0)).stateNode=c,s.return=t,t.child=s,ra=t,aa=null,s=!0):s=!1}s||ca(t)}if(null!==(c=t.memoizedState)&&null!==(c=c.dehydrated))return gd(c)?t.lanes=32:t.lanes=536870912,null;cl(t)}return c=a.children,a=a.fallback,o?(sl(),c=Hl({mode:"hidden",children:c},o=t.mode),a=$r(a,o,n,null),c.return=t,a.return=t,c.sibling=a,t.child=c,(o=t.child).memoizedState=zl(n),o.childLanes=$l(e,r,n),t.memoizedState=Il,a):(il(t),ql(t,c))}if(null!==(s=e.memoizedState)&&null!==(c=s.dehydrated)){if(l)256&t.flags?(il(t),t.flags&=-257,t=Gl(e,t,n)):null!==t.memoizedState?(sl(),t.child=e.child,t.flags|=128,t=null):(sl(),o=a.fallback,c=t.mode,a=Hl({mode:"visible",children:a.children},c),(o=$r(o,c,n,null)).flags|=2,a.return=t,o.return=t,a.sibling=o,t.child=a,nl(t,e.child,null,n),(a=t.child).memoizedState=zl(n),a.childLanes=$l(e,r,n),t.memoizedState=Il,t=o);else if(il(t),gd(c)){if(r=c.nextSibling&&c.nextSibling.dataset)var u=r.dgst;r=u,(a=Error(i(419))).stack="",a.digest=r,ha({value:a,source:null,stack:null}),t=Gl(e,t,n)}else if(Ll||xa(e,t,n,!1),r=0!==(n&e.childLanes),Ll||r){if(null!==(r=rc)&&(0!==(a=0!==((a=42&(a=n&-n)?1:Ae(a))&(r.suspendedLanes|n))?0:a)&&a!==s.retryLane))throw s.retryLane=a,Mr(e,a),Bc(r,e,a),Cl;"$?"===c.data||Wc(),t=Gl(e,t,n)}else"$?"===c.data?(t.flags|=192,t.child=e.child,t=null):(e=s.treeContext,aa=bd(c.nextSibling),ra=t,oa=!0,ia=null,la=!1,null!==e&&(Qr[Kr++]=Xr,Qr[Kr++]=Zr,Qr[Kr++]=Yr,Xr=e.id,Zr=e.overflow,Yr=t),(t=ql(t,a.children)).flags|=4096);return t}return o?(sl(),o=a.fallback,c=t.mode,u=(s=e.child).sibling,(a=Fr(s,{mode:"hidden",children:a.children})).subtreeFlags=65011712&s.subtreeFlags,null!==u?o=Fr(u,o):(o=$r(o,c,n,null)).flags|=2,o.return=t,a.return=t,a.sibling=o,t.child=a,a=o,o=t.child,null===(c=e.child.memoizedState)?c=zl(n):(null!==(s=c.cachePool)?(u=Pa._currentValue,s=s.parent!==u?{parent:u,pool:u}:s):s=Ha(),c={baseLanes:c.baseLanes|n,cachePool:s}),o.memoizedState=c,o.childLanes=$l(e,r,n),t.memoizedState=Il,a):(il(t),e=(n=e.child).sibling,(n=Fr(n,{mode:"visible",children:a.children})).return=t,n.sibling=null,null!==e&&(null===(r=t.deletions)?(t.deletions=[e],t.flags|=16):r.push(e)),t.child=n,t.memoizedState=null,n)}function ql(e,t){return(t=Hl({mode:"visible",children:t},e.mode)).return=e,e.child=t}function Hl(e,t){return(e=Br(22,e,null,t)).lanes=0,e.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null},e}function Gl(e,t,n){return nl(t,e.child,null,n),(e=ql(t,t.pendingProps.children)).flags|=2,t.memoizedState=null,e}function Vl(e,t,n){e.lanes|=t;var r=e.alternate;null!==r&&(r.lanes|=t),wa(e.return,t,n)}function Wl(e,t,n,r,a){var o=e.memoizedState;null===o?e.memoizedState={isBackwards:t,rendering:null,renderingStartTime:0,last:r,tail:n,tailMode:a}:(o.isBackwards=t,o.rendering=null,o.renderingStartTime=0,o.last=r,o.tail=n,o.tailMode=a)}function Ql(e,t,n){var r=t.pendingProps,a=r.revealOrder,o=r.tail;if(Al(e,t,r.children,n),2&(r=ul.current))r=1&r|2,t.flags|=128;else{if(null!==e&&128&e.flags)e:for(e=t.child;null!==e;){if(13===e.tag)null!==e.memoizedState&&Vl(e,n,t);else if(19===e.tag)Vl(e,n,t);else if(null!==e.child){e.child.return=e,e=e.child;continue}if(e===t)break e;for(;null===e.sibling;){if(null===e.return||e.return===t)break e;e=e.return}e.sibling.return=e.return,e=e.sibling}r&=1}switch($(ul,r),a){case"forwards":for(n=t.child,a=null;null!==n;)null!==(e=n.alternate)&&null===dl(e)&&(a=n),n=n.sibling;null===(n=a)?(a=t.child,t.child=null):(a=n.sibling,n.sibling=null),Wl(t,!1,a,n,o);break;case"backwards":for(n=null,a=t.child,t.child=null;null!==a;){if(null!==(e=a.alternate)&&null===dl(e)){t.child=a;break}e=a.sibling,a.sibling=n,n=a,a=e}Wl(t,!0,n,null,o);break;case"together":Wl(t,!1,null,null,void 0);break;default:t.memoizedState=null}return t.child}function Kl(e,t,n){if(null!==e&&(t.dependencies=e.dependencies),pc|=t.lanes,0===(n&t.childLanes)){if(null===e)return null;if(xa(e,t,n,!1),0===(n&t.childLanes))return null}if(null!==e&&t.child!==e.child)throw Error(i(153));if(null!==t.child){for(n=Fr(e=t.child,e.pendingProps),t.child=n,n.return=t;null!==e.sibling;)e=e.sibling,(n=n.sibling=Fr(e,e.pendingProps)).return=t;n.sibling=null}return t.child}function Yl(e,t){return 0!==(e.lanes&t)||!(null===(e=e.dependencies)||!_a(e))}function Xl(e,t,n){if(null!==e)if(e.memoizedProps!==t.pendingProps)Ll=!0;else{if(!(Yl(e,n)||128&t.flags))return Ll=!1,function(e,t,n){switch(t.tag){case 3:V(t,t.stateNode.containerInfo),va(0,Pa,e.memoizedState.cache),pa();break;case 27:case 5:Q(t);break;case 4:V(t,t.stateNode.containerInfo);break;case 10:va(0,t.type,t.memoizedProps.value);break;case 13:var r=t.memoizedState;if(null!==r)return null!==r.dehydrated?(il(t),t.flags|=128,null):0!==(n&t.child.childLanes)?Ul(e,t,n):(il(t),null!==(e=Kl(e,t,n))?e.sibling:null);il(t);break;case 19:var a=!!(128&e.flags);if((r=0!==(n&t.childLanes))||(xa(e,t,n,!1),r=0!==(n&t.childLanes)),a){if(r)return Ql(e,t,n);t.flags|=128}if(null!==(a=t.memoizedState)&&(a.rendering=null,a.tail=null,a.lastEffect=null),$(ul,ul.current),r)break;return null;case 22:case 23:return t.lanes=0,Pl(e,t,n);case 24:va(0,Pa,e.memoizedState.cache)}return Kl(e,t,n)}(e,t,n);Ll=!!(131072&e.flags)}else Ll=!1,oa&&1048576&t.flags&&ea(t,Wr,t.index);switch(t.lanes=0,t.tag){case 16:e:{e=t.pendingProps;var r=t.elementType,a=r._init;if(r=a(r._payload),t.type=r,"function"!=typeof r){if(null!=r){if((a=r.$$typeof)===S){t.tag=11,t=Tl(null,t,r,e,n);break e}if(a===E){t.tag=14,t=jl(null,t,r,e,n);break e}}throw t=P(r)||r,Error(i(306,t,""))}Dr(r)?(e=gl(r,e),t.tag=1,t=Dl(null,t,r,e,n)):(t.tag=0,t=Rl(null,t,r,e,n))}return t;case 0:return Rl(e,t,t.type,t.pendingProps,n);case 1:return Dl(e,t,r=t.type,a=gl(r,t.pendingProps),n);case 3:e:{if(V(t,t.stateNode.containerInfo),null===e)throw Error(i(387));r=t.pendingProps;var o=t.memoizedState;a=o.element,ro(e,t),uo(t,r,null,n);var l=t.memoizedState;if(r=l.cache,va(0,Pa,r),r!==o.cache&&Sa(t,[Pa],n,!0),co(),r=l.element,o.isDehydrated){if(o={element:r,isDehydrated:!1,cache:l.cache},t.updateQueue.baseState=o,t.memoizedState=o,256&t.flags){t=Fl(e,t,r,n);break e}if(r!==a){ha(a=_r(Error(i(424)),t)),t=Fl(e,t,r,n);break e}if(9===(e=t.stateNode.containerInfo).nodeType)e=e.body;else e="HTML"===e.nodeName?e.ownerDocument.body:e;for(aa=bd(e.firstChild),ra=t,oa=!0,ia=null,la=!0,n=rl(t,null,r,n),t.child=n;n;)n.flags=-3&n.flags|4096,n=n.sibling}else{if(pa(),r===a){t=Kl(e,t,n);break e}Al(e,t,r,n)}t=t.child}return t;case 26:return Ol(e,t),null===e?(n=Ad(t.type,null,t.pendingProps,null))?t.memoizedState=n:oa||(n=t.type,e=t.pendingProps,(r=rd(H.current).createElement(n))[Pe]=t,r[Ne]=e,ed(r,n,e),Ge(r),t.stateNode=r):t.memoizedState=Ad(t.type,e.memoizedProps,t.pendingProps,e.memoizedState),null;case 27:return Q(t),null===e&&oa&&(r=t.stateNode=kd(t.type,t.pendingProps,H.current),ra=t,la=!0,a=aa,pd(t.type)?(yd=a,aa=bd(r.firstChild)):aa=a),Al(e,t,t.pendingProps.children,n),Ol(e,t),null===e&&(t.flags|=4194304),t.child;case 5:return null===e&&oa&&((a=r=aa)&&(null!==(r=function(e,t,n,r){for(;1===e.nodeType;){var a=n;if(e.nodeName.toLowerCase()!==t.toLowerCase()){if(!r&&("INPUT"!==e.nodeName||"hidden"!==e.type))break}else if(r){if(!e[Ie])switch(t){case"meta":if(!e.hasAttribute("itemprop"))break;return e;case"link":if("stylesheet"===(o=e.getAttribute("rel"))&&e.hasAttribute("data-precedence"))break;if(o!==a.rel||e.getAttribute("href")!==(null==a.href||""===a.href?null:a.href)||e.getAttribute("crossorigin")!==(null==a.crossOrigin?null:a.crossOrigin)||e.getAttribute("title")!==(null==a.title?null:a.title))break;return e;case"style":if(e.hasAttribute("data-precedence"))break;return e;case"script":if(((o=e.getAttribute("src"))!==(null==a.src?null:a.src)||e.getAttribute("type")!==(null==a.type?null:a.type)||e.getAttribute("crossorigin")!==(null==a.crossOrigin?null:a.crossOrigin))&&o&&e.hasAttribute("async")&&!e.hasAttribute("itemprop"))break;return e;default:return e}}else{if("input"!==t||"hidden"!==e.type)return e;var o=null==a.name?null:""+a.name;if("hidden"===a.type&&e.getAttribute("name")===o)return e}if(null===(e=bd(e.nextSibling)))break}return null}(r,t.type,t.pendingProps,la))?(t.stateNode=r,ra=t,aa=bd(r.firstChild),la=!1,a=!0):a=!1),a||ca(t)),Q(t),a=t.type,o=t.pendingProps,l=null!==e?e.memoizedProps:null,r=o.children,id(a,o)?r=null:null!==l&&id(a,l)&&(t.flags|=32),null!==t.memoizedState&&(a=Po(e,t,Ro,null,null,n),Qd._currentValue=a),Ol(e,t),Al(e,t,r,n),t.child;case 6:return null===e&&oa&&((e=n=aa)&&(null!==(n=function(e,t,n){if(""===t)return null;for(;3!==e.nodeType;){if((1!==e.nodeType||"INPUT"!==e.nodeName||"hidden"!==e.type)&&!n)return null;if(null===(e=bd(e.nextSibling)))return null}return e}(n,t.pendingProps,la))?(t.stateNode=n,ra=t,aa=null,e=!0):e=!1),e||ca(t)),null;case 13:return Ul(e,t,n);case 4:return V(t,t.stateNode.containerInfo),r=t.pendingProps,null===e?t.child=nl(t,null,r,n):Al(e,t,r,n),t.child;case 11:return Tl(e,t,t.type,t.pendingProps,n);case 7:return Al(e,t,t.pendingProps,n),t.child;case 8:case 12:return Al(e,t,t.pendingProps.children,n),t.child;case 10:return r=t.pendingProps,va(0,t.type,r.value),Al(e,t,r.children,n),t.child;case 9:return a=t.type._context,r=t.pendingProps.children,Ea(t),r=r(a=Ca(a)),t.flags|=1,Al(e,t,r,n),t.child;case 14:return jl(e,t,t.type,t.pendingProps,n);case 15:return Ml(e,t,t.type,t.pendingProps,n);case 19:return Ql(e,t,n);case 31:return r=t.pendingProps,n=t.mode,r={mode:r.mode,children:r.children},null===e?((n=Hl(r,n)).ref=t.ref,t.child=n,n.return=t,t=n):((n=Fr(e.child,r)).ref=t.ref,t.child=n,n.return=t,t=n),t;case 22:return Pl(e,t,n);case 24:return Ea(t),r=Ca(Pa),null===e?(null===(a=Ua())&&(a=rc,o=Na(),a.pooledCache=o,o.refCount++,null!==o&&(a.pooledCacheLanes|=n),a=o),t.memoizedState={parent:r,cache:a},no(t),va(0,Pa,a)):(0!==(e.lanes&n)&&(ro(e,t),uo(t,null,null,n),co()),a=e.memoizedState,o=t.memoizedState,a.parent!==r?(a={parent:r,cache:r},t.memoizedState=a,0===t.lanes&&(t.memoizedState=t.updateQueue.baseState=a),va(0,Pa,r)):(r=o.cache,va(0,Pa,r),r!==a.cache&&Sa(t,[Pa],n,!0))),Al(e,t,t.pendingProps.children,n),t.child;case 29:throw t.pendingProps}throw Error(i(156,t.tag))}function Zl(e){e.flags|=4}function Jl(e,t){if("stylesheet"!==t.type||4&t.state.loading)e.flags&=-16777217;else if(e.flags|=16777216,!$d(t)){if(null!==(t=al.current)&&((4194048&oc)===oc?null!==ol:(62914560&oc)!==oc&&!(536870912&oc)||t!==ol))throw Za=Qa,Va;e.flags|=8192}}function es(e,t){null!==t&&(e.flags|=4),16384&e.flags&&(t=22!==e.tag?xe():536870912,e.lanes|=t,bc|=t)}function ts(e,t){if(!oa)switch(e.tailMode){case"hidden":t=e.tail;for(var n=null;null!==t;)null!==t.alternate&&(n=t),t=t.sibling;null===n?e.tail=null:n.sibling=null;break;case"collapsed":n=e.tail;for(var r=null;null!==n;)null!==n.alternate&&(r=n),n=n.sibling;null===r?t||null===e.tail?e.tail=null:e.tail.sibling=null:r.sibling=null}}function ns(e){var t=null!==e.alternate&&e.alternate.child===e.child,n=0,r=0;if(t)for(var a=e.child;null!==a;)n|=a.lanes|a.childLanes,r|=65011712&a.subtreeFlags,r|=65011712&a.flags,a.return=e,a=a.sibling;else for(a=e.child;null!==a;)n|=a.lanes|a.childLanes,r|=a.subtreeFlags,r|=a.flags,a.return=e,a=a.sibling;return e.subtreeFlags|=r,e.childLanes=n,t}function rs(e,t,n){var r=t.pendingProps;switch(na(t),t.tag){case 31:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:case 1:return ns(t),null;case 3:return n=t.stateNode,r=null,null!==e&&(r=e.memoizedState.cache),t.memoizedState.cache!==r&&(t.flags|=2048),ka(Pa),W(),n.pendingContext&&(n.context=n.pendingContext,n.pendingContext=null),null!==e&&null!==e.child||(fa(t)?Zl(t):null===e||e.memoizedState.isDehydrated&&!(256&t.flags)||(t.flags|=1024,ma())),ns(t),null;case 26:return n=t.memoizedState,null===e?(Zl(t),null!==n?(ns(t),Jl(t,n)):(ns(t),t.flags&=-16777217)):n?n!==e.memoizedState?(Zl(t),ns(t),Jl(t,n)):(ns(t),t.flags&=-16777217):(e.memoizedProps!==r&&Zl(t),ns(t),t.flags&=-16777217),null;case 27:K(t),n=H.current;var a=t.type;if(null!==e&&null!=t.stateNode)e.memoizedProps!==r&&Zl(t);else{if(!r){if(null===t.stateNode)throw Error(i(166));return ns(t),null}e=U.current,fa(t)?ua(t):(e=kd(a,r,n),t.stateNode=e,Zl(t))}return ns(t),null;case 5:if(K(t),n=t.type,null!==e&&null!=t.stateNode)e.memoizedProps!==r&&Zl(t);else{if(!r){if(null===t.stateNode)throw Error(i(166));return ns(t),null}if(e=U.current,fa(t))ua(t);else{switch(a=rd(H.current),e){case 1:e=a.createElementNS("http://www.w3.org/2000/svg",n);break;case 2:e=a.createElementNS("http://www.w3.org/1998/Math/MathML",n);break;default:switch(n){case"svg":e=a.createElementNS("http://www.w3.org/2000/svg",n);break;case"math":e=a.createElementNS("http://www.w3.org/1998/Math/MathML",n);break;case"script":(e=a.createElement("div")).innerHTML="<script><\/script>",e=e.removeChild(e.firstChild);break;case"select":e="string"==typeof r.is?a.createElement("select",{is:r.is}):a.createElement("select"),r.multiple?e.multiple=!0:r.size&&(e.size=r.size);break;default:e="string"==typeof r.is?a.createElement(n,{is:r.is}):a.createElement(n)}}e[Pe]=t,e[Ne]=r;e:for(a=t.child;null!==a;){if(5===a.tag||6===a.tag)e.appendChild(a.stateNode);else if(4!==a.tag&&27!==a.tag&&null!==a.child){a.child.return=a,a=a.child;continue}if(a===t)break e;for(;null===a.sibling;){if(null===a.return||a.return===t)break e;a=a.return}a.sibling.return=a.return,a=a.sibling}t.stateNode=e;e:switch(ed(e,n,r),n){case"button":case"input":case"select":case"textarea":e=!!r.autoFocus;break e;case"img":e=!0;break e;default:e=!1}e&&Zl(t)}}return ns(t),t.flags&=-16777217,null;case 6:if(e&&null!=t.stateNode)e.memoizedProps!==r&&Zl(t);else{if("string"!=typeof r&&null===t.stateNode)throw Error(i(166));if(e=H.current,fa(t)){if(e=t.stateNode,n=t.memoizedProps,r=null,null!==(a=ra))switch(a.tag){case 27:case 5:r=a.memoizedProps}e[Pe]=t,(e=!!(e.nodeValue===n||null!==r&&!0===r.suppressHydrationWarning||Yu(e.nodeValue,n)))||ca(t)}else(e=rd(e).createTextNode(r))[Pe]=t,t.stateNode=e}return ns(t),null;case 13:if(r=t.memoizedState,null===e||null!==e.memoizedState&&null!==e.memoizedState.dehydrated){if(a=fa(t),null!==r&&null!==r.dehydrated){if(null===e){if(!a)throw Error(i(318));if(!(a=null!==(a=t.memoizedState)?a.dehydrated:null))throw Error(i(317));a[Pe]=t}else pa(),!(128&t.flags)&&(t.memoizedState=null),t.flags|=4;ns(t),a=!1}else a=ma(),null!==e&&null!==e.memoizedState&&(e.memoizedState.hydrationErrors=a),a=!0;if(!a)return 256&t.flags?(cl(t),t):(cl(t),null)}if(cl(t),128&t.flags)return t.lanes=n,t;if(n=null!==r,e=null!==e&&null!==e.memoizedState,n){a=null,null!==(r=t.child).alternate&&null!==r.alternate.memoizedState&&null!==r.alternate.memoizedState.cachePool&&(a=r.alternate.memoizedState.cachePool.pool);var o=null;null!==r.memoizedState&&null!==r.memoizedState.cachePool&&(o=r.memoizedState.cachePool.pool),o!==a&&(r.flags|=2048)}return n!==e&&n&&(t.child.flags|=8192),es(t,t.updateQueue),ns(t),null;case 4:return W(),null===e&&zu(t.stateNode.containerInfo),ns(t),null;case 10:return ka(t.type),ns(t),null;case 19:if(z(ul),null===(a=t.memoizedState))return ns(t),null;if(r=!!(128&t.flags),null===(o=a.rendering))if(r)ts(a,!1);else{if(0!==fc||null!==e&&128&e.flags)for(e=t.child;null!==e;){if(null!==(o=dl(e))){for(t.flags|=128,ts(a,!1),e=o.updateQueue,t.updateQueue=e,es(t,e),t.subtreeFlags=0,e=n,n=t.child;null!==n;)Ir(n,e),n=n.sibling;return $(ul,1&ul.current|2),t.child}e=e.sibling}null!==a.tail&&te()>Sc&&(t.flags|=128,r=!0,ts(a,!1),t.lanes=4194304)}else{if(!r)if(null!==(e=dl(o))){if(t.flags|=128,r=!0,e=e.updateQueue,t.updateQueue=e,es(t,e),ts(a,!0),null===a.tail&&"hidden"===a.tailMode&&!o.alternate&&!oa)return ns(t),null}else 2*te()-a.renderingStartTime>Sc&&536870912!==n&&(t.flags|=128,r=!0,ts(a,!1),t.lanes=4194304);a.isBackwards?(o.sibling=t.child,t.child=o):(null!==(e=a.last)?e.sibling=o:t.child=o,a.last=o)}return null!==a.tail?(t=a.tail,a.rendering=t,a.tail=t.sibling,a.renderingStartTime=te(),t.sibling=null,e=ul.current,$(ul,r?1&e|2:1&e),t):(ns(t),null);case 22:case 23:return cl(t),yo(),r=null!==t.memoizedState,null!==e?null!==e.memoizedState!==r&&(t.flags|=8192):r&&(t.flags|=8192),r?!!(536870912&n)&&!(128&t.flags)&&(ns(t),6&t.subtreeFlags&&(t.flags|=8192)):ns(t),null!==(n=t.updateQueue)&&es(t,n.retryQueue),n=null,null!==e&&null!==e.memoizedState&&null!==e.memoizedState.cachePool&&(n=e.memoizedState.cachePool.pool),r=null,null!==t.memoizedState&&null!==t.memoizedState.cachePool&&(r=t.memoizedState.cachePool.pool),r!==n&&(t.flags|=2048),null!==e&&z($a),null;case 24:return n=null,null!==e&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),ka(Pa),ns(t),null;case 25:case 30:return null}throw Error(i(156,t.tag))}function as(e,t){switch(na(t),t.tag){case 1:return 65536&(e=t.flags)?(t.flags=-65537&e|128,t):null;case 3:return ka(Pa),W(),65536&(e=t.flags)&&!(128&e)?(t.flags=-65537&e|128,t):null;case 26:case 27:case 5:return K(t),null;case 13:if(cl(t),null!==(e=t.memoizedState)&&null!==e.dehydrated){if(null===t.alternate)throw Error(i(340));pa()}return 65536&(e=t.flags)?(t.flags=-65537&e|128,t):null;case 19:return z(ul),null;case 4:return W(),null;case 10:return ka(t.type),null;case 22:case 23:return cl(t),yo(),null!==e&&z($a),65536&(e=t.flags)?(t.flags=-65537&e|128,t):null;case 24:return ka(Pa),null;default:return null}}function os(e,t){switch(na(t),t.tag){case 3:ka(Pa),W();break;case 26:case 27:case 5:K(t);break;case 4:W();break;case 13:cl(t);break;case 19:z(ul);break;case 10:ka(t.type);break;case 22:case 23:cl(t),yo(),null!==e&&z($a);break;case 24:ka(Pa)}}function is(e,t){try{var n=t.updateQueue,r=null!==n?n.lastEffect:null;if(null!==r){var a=r.next;n=a;do{if((n.tag&e)===e){r=void 0;var o=n.create,i=n.inst;r=o(),i.destroy=r}n=n.next}while(n!==a)}}catch(l){uu(t,t.return,l)}}function ls(e,t,n){try{var r=t.updateQueue,a=null!==r?r.lastEffect:null;if(null!==a){var o=a.next;r=o;do{if((r.tag&e)===e){var i=r.inst,l=i.destroy;if(void 0!==l){i.destroy=void 0,a=t;var s=n,c=l;try{c()}catch(u){uu(a,s,u)}}}r=r.next}while(r!==o)}}catch(u){uu(t,t.return,u)}}function ss(e){var t=e.updateQueue;if(null!==t){var n=e.stateNode;try{po(t,n)}catch(r){uu(e,e.return,r)}}}function cs(e,t,n){n.props=gl(e.type,e.memoizedProps),n.state=e.memoizedState;try{n.componentWillUnmount()}catch(r){uu(e,t,r)}}function us(e,t){try{var n=e.ref;if(null!==n){switch(e.tag){case 26:case 27:case 5:var r=e.stateNode;break;default:r=e.stateNode}"function"==typeof n?e.refCleanup=n(r):n.current=r}}catch(a){uu(e,t,a)}}function ds(e,t){var n=e.ref,r=e.refCleanup;if(null!==n)if("function"==typeof r)try{r()}catch(a){uu(e,t,a)}finally{e.refCleanup=null,null!=(e=e.alternate)&&(e.refCleanup=null)}else if("function"==typeof n)try{n(null)}catch(o){uu(e,t,o)}else n.current=null}function fs(e){var t=e.type,n=e.memoizedProps,r=e.stateNode;try{e:switch(t){case"button":case"input":case"select":case"textarea":n.autoFocus&&r.focus();break e;case"img":n.src?r.src=n.src:n.srcSet&&(r.srcset=n.srcSet)}}catch(a){uu(e,e.return,a)}}function ps(e,t,n){try{var r=e.stateNode;!function(e,t,n,r){switch(t){case"div":case"span":case"svg":case"path":case"a":case"g":case"p":case"li":break;case"input":var a=null,o=null,l=null,s=null,c=null,u=null,d=null;for(m in n){var f=n[m];if(n.hasOwnProperty(m)&&null!=f)switch(m){case"checked":case"value":break;case"defaultValue":c=f;default:r.hasOwnProperty(m)||Zu(e,t,m,null,r,f)}}for(var p in r){var m=r[p];if(f=n[p],r.hasOwnProperty(p)&&(null!=m||null!=f))switch(p){case"type":o=m;break;case"name":a=m;break;case"checked":u=m;break;case"defaultChecked":d=m;break;case"value":l=m;break;case"defaultValue":s=m;break;case"children":case"dangerouslySetInnerHTML":if(null!=m)throw Error(i(137,t));break;default:m!==f&&Zu(e,t,p,m,r,f)}}return void gt(e,l,s,c,u,d,o,a);case"select":for(o in m=l=s=p=null,n)if(c=n[o],n.hasOwnProperty(o)&&null!=c)switch(o){case"value":break;case"multiple":m=c;default:r.hasOwnProperty(o)||Zu(e,t,o,null,r,c)}for(a in r)if(o=r[a],c=n[a],r.hasOwnProperty(a)&&(null!=o||null!=c))switch(a){case"value":p=o;break;case"defaultValue":s=o;break;case"multiple":l=o;default:o!==c&&Zu(e,t,a,o,r,c)}return t=s,n=l,r=m,void(null!=p?vt(e,!!n,p,!1):!!r!=!!n&&(null!=t?vt(e,!!n,t,!0):vt(e,!!n,n?[]:"",!1)));case"textarea":for(s in m=p=null,n)if(a=n[s],n.hasOwnProperty(s)&&null!=a&&!r.hasOwnProperty(s))switch(s){case"value":case"children":break;default:Zu(e,t,s,null,r,a)}for(l in r)if(a=r[l],o=n[l],r.hasOwnProperty(l)&&(null!=a||null!=o))switch(l){case"value":p=a;break;case"defaultValue":m=a;break;case"children":break;case"dangerouslySetInnerHTML":if(null!=a)throw Error(i(91));break;default:a!==o&&Zu(e,t,l,a,r,o)}return void kt(e,p,m);case"option":for(var h in n)if(p=n[h],n.hasOwnProperty(h)&&null!=p&&!r.hasOwnProperty(h))if("selected"===h)e.selected=!1;else Zu(e,t,h,null,r,p);for(c in r)if(p=r[c],m=n[c],r.hasOwnProperty(c)&&p!==m&&(null!=p||null!=m))if("selected"===c)e.selected=p&&"function"!=typeof p&&"symbol"!=typeof p;else Zu(e,t,c,p,r,m);return;case"img":case"link":case"area":case"base":case"br":case"col":case"embed":case"hr":case"keygen":case"meta":case"param":case"source":case"track":case"wbr":case"menuitem":for(var g in n)p=n[g],n.hasOwnProperty(g)&&null!=p&&!r.hasOwnProperty(g)&&Zu(e,t,g,null,r,p);for(u in r)if(p=r[u],m=n[u],r.hasOwnProperty(u)&&p!==m&&(null!=p||null!=m))switch(u){case"children":case"dangerouslySetInnerHTML":if(null!=p)throw Error(i(137,t));break;default:Zu(e,t,u,p,r,m)}return;default:if(Ct(t)){for(var b in n)p=n[b],n.hasOwnProperty(b)&&void 0!==p&&!r.hasOwnProperty(b)&&Ju(e,t,b,void 0,r,p);for(d in r)p=r[d],m=n[d],!r.hasOwnProperty(d)||p===m||void 0===p&&void 0===m||Ju(e,t,d,p,r,m);return}}for(var y in n)p=n[y],n.hasOwnProperty(y)&&null!=p&&!r.hasOwnProperty(y)&&Zu(e,t,y,null,r,p);for(f in r)p=r[f],m=n[f],!r.hasOwnProperty(f)||p===m||null==p&&null==m||Zu(e,t,f,p,r,m)}(r,e.type,n,t),r[Ne]=t}catch(a){uu(e,e.return,a)}}function ms(e){return 5===e.tag||3===e.tag||26===e.tag||27===e.tag&&pd(e.type)||4===e.tag}function hs(e){e:for(;;){for(;null===e.sibling;){if(null===e.return||ms(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;5!==e.tag&&6!==e.tag&&18!==e.tag;){if(27===e.tag&&pd(e.type))continue e;if(2&e.flags)continue e;if(null===e.child||4===e.tag)continue e;e.child.return=e,e=e.child}if(!(2&e.flags))return e.stateNode}}function gs(e,t,n){var r=e.tag;if(5===r||6===r)e=e.stateNode,t?(9===n.nodeType?n.body:"HTML"===n.nodeName?n.ownerDocument.body:n).insertBefore(e,t):((t=9===n.nodeType?n.body:"HTML"===n.nodeName?n.ownerDocument.body:n).appendChild(e),null!=(n=n._reactRootContainer)||null!==t.onclick||(t.onclick=Xu));else if(4!==r&&(27===r&&pd(e.type)&&(n=e.stateNode,t=null),null!==(e=e.child)))for(gs(e,t,n),e=e.sibling;null!==e;)gs(e,t,n),e=e.sibling}function bs(e,t,n){var r=e.tag;if(5===r||6===r)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(4!==r&&(27===r&&pd(e.type)&&(n=e.stateNode),null!==(e=e.child)))for(bs(e,t,n),e=e.sibling;null!==e;)bs(e,t,n),e=e.sibling}function ys(e){var t=e.stateNode,n=e.memoizedProps;try{for(var r=e.type,a=t.attributes;a.length;)t.removeAttributeNode(a[0]);ed(t,r,n),t[Pe]=e,t[Ne]=n}catch(o){uu(e,e.return,o)}}var vs=!1,ks=!1,ws=!1,Ss="function"==typeof WeakSet?WeakSet:Set,xs=null;function _s(e,t,n){var r=n.flags;switch(n.tag){case 0:case 11:case 15:Ds(e,n),4&r&&is(5,n);break;case 1:if(Ds(e,n),4&r)if(e=n.stateNode,null===t)try{e.componentDidMount()}catch(i){uu(n,n.return,i)}else{var a=gl(n.type,t.memoizedProps);t=t.memoizedState;try{e.componentDidUpdate(a,t,e.__reactInternalSnapshotBeforeUpdate)}catch(l){uu(n,n.return,l)}}64&r&&ss(n),512&r&&us(n,n.return);break;case 3:if(Ds(e,n),64&r&&null!==(e=n.updateQueue)){if(t=null,null!==n.child)switch(n.child.tag){case 27:case 5:case 1:t=n.child.stateNode}try{po(e,t)}catch(i){uu(n,n.return,i)}}break;case 27:null===t&&4&r&&ys(n);case 26:case 5:Ds(e,n),null===t&&4&r&&fs(n),512&r&&us(n,n.return);break;case 12:Ds(e,n);break;case 13:Ds(e,n),4&r&&js(e,n),64&r&&(null!==(e=n.memoizedState)&&(null!==(e=e.dehydrated)&&function(e,t){var n=e.ownerDocument;if("$?"!==e.data||"complete"===n.readyState)t();else{var r=function(){t(),n.removeEventListener("DOMContentLoaded",r)};n.addEventListener("DOMContentLoaded",r),e._reactRetry=r}}(e,n=mu.bind(null,n))));break;case 22:if(!(r=null!==n.memoizedState||vs)){t=null!==t&&null!==t.memoizedState||ks,a=vs;var o=ks;vs=r,(ks=t)&&!o?Is(e,n,!!(8772&n.subtreeFlags)):Ds(e,n),vs=a,ks=o}break;case 30:break;default:Ds(e,n)}}function Es(e){var t=e.alternate;null!==t&&(e.alternate=null,Es(t)),e.child=null,e.deletions=null,e.sibling=null,5===e.tag&&(null!==(t=e.stateNode)&&ze(t)),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}var Cs=null,Ls=!1;function As(e,t,n){for(n=n.child;null!==n;)Ts(e,t,n),n=n.sibling}function Ts(e,t,n){if(de&&"function"==typeof de.onCommitFiberUnmount)try{de.onCommitFiberUnmount(ue,n)}catch(o){}switch(n.tag){case 26:ks||ds(n,t),As(e,t,n),n.memoizedState?n.memoizedState.count--:n.stateNode&&(n=n.stateNode).parentNode.removeChild(n);break;case 27:ks||ds(n,t);var r=Cs,a=Ls;pd(n.type)&&(Cs=n.stateNode,Ls=!1),As(e,t,n),wd(n.stateNode),Cs=r,Ls=a;break;case 5:ks||ds(n,t);case 6:if(r=Cs,a=Ls,Cs=null,As(e,t,n),Ls=a,null!==(Cs=r))if(Ls)try{(9===Cs.nodeType?Cs.body:"HTML"===Cs.nodeName?Cs.ownerDocument.body:Cs).removeChild(n.stateNode)}catch(i){uu(n,t,i)}else try{Cs.removeChild(n.stateNode)}catch(i){uu(n,t,i)}break;case 18:null!==Cs&&(Ls?(md(9===(e=Cs).nodeType?e.body:"HTML"===e.nodeName?e.ownerDocument.body:e,n.stateNode),Af(e)):md(Cs,n.stateNode));break;case 4:r=Cs,a=Ls,Cs=n.stateNode.containerInfo,Ls=!0,As(e,t,n),Cs=r,Ls=a;break;case 0:case 11:case 14:case 15:ks||ls(2,n,t),ks||ls(4,n,t),As(e,t,n);break;case 1:ks||(ds(n,t),"function"==typeof(r=n.stateNode).componentWillUnmount&&cs(n,t,r)),As(e,t,n);break;case 21:As(e,t,n);break;case 22:ks=(r=ks)||null!==n.memoizedState,As(e,t,n),ks=r;break;default:As(e,t,n)}}function js(e,t){if(null===t.memoizedState&&(null!==(e=t.alternate)&&(null!==(e=e.memoizedState)&&null!==(e=e.dehydrated))))try{Af(e)}catch(n){uu(t,t.return,n)}}function Ms(e,t){var n=function(e){switch(e.tag){case 13:case 19:var t=e.stateNode;return null===t&&(t=e.stateNode=new Ss),t;case 22:return null===(t=(e=e.stateNode)._retryCache)&&(t=e._retryCache=new Ss),t;default:throw Error(i(435,e.tag))}}(e);t.forEach(function(t){var r=hu.bind(null,e,t);n.has(t)||(n.add(t),t.then(r,r))})}function Ps(e,t){var n=t.deletions;if(null!==n)for(var r=0;r<n.length;r++){var a=n[r],o=e,l=t,s=l;e:for(;null!==s;){switch(s.tag){case 27:if(pd(s.type)){Cs=s.stateNode,Ls=!1;break e}break;case 5:Cs=s.stateNode,Ls=!1;break e;case 3:case 4:Cs=s.stateNode.containerInfo,Ls=!0;break e}s=s.return}if(null===Cs)throw Error(i(160));Ts(o,l,a),Cs=null,Ls=!1,null!==(o=a.alternate)&&(o.return=null),a.return=null}if(13878&t.subtreeFlags)for(t=t.child;null!==t;)Os(t,e),t=t.sibling}var Ns=null;function Os(e,t){var n=e.alternate,r=e.flags;switch(e.tag){case 0:case 11:case 14:case 15:Ps(t,e),Rs(e),4&r&&(ls(3,e,e.return),is(3,e),ls(5,e,e.return));break;case 1:Ps(t,e),Rs(e),512&r&&(ks||null===n||ds(n,n.return)),64&r&&vs&&(null!==(e=e.updateQueue)&&(null!==(r=e.callbacks)&&(n=e.shared.hiddenCallbacks,e.shared.hiddenCallbacks=null===n?r:n.concat(r))));break;case 26:var a=Ns;if(Ps(t,e),Rs(e),512&r&&(ks||null===n||ds(n,n.return)),4&r){var o=null!==n?n.memoizedState:null;if(r=e.memoizedState,null===n)if(null===r)if(null===e.stateNode){e:{r=e.type,n=e.memoizedProps,a=a.ownerDocument||a;t:switch(r){case"title":(!(o=a.getElementsByTagName("title")[0])||o[Ie]||o[Pe]||"http://www.w3.org/2000/svg"===o.namespaceURI||o.hasAttribute("itemprop"))&&(o=a.createElement(r),a.head.insertBefore(o,a.querySelector("head > title"))),ed(o,r,n),o[Pe]=e,Ge(o),r=o;break e;case"link":var l=Id("link","href",a).get(r+(n.href||""));if(l)for(var s=0;s<l.length;s++)if((o=l[s]).getAttribute("href")===(null==n.href||""===n.href?null:n.href)&&o.getAttribute("rel")===(null==n.rel?null:n.rel)&&o.getAttribute("title")===(null==n.title?null:n.title)&&o.getAttribute("crossorigin")===(null==n.crossOrigin?null:n.crossOrigin)){l.splice(s,1);break t}ed(o=a.createElement(r),r,n),a.head.appendChild(o);break;case"meta":if(l=Id("meta","content",a).get(r+(n.content||"")))for(s=0;s<l.length;s++)if((o=l[s]).getAttribute("content")===(null==n.content?null:""+n.content)&&o.getAttribute("name")===(null==n.name?null:n.name)&&o.getAttribute("property")===(null==n.property?null:n.property)&&o.getAttribute("http-equiv")===(null==n.httpEquiv?null:n.httpEquiv)&&o.getAttribute("charset")===(null==n.charSet?null:n.charSet)){l.splice(s,1);break t}ed(o=a.createElement(r),r,n),a.head.appendChild(o);break;default:throw Error(i(468,r))}o[Pe]=e,Ge(o),r=o}e.stateNode=r}else zd(a,e.type,e.stateNode);else e.stateNode=Od(a,r,e.memoizedProps);else o!==r?(null===o?null!==n.stateNode&&(n=n.stateNode).parentNode.removeChild(n):o.count--,null===r?zd(a,e.type,e.stateNode):Od(a,r,e.memoizedProps)):null===r&&null!==e.stateNode&&ps(e,e.memoizedProps,n.memoizedProps)}break;case 27:Ps(t,e),Rs(e),512&r&&(ks||null===n||ds(n,n.return)),null!==n&&4&r&&ps(e,e.memoizedProps,n.memoizedProps);break;case 5:if(Ps(t,e),Rs(e),512&r&&(ks||null===n||ds(n,n.return)),32&e.flags){a=e.stateNode;try{St(a,"")}catch(m){uu(e,e.return,m)}}4&r&&null!=e.stateNode&&ps(e,a=e.memoizedProps,null!==n?n.memoizedProps:a),1024&r&&(ws=!0);break;case 6:if(Ps(t,e),Rs(e),4&r){if(null===e.stateNode)throw Error(i(162));r=e.memoizedProps,n=e.stateNode;try{n.nodeValue=r}catch(m){uu(e,e.return,m)}}break;case 3:if(Fd=null,a=Ns,Ns=_d(t.containerInfo),Ps(t,e),Ns=a,Rs(e),4&r&&null!==n&&n.memoizedState.isDehydrated)try{Af(t.containerInfo)}catch(m){uu(e,e.return,m)}ws&&(ws=!1,Bs(e));break;case 4:r=Ns,Ns=_d(e.stateNode.containerInfo),Ps(t,e),Rs(e),Ns=r;break;case 12:default:Ps(t,e),Rs(e);break;case 13:Ps(t,e),Rs(e),8192&e.child.flags&&null!==e.memoizedState!=(null!==n&&null!==n.memoizedState)&&(wc=te()),4&r&&(null!==(r=e.updateQueue)&&(e.updateQueue=null,Ms(e,r)));break;case 22:a=null!==e.memoizedState;var c=null!==n&&null!==n.memoizedState,u=vs,d=ks;if(vs=u||a,ks=d||c,Ps(t,e),ks=d,vs=u,Rs(e),8192&r)e:for(t=e.stateNode,t._visibility=a?-2&t._visibility:1|t._visibility,a&&(null===n||c||vs||ks||Fs(e)),n=null,t=e;;){if(5===t.tag||26===t.tag){if(null===n){c=n=t;try{if(o=c.stateNode,a)"function"==typeof(l=o.style).setProperty?l.setProperty("display","none","important"):l.display="none";else{s=c.stateNode;var f=c.memoizedProps.style,p=null!=f&&f.hasOwnProperty("display")?f.display:null;s.style.display=null==p||"boolean"==typeof p?"":(""+p).trim()}}catch(m){uu(c,c.return,m)}}}else if(6===t.tag){if(null===n){c=t;try{c.stateNode.nodeValue=a?"":c.memoizedProps}catch(m){uu(c,c.return,m)}}}else if((22!==t.tag&&23!==t.tag||null===t.memoizedState||t===e)&&null!==t.child){t.child.return=t,t=t.child;continue}if(t===e)break e;for(;null===t.sibling;){if(null===t.return||t.return===e)break e;n===t&&(n=null),t=t.return}n===t&&(n=null),t.sibling.return=t.return,t=t.sibling}4&r&&(null!==(r=e.updateQueue)&&(null!==(n=r.retryQueue)&&(r.retryQueue=null,Ms(e,n))));break;case 19:Ps(t,e),Rs(e),4&r&&(null!==(r=e.updateQueue)&&(e.updateQueue=null,Ms(e,r)));case 30:case 21:}}function Rs(e){var t=e.flags;if(2&t){try{for(var n,r=e.return;null!==r;){if(ms(r)){n=r;break}r=r.return}if(null==n)throw Error(i(160));switch(n.tag){case 27:var a=n.stateNode;bs(e,hs(e),a);break;case 5:var o=n.stateNode;32&n.flags&&(St(o,""),n.flags&=-33),bs(e,hs(e),o);break;case 3:case 4:var l=n.stateNode.containerInfo;gs(e,hs(e),l);break;default:throw Error(i(161))}}catch(s){uu(e,e.return,s)}e.flags&=-3}4096&t&&(e.flags&=-4097)}function Bs(e){if(1024&e.subtreeFlags)for(e=e.child;null!==e;){var t=e;Bs(t),5===t.tag&&1024&t.flags&&t.stateNode.reset(),e=e.sibling}}function Ds(e,t){if(8772&t.subtreeFlags)for(t=t.child;null!==t;)_s(e,t.alternate,t),t=t.sibling}function Fs(e){for(e=e.child;null!==e;){var t=e;switch(t.tag){case 0:case 11:case 14:case 15:ls(4,t,t.return),Fs(t);break;case 1:ds(t,t.return);var n=t.stateNode;"function"==typeof n.componentWillUnmount&&cs(t,t.return,n),Fs(t);break;case 27:wd(t.stateNode);case 26:case 5:ds(t,t.return),Fs(t);break;case 22:null===t.memoizedState&&Fs(t);break;default:Fs(t)}e=e.sibling}}function Is(e,t,n){for(n=n&&!!(8772&t.subtreeFlags),t=t.child;null!==t;){var r=t.alternate,a=e,o=t,i=o.flags;switch(o.tag){case 0:case 11:case 15:Is(a,o,n),is(4,o);break;case 1:if(Is(a,o,n),"function"==typeof(a=(r=o).stateNode).componentDidMount)try{a.componentDidMount()}catch(c){uu(r,r.return,c)}if(null!==(a=(r=o).updateQueue)){var l=r.stateNode;try{var s=a.shared.hiddenCallbacks;if(null!==s)for(a.shared.hiddenCallbacks=null,a=0;a<s.length;a++)fo(s[a],l)}catch(c){uu(r,r.return,c)}}n&&64&i&&ss(o),us(o,o.return);break;case 27:ys(o);case 26:case 5:Is(a,o,n),n&&null===r&&4&i&&fs(o),us(o,o.return);break;case 12:Is(a,o,n);break;case 13:Is(a,o,n),n&&4&i&&js(a,o);break;case 22:null===o.memoizedState&&Is(a,o,n),us(o,o.return);break;case 30:break;default:Is(a,o,n)}t=t.sibling}}function zs(e,t){var n=null;null!==e&&null!==e.memoizedState&&null!==e.memoizedState.cachePool&&(n=e.memoizedState.cachePool.pool),e=null,null!==t.memoizedState&&null!==t.memoizedState.cachePool&&(e=t.memoizedState.cachePool.pool),e!==n&&(null!=e&&e.refCount++,null!=n&&Oa(n))}function $s(e,t){e=null,null!==t.alternate&&(e=t.alternate.memoizedState.cache),(t=t.memoizedState.cache)!==e&&(t.refCount++,null!=e&&Oa(e))}function Us(e,t,n,r){if(10256&t.subtreeFlags)for(t=t.child;null!==t;)qs(e,t,n,r),t=t.sibling}function qs(e,t,n,r){var a=t.flags;switch(t.tag){case 0:case 11:case 15:Us(e,t,n,r),2048&a&&is(9,t);break;case 1:case 13:default:Us(e,t,n,r);break;case 3:Us(e,t,n,r),2048&a&&(e=null,null!==t.alternate&&(e=t.alternate.memoizedState.cache),(t=t.memoizedState.cache)!==e&&(t.refCount++,null!=e&&Oa(e)));break;case 12:if(2048&a){Us(e,t,n,r),e=t.stateNode;try{var o=t.memoizedProps,i=o.id,l=o.onPostCommit;"function"==typeof l&&l(i,null===t.alternate?"mount":"update",e.passiveEffectDuration,-0)}catch(s){uu(t,t.return,s)}}else Us(e,t,n,r);break;case 23:break;case 22:o=t.stateNode,i=t.alternate,null!==t.memoizedState?2&o._visibility?Us(e,t,n,r):Gs(e,t):2&o._visibility?Us(e,t,n,r):(o._visibility|=2,Hs(e,t,n,r,!!(10256&t.subtreeFlags))),2048&a&&zs(i,t);break;case 24:Us(e,t,n,r),2048&a&&$s(t.alternate,t)}}function Hs(e,t,n,r,a){for(a=a&&!!(10256&t.subtreeFlags),t=t.child;null!==t;){var o=e,i=t,l=n,s=r,c=i.flags;switch(i.tag){case 0:case 11:case 15:Hs(o,i,l,s,a),is(8,i);break;case 23:break;case 22:var u=i.stateNode;null!==i.memoizedState?2&u._visibility?Hs(o,i,l,s,a):Gs(o,i):(u._visibility|=2,Hs(o,i,l,s,a)),a&&2048&c&&zs(i.alternate,i);break;case 24:Hs(o,i,l,s,a),a&&2048&c&&$s(i.alternate,i);break;default:Hs(o,i,l,s,a)}t=t.sibling}}function Gs(e,t){if(10256&t.subtreeFlags)for(t=t.child;null!==t;){var n=e,r=t,a=r.flags;switch(r.tag){case 22:Gs(n,r),2048&a&&zs(r.alternate,r);break;case 24:Gs(n,r),2048&a&&$s(r.alternate,r);break;default:Gs(n,r)}t=t.sibling}}var Vs=8192;function Ws(e){if(e.subtreeFlags&Vs)for(e=e.child;null!==e;)Qs(e),e=e.sibling}function Qs(e){switch(e.tag){case 26:Ws(e),e.flags&Vs&&null!==e.memoizedState&&function(e,t,n){if(null===Ud)throw Error(i(475));var r=Ud;if(!("stylesheet"!==t.type||"string"==typeof n.media&&!1===matchMedia(n.media).matches||4&t.state.loading)){if(null===t.instance){var a=Td(n.href),o=e.querySelector(jd(a));if(o)return null!==(e=o._p)&&"object"==typeof e&&"function"==typeof e.then&&(r.count++,r=Hd.bind(r),e.then(r,r)),t.state.loading|=4,t.instance=o,void Ge(o);o=e.ownerDocument||e,n=Md(n),(a=Sd.get(a))&&Bd(n,a),Ge(o=o.createElement("link"));var l=o;l._p=new Promise(function(e,t){l.onload=e,l.onerror=t}),ed(o,"link",n),t.instance=o}null===r.stylesheets&&(r.stylesheets=new Map),r.stylesheets.set(t,e),(e=t.state.preload)&&!(3&t.state.loading)&&(r.count++,t=Hd.bind(r),e.addEventListener("load",t),e.addEventListener("error",t))}}(Ns,e.memoizedState,e.memoizedProps);break;case 5:default:Ws(e);break;case 3:case 4:var t=Ns;Ns=_d(e.stateNode.containerInfo),Ws(e),Ns=t;break;case 22:null===e.memoizedState&&(null!==(t=e.alternate)&&null!==t.memoizedState?(t=Vs,Vs=16777216,Ws(e),Vs=t):Ws(e))}}function Ks(e){var t=e.alternate;if(null!==t&&null!==(e=t.child)){t.child=null;do{t=e.sibling,e.sibling=null,e=t}while(null!==e)}}function Ys(e){var t=e.deletions;if(16&e.flags){if(null!==t)for(var n=0;n<t.length;n++){var r=t[n];xs=r,Js(r,e)}Ks(e)}if(10256&e.subtreeFlags)for(e=e.child;null!==e;)Xs(e),e=e.sibling}function Xs(e){switch(e.tag){case 0:case 11:case 15:Ys(e),2048&e.flags&&ls(9,e,e.return);break;case 3:case 12:default:Ys(e);break;case 22:var t=e.stateNode;null!==e.memoizedState&&2&t._visibility&&(null===e.return||13!==e.return.tag)?(t._visibility&=-3,Zs(e)):Ys(e)}}function Zs(e){var t=e.deletions;if(16&e.flags){if(null!==t)for(var n=0;n<t.length;n++){var r=t[n];xs=r,Js(r,e)}Ks(e)}for(e=e.child;null!==e;){switch((t=e).tag){case 0:case 11:case 15:ls(8,t,t.return),Zs(t);break;case 22:2&(n=t.stateNode)._visibility&&(n._visibility&=-3,Zs(t));break;default:Zs(t)}e=e.sibling}}function Js(e,t){for(;null!==xs;){var n=xs;switch(n.tag){case 0:case 11:case 15:ls(8,n,t);break;case 23:case 22:if(null!==n.memoizedState&&null!==n.memoizedState.cachePool){var r=n.memoizedState.cachePool.pool;null!=r&&r.refCount++}break;case 24:Oa(n.memoizedState.cache)}if(null!==(r=n.child))r.return=n,xs=r;else e:for(n=e;null!==xs;){var a=(r=xs).sibling,o=r.return;if(Es(r),r===n){xs=null;break e}if(null!==a){a.return=o,xs=a;break e}xs=o}}}var ec={getCacheForType:function(e){var t=Ca(Pa),n=t.data.get(e);return void 0===n&&(n=e(),t.data.set(e,n)),n}},tc="function"==typeof WeakMap?WeakMap:Map,nc=0,rc=null,ac=null,oc=0,ic=0,lc=null,sc=!1,cc=!1,uc=!1,dc=0,fc=0,pc=0,mc=0,hc=0,gc=0,bc=0,yc=null,vc=null,kc=!1,wc=0,Sc=1/0,xc=null,_c=null,Ec=0,Cc=null,Lc=null,Ac=0,Tc=0,jc=null,Mc=null,Pc=0,Nc=null;function Oc(){if(2&nc&&0!==oc)return oc&-oc;if(null!==O.T){return 0!==Da?Da:Tu()}return je()}function Rc(){0===gc&&(gc=536870912&oc&&!oa?536870912:Se());var e=al.current;return null!==e&&(e.flags|=32),gc}function Bc(e,t,n){(e!==rc||2!==ic&&9!==ic)&&null===e.cancelPendingCommit||(qc(e,0),zc(e,oc,gc,!1)),Ee(e,n),2&nc&&e===rc||(e===rc&&(!(2&nc)&&(mc|=n),4===fc&&zc(e,oc,gc,!1)),Su(e))}function Dc(e,t,n){if(6&nc)throw Error(i(327));for(var r=!n&&!(124&t)&&0===(t&e.expiredLanes)||ke(e,t),a=r?function(e,t){var n=nc;nc|=2;var r=Gc(),a=Vc();rc!==e||oc!==t?(xc=null,Sc=te()+500,qc(e,t)):cc=ke(e,t);e:for(;;)try{if(0!==ic&&null!==ac){t=ac;var o=lc;t:switch(ic){case 1:ic=0,lc=null,Jc(e,t,o,1);break;case 2:case 9:if(Ka(o)){ic=0,lc=null,Zc(t);break}t=function(){2!==ic&&9!==ic||rc!==e||(ic=7),Su(e)},o.then(t,t);break e;case 3:ic=7;break e;case 4:ic=5;break e;case 7:Ka(o)?(ic=0,lc=null,Zc(t)):(ic=0,lc=null,Jc(e,t,o,7));break;case 5:var l=null;switch(ac.tag){case 26:l=ac.memoizedState;case 5:case 27:var s=ac;if(!l||$d(l)){ic=0,lc=null;var c=s.sibling;if(null!==c)ac=c;else{var u=s.return;null!==u?(ac=u,eu(u)):ac=null}break t}}ic=0,lc=null,Jc(e,t,o,5);break;case 6:ic=0,lc=null,Jc(e,t,o,6);break;case 8:Uc(),fc=6;break e;default:throw Error(i(462))}}Yc();break}catch(d){Hc(e,d)}return ya=ba=null,O.H=r,O.A=a,nc=n,null!==ac?0:(rc=null,oc=0,Ar(),fc)}(e,t):Qc(e,t,!0),o=r;;){if(0===a){cc&&!r&&zc(e,t,0,!1);break}if(n=e.current.alternate,!o||Ic(n)){if(2===a){if(o=t,e.errorRecoveryDisabledLanes&o)var l=0;else l=0!==(l=-536870913&e.pendingLanes)?l:536870912&l?536870912:0;if(0!==l){t=l;e:{var s=e;a=yc;var c=s.current.memoizedState.isDehydrated;if(c&&(qc(s,l).flags|=256),2!==(l=Qc(s,l,!1))){if(uc&&!c){s.errorRecoveryDisabledLanes|=o,mc|=o,a=4;break e}o=vc,vc=a,null!==o&&(null===vc?vc=o:vc.push.apply(vc,o))}a=l}if(o=!1,2!==a)continue}}if(1===a){qc(e,0),zc(e,t,0,!0);break}e:{switch(r=e,o=a){case 0:case 1:throw Error(i(345));case 4:if((4194048&t)!==t)break;case 6:zc(r,t,gc,!sc);break e;case 2:vc=null;break;case 3:case 5:break;default:throw Error(i(329))}if((62914560&t)===t&&10<(a=wc+300-te())){if(zc(r,t,gc,!sc),0!==ve(r,0,!0))break e;r.timeoutHandle=sd(Fc.bind(null,r,n,vc,xc,kc,t,gc,mc,bc,sc,o,2,-0,0),a)}else Fc(r,n,vc,xc,kc,t,gc,mc,bc,sc,o,0,-0,0)}break}a=Qc(e,t,!1),o=!1}Su(e)}function Fc(e,t,n,r,a,o,l,s,c,u,d,f,p,m){if(e.timeoutHandle=-1,(8192&(f=t.subtreeFlags)||!(16785408&~f))&&(Ud={stylesheets:null,count:0,unsuspend:qd},Qs(t),null!==(f=function(){if(null===Ud)throw Error(i(475));var e=Ud;return e.stylesheets&&0===e.count&&Vd(e,e.stylesheets),0<e.count?function(t){var n=setTimeout(function(){if(e.stylesheets&&Vd(e,e.stylesheets),e.unsuspend){var t=e.unsuspend;e.unsuspend=null,t()}},6e4);return e.unsuspend=t,function(){e.unsuspend=null,clearTimeout(n)}}:null}())))return e.cancelPendingCommit=f(nu.bind(null,e,t,o,n,r,a,l,s,c,d,1,p,m)),void zc(e,o,l,!u);nu(e,t,o,n,r,a,l,s,c)}function Ic(e){for(var t=e;;){var n=t.tag;if((0===n||11===n||15===n)&&16384&t.flags&&(null!==(n=t.updateQueue)&&null!==(n=n.stores)))for(var r=0;r<n.length;r++){var a=n[r],o=a.getSnapshot;a=a.value;try{if(!Kn(o(),a))return!1}catch(i){return!1}}if(n=t.child,16384&t.subtreeFlags&&null!==n)n.return=t,t=n;else{if(t===e)break;for(;null===t.sibling;){if(null===t.return||t.return===e)return!0;t=t.return}t.sibling.return=t.return,t=t.sibling}}return!0}function zc(e,t,n,r){t&=~hc,t&=~mc,e.suspendedLanes|=t,e.pingedLanes&=~t,r&&(e.warmLanes|=t),r=e.expirationTimes;for(var a=t;0<a;){var o=31-pe(a),i=1<<o;r[o]=-1,a&=~i}0!==n&&Ce(e,n,t)}function $c(){return!!(6&nc)||(xu(0,!1),!1)}function Uc(){if(null!==ac){if(0===ic)var e=ac.return;else ya=ba=null,Fo(e=ac),Ki=null,Yi=0,e=ac;for(;null!==e;)os(e.alternate,e),e=e.return;ac=null}}function qc(e,t){var n=e.timeoutHandle;-1!==n&&(e.timeoutHandle=-1,cd(n)),null!==(n=e.cancelPendingCommit)&&(e.cancelPendingCommit=null,n()),Uc(),rc=e,ac=n=Fr(e.current,null),oc=t,ic=0,lc=null,sc=!1,cc=ke(e,t),uc=!1,bc=gc=hc=mc=pc=fc=0,vc=yc=null,kc=!1,8&t&&(t|=32&t);var r=e.entangledLanes;if(0!==r)for(e=e.entanglements,r&=t;0<r;){var a=31-pe(r),o=1<<a;t|=e[a],r&=~o}return dc=t,Ar(),n}function Hc(e,t){ko=null,O.H=Gi,t===Ga||t===Wa?(t=Ja(),ic=3):t===Va?(t=Ja(),ic=4):ic=t===Cl?8:null!==t&&"object"==typeof t&&"function"==typeof t.then?6:1,lc=t,null===ac&&(fc=1,wl(e,_r(t,e.current)))}function Gc(){var e=O.H;return O.H=Gi,null===e?Gi:e}function Vc(){var e=O.A;return O.A=ec,e}function Wc(){fc=4,sc||(4194048&oc)!==oc&&null!==al.current||(cc=!0),!(134217727&pc)&&!(134217727&mc)||null===rc||zc(rc,oc,gc,!1)}function Qc(e,t,n){var r=nc;nc|=2;var a=Gc(),o=Vc();rc===e&&oc===t||(xc=null,qc(e,t)),t=!1;var i=fc;e:for(;;)try{if(0!==ic&&null!==ac){var l=ac,s=lc;switch(ic){case 8:Uc(),i=6;break e;case 3:case 2:case 9:case 6:null===al.current&&(t=!0);var c=ic;if(ic=0,lc=null,Jc(e,l,s,c),n&&cc){i=0;break e}break;default:c=ic,ic=0,lc=null,Jc(e,l,s,c)}}Kc(),i=fc;break}catch(u){Hc(e,u)}return t&&e.shellSuspendCounter++,ya=ba=null,nc=r,O.H=a,O.A=o,null===ac&&(rc=null,oc=0,Ar()),i}function Kc(){for(;null!==ac;)Xc(ac)}function Yc(){for(;null!==ac&&!J();)Xc(ac)}function Xc(e){var t=Xl(e.alternate,e,dc);e.memoizedProps=e.pendingProps,null===t?eu(e):ac=t}function Zc(e){var t=e,n=t.alternate;switch(t.tag){case 15:case 0:t=Bl(n,t,t.pendingProps,t.type,void 0,oc);break;case 11:t=Bl(n,t,t.pendingProps,t.type.render,t.ref,oc);break;case 5:Fo(t);default:os(n,t),t=Xl(n,t=ac=Ir(t,dc),dc)}e.memoizedProps=e.pendingProps,null===t?eu(e):ac=t}function Jc(e,t,n,r){ya=ba=null,Fo(t),Ki=null,Yi=0;var a=t.return;try{if(function(e,t,n,r,a){if(n.flags|=32768,null!==r&&"object"==typeof r&&"function"==typeof r.then){if(null!==(t=n.alternate)&&xa(t,n,a,!0),null!==(n=al.current)){switch(n.tag){case 13:return null===ol?Wc():null===n.alternate&&0===fc&&(fc=3),n.flags&=-257,n.flags|=65536,n.lanes=a,r===Qa?n.flags|=16384:(null===(t=n.updateQueue)?n.updateQueue=new Set([r]):t.add(r),du(e,r,a)),!1;case 22:return n.flags|=65536,r===Qa?n.flags|=16384:(null===(t=n.updateQueue)?(t={transitions:null,markerInstances:null,retryQueue:new Set([r])},n.updateQueue=t):null===(n=t.retryQueue)?t.retryQueue=new Set([r]):n.add(r),du(e,r,a)),!1}throw Error(i(435,n.tag))}return du(e,r,a),Wc(),!1}if(oa)return null!==(t=al.current)?(!(65536&t.flags)&&(t.flags|=256),t.flags|=65536,t.lanes=a,r!==sa&&ha(_r(e=Error(i(422),{cause:r}),n))):(r!==sa&&ha(_r(t=Error(i(423),{cause:r}),n)),(e=e.current.alternate).flags|=65536,a&=-a,e.lanes|=a,r=_r(r,n),lo(e,a=xl(e.stateNode,r,a)),4!==fc&&(fc=2)),!1;var o=Error(i(520),{cause:r});if(o=_r(o,n),null===yc?yc=[o]:yc.push(o),4!==fc&&(fc=2),null===t)return!0;r=_r(r,n),n=t;do{switch(n.tag){case 3:return n.flags|=65536,e=a&-a,n.lanes|=e,lo(n,e=xl(n.stateNode,r,e)),!1;case 1:if(t=n.type,o=n.stateNode,!(128&n.flags||"function"!=typeof t.getDerivedStateFromError&&(null===o||"function"!=typeof o.componentDidCatch||null!==_c&&_c.has(o))))return n.flags|=65536,a&=-a,n.lanes|=a,El(a=_l(a),e,n,r),lo(n,a),!1}n=n.return}while(null!==n);return!1}(e,a,t,n,oc))return fc=1,wl(e,_r(n,e.current)),void(ac=null)}catch(o){if(null!==a)throw ac=a,o;return fc=1,wl(e,_r(n,e.current)),void(ac=null)}32768&t.flags?(oa||1===r?e=!0:cc||536870912&oc?e=!1:(sc=e=!0,(2===r||9===r||3===r||6===r)&&(null!==(r=al.current)&&13===r.tag&&(r.flags|=16384))),tu(t,e)):eu(t)}function eu(e){var t=e;do{if(32768&t.flags)return void tu(t,sc);e=t.return;var n=rs(t.alternate,t,dc);if(null!==n)return void(ac=n);if(null!==(t=t.sibling))return void(ac=t);ac=t=e}while(null!==t);0===fc&&(fc=5)}function tu(e,t){do{var n=as(e.alternate,e);if(null!==n)return n.flags&=32767,void(ac=n);if(null!==(n=e.return)&&(n.flags|=32768,n.subtreeFlags=0,n.deletions=null),!t&&null!==(e=e.sibling))return void(ac=e);ac=e=n}while(null!==e);fc=6,ac=null}function nu(e,t,n,r,a,o,l,s,c){e.cancelPendingCommit=null;do{lu()}while(0!==Ec);if(6&nc)throw Error(i(327));if(null!==t){if(t===e.current)throw Error(i(177));if(o=t.lanes|t.childLanes,function(e,t,n,r,a,o){var i=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var l=e.entanglements,s=e.expirationTimes,c=e.hiddenUpdates;for(n=i&~n;0<n;){var u=31-pe(n),d=1<<u;l[u]=0,s[u]=-1;var f=c[u];if(null!==f)for(c[u]=null,u=0;u<f.length;u++){var p=f[u];null!==p&&(p.lane&=-536870913)}n&=~d}0!==r&&Ce(e,r,0),0!==o&&0===a&&0!==e.tag&&(e.suspendedLanes|=o&~(i&~t))}(e,n,o|=Lr,l,s,c),e===rc&&(ac=rc=null,oc=0),Lc=t,Cc=e,Ac=n,Tc=o,jc=a,Mc=r,10256&t.subtreeFlags||10256&t.flags?(e.callbackNode=null,e.callbackPriority=0,X(oe,function(){return su(),null})):(e.callbackNode=null,e.callbackPriority=0),r=!!(13878&t.flags),13878&t.subtreeFlags||r){r=O.T,O.T=null,a=R.p,R.p=2,l=nc,nc|=4;try{!function(e,t){if(e=e.containerInfo,td=nf,tr(e=er(e))){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{var r=(n=(n=e.ownerDocument)&&n.defaultView||window).getSelection&&n.getSelection();if(r&&0!==r.rangeCount){n=r.anchorNode;var a=r.anchorOffset,o=r.focusNode;r=r.focusOffset;try{n.nodeType,o.nodeType}catch(g){n=null;break e}var l=0,s=-1,c=-1,u=0,d=0,f=e,p=null;t:for(;;){for(var m;f!==n||0!==a&&3!==f.nodeType||(s=l+a),f!==o||0!==r&&3!==f.nodeType||(c=l+r),3===f.nodeType&&(l+=f.nodeValue.length),null!==(m=f.firstChild);)p=f,f=m;for(;;){if(f===e)break t;if(p===n&&++u===a&&(s=l),p===o&&++d===r&&(c=l),null!==(m=f.nextSibling))break;p=(f=p).parentNode}f=m}n=-1===s||-1===c?null:{start:s,end:c}}else n=null}n=n||{start:0,end:0}}else n=null;for(nd={focusedElem:e,selectionRange:n},nf=!1,xs=t;null!==xs;)if(e=(t=xs).child,1024&t.subtreeFlags&&null!==e)e.return=t,xs=e;else for(;null!==xs;){switch(o=(t=xs).alternate,e=t.flags,t.tag){case 0:case 11:case 15:case 5:case 26:case 27:case 6:case 4:case 17:break;case 1:if(1024&e&&null!==o){e=void 0,n=t,a=o.memoizedProps,o=o.memoizedState,r=n.stateNode;try{var h=gl(n.type,a,(n.elementType,n.type));e=r.getSnapshotBeforeUpdate(h,o),r.__reactInternalSnapshotBeforeUpdate=e}catch(b){uu(n,n.return,b)}}break;case 3:if(1024&e)if(9===(n=(e=t.stateNode.containerInfo).nodeType))hd(e);else if(1===n)switch(e.nodeName){case"HEAD":case"HTML":case"BODY":hd(e);break;default:e.textContent=""}break;default:if(1024&e)throw Error(i(163))}if(null!==(e=t.sibling)){e.return=t.return,xs=e;break}xs=t.return}}(e,t)}finally{nc=l,R.p=a,O.T=r}}Ec=1,ru(),au(),ou()}}function ru(){if(1===Ec){Ec=0;var e=Cc,t=Lc,n=!!(13878&t.flags);if(13878&t.subtreeFlags||n){n=O.T,O.T=null;var r=R.p;R.p=2;var a=nc;nc|=4;try{Os(t,e);var o=nd,i=er(e.containerInfo),l=o.focusedElem,s=o.selectionRange;if(i!==l&&l&&l.ownerDocument&&Jn(l.ownerDocument.documentElement,l)){if(null!==s&&tr(l)){var c=s.start,u=s.end;if(void 0===u&&(u=c),"selectionStart"in l)l.selectionStart=c,l.selectionEnd=Math.min(u,l.value.length);else{var d=l.ownerDocument||document,f=d&&d.defaultView||window;if(f.getSelection){var p=f.getSelection(),m=l.textContent.length,h=Math.min(s.start,m),g=void 0===s.end?h:Math.min(s.end,m);!p.extend&&h>g&&(i=g,g=h,h=i);var b=Zn(l,h),y=Zn(l,g);if(b&&y&&(1!==p.rangeCount||p.anchorNode!==b.node||p.anchorOffset!==b.offset||p.focusNode!==y.node||p.focusOffset!==y.offset)){var v=d.createRange();v.setStart(b.node,b.offset),p.removeAllRanges(),h>g?(p.addRange(v),p.extend(y.node,y.offset)):(v.setEnd(y.node,y.offset),p.addRange(v))}}}}for(d=[],p=l;p=p.parentNode;)1===p.nodeType&&d.push({element:p,left:p.scrollLeft,top:p.scrollTop});for("function"==typeof l.focus&&l.focus(),l=0;l<d.length;l++){var k=d[l];k.element.scrollLeft=k.left,k.element.scrollTop=k.top}}nf=!!td,nd=td=null}finally{nc=a,R.p=r,O.T=n}}e.current=t,Ec=2}}function au(){if(2===Ec){Ec=0;var e=Cc,t=Lc,n=!!(8772&t.flags);if(8772&t.subtreeFlags||n){n=O.T,O.T=null;var r=R.p;R.p=2;var a=nc;nc|=4;try{_s(e,t.alternate,t)}finally{nc=a,R.p=r,O.T=n}}Ec=3}}function ou(){if(4===Ec||3===Ec){Ec=0,ee();var e=Cc,t=Lc,n=Ac,r=Mc;10256&t.subtreeFlags||10256&t.flags?Ec=5:(Ec=0,Lc=Cc=null,iu(e,e.pendingLanes));var a=e.pendingLanes;if(0===a&&(_c=null),Te(n),t=t.stateNode,de&&"function"==typeof de.onCommitFiberRoot)try{de.onCommitFiberRoot(ue,t,void 0,!(128&~t.current.flags))}catch(s){}if(null!==r){t=O.T,a=R.p,R.p=2,O.T=null;try{for(var o=e.onRecoverableError,i=0;i<r.length;i++){var l=r[i];o(l.value,{componentStack:l.stack})}}finally{O.T=t,R.p=a}}3&Ac&&lu(),Su(e),a=e.pendingLanes,4194090&n&&42&a?e===Nc?Pc++:(Pc=0,Nc=e):Pc=0,xu(0,!1)}}function iu(e,t){0===(e.pooledCacheLanes&=t)&&(null!=(t=e.pooledCache)&&(e.pooledCache=null,Oa(t)))}function lu(e){return ru(),au(),ou(),su()}function su(){if(5!==Ec)return!1;var e=Cc,t=Tc;Tc=0;var n=Te(Ac),r=O.T,a=R.p;try{R.p=32>n?32:n,O.T=null,n=jc,jc=null;var o=Cc,l=Ac;if(Ec=0,Lc=Cc=null,Ac=0,6&nc)throw Error(i(331));var s=nc;if(nc|=4,Xs(o.current),qs(o,o.current,l,n),nc=s,xu(0,!1),de&&"function"==typeof de.onPostCommitFiberRoot)try{de.onPostCommitFiberRoot(ue,o)}catch(c){}return!0}finally{R.p=a,O.T=r,iu(e,t)}}function cu(e,t,n){t=_r(n,t),null!==(e=oo(e,t=xl(e.stateNode,t,2),2))&&(Ee(e,2),Su(e))}function uu(e,t,n){if(3===e.tag)cu(e,e,n);else for(;null!==t;){if(3===t.tag){cu(t,e,n);break}if(1===t.tag){var r=t.stateNode;if("function"==typeof t.type.getDerivedStateFromError||"function"==typeof r.componentDidCatch&&(null===_c||!_c.has(r))){e=_r(n,e),null!==(r=oo(t,n=_l(2),2))&&(El(n,r,t,e),Ee(r,2),Su(r));break}}t=t.return}}function du(e,t,n){var r=e.pingCache;if(null===r){r=e.pingCache=new tc;var a=new Set;r.set(t,a)}else void 0===(a=r.get(t))&&(a=new Set,r.set(t,a));a.has(n)||(uc=!0,a.add(n),e=fu.bind(null,e,t,n),t.then(e,e))}function fu(e,t,n){var r=e.pingCache;null!==r&&r.delete(t),e.pingedLanes|=e.suspendedLanes&n,e.warmLanes&=~n,rc===e&&(oc&n)===n&&(4===fc||3===fc&&(62914560&oc)===oc&&300>te()-wc?!(2&nc)&&qc(e,0):hc|=n,bc===oc&&(bc=0)),Su(e)}function pu(e,t){0===t&&(t=xe()),null!==(e=Mr(e,t))&&(Ee(e,t),Su(e))}function mu(e){var t=e.memoizedState,n=0;null!==t&&(n=t.retryLane),pu(e,n)}function hu(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,a=e.memoizedState;null!==a&&(n=a.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(i(314))}null!==r&&r.delete(t),pu(e,n)}var gu=null,bu=null,yu=!1,vu=!1,ku=!1,wu=0;function Su(e){e!==bu&&null===e.next&&(null===bu?gu=bu=e:bu=bu.next=e),vu=!0,yu||(yu=!0,dd(function(){6&nc?X(re,_u):Eu()}))}function xu(e,t){if(!ku&&vu){ku=!0;do{for(var n=!1,r=gu;null!==r;){if(!t)if(0!==e){var a=r.pendingLanes;if(0===a)var o=0;else{var i=r.suspendedLanes,l=r.pingedLanes;o=(1<<31-pe(42|e)+1)-1,o=201326741&(o&=a&~(i&~l))?201326741&o|1:o?2|o:0}0!==o&&(n=!0,Au(r,o))}else o=oc,!(3&(o=ve(r,r===rc?o:0,null!==r.cancelPendingCommit||-1!==r.timeoutHandle)))||ke(r,o)||(n=!0,Au(r,o));r=r.next}}while(n);ku=!1}}function _u(){Eu()}function Eu(){vu=yu=!1;var e=0;0!==wu&&(function(){var e=window.event;if(e&&"popstate"===e.type)return e!==ld&&(ld=e,!0);return ld=null,!1}()&&(e=wu),wu=0);for(var t=te(),n=null,r=gu;null!==r;){var a=r.next,o=Cu(r,t);0===o?(r.next=null,null===n?gu=a:n.next=a,null===a&&(bu=n)):(n=r,(0!==e||3&o)&&(vu=!0)),r=a}xu(e,!1)}function Cu(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,a=e.expirationTimes,o=-62914561&e.pendingLanes;0<o;){var i=31-pe(o),l=1<<i,s=a[i];-1===s?0!==(l&n)&&0===(l&r)||(a[i]=we(l,t)):s<=t&&(e.expiredLanes|=l),o&=~l}if(n=oc,n=ve(e,e===(t=rc)?n:0,null!==e.cancelPendingCommit||-1!==e.timeoutHandle),r=e.callbackNode,0===n||e===t&&(2===ic||9===ic)||null!==e.cancelPendingCommit)return null!==r&&null!==r&&Z(r),e.callbackNode=null,e.callbackPriority=0;if(!(3&n)||ke(e,n)){if((t=n&-n)===e.callbackPriority)return t;switch(null!==r&&Z(r),Te(n)){case 2:case 8:n=ae;break;case 32:default:n=oe;break;case 268435456:n=le}return r=Lu.bind(null,e),n=X(n,r),e.callbackPriority=t,e.callbackNode=n,t}return null!==r&&null!==r&&Z(r),e.callbackPriority=2,e.callbackNode=null,2}function Lu(e,t){if(0!==Ec&&5!==Ec)return e.callbackNode=null,e.callbackPriority=0,null;var n=e.callbackNode;if(lu()&&e.callbackNode!==n)return null;var r=oc;return 0===(r=ve(e,e===rc?r:0,null!==e.cancelPendingCommit||-1!==e.timeoutHandle))?null:(Dc(e,r,t),Cu(e,te()),null!=e.callbackNode&&e.callbackNode===n?Lu.bind(null,e):null)}function Au(e,t){if(lu())return null;Dc(e,t,!0)}function Tu(){return 0===wu&&(wu=Se()),wu}function ju(e){return null==e||"symbol"==typeof e||"boolean"==typeof e?null:"function"==typeof e?e:Tt(""+e)}function Mu(e,t){var n=t.ownerDocument.createElement("input");return n.name=t.name,n.value=t.value,e.id&&n.setAttribute("form",e.id),t.parentNode.insertBefore(n,t),e=new FormData(e),n.parentNode.removeChild(n),e}for(var Pu=0;Pu<wr.length;Pu++){var Nu=wr[Pu];Sr(Nu.toLowerCase(),"on"+(Nu[0].toUpperCase()+Nu.slice(1)))}Sr(pr,"onAnimationEnd"),Sr(mr,"onAnimationIteration"),Sr(hr,"onAnimationStart"),Sr("dblclick","onDoubleClick"),Sr("focusin","onFocus"),Sr("focusout","onBlur"),Sr(gr,"onTransitionRun"),Sr(br,"onTransitionStart"),Sr(yr,"onTransitionCancel"),Sr(vr,"onTransitionEnd"),Ke("onMouseEnter",["mouseout","mouseover"]),Ke("onMouseLeave",["mouseout","mouseover"]),Ke("onPointerEnter",["pointerout","pointerover"]),Ke("onPointerLeave",["pointerout","pointerover"]),Qe("onChange","change click focusin focusout input keydown keyup selectionchange".split(" ")),Qe("onSelect","focusout contextmenu dragend focusin keydown keyup mousedown mouseup selectionchange".split(" ")),Qe("onBeforeInput",["compositionend","keypress","textInput","paste"]),Qe("onCompositionEnd","compositionend focusout keydown keypress keyup mousedown".split(" ")),Qe("onCompositionStart","compositionstart focusout keydown keypress keyup mousedown".split(" ")),Qe("onCompositionUpdate","compositionupdate focusout keydown keypress keyup mousedown".split(" "));var Ou="abort canplay canplaythrough durationchange emptied encrypted ended error loadeddata loadedmetadata loadstart pause play playing progress ratechange resize seeked seeking stalled suspend timeupdate volumechange waiting".split(" "),Ru=new Set("beforetoggle cancel close invalid load scroll scrollend toggle".split(" ").concat(Ou));function Bu(e,t){t=!!(4&t);for(var n=0;n<e.length;n++){var r=e[n],a=r.event;r=r.listeners;e:{var o=void 0;if(t)for(var i=r.length-1;0<=i;i--){var l=r[i],s=l.instance,c=l.currentTarget;if(l=l.listener,s!==o&&a.isPropagationStopped())break e;o=l,a.currentTarget=c;try{o(a)}catch(u){bl(u)}a.currentTarget=null,o=s}else for(i=0;i<r.length;i++){if(s=(l=r[i]).instance,c=l.currentTarget,l=l.listener,s!==o&&a.isPropagationStopped())break e;o=l,a.currentTarget=c;try{o(a)}catch(u){bl(u)}a.currentTarget=null,o=s}}}}function Du(e,t){var n=t[Re];void 0===n&&(n=t[Re]=new Set);var r=e+"__bubble";n.has(r)||($u(t,e,2,!1),n.add(r))}function Fu(e,t,n){var r=0;t&&(r|=4),$u(n,e,r,t)}var Iu="_reactListening"+Math.random().toString(36).slice(2);function zu(e){if(!e[Iu]){e[Iu]=!0,Ve.forEach(function(t){"selectionchange"!==t&&(Ru.has(t)||Fu(t,!1,e),Fu(t,!0,e))});var t=9===e.nodeType?e:e.ownerDocument;null===t||t[Iu]||(t[Iu]=!0,Fu("selectionchange",!1,t))}}function $u(e,t,n,r){switch(uf(t)){case 2:var a=rf;break;case 8:a=af;break;default:a=of}n=a.bind(null,t,n,e),a=void 0,!It||"touchstart"!==t&&"touchmove"!==t&&"wheel"!==t||(a=!0),r?void 0!==a?e.addEventListener(t,n,{capture:!0,passive:a}):e.addEventListener(t,n,!0):void 0!==a?e.addEventListener(t,n,{passive:a}):e.addEventListener(t,n,!1)}function Uu(e,t,n,r,a){var o=r;if(!(1&t||2&t||null===r))e:for(;;){if(null===r)return;var i=r.tag;if(3===i||4===i){var l=r.stateNode.containerInfo;if(l===a)break;if(4===i)for(i=r.return;null!==i;){var c=i.tag;if((3===c||4===c)&&i.stateNode.containerInfo===a)return;i=i.return}for(;null!==l;){if(null===(i=$e(l)))return;if(5===(c=i.tag)||6===c||26===c||27===c){r=o=i;continue e}l=l.parentNode}}r=r.return}Bt(function(){var r=o,a=Mt(n),i=[];e:{var l=kr.get(e);if(void 0!==l){var c=Jt,u=e;switch(e){case"keypress":if(0===Gt(n))break e;case"keydown":case"keyup":c=hn;break;case"focusin":u="focus",c=on;break;case"focusout":u="blur",c=on;break;case"beforeblur":case"afterblur":c=on;break;case"click":if(2===n.button)break e;case"auxclick":case"dblclick":case"mousedown":case"mousemove":case"mouseup":case"mouseout":case"mouseover":case"contextmenu":c=rn;break;case"drag":case"dragend":case"dragenter":case"dragexit":case"dragleave":case"dragover":case"dragstart":case"drop":c=an;break;case"touchcancel":case"touchend":case"touchmove":case"touchstart":c=bn;break;case pr:case mr:case hr:c=ln;break;case vr:c=yn;break;case"scroll":case"scrollend":c=tn;break;case"wheel":c=vn;break;case"copy":case"cut":case"paste":c=sn;break;case"gotpointercapture":case"lostpointercapture":case"pointercancel":case"pointerdown":case"pointermove":case"pointerout":case"pointerover":case"pointerup":c=gn;break;case"toggle":case"beforetoggle":c=kn}var d=!!(4&t),f=!d&&("scroll"===e||"scrollend"===e),p=d?null!==l?l+"Capture":null:l;d=[];for(var m,h=r;null!==h;){var g=h;if(m=g.stateNode,5!==(g=g.tag)&&26!==g&&27!==g||null===m||null===p||null!=(g=Dt(h,p))&&d.push(qu(h,g,m)),f)break;h=h.return}0<d.length&&(l=new c(l,u,null,n,a),i.push({event:l,listeners:d}))}}if(!(7&t)){if(c="mouseout"===e||"pointerout"===e,(!(l="mouseover"===e||"pointerover"===e)||n===jt||!(u=n.relatedTarget||n.fromElement)||!$e(u)&&!u[Oe])&&(c||l)&&(l=a.window===a?a:(l=a.ownerDocument)?l.defaultView||l.parentWindow:window,c?(c=r,null!==(u=(u=n.relatedTarget||n.toElement)?$e(u):null)&&(f=s(u),d=u.tag,u!==f||5!==d&&27!==d&&6!==d)&&(u=null)):(c=null,u=r),c!==u)){if(d=rn,g="onMouseLeave",p="onMouseEnter",h="mouse","pointerout"!==e&&"pointerover"!==e||(d=gn,g="onPointerLeave",p="onPointerEnter",h="pointer"),f=null==c?l:qe(c),m=null==u?l:qe(u),(l=new d(g,h+"leave",c,n,a)).target=f,l.relatedTarget=m,g=null,$e(a)===r&&((d=new d(p,h+"enter",u,n,a)).target=m,d.relatedTarget=f,g=d),f=g,c&&u)e:{for(p=u,h=0,m=d=c;m;m=Gu(m))h++;for(m=0,g=p;g;g=Gu(g))m++;for(;0<h-m;)d=Gu(d),h--;for(;0<m-h;)p=Gu(p),m--;for(;h--;){if(d===p||null!==p&&d===p.alternate)break e;d=Gu(d),p=Gu(p)}d=null}else d=null;null!==c&&Vu(i,l,c,d,!1),null!==u&&null!==f&&Vu(i,f,u,d,!0)}if("select"===(c=(l=r?qe(r):window).nodeName&&l.nodeName.toLowerCase())||"input"===c&&"file"===l.type)var b=Fn;else if(Pn(l))if(In)b=Qn;else{b=Vn;var y=Gn}else!(c=l.nodeName)||"input"!==c.toLowerCase()||"checkbox"!==l.type&&"radio"!==l.type?r&&Ct(r.elementType)&&(b=Fn):b=Wn;switch(b&&(b=b(e,r))?Nn(i,b,n,a):(y&&y(e,l,r),"focusout"===e&&r&&"number"===l.type&&null!=r.memoizedProps.value&&yt(l,"number",l.value)),y=r?qe(r):window,e){case"focusin":(Pn(y)||"true"===y.contentEditable)&&(rr=y,ar=r,or=null);break;case"focusout":or=ar=rr=null;break;case"mousedown":ir=!0;break;case"contextmenu":case"mouseup":case"dragend":ir=!1,lr(i,n,a);break;case"selectionchange":if(nr)break;case"keydown":case"keyup":lr(i,n,a)}var v;if(Sn)e:{switch(e){case"compositionstart":var k="onCompositionStart";break e;case"compositionend":k="onCompositionEnd";break e;case"compositionupdate":k="onCompositionUpdate";break e}k=void 0}else jn?An(e,n)&&(k="onCompositionEnd"):"keydown"===e&&229===n.keyCode&&(k="onCompositionStart");k&&(En&&"ko"!==n.locale&&(jn||"onCompositionStart"!==k?"onCompositionEnd"===k&&jn&&(v=Ht()):(Ut="value"in($t=a)?$t.value:$t.textContent,jn=!0)),0<(y=Hu(r,k)).length&&(k=new cn(k,e,null,n,a),i.push({event:k,listeners:y}),v?k.data=v:null!==(v=Tn(n))&&(k.data=v))),(v=_n?function(e,t){switch(e){case"compositionend":return Tn(t);case"keypress":return 32!==t.which?null:(Ln=!0,Cn);case"textInput":return(e=t.data)===Cn&&Ln?null:e;default:return null}}(e,n):function(e,t){if(jn)return"compositionend"===e||!Sn&&An(e,t)?(e=Ht(),qt=Ut=$t=null,jn=!1,e):null;switch(e){case"paste":default:return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1<t.char.length)return t.char;if(t.which)return String.fromCharCode(t.which)}return null;case"compositionend":return En&&"ko"!==t.locale?null:t.data}}(e,n))&&(0<(k=Hu(r,"onBeforeInput")).length&&(y=new cn("onBeforeInput","beforeinput",null,n,a),i.push({event:y,listeners:k}),y.data=v)),function(e,t,n,r,a){if("submit"===t&&n&&n.stateNode===a){var o=ju((a[Ne]||null).action),i=r.submitter;i&&null!==(t=(t=i[Ne]||null)?ju(t.formAction):i.getAttribute("formAction"))&&(o=t,i=null);var l=new Jt("action","action",null,r,a);e.push({event:l,listeners:[{instance:null,listener:function(){if(r.defaultPrevented){if(0!==wu){var e=i?Mu(a,i):new FormData(a);Mi(n,{pending:!0,data:e,method:a.method,action:o},null,e)}}else"function"==typeof o&&(l.preventDefault(),e=i?Mu(a,i):new FormData(a),Mi(n,{pending:!0,data:e,method:a.method,action:o},o,e))},currentTarget:a}]})}}(i,e,r,n,a)}Bu(i,t)})}function qu(e,t,n){return{instance:e,listener:t,currentTarget:n}}function Hu(e,t){for(var n=t+"Capture",r=[];null!==e;){var a=e,o=a.stateNode;if(5!==(a=a.tag)&&26!==a&&27!==a||null===o||(null!=(a=Dt(e,n))&&r.unshift(qu(e,a,o)),null!=(a=Dt(e,t))&&r.push(qu(e,a,o))),3===e.tag)return r;e=e.return}return[]}function Gu(e){if(null===e)return null;do{e=e.return}while(e&&5!==e.tag&&27!==e.tag);return e||null}function Vu(e,t,n,r,a){for(var o=t._reactName,i=[];null!==n&&n!==r;){var l=n,s=l.alternate,c=l.stateNode;if(l=l.tag,null!==s&&s===r)break;5!==l&&26!==l&&27!==l||null===c||(s=c,a?null!=(c=Dt(n,o))&&i.unshift(qu(n,c,s)):a||null!=(c=Dt(n,o))&&i.push(qu(n,c,s))),n=n.return}0!==i.length&&e.push({event:t,listeners:i})}var Wu=/\r\n?/g,Qu=/\u0000|\uFFFD/g;function Ku(e){return("string"==typeof e?e:""+e).replace(Wu,"\n").replace(Qu,"")}function Yu(e,t){return t=Ku(t),Ku(e)===t}function Xu(){}function Zu(e,t,n,r,a,o){switch(n){case"children":"string"==typeof r?"body"===t||"textarea"===t&&""===r||St(e,r):("number"==typeof r||"bigint"==typeof r)&&"body"!==t&&St(e,""+r);break;case"className":nt(e,"class",r);break;case"tabIndex":nt(e,"tabindex",r);break;case"dir":case"role":case"viewBox":case"width":case"height":nt(e,n,r);break;case"style":Et(e,r,o);break;case"data":if("object"!==t){nt(e,"data",r);break}case"src":case"href":if(""===r&&("a"!==t||"href"!==n)){e.removeAttribute(n);break}if(null==r||"function"==typeof r||"symbol"==typeof r||"boolean"==typeof r){e.removeAttribute(n);break}r=Tt(""+r),e.setAttribute(n,r);break;case"action":case"formAction":if("function"==typeof r){e.setAttribute(n,"javascript:throw new Error('A React form was unexpectedly submitted. If you called form.submit() manually, consider using form.requestSubmit() instead. If you\\'re trying to use event.stopPropagation() in a submit event handler, consider also calling event.preventDefault().')");break}if("function"==typeof o&&("formAction"===n?("input"!==t&&Zu(e,t,"name",a.name,a,null),Zu(e,t,"formEncType",a.formEncType,a,null),Zu(e,t,"formMethod",a.formMethod,a,null),Zu(e,t,"formTarget",a.formTarget,a,null)):(Zu(e,t,"encType",a.encType,a,null),Zu(e,t,"method",a.method,a,null),Zu(e,t,"target",a.target,a,null))),null==r||"symbol"==typeof r||"boolean"==typeof r){e.removeAttribute(n);break}r=Tt(""+r),e.setAttribute(n,r);break;case"onClick":null!=r&&(e.onclick=Xu);break;case"onScroll":null!=r&&Du("scroll",e);break;case"onScrollEnd":null!=r&&Du("scrollend",e);break;case"dangerouslySetInnerHTML":if(null!=r){if("object"!=typeof r||!("__html"in r))throw Error(i(61));if(null!=(n=r.__html)){if(null!=a.children)throw Error(i(60));e.innerHTML=n}}break;case"multiple":e.multiple=r&&"function"!=typeof r&&"symbol"!=typeof r;break;case"muted":e.muted=r&&"function"!=typeof r&&"symbol"!=typeof r;break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"defaultValue":case"defaultChecked":case"innerHTML":case"ref":case"autoFocus":break;case"xlinkHref":if(null==r||"function"==typeof r||"boolean"==typeof r||"symbol"==typeof r){e.removeAttribute("xlink:href");break}n=Tt(""+r),e.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",n);break;case"contentEditable":case"spellCheck":case"draggable":case"value":case"autoReverse":case"externalResourcesRequired":case"focusable":case"preserveAlpha":null!=r&&"function"!=typeof r&&"symbol"!=typeof r?e.setAttribute(n,""+r):e.removeAttribute(n);break;case"inert":case"allowFullScreen":case"async":case"autoPlay":case"controls":case"default":case"defer":case"disabled":case"disablePictureInPicture":case"disableRemotePlayback":case"formNoValidate":case"hidden":case"loop":case"noModule":case"noValidate":case"open":case"playsInline":case"readOnly":case"required":case"reversed":case"scoped":case"seamless":case"itemScope":r&&"function"!=typeof r&&"symbol"!=typeof r?e.setAttribute(n,""):e.removeAttribute(n);break;case"capture":case"download":!0===r?e.setAttribute(n,""):!1!==r&&null!=r&&"function"!=typeof r&&"symbol"!=typeof r?e.setAttribute(n,r):e.removeAttribute(n);break;case"cols":case"rows":case"size":case"span":null!=r&&"function"!=typeof r&&"symbol"!=typeof r&&!isNaN(r)&&1<=r?e.setAttribute(n,r):e.removeAttribute(n);break;case"rowSpan":case"start":null==r||"function"==typeof r||"symbol"==typeof r||isNaN(r)?e.removeAttribute(n):e.setAttribute(n,r);break;case"popover":Du("beforetoggle",e),Du("toggle",e),tt(e,"popover",r);break;case"xlinkActuate":rt(e,"http://www.w3.org/1999/xlink","xlink:actuate",r);break;case"xlinkArcrole":rt(e,"http://www.w3.org/1999/xlink","xlink:arcrole",r);break;case"xlinkRole":rt(e,"http://www.w3.org/1999/xlink","xlink:role",r);break;case"xlinkShow":rt(e,"http://www.w3.org/1999/xlink","xlink:show",r);break;case"xlinkTitle":rt(e,"http://www.w3.org/1999/xlink","xlink:title",r);break;case"xlinkType":rt(e,"http://www.w3.org/1999/xlink","xlink:type",r);break;case"xmlBase":rt(e,"http://www.w3.org/XML/1998/namespace","xml:base",r);break;case"xmlLang":rt(e,"http://www.w3.org/XML/1998/namespace","xml:lang",r);break;case"xmlSpace":rt(e,"http://www.w3.org/XML/1998/namespace","xml:space",r);break;case"is":tt(e,"is",r);break;case"innerText":case"textContent":break;default:(!(2<n.length)||"o"!==n[0]&&"O"!==n[0]||"n"!==n[1]&&"N"!==n[1])&&tt(e,n=Lt.get(n)||n,r)}}function Ju(e,t,n,r,a,o){switch(n){case"style":Et(e,r,o);break;case"dangerouslySetInnerHTML":if(null!=r){if("object"!=typeof r||!("__html"in r))throw Error(i(61));if(null!=(n=r.__html)){if(null!=a.children)throw Error(i(60));e.innerHTML=n}}break;case"children":"string"==typeof r?St(e,r):("number"==typeof r||"bigint"==typeof r)&&St(e,""+r);break;case"onScroll":null!=r&&Du("scroll",e);break;case"onScrollEnd":null!=r&&Du("scrollend",e);break;case"onClick":null!=r&&(e.onclick=Xu);break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"innerHTML":case"ref":case"innerText":case"textContent":break;default:We.hasOwnProperty(n)||("o"!==n[0]||"n"!==n[1]||(a=n.endsWith("Capture"),t=n.slice(2,a?n.length-7:void 0),"function"==typeof(o=null!=(o=e[Ne]||null)?o[n]:null)&&e.removeEventListener(t,o,a),"function"!=typeof r)?n in e?e[n]=r:!0===r?e.setAttribute(n,""):tt(e,n,r):("function"!=typeof o&&null!==o&&(n in e?e[n]=null:e.hasAttribute(n)&&e.removeAttribute(n)),e.addEventListener(t,r,a)))}}function ed(e,t,n){switch(t){case"div":case"span":case"svg":case"path":case"a":case"g":case"p":case"li":break;case"img":Du("error",e),Du("load",e);var r,a=!1,o=!1;for(r in n)if(n.hasOwnProperty(r)){var l=n[r];if(null!=l)switch(r){case"src":a=!0;break;case"srcSet":o=!0;break;case"children":case"dangerouslySetInnerHTML":throw Error(i(137,t));default:Zu(e,t,r,l,n,null)}}return o&&Zu(e,t,"srcSet",n.srcSet,n,null),void(a&&Zu(e,t,"src",n.src,n,null));case"input":Du("invalid",e);var s=r=l=o=null,c=null,u=null;for(a in n)if(n.hasOwnProperty(a)){var d=n[a];if(null!=d)switch(a){case"name":o=d;break;case"type":l=d;break;case"checked":c=d;break;case"defaultChecked":u=d;break;case"value":r=d;break;case"defaultValue":s=d;break;case"children":case"dangerouslySetInnerHTML":if(null!=d)throw Error(i(137,t));break;default:Zu(e,t,a,d,n,null)}}return bt(e,r,s,c,u,l,o,!1),void dt(e);case"select":for(o in Du("invalid",e),a=l=r=null,n)if(n.hasOwnProperty(o)&&null!=(s=n[o]))switch(o){case"value":r=s;break;case"defaultValue":l=s;break;case"multiple":a=s;default:Zu(e,t,o,s,n,null)}return t=r,n=l,e.multiple=!!a,void(null!=t?vt(e,!!a,t,!1):null!=n&&vt(e,!!a,n,!0));case"textarea":for(l in Du("invalid",e),r=o=a=null,n)if(n.hasOwnProperty(l)&&null!=(s=n[l]))switch(l){case"value":a=s;break;case"defaultValue":o=s;break;case"children":r=s;break;case"dangerouslySetInnerHTML":if(null!=s)throw Error(i(91));break;default:Zu(e,t,l,s,n,null)}return wt(e,a,o,r),void dt(e);case"option":for(c in n)if(n.hasOwnProperty(c)&&null!=(a=n[c]))if("selected"===c)e.selected=a&&"function"!=typeof a&&"symbol"!=typeof a;else Zu(e,t,c,a,n,null);return;case"dialog":Du("beforetoggle",e),Du("toggle",e),Du("cancel",e),Du("close",e);break;case"iframe":case"object":Du("load",e);break;case"video":case"audio":for(a=0;a<Ou.length;a++)Du(Ou[a],e);break;case"image":Du("error",e),Du("load",e);break;case"details":Du("toggle",e);break;case"embed":case"source":case"link":Du("error",e),Du("load",e);case"area":case"base":case"br":case"col":case"hr":case"keygen":case"meta":case"param":case"track":case"wbr":case"menuitem":for(u in n)if(n.hasOwnProperty(u)&&null!=(a=n[u]))switch(u){case"children":case"dangerouslySetInnerHTML":throw Error(i(137,t));default:Zu(e,t,u,a,n,null)}return;default:if(Ct(t)){for(d in n)n.hasOwnProperty(d)&&(void 0!==(a=n[d])&&Ju(e,t,d,a,n,void 0));return}}for(s in n)n.hasOwnProperty(s)&&(null!=(a=n[s])&&Zu(e,t,s,a,n,null))}var td=null,nd=null;function rd(e){return 9===e.nodeType?e:e.ownerDocument}function ad(e){switch(e){case"http://www.w3.org/2000/svg":return 1;case"http://www.w3.org/1998/Math/MathML":return 2;default:return 0}}function od(e,t){if(0===e)switch(t){case"svg":return 1;case"math":return 2;default:return 0}return 1===e&&"foreignObject"===t?0:e}function id(e,t){return"textarea"===e||"noscript"===e||"string"==typeof t.children||"number"==typeof t.children||"bigint"==typeof t.children||"object"==typeof t.dangerouslySetInnerHTML&&null!==t.dangerouslySetInnerHTML&&null!=t.dangerouslySetInnerHTML.__html}var ld=null;var sd="function"==typeof setTimeout?setTimeout:void 0,cd="function"==typeof clearTimeout?clearTimeout:void 0,ud="function"==typeof Promise?Promise:void 0,dd="function"==typeof queueMicrotask?queueMicrotask:void 0!==ud?function(e){return ud.resolve(null).then(e).catch(fd)}:sd;function fd(e){setTimeout(function(){throw e})}function pd(e){return"head"===e}function md(e,t){var n=t,r=0,a=0;do{var o=n.nextSibling;if(e.removeChild(n),o&&8===o.nodeType)if("/$"===(n=o.data)){if(0<r&&8>r){n=r;var i=e.ownerDocument;if(1&n&&wd(i.documentElement),2&n&&wd(i.body),4&n)for(wd(n=i.head),i=n.firstChild;i;){var l=i.nextSibling,s=i.nodeName;i[Ie]||"SCRIPT"===s||"STYLE"===s||"LINK"===s&&"stylesheet"===i.rel.toLowerCase()||n.removeChild(i),i=l}}if(0===a)return e.removeChild(o),void Af(t);a--}else"$"===n||"$?"===n||"$!"===n?a++:r=n.charCodeAt(0)-48;else r=0;n=o}while(n);Af(t)}function hd(e){var t=e.firstChild;for(t&&10===t.nodeType&&(t=t.nextSibling);t;){var n=t;switch(t=t.nextSibling,n.nodeName){case"HTML":case"HEAD":case"BODY":hd(n),ze(n);continue;case"SCRIPT":case"STYLE":continue;case"LINK":if("stylesheet"===n.rel.toLowerCase())continue}e.removeChild(n)}}function gd(e){return"$!"===e.data||"$?"===e.data&&"complete"===e.ownerDocument.readyState}function bd(e){for(;null!=e;e=e.nextSibling){var t=e.nodeType;if(1===t||3===t)break;if(8===t){if("$"===(t=e.data)||"$!"===t||"$?"===t||"F!"===t||"F"===t)break;if("/$"===t)return null}}return e}var yd=null;function vd(e){e=e.previousSibling;for(var t=0;e;){if(8===e.nodeType){var n=e.data;if("$"===n||"$!"===n||"$?"===n){if(0===t)return e;t--}else"/$"===n&&t++}e=e.previousSibling}return null}function kd(e,t,n){switch(t=rd(n),e){case"html":if(!(e=t.documentElement))throw Error(i(452));return e;case"head":if(!(e=t.head))throw Error(i(453));return e;case"body":if(!(e=t.body))throw Error(i(454));return e;default:throw Error(i(451))}}function wd(e){for(var t=e.attributes;t.length;)e.removeAttributeNode(t[0]);ze(e)}var Sd=new Map,xd=new Set;function _d(e){return"function"==typeof e.getRootNode?e.getRootNode():9===e.nodeType?e:e.ownerDocument}var Ed=R.d;R.d={f:function(){var e=Ed.f(),t=$c();return e||t},r:function(e){var t=Ue(e);null!==t&&5===t.tag&&"form"===t.type?Ni(t):Ed.r(e)},D:function(e){Ed.D(e),Ld("dns-prefetch",e,null)},C:function(e,t){Ed.C(e,t),Ld("preconnect",e,t)},L:function(e,t,n){Ed.L(e,t,n);var r=Cd;if(r&&e&&t){var a='link[rel="preload"][as="'+ht(t)+'"]';"image"===t&&n&&n.imageSrcSet?(a+='[imagesrcset="'+ht(n.imageSrcSet)+'"]',"string"==typeof n.imageSizes&&(a+='[imagesizes="'+ht(n.imageSizes)+'"]')):a+='[href="'+ht(e)+'"]';var o=a;switch(t){case"style":o=Td(e);break;case"script":o=Pd(e)}Sd.has(o)||(e=f({rel:"preload",href:"image"===t&&n&&n.imageSrcSet?void 0:e,as:t},n),Sd.set(o,e),null!==r.querySelector(a)||"style"===t&&r.querySelector(jd(o))||"script"===t&&r.querySelector(Nd(o))||(ed(t=r.createElement("link"),"link",e),Ge(t),r.head.appendChild(t)))}},m:function(e,t){Ed.m(e,t);var n=Cd;if(n&&e){var r=t&&"string"==typeof t.as?t.as:"script",a='link[rel="modulepreload"][as="'+ht(r)+'"][href="'+ht(e)+'"]',o=a;switch(r){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":o=Pd(e)}if(!Sd.has(o)&&(e=f({rel:"modulepreload",href:e},t),Sd.set(o,e),null===n.querySelector(a))){switch(r){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(n.querySelector(Nd(o)))return}ed(r=n.createElement("link"),"link",e),Ge(r),n.head.appendChild(r)}}},X:function(e,t){Ed.X(e,t);var n=Cd;if(n&&e){var r=He(n).hoistableScripts,a=Pd(e),o=r.get(a);o||((o=n.querySelector(Nd(a)))||(e=f({src:e,async:!0},t),(t=Sd.get(a))&&Dd(e,t),Ge(o=n.createElement("script")),ed(o,"link",e),n.head.appendChild(o)),o={type:"script",instance:o,count:1,state:null},r.set(a,o))}},S:function(e,t,n){Ed.S(e,t,n);var r=Cd;if(r&&e){var a=He(r).hoistableStyles,o=Td(e);t=t||"default";var i=a.get(o);if(!i){var l={loading:0,preload:null};if(i=r.querySelector(jd(o)))l.loading=5;else{e=f({rel:"stylesheet",href:e,"data-precedence":t},n),(n=Sd.get(o))&&Bd(e,n);var s=i=r.createElement("link");Ge(s),ed(s,"link",e),s._p=new Promise(function(e,t){s.onload=e,s.onerror=t}),s.addEventListener("load",function(){l.loading|=1}),s.addEventListener("error",function(){l.loading|=2}),l.loading|=4,Rd(i,t,r)}i={type:"stylesheet",instance:i,count:1,state:l},a.set(o,i)}}},M:function(e,t){Ed.M(e,t);var n=Cd;if(n&&e){var r=He(n).hoistableScripts,a=Pd(e),o=r.get(a);o||((o=n.querySelector(Nd(a)))||(e=f({src:e,async:!0,type:"module"},t),(t=Sd.get(a))&&Dd(e,t),Ge(o=n.createElement("script")),ed(o,"link",e),n.head.appendChild(o)),o={type:"script",instance:o,count:1,state:null},r.set(a,o))}}};var Cd="undefined"==typeof document?null:document;function Ld(e,t,n){var r=Cd;if(r&&"string"==typeof t&&t){var a=ht(t);a='link[rel="'+e+'"][href="'+a+'"]',"string"==typeof n&&(a+='[crossorigin="'+n+'"]'),xd.has(a)||(xd.add(a),e={rel:e,crossOrigin:n,href:t},null===r.querySelector(a)&&(ed(t=r.createElement("link"),"link",e),Ge(t),r.head.appendChild(t)))}}function Ad(e,t,n,r){var a,o,l,s,c=(c=H.current)?_d(c):null;if(!c)throw Error(i(446));switch(e){case"meta":case"title":return null;case"style":return"string"==typeof n.precedence&&"string"==typeof n.href?(t=Td(n.href),(r=(n=He(c).hoistableStyles).get(t))||(r={type:"style",instance:null,count:0,state:null},n.set(t,r)),r):{type:"void",instance:null,count:0,state:null};case"link":if("stylesheet"===n.rel&&"string"==typeof n.href&&"string"==typeof n.precedence){e=Td(n.href);var u=He(c).hoistableStyles,d=u.get(e);if(d||(c=c.ownerDocument||c,d={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},u.set(e,d),(u=c.querySelector(jd(e)))&&!u._p&&(d.instance=u,d.state.loading=5),Sd.has(e)||(n={rel:"preload",as:"style",href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},Sd.set(e,n),u||(a=c,o=e,l=n,s=d.state,a.querySelector('link[rel="preload"][as="style"]['+o+"]")?s.loading=1:(o=a.createElement("link"),s.preload=o,o.addEventListener("load",function(){return s.loading|=1}),o.addEventListener("error",function(){return s.loading|=2}),ed(o,"link",l),Ge(o),a.head.appendChild(o))))),t&&null===r)throw Error(i(528,""));return d}if(t&&null!==r)throw Error(i(529,""));return null;case"script":return t=n.async,"string"==typeof(n=n.src)&&t&&"function"!=typeof t&&"symbol"!=typeof t?(t=Pd(n),(r=(n=He(c).hoistableScripts).get(t))||(r={type:"script",instance:null,count:0,state:null},n.set(t,r)),r):{type:"void",instance:null,count:0,state:null};default:throw Error(i(444,e))}}function Td(e){return'href="'+ht(e)+'"'}function jd(e){return'link[rel="stylesheet"]['+e+"]"}function Md(e){return f({},e,{"data-precedence":e.precedence,precedence:null})}function Pd(e){return'[src="'+ht(e)+'"]'}function Nd(e){return"script[async]"+e}function Od(e,t,n){if(t.count++,null===t.instance)switch(t.type){case"style":var r=e.querySelector('style[data-href~="'+ht(n.href)+'"]');if(r)return t.instance=r,Ge(r),r;var a=f({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return Ge(r=(e.ownerDocument||e).createElement("style")),ed(r,"style",a),Rd(r,n.precedence,e),t.instance=r;case"stylesheet":a=Td(n.href);var o=e.querySelector(jd(a));if(o)return t.state.loading|=4,t.instance=o,Ge(o),o;r=Md(n),(a=Sd.get(a))&&Bd(r,a),Ge(o=(e.ownerDocument||e).createElement("link"));var l=o;return l._p=new Promise(function(e,t){l.onload=e,l.onerror=t}),ed(o,"link",r),t.state.loading|=4,Rd(o,n.precedence,e),t.instance=o;case"script":return o=Pd(n.src),(a=e.querySelector(Nd(o)))?(t.instance=a,Ge(a),a):(r=n,(a=Sd.get(o))&&Dd(r=f({},n),a),Ge(a=(e=e.ownerDocument||e).createElement("script")),ed(a,"link",r),e.head.appendChild(a),t.instance=a);case"void":return null;default:throw Error(i(443,t.type))}else"stylesheet"===t.type&&!(4&t.state.loading)&&(r=t.instance,t.state.loading|=4,Rd(r,n.precedence,e));return t.instance}function Rd(e,t,n){for(var r=n.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),a=r.length?r[r.length-1]:null,o=a,i=0;i<r.length;i++){var l=r[i];if(l.dataset.precedence===t)o=l;else if(o!==a)break}o?o.parentNode.insertBefore(e,o.nextSibling):(t=9===n.nodeType?n.head:n).insertBefore(e,t.firstChild)}function Bd(e,t){null==e.crossOrigin&&(e.crossOrigin=t.crossOrigin),null==e.referrerPolicy&&(e.referrerPolicy=t.referrerPolicy),null==e.title&&(e.title=t.title)}function Dd(e,t){null==e.crossOrigin&&(e.crossOrigin=t.crossOrigin),null==e.referrerPolicy&&(e.referrerPolicy=t.referrerPolicy),null==e.integrity&&(e.integrity=t.integrity)}var Fd=null;function Id(e,t,n){if(null===Fd){var r=new Map,a=Fd=new Map;a.set(n,r)}else(r=(a=Fd).get(n))||(r=new Map,a.set(n,r));if(r.has(e))return r;for(r.set(e,null),n=n.getElementsByTagName(e),a=0;a<n.length;a++){var o=n[a];if(!(o[Ie]||o[Pe]||"link"===e&&"stylesheet"===o.getAttribute("rel"))&&"http://www.w3.org/2000/svg"!==o.namespaceURI){var i=o.getAttribute(t)||"";i=e+i;var l=r.get(i);l?l.push(o):r.set(i,[o])}}return r}function zd(e,t,n){(e=e.ownerDocument||e).head.insertBefore(n,"title"===t?e.querySelector("head > title"):null)}function $d(e){return!!("stylesheet"!==e.type||3&e.state.loading)}var Ud=null;function qd(){}function Hd(){if(this.count--,0===this.count)if(this.stylesheets)Vd(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}var Gd=null;function Vd(e,t){e.stylesheets=null,null!==e.unsuspend&&(e.count++,Gd=new Map,t.forEach(Wd,e),Gd=null,Hd.call(e))}function Wd(e,t){if(!(4&t.state.loading)){var n=Gd.get(e);if(n)var r=n.get(null);else{n=new Map,Gd.set(e,n);for(var a=e.querySelectorAll("link[data-precedence],style[data-precedence]"),o=0;o<a.length;o++){var i=a[o];"LINK"!==i.nodeName&&"not all"===i.getAttribute("media")||(n.set(i.dataset.precedence,i),r=i)}r&&n.set(null,r)}i=(a=t.instance).getAttribute("data-precedence"),(o=n.get(i)||r)===r&&n.set(null,a),n.set(i,a),this.count++,r=Hd.bind(this),a.addEventListener("load",r),a.addEventListener("error",r),o?o.parentNode.insertBefore(a,o.nextSibling):(e=9===e.nodeType?e.head:e).insertBefore(a,e.firstChild),t.state.loading|=4}}var Qd={$$typeof:w,Provider:null,Consumer:null,_currentValue:B,_currentValue2:B,_threadCount:0};function Kd(e,t,n,r,a,o,i,l){this.tag=1,this.containerInfo=e,this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.next=this.pendingContext=this.context=this.cancelPendingCommit=null,this.callbackPriority=0,this.expirationTimes=_e(-1),this.entangledLanes=this.shellSuspendCounter=this.errorRecoveryDisabledLanes=this.expiredLanes=this.warmLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=_e(0),this.hiddenUpdates=_e(null),this.identifierPrefix=r,this.onUncaughtError=a,this.onCaughtError=o,this.onRecoverableError=i,this.pooledCache=null,this.pooledCacheLanes=0,this.formState=l,this.incompleteTransitions=new Map}function Yd(e,t,n,r,a,o,i,l,s,c,u,d){return e=new Kd(e,t,n,i,l,s,c,d),t=1,!0===o&&(t|=24),o=Br(3,null,null,t),e.current=o,o.stateNode=e,(t=Na()).refCount++,e.pooledCache=t,t.refCount++,o.memoizedState={element:r,isDehydrated:n,cache:t},no(o),e}function Xd(e){return e?e=Or:Or}function Zd(e,t,n,r,a,o){a=Xd(a),null===r.context?r.context=a:r.pendingContext=a,(r=ao(t)).payload={element:n},null!==(o=void 0===o?null:o)&&(r.callback=o),null!==(n=oo(e,r,t))&&(Bc(n,0,t),io(n,e,t))}function Jd(e,t){if(null!==(e=e.memoizedState)&&null!==e.dehydrated){var n=e.retryLane;e.retryLane=0!==n&&n<t?n:t}}function ef(e,t){Jd(e,t),(e=e.alternate)&&Jd(e,t)}function tf(e){if(13===e.tag){var t=Mr(e,67108864);null!==t&&Bc(t,0,67108864),ef(e,67108864)}}var nf=!0;function rf(e,t,n,r){var a=O.T;O.T=null;var o=R.p;try{R.p=2,of(e,t,n,r)}finally{R.p=o,O.T=a}}function af(e,t,n,r){var a=O.T;O.T=null;var o=R.p;try{R.p=8,of(e,t,n,r)}finally{R.p=o,O.T=a}}function of(e,t,n,r){if(nf){var a=lf(r);if(null===a)Uu(e,t,r,sf,n),vf(e,r);else if(function(e,t,n,r,a){switch(t){case"focusin":return ff=kf(ff,e,t,n,r,a),!0;case"dragenter":return pf=kf(pf,e,t,n,r,a),!0;case"mouseover":return mf=kf(mf,e,t,n,r,a),!0;case"pointerover":var o=a.pointerId;return hf.set(o,kf(hf.get(o)||null,e,t,n,r,a)),!0;case"gotpointercapture":return o=a.pointerId,gf.set(o,kf(gf.get(o)||null,e,t,n,r,a)),!0}return!1}(a,e,t,n,r))r.stopPropagation();else if(vf(e,r),4&t&&-1<yf.indexOf(e)){for(;null!==a;){var o=Ue(a);if(null!==o)switch(o.tag){case 3:if((o=o.stateNode).current.memoizedState.isDehydrated){var i=ye(o.pendingLanes);if(0!==i){var l=o;for(l.pendingLanes|=2,l.entangledLanes|=2;i;){var s=1<<31-pe(i);l.entanglements[1]|=s,i&=~s}Su(o),!(6&nc)&&(Sc=te()+500,xu(0,!1))}}break;case 13:null!==(l=Mr(o,2))&&Bc(l,0,2),$c(),ef(o,2)}if(null===(o=lf(r))&&Uu(e,t,r,sf,n),o===a)break;a=o}null!==a&&r.stopPropagation()}else Uu(e,t,r,null,n)}}function lf(e){return cf(e=Mt(e))}var sf=null;function cf(e){if(sf=null,null!==(e=$e(e))){var t=s(e);if(null===t)e=null;else{var n=t.tag;if(13===n){if(null!==(e=c(t)))return e;e=null}else if(3===n){if(t.stateNode.current.memoizedState.isDehydrated)return 3===t.tag?t.stateNode.containerInfo:null;e=null}else t!==e&&(e=null)}}return sf=e,null}function uf(e){switch(e){case"beforetoggle":case"cancel":case"click":case"close":case"contextmenu":case"copy":case"cut":case"auxclick":case"dblclick":case"dragend":case"dragstart":case"drop":case"focusin":case"focusout":case"input":case"invalid":case"keydown":case"keypress":case"keyup":case"mousedown":case"mouseup":case"paste":case"pause":case"play":case"pointercancel":case"pointerdown":case"pointerup":case"ratechange":case"reset":case"resize":case"seeked":case"submit":case"toggle":case"touchcancel":case"touchend":case"touchstart":case"volumechange":case"change":case"selectionchange":case"textInput":case"compositionstart":case"compositionend":case"compositionupdate":case"beforeblur":case"afterblur":case"beforeinput":case"blur":case"fullscreenchange":case"focus":case"hashchange":case"popstate":case"select":case"selectstart":return 2;case"drag":case"dragenter":case"dragexit":case"dragleave":case"dragover":case"mousemove":case"mouseout":case"mouseover":case"pointermove":case"pointerout":case"pointerover":case"scroll":case"touchmove":case"wheel":case"mouseenter":case"mouseleave":case"pointerenter":case"pointerleave":return 8;case"message":switch(ne()){case re:return 2;case ae:return 8;case oe:case ie:return 32;case le:return 268435456;default:return 32}default:return 32}}var df=!1,ff=null,pf=null,mf=null,hf=new Map,gf=new Map,bf=[],yf="mousedown mouseup touchcancel touchend touchstart auxclick dblclick pointercancel pointerdown pointerup dragend dragstart drop compositionend compositionstart keydown keypress keyup input textInput copy cut paste click change contextmenu reset".split(" ");function vf(e,t){switch(e){case"focusin":case"focusout":ff=null;break;case"dragenter":case"dragleave":pf=null;break;case"mouseover":case"mouseout":mf=null;break;case"pointerover":case"pointerout":hf.delete(t.pointerId);break;case"gotpointercapture":case"lostpointercapture":gf.delete(t.pointerId)}}function kf(e,t,n,r,a,o){return null===e||e.nativeEvent!==o?(e={blockedOn:t,domEventName:n,eventSystemFlags:r,nativeEvent:o,targetContainers:[a]},null!==t&&(null!==(t=Ue(t))&&tf(t)),e):(e.eventSystemFlags|=r,t=e.targetContainers,null!==a&&-1===t.indexOf(a)&&t.push(a),e)}function wf(e){var t=$e(e.target);if(null!==t){var n=s(t);if(null!==n)if(13===(t=n.tag)){if(null!==(t=c(n)))return e.blockedOn=t,void function(e,t){var n=R.p;try{return R.p=e,t()}finally{R.p=n}}(e.priority,function(){if(13===n.tag){var e=Oc();e=Ae(e);var t=Mr(n,e);null!==t&&Bc(t,0,e),ef(n,e)}})}else if(3===t&&n.stateNode.current.memoizedState.isDehydrated)return void(e.blockedOn=3===n.tag?n.stateNode.containerInfo:null)}e.blockedOn=null}function Sf(e){if(null!==e.blockedOn)return!1;for(var t=e.targetContainers;0<t.length;){var n=lf(e.nativeEvent);if(null!==n)return null!==(t=Ue(n))&&tf(t),e.blockedOn=n,!1;var r=new(n=e.nativeEvent).constructor(n.type,n);jt=r,n.target.dispatchEvent(r),jt=null,t.shift()}return!0}function xf(e,t,n){Sf(e)&&n.delete(t)}function _f(){df=!1,null!==ff&&Sf(ff)&&(ff=null),null!==pf&&Sf(pf)&&(pf=null),null!==mf&&Sf(mf)&&(mf=null),hf.forEach(xf),gf.forEach(xf)}function Ef(e,t){e.blockedOn===t&&(e.blockedOn=null,df||(df=!0,r.unstable_scheduleCallback(r.unstable_NormalPriority,_f)))}var Cf=null;function Lf(e){Cf!==e&&(Cf=e,r.unstable_scheduleCallback(r.unstable_NormalPriority,function(){Cf===e&&(Cf=null);for(var t=0;t<e.length;t+=3){var n=e[t],r=e[t+1],a=e[t+2];if("function"!=typeof r){if(null===cf(r||n))continue;break}var o=Ue(n);null!==o&&(e.splice(t,3),t-=3,Mi(o,{pending:!0,data:a,method:n.method,action:r},r,a))}}))}function Af(e){function t(t){return Ef(t,e)}null!==ff&&Ef(ff,e),null!==pf&&Ef(pf,e),null!==mf&&Ef(mf,e),hf.forEach(t),gf.forEach(t);for(var n=0;n<bf.length;n++){var r=bf[n];r.blockedOn===e&&(r.blockedOn=null)}for(;0<bf.length&&null===(n=bf[0]).blockedOn;)wf(n),null===n.blockedOn&&bf.shift();if(null!=(n=(e.ownerDocument||e).$$reactFormReplay))for(r=0;r<n.length;r+=3){var a=n[r],o=n[r+1],i=a[Ne]||null;if("function"==typeof o)i||Lf(n);else if(i){var l=null;if(o&&o.hasAttribute("formAction")){if(a=o,i=o[Ne]||null)l=i.formAction;else if(null!==cf(a))continue}else l=i.action;"function"==typeof l?n[r+1]=l:(n.splice(r,3),r-=3),Lf(n)}}}function Tf(e){this._internalRoot=e}function jf(e){this._internalRoot=e}jf.prototype.render=Tf.prototype.render=function(e){var t=this._internalRoot;if(null===t)throw Error(i(409));Zd(t.current,Oc(),e,t,null,null)},jf.prototype.unmount=Tf.prototype.unmount=function(){var e=this._internalRoot;if(null!==e){this._internalRoot=null;var t=e.containerInfo;Zd(e.current,2,null,e,null,null),$c(),t[Oe]=null}},jf.prototype.unstable_scheduleHydration=function(e){if(e){var t=je();e={blockedOn:null,target:e,priority:t};for(var n=0;n<bf.length&&0!==t&&t<bf[n].priority;n++);bf.splice(n,0,e),0===n&&wf(e)}};var Mf=a.version;if("19.1.1"!==Mf)throw Error(i(527,Mf,"19.1.1"));R.findDOMNode=function(e){var t=e._reactInternals;if(void 0===t){if("function"==typeof e.render)throw Error(i(188));throw e=Object.keys(e).join(","),Error(i(268,e))}return e=function(e){var t=e.alternate;if(!t){if(null===(t=s(e)))throw Error(i(188));return t!==e?null:e}for(var n=e,r=t;;){var a=n.return;if(null===a)break;var o=a.alternate;if(null===o){if(null!==(r=a.return)){n=r;continue}break}if(a.child===o.child){for(o=a.child;o;){if(o===n)return u(a),e;if(o===r)return u(a),t;o=o.sibling}throw Error(i(188))}if(n.return!==r.return)n=a,r=o;else{for(var l=!1,c=a.child;c;){if(c===n){l=!0,n=a,r=o;break}if(c===r){l=!0,r=a,n=o;break}c=c.sibling}if(!l){for(c=o.child;c;){if(c===n){l=!0,n=o,r=a;break}if(c===r){l=!0,r=o,n=a;break}c=c.sibling}if(!l)throw Error(i(189))}}if(n.alternate!==r)throw Error(i(190))}if(3!==n.tag)throw Error(i(188));return n.stateNode.current===n?e:t}(t),e=null===(e=null!==e?d(e):null)?null:e.stateNode};var Pf={bundleType:0,version:"19.1.1",rendererPackageName:"react-dom",currentDispatcherRef:O,reconcilerVersion:"19.1.1"};if("undefined"!=typeof __REACT_DEVTOOLS_GLOBAL_HOOK__){var Nf=__REACT_DEVTOOLS_GLOBAL_HOOK__;if(!Nf.isDisabled&&Nf.supportsFiber)try{ue=Nf.inject(Pf),de=Nf}catch(Rf){}}t.createRoot=function(e,t){if(!l(e))throw Error(i(299));var n=!1,r="",a=yl,o=vl,s=kl;return null!=t&&(!0===t.unstable_strictMode&&(n=!0),void 0!==t.identifierPrefix&&(r=t.identifierPrefix),void 0!==t.onUncaughtError&&(a=t.onUncaughtError),void 0!==t.onCaughtError&&(o=t.onCaughtError),void 0!==t.onRecoverableError&&(s=t.onRecoverableError),void 0!==t.unstable_transitionCallbacks&&t.unstable_transitionCallbacks),t=Yd(e,1,!1,null,0,n,r,a,o,s,0,null),e[Oe]=t.current,zu(e),new Tf(t)},t.hydrateRoot=function(e,t,n){if(!l(e))throw Error(i(299));var r=!1,a="",o=yl,s=vl,c=kl,u=null;return null!=n&&(!0===n.unstable_strictMode&&(r=!0),void 0!==n.identifierPrefix&&(a=n.identifierPrefix),void 0!==n.onUncaughtError&&(o=n.onUncaughtError),void 0!==n.onCaughtError&&(s=n.onCaughtError),void 0!==n.onRecoverableError&&(c=n.onRecoverableError),void 0!==n.unstable_transitionCallbacks&&n.unstable_transitionCallbacks,void 0!==n.formState&&(u=n.formState)),(t=Yd(e,1,!0,t,0,r,a,o,s,c,0,u)).context=Xd(null),n=t.current,(a=ao(r=Ae(r=Oc()))).callback=null,oo(n,a,r),n=r,t.current.lanes=n,Ee(t,n),Su(t),e[Oe]=t.current,zu(e),new jf(t)},t.version="19.1.1"},1312:(e,t,n)=>{"use strict";n.d(t,{A:()=>c,T:()=>s});var r=n(6540),a=n(4848);function o(e,t){const n=e.split(/(\{\w+\})/).map((e,n)=>{if(n%2==1){const n=t?.[e.slice(1,-1)];if(void 0!==n)return n}return e});return n.some(e=>(0,r.isValidElement)(e))?n.map((e,t)=>(0,r.isValidElement)(e)?r.cloneElement(e,{key:t}):e).filter(e=>""!==e):n.join("")}var i=n(2654);function l({id:e,message:t}){if(void 0===e&&void 0===t)throw new Error("Docusaurus translation declarations must have at least a translation id or a default translation message");return i[e??t]??t??e}function s({message:e,id:t},n){return o(l({message:e,id:t}),n)}function c({children:e,id:t,values:n}){if(e&&"string"!=typeof e)throw console.warn("Illegal <Translate> children",e),new Error("The Docusaurus <Translate> component only accept simple string values");const r=l({message:e,id:t});return(0,a.jsx)(a.Fragment,{children:o(r,n)})}},1422:(e,t,n)=>{"use strict";n.d(t,{N:()=>h,u:()=>s});var r=n(6540),a=n(205),o=n(3109),i=n(4848);const l="ease-in-out";function s({initialState:e}){const[t,n]=(0,r.useState)(e??!1),a=(0,r.useCallback)(()=>{n(e=>!e)},[]);return{collapsed:t,setCollapsed:n,toggleCollapsed:a}}const c={display:"none",overflow:"hidden",height:"0px"},u={display:"block",overflow:"visible",height:"auto"};function d(e,t){const n=t?c:u;e.style.display=n.display,e.style.overflow=n.overflow,e.style.height=n.height}function f({collapsibleRef:e,collapsed:t,animation:n}){const a=(0,r.useRef)(!1);(0,r.useEffect)(()=>{const r=e.current;function i(){const e=r.scrollHeight,t=n?.duration??function(e){if((0,o.O)())return 1;const t=e/36;return Math.round(10*(4+15*t**.25+t/5))}(e);return{transition:`height ${t}ms ${n?.easing??l}`,height:`${e}px`}}function s(){const e=i();r.style.transition=e.transition,r.style.height=e.height}if(!a.current)return d(r,t),void(a.current=!0);return r.style.willChange="height",function(){const e=requestAnimationFrame(()=>{t?(s(),requestAnimationFrame(()=>{r.style.height=c.height,r.style.overflow=c.overflow})):(r.style.display="block",requestAnimationFrame(()=>{s()}))});return()=>cancelAnimationFrame(e)}()},[e,t,n])}function p({as:e="div",collapsed:t,children:n,animation:a,onCollapseTransitionEnd:o,className:l}){const s=(0,r.useRef)(null);return f({collapsibleRef:s,collapsed:t,animation:a}),(0,i.jsx)(e,{ref:s,onTransitionEnd:e=>{"height"===e.propertyName&&(d(s.current,t),o?.(t))},className:l,children:n})}function m({collapsed:e,...t}){const[n,o]=(0,r.useState)(!e),[l,s]=(0,r.useState)(e);return(0,a.A)(()=>{e||o(!0)},[e]),(0,a.A)(()=>{n&&s(e)},[n,e]),n?(0,i.jsx)(p,{...t,collapsed:l}):null}function h({lazy:e,...t}){const n=e?m:p;return(0,i.jsx)(n,{...t})}},1463:(e,t,n)=>{"use strict";n.d(t,{A:()=>o});n(6540);var r=n(5260),a=n(4848);function o({locale:e,version:t,tag:n}){const o=e;return(0,a.jsxs)(r.A,{children:[e&&(0,a.jsx)("meta",{name:"docusaurus_locale",content:e}),t&&(0,a.jsx)("meta",{name:"docusaurus_version",content:t}),n&&(0,a.jsx)("meta",{name:"docusaurus_tag",content:n}),o&&(0,a.jsx)("meta",{name:"docsearch:language",content:o}),t&&(0,a.jsx)("meta",{name:"docsearch:version",content:t}),n&&(0,a.jsx)("meta",{name:"docsearch:docusaurus_tag",content:n})]})}},1513:(e,t,n)=>{"use strict";n.d(t,{zR:()=>k,TM:()=>C,yJ:()=>p,sC:()=>A,AO:()=>f});var r=n(8168);function a(e){return"/"===e.charAt(0)}function o(e,t){for(var n=t,r=n+1,a=e.length;r<a;n+=1,r+=1)e[n]=e[r];e.pop()}const i=function(e,t){void 0===t&&(t="");var n,r=e&&e.split("/")||[],i=t&&t.split("/")||[],l=e&&a(e),s=t&&a(t),c=l||s;if(e&&a(e)?i=r:r.length&&(i.pop(),i=i.concat(r)),!i.length)return"/";if(i.length){var u=i[i.length-1];n="."===u||".."===u||""===u}else n=!1;for(var d=0,f=i.length;f>=0;f--){var p=i[f];"."===p?o(i,f):".."===p?(o(i,f),d++):d&&(o(i,f),d--)}if(!c)for(;d--;d)i.unshift("..");!c||""===i[0]||i[0]&&a(i[0])||i.unshift("");var m=i.join("/");return n&&"/"!==m.substr(-1)&&(m+="/"),m};var l=n(1561);function s(e){return"/"===e.charAt(0)?e:"/"+e}function c(e){return"/"===e.charAt(0)?e.substr(1):e}function u(e,t){return function(e,t){return 0===e.toLowerCase().indexOf(t.toLowerCase())&&-1!=="/?#".indexOf(e.charAt(t.length))}(e,t)?e.substr(t.length):e}function d(e){return"/"===e.charAt(e.length-1)?e.slice(0,-1):e}function f(e){var t=e.pathname,n=e.search,r=e.hash,a=t||"/";return n&&"?"!==n&&(a+="?"===n.charAt(0)?n:"?"+n),r&&"#"!==r&&(a+="#"===r.charAt(0)?r:"#"+r),a}function p(e,t,n,a){var o;"string"==typeof e?(o=function(e){var t=e||"/",n="",r="",a=t.indexOf("#");-1!==a&&(r=t.substr(a),t=t.substr(0,a));var o=t.indexOf("?");return-1!==o&&(n=t.substr(o),t=t.substr(0,o)),{pathname:t,search:"?"===n?"":n,hash:"#"===r?"":r}}(e),o.state=t):(void 0===(o=(0,r.A)({},e)).pathname&&(o.pathname=""),o.search?"?"!==o.search.charAt(0)&&(o.search="?"+o.search):o.search="",o.hash?"#"!==o.hash.charAt(0)&&(o.hash="#"+o.hash):o.hash="",void 0!==t&&void 0===o.state&&(o.state=t));try{o.pathname=decodeURI(o.pathname)}catch(l){throw l instanceof URIError?new URIError('Pathname "'+o.pathname+'" could not be decoded. This is likely caused by an invalid percent-encoding.'):l}return n&&(o.key=n),a?o.pathname?"/"!==o.pathname.charAt(0)&&(o.pathname=i(o.pathname,a.pathname)):o.pathname=a.pathname:o.pathname||(o.pathname="/"),o}function m(){var e=null;var t=[];return{setPrompt:function(t){return e=t,function(){e===t&&(e=null)}},confirmTransitionTo:function(t,n,r,a){if(null!=e){var o="function"==typeof e?e(t,n):e;"string"==typeof o?"function"==typeof r?r(o,a):a(!0):a(!1!==o)}else a(!0)},appendListener:function(e){var n=!0;function r(){n&&e.apply(void 0,arguments)}return t.push(r),function(){n=!1,t=t.filter(function(e){return e!==r})}},notifyListeners:function(){for(var e=arguments.length,n=new Array(e),r=0;r<e;r++)n[r]=arguments[r];t.forEach(function(e){return e.apply(void 0,n)})}}}var h=!("undefined"==typeof window||!window.document||!window.document.createElement);function g(e,t){t(window.confirm(e))}var b="popstate",y="hashchange";function v(){try{return window.history.state||{}}catch(e){return{}}}function k(e){void 0===e&&(e={}),h||(0,l.A)(!1);var t,n=window.history,a=(-1===(t=window.navigator.userAgent).indexOf("Android 2.")&&-1===t.indexOf("Android 4.0")||-1===t.indexOf("Mobile Safari")||-1!==t.indexOf("Chrome")||-1!==t.indexOf("Windows Phone"))&&window.history&&"pushState"in window.history,o=!(-1===window.navigator.userAgent.indexOf("Trident")),i=e,c=i.forceRefresh,k=void 0!==c&&c,w=i.getUserConfirmation,S=void 0===w?g:w,x=i.keyLength,_=void 0===x?6:x,E=e.basename?d(s(e.basename)):"";function C(e){var t=e||{},n=t.key,r=t.state,a=window.location,o=a.pathname+a.search+a.hash;return E&&(o=u(o,E)),p(o,r,n)}function L(){return Math.random().toString(36).substr(2,_)}var A=m();function T(e){(0,r.A)($,e),$.length=n.length,A.notifyListeners($.location,$.action)}function j(e){(function(e){return void 0===e.state&&-1===navigator.userAgent.indexOf("CriOS")})(e)||N(C(e.state))}function M(){N(C(v()))}var P=!1;function N(e){if(P)P=!1,T();else{A.confirmTransitionTo(e,"POP",S,function(t){t?T({action:"POP",location:e}):function(e){var t=$.location,n=R.indexOf(t.key);-1===n&&(n=0);var r=R.indexOf(e.key);-1===r&&(r=0);var a=n-r;a&&(P=!0,D(a))}(e)})}}var O=C(v()),R=[O.key];function B(e){return E+f(e)}function D(e){n.go(e)}var F=0;function I(e){1===(F+=e)&&1===e?(window.addEventListener(b,j),o&&window.addEventListener(y,M)):0===F&&(window.removeEventListener(b,j),o&&window.removeEventListener(y,M))}var z=!1;var $={length:n.length,action:"POP",location:O,createHref:B,push:function(e,t){var r="PUSH",o=p(e,t,L(),$.location);A.confirmTransitionTo(o,r,S,function(e){if(e){var t=B(o),i=o.key,l=o.state;if(a)if(n.pushState({key:i,state:l},null,t),k)window.location.href=t;else{var s=R.indexOf($.location.key),c=R.slice(0,s+1);c.push(o.key),R=c,T({action:r,location:o})}else window.location.href=t}})},replace:function(e,t){var r="REPLACE",o=p(e,t,L(),$.location);A.confirmTransitionTo(o,r,S,function(e){if(e){var t=B(o),i=o.key,l=o.state;if(a)if(n.replaceState({key:i,state:l},null,t),k)window.location.replace(t);else{var s=R.indexOf($.location.key);-1!==s&&(R[s]=o.key),T({action:r,location:o})}else window.location.replace(t)}})},go:D,goBack:function(){D(-1)},goForward:function(){D(1)},block:function(e){void 0===e&&(e=!1);var t=A.setPrompt(e);return z||(I(1),z=!0),function(){return z&&(z=!1,I(-1)),t()}},listen:function(e){var t=A.appendListener(e);return I(1),function(){I(-1),t()}}};return $}var w="hashchange",S={hashbang:{encodePath:function(e){return"!"===e.charAt(0)?e:"!/"+c(e)},decodePath:function(e){return"!"===e.charAt(0)?e.substr(1):e}},noslash:{encodePath:c,decodePath:s},slash:{encodePath:s,decodePath:s}};function x(e){var t=e.indexOf("#");return-1===t?e:e.slice(0,t)}function _(){var e=window.location.href,t=e.indexOf("#");return-1===t?"":e.substring(t+1)}function E(e){window.location.replace(x(window.location.href)+"#"+e)}function C(e){void 0===e&&(e={}),h||(0,l.A)(!1);var t=window.history,n=(window.navigator.userAgent.indexOf("Firefox"),e),a=n.getUserConfirmation,o=void 0===a?g:a,i=n.hashType,c=void 0===i?"slash":i,b=e.basename?d(s(e.basename)):"",y=S[c],v=y.encodePath,k=y.decodePath;function C(){var e=k(_());return b&&(e=u(e,b)),p(e)}var L=m();function A(e){(0,r.A)(z,e),z.length=t.length,L.notifyListeners(z.location,z.action)}var T=!1,j=null;function M(){var e,t,n=_(),r=v(n);if(n!==r)E(r);else{var a=C(),i=z.location;if(!T&&(t=a,(e=i).pathname===t.pathname&&e.search===t.search&&e.hash===t.hash))return;if(j===f(a))return;j=null,function(e){if(T)T=!1,A();else{var t="POP";L.confirmTransitionTo(e,t,o,function(n){n?A({action:t,location:e}):function(e){var t=z.location,n=R.lastIndexOf(f(t));-1===n&&(n=0);var r=R.lastIndexOf(f(e));-1===r&&(r=0);var a=n-r;a&&(T=!0,B(a))}(e)})}}(a)}}var P=_(),N=v(P);P!==N&&E(N);var O=C(),R=[f(O)];function B(e){t.go(e)}var D=0;function F(e){1===(D+=e)&&1===e?window.addEventListener(w,M):0===D&&window.removeEventListener(w,M)}var I=!1;var z={length:t.length,action:"POP",location:O,createHref:function(e){var t=document.querySelector("base"),n="";return t&&t.getAttribute("href")&&(n=x(window.location.href)),n+"#"+v(b+f(e))},push:function(e,t){var n="PUSH",r=p(e,void 0,void 0,z.location);L.confirmTransitionTo(r,n,o,function(e){if(e){var t=f(r),a=v(b+t);if(_()!==a){j=t,function(e){window.location.hash=e}(a);var o=R.lastIndexOf(f(z.location)),i=R.slice(0,o+1);i.push(t),R=i,A({action:n,location:r})}else A()}})},replace:function(e,t){var n="REPLACE",r=p(e,void 0,void 0,z.location);L.confirmTransitionTo(r,n,o,function(e){if(e){var t=f(r),a=v(b+t);_()!==a&&(j=t,E(a));var o=R.indexOf(f(z.location));-1!==o&&(R[o]=t),A({action:n,location:r})}})},go:B,goBack:function(){B(-1)},goForward:function(){B(1)},block:function(e){void 0===e&&(e=!1);var t=L.setPrompt(e);return I||(F(1),I=!0),function(){return I&&(I=!1,F(-1)),t()}},listen:function(e){var t=L.appendListener(e);return F(1),function(){F(-1),t()}}};return z}function L(e,t,n){return Math.min(Math.max(e,t),n)}function A(e){void 0===e&&(e={});var t=e,n=t.getUserConfirmation,a=t.initialEntries,o=void 0===a?["/"]:a,i=t.initialIndex,l=void 0===i?0:i,s=t.keyLength,c=void 0===s?6:s,u=m();function d(e){(0,r.A)(k,e),k.length=k.entries.length,u.notifyListeners(k.location,k.action)}function h(){return Math.random().toString(36).substr(2,c)}var g=L(l,0,o.length-1),b=o.map(function(e){return p(e,void 0,"string"==typeof e?h():e.key||h())}),y=f;function v(e){var t=L(k.index+e,0,k.entries.length-1),r=k.entries[t];u.confirmTransitionTo(r,"POP",n,function(e){e?d({action:"POP",location:r,index:t}):d()})}var k={length:b.length,action:"POP",location:b[g],index:g,entries:b,createHref:y,push:function(e,t){var r="PUSH",a=p(e,t,h(),k.location);u.confirmTransitionTo(a,r,n,function(e){if(e){var t=k.index+1,n=k.entries.slice(0);n.length>t?n.splice(t,n.length-t,a):n.push(a),d({action:r,location:a,index:t,entries:n})}})},replace:function(e,t){var r="REPLACE",a=p(e,t,h(),k.location);u.confirmTransitionTo(a,r,n,function(e){e&&(k.entries[k.index]=a,d({action:r,location:a}))})},go:v,goBack:function(){v(-1)},goForward:function(){v(1)},canGo:function(e){var t=k.index+e;return t>=0&&t<k.entries.length},block:function(e){return void 0===e&&(e=!1),u.setPrompt(e)},listen:function(e){return u.appendListener(e)}};return k}},1561:(e,t,n)=>{"use strict";n.d(t,{A:()=>o});var r=!0,a="Invariant failed";function o(e,t){if(!e){if(r)throw new Error(a);var n="function"==typeof t?t():t,o=n?"".concat(a,": ").concat(n):a;throw new Error(o)}}},1635:(e,t,n)=>{"use strict";n.r(t),n.d(t,{__addDisposableResource:()=>R,__assign:()=>o,__asyncDelegator:()=>E,__asyncGenerator:()=>_,__asyncValues:()=>C,__await:()=>x,__awaiter:()=>m,__classPrivateFieldGet:()=>P,__classPrivateFieldIn:()=>O,__classPrivateFieldSet:()=>N,__createBinding:()=>g,__decorate:()=>l,__disposeResources:()=>D,__esDecorate:()=>c,__exportStar:()=>b,__extends:()=>a,__generator:()=>h,__importDefault:()=>M,__importStar:()=>j,__makeTemplateObject:()=>L,__metadata:()=>p,__param:()=>s,__propKey:()=>d,__read:()=>v,__rest:()=>i,__rewriteRelativeImportExtension:()=>F,__runInitializers:()=>u,__setFunctionName:()=>f,__spread:()=>k,__spreadArray:()=>S,__spreadArrays:()=>w,__values:()=>y,default:()=>I});var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},r(e,t)};function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}var o=function(){return o=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var a in t=arguments[n])Object.prototype.hasOwnProperty.call(t,a)&&(e[a]=t[a]);return e},o.apply(this,arguments)};function i(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var a=0;for(r=Object.getOwnPropertySymbols(e);a<r.length;a++)t.indexOf(r[a])<0&&Object.prototype.propertyIsEnumerable.call(e,r[a])&&(n[r[a]]=e[r[a]])}return n}function l(e,t,n,r){var a,o=arguments.length,i=o<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,n):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,n,r);else for(var l=e.length-1;l>=0;l--)(a=e[l])&&(i=(o<3?a(i):o>3?a(t,n,i):a(t,n))||i);return o>3&&i&&Object.defineProperty(t,n,i),i}function s(e,t){return function(n,r){t(n,r,e)}}function c(e,t,n,r,a,o){function i(e){if(void 0!==e&&"function"!=typeof e)throw new TypeError("Function expected");return e}for(var l,s=r.kind,c="getter"===s?"get":"setter"===s?"set":"value",u=!t&&e?r.static?e:e.prototype:null,d=t||(u?Object.getOwnPropertyDescriptor(u,r.name):{}),f=!1,p=n.length-1;p>=0;p--){var m={};for(var h in r)m[h]="access"===h?{}:r[h];for(var h in r.access)m.access[h]=r.access[h];m.addInitializer=function(e){if(f)throw new TypeError("Cannot add initializers after decoration has completed");o.push(i(e||null))};var g=(0,n[p])("accessor"===s?{get:d.get,set:d.set}:d[c],m);if("accessor"===s){if(void 0===g)continue;if(null===g||"object"!=typeof g)throw new TypeError("Object expected");(l=i(g.get))&&(d.get=l),(l=i(g.set))&&(d.set=l),(l=i(g.init))&&a.unshift(l)}else(l=i(g))&&("field"===s?a.unshift(l):d[c]=l)}u&&Object.defineProperty(u,r.name,d),f=!0}function u(e,t,n){for(var r=arguments.length>2,a=0;a<t.length;a++)n=r?t[a].call(e,n):t[a].call(e);return r?n:void 0}function d(e){return"symbol"==typeof e?e:"".concat(e)}function f(e,t,n){return"symbol"==typeof t&&(t=t.description?"[".concat(t.description,"]"):""),Object.defineProperty(e,"name",{configurable:!0,value:n?"".concat(n," ",t):t})}function p(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)}function m(e,t,n,r){return new(n||(n=Promise))(function(a,o){function i(e){try{s(r.next(e))}catch(t){o(t)}}function l(e){try{s(r.throw(e))}catch(t){o(t)}}function s(e){var t;e.done?a(e.value):(t=e.value,t instanceof n?t:new n(function(e){e(t)})).then(i,l)}s((r=r.apply(e,t||[])).next())})}function h(e,t){var n,r,a,o={label:0,sent:function(){if(1&a[0])throw a[1];return a[1]},trys:[],ops:[]},i=Object.create(("function"==typeof Iterator?Iterator:Object).prototype);return i.next=l(0),i.throw=l(1),i.return=l(2),"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function l(l){return function(s){return function(l){if(n)throw new TypeError("Generator is already executing.");for(;i&&(i=0,l[0]&&(o=0)),o;)try{if(n=1,r&&(a=2&l[0]?r.return:l[0]?r.throw||((a=r.return)&&a.call(r),0):r.next)&&!(a=a.call(r,l[1])).done)return a;switch(r=0,a&&(l=[2&l[0],a.value]),l[0]){case 0:case 1:a=l;break;case 4:return o.label++,{value:l[1],done:!1};case 5:o.label++,r=l[1],l=[0];continue;case 7:l=o.ops.pop(),o.trys.pop();continue;default:if(!(a=o.trys,(a=a.length>0&&a[a.length-1])||6!==l[0]&&2!==l[0])){o=0;continue}if(3===l[0]&&(!a||l[1]>a[0]&&l[1]<a[3])){o.label=l[1];break}if(6===l[0]&&o.label<a[1]){o.label=a[1],a=l;break}if(a&&o.label<a[2]){o.label=a[2],o.ops.push(l);break}a[2]&&o.ops.pop(),o.trys.pop();continue}l=t.call(e,o)}catch(s){l=[6,s],r=0}finally{n=a=0}if(5&l[0])throw l[1];return{value:l[0]?l[1]:void 0,done:!0}}([l,s])}}}var g=Object.create?function(e,t,n,r){void 0===r&&(r=n);var a=Object.getOwnPropertyDescriptor(t,n);a&&!("get"in a?!t.__esModule:a.writable||a.configurable)||(a={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,r,a)}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]};function b(e,t){for(var n in e)"default"===n||Object.prototype.hasOwnProperty.call(t,n)||g(t,e,n)}function y(e){var t="function"==typeof Symbol&&Symbol.iterator,n=t&&e[t],r=0;if(n)return n.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&r>=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function v(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,a,o=n.call(e),i=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)i.push(r.value)}catch(l){a={error:l}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(a)throw a.error}}return i}function k(){for(var e=[],t=0;t<arguments.length;t++)e=e.concat(v(arguments[t]));return e}function w(){for(var e=0,t=0,n=arguments.length;t<n;t++)e+=arguments[t].length;var r=Array(e),a=0;for(t=0;t<n;t++)for(var o=arguments[t],i=0,l=o.length;i<l;i++,a++)r[a]=o[i];return r}function S(e,t,n){if(n||2===arguments.length)for(var r,a=0,o=t.length;a<o;a++)!r&&a in t||(r||(r=Array.prototype.slice.call(t,0,a)),r[a]=t[a]);return e.concat(r||Array.prototype.slice.call(t))}function x(e){return this instanceof x?(this.v=e,this):new x(e)}function _(e,t,n){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var r,a=n.apply(e,t||[]),o=[];return r=Object.create(("function"==typeof AsyncIterator?AsyncIterator:Object).prototype),i("next"),i("throw"),i("return",function(e){return function(t){return Promise.resolve(t).then(e,c)}}),r[Symbol.asyncIterator]=function(){return this},r;function i(e,t){a[e]&&(r[e]=function(t){return new Promise(function(n,r){o.push([e,t,n,r])>1||l(e,t)})},t&&(r[e]=t(r[e])))}function l(e,t){try{(n=a[e](t)).value instanceof x?Promise.resolve(n.value.v).then(s,c):u(o[0][2],n)}catch(r){u(o[0][3],r)}var n}function s(e){l("next",e)}function c(e){l("throw",e)}function u(e,t){e(t),o.shift(),o.length&&l(o[0][0],o[0][1])}}function E(e){var t,n;return t={},r("next"),r("throw",function(e){throw e}),r("return"),t[Symbol.iterator]=function(){return this},t;function r(r,a){t[r]=e[r]?function(t){return(n=!n)?{value:x(e[r](t)),done:!1}:a?a(t):t}:a}}function C(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t,n=e[Symbol.asyncIterator];return n?n.call(e):(e=y(e),t={},r("next"),r("throw"),r("return"),t[Symbol.asyncIterator]=function(){return this},t);function r(n){t[n]=e[n]&&function(t){return new Promise(function(r,a){(function(e,t,n,r){Promise.resolve(r).then(function(t){e({value:t,done:n})},t)})(r,a,(t=e[n](t)).done,t.value)})}}}function L(e,t){return Object.defineProperty?Object.defineProperty(e,"raw",{value:t}):e.raw=t,e}var A=Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t},T=function(e){return T=Object.getOwnPropertyNames||function(e){var t=[];for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[t.length]=n);return t},T(e)};function j(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n=T(e),r=0;r<n.length;r++)"default"!==n[r]&&g(t,e,n[r]);return A(t,e),t}function M(e){return e&&e.__esModule?e:{default:e}}function P(e,t,n,r){if("a"===n&&!r)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!r:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===n?r:"a"===n?r.call(e):r?r.value:t.get(e)}function N(e,t,n,r,a){if("m"===r)throw new TypeError("Private method is not writable");if("a"===r&&!a)throw new TypeError("Private accessor was defined without a setter");if("function"==typeof t?e!==t||!a:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return"a"===r?a.call(e,n):a?a.value=n:t.set(e,n),n}function O(e,t){if(null===t||"object"!=typeof t&&"function"!=typeof t)throw new TypeError("Cannot use 'in' operator on non-object");return"function"==typeof e?t===e:e.has(t)}function R(e,t,n){if(null!=t){if("object"!=typeof t&&"function"!=typeof t)throw new TypeError("Object expected.");var r,a;if(n){if(!Symbol.asyncDispose)throw new TypeError("Symbol.asyncDispose is not defined.");r=t[Symbol.asyncDispose]}if(void 0===r){if(!Symbol.dispose)throw new TypeError("Symbol.dispose is not defined.");r=t[Symbol.dispose],n&&(a=r)}if("function"!=typeof r)throw new TypeError("Object not disposable.");a&&(r=function(){try{a.call(this)}catch(e){return Promise.reject(e)}}),e.stack.push({value:t,dispose:r,async:n})}else n&&e.stack.push({async:!0});return t}var B="function"==typeof SuppressedError?SuppressedError:function(e,t,n){var r=new Error(n);return r.name="SuppressedError",r.error=e,r.suppressed=t,r};function D(e){function t(t){e.error=e.hasError?new B(t,e.error,"An error was suppressed during disposal."):t,e.hasError=!0}var n,r=0;return function a(){for(;n=e.stack.pop();)try{if(!n.async&&1===r)return r=0,e.stack.push(n),Promise.resolve().then(a);if(n.dispose){var o=n.dispose.call(n.value);if(n.async)return r|=2,Promise.resolve(o).then(a,function(e){return t(e),a()})}else r|=1}catch(i){t(i)}if(1===r)return e.hasError?Promise.reject(e.error):Promise.resolve();if(e.hasError)throw e.error}()}function F(e,t){return"string"==typeof e&&/^\.\.?\//.test(e)?e.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i,function(e,n,r,a,o){return n?t?".jsx":".js":!r||a&&o?r+a+"."+o.toLowerCase()+"js":e}):e}const I={__extends:a,__assign:o,__rest:i,__decorate:l,__param:s,__esDecorate:c,__runInitializers:u,__propKey:d,__setFunctionName:f,__metadata:p,__awaiter:m,__generator:h,__createBinding:g,__exportStar:b,__values:y,__read:v,__spread:k,__spreadArrays:w,__spreadArray:S,__await:x,__asyncGenerator:_,__asyncDelegator:E,__asyncValues:C,__makeTemplateObject:L,__importStar:j,__importDefault:M,__classPrivateFieldGet:P,__classPrivateFieldSet:N,__classPrivateFieldIn:O,__addDisposableResource:R,__disposeResources:D,__rewriteRelativeImportExtension:F}},1656:(e,t,n)=>{"use strict";n.d(t,{A:()=>Lt});var r=n(6540),a=n(4164),o=n(7489),i=n(5500),l=n(6347),s=n(1312),c=n(5062),u=n(4848);const d="__docusaurus_skipToContent_fallback";function f(e){e.setAttribute("tabindex","-1"),e.focus(),e.removeAttribute("tabindex")}function p(){const e=(0,r.useRef)(null),{action:t}=(0,l.W6)(),n=(0,r.useCallback)(e=>{e.preventDefault();const t=document.querySelector("main:first-of-type")??document.getElementById(d);t&&f(t)},[]);return(0,c.$)(({location:n})=>{e.current&&!n.hash&&"PUSH"===t&&f(e.current)}),{containerRef:e,onClick:n}}const m=(0,s.T)({id:"theme.common.skipToMainContent",description:"The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation",message:"Skip to main content"});function h(e){const t=e.children??m,{containerRef:n,onClick:r}=p();return(0,u.jsx)("div",{ref:n,role:"region","aria-label":m,children:(0,u.jsx)("a",{...e,href:`#${d}`,onClick:r,children:t})})}var g=n(7559),b=n(4090);const y={skipToContent:"skipToContent_fXgn"};function v(){return(0,u.jsx)(h,{className:y.skipToContent})}var k=n(6342),w=n(5041);function S({width:e=21,height:t=21,color:n="currentColor",strokeWidth:r=1.2,className:a,...o}){return(0,u.jsx)("svg",{viewBox:"0 0 15 15",width:e,height:t,...o,children:(0,u.jsx)("g",{stroke:n,strokeWidth:r,children:(0,u.jsx)("path",{d:"M.75.75l13.5 13.5M14.25.75L.75 14.25"})})})}const x={closeButton:"closeButton_CVFx"};function _(e){return(0,u.jsx)("button",{type:"button","aria-label":(0,s.T)({id:"theme.AnnouncementBar.closeButtonAriaLabel",message:"Close",description:"The ARIA label for close button of announcement bar"}),...e,className:(0,a.A)("clean-btn close",x.closeButton,e.className),children:(0,u.jsx)(S,{width:14,height:14,strokeWidth:3.1})})}const E={content:"content_knG7"};function C(e){const{announcementBar:t}=(0,k.p)(),{content:n}=t;return(0,u.jsx)("div",{...e,className:(0,a.A)(E.content,e.className),dangerouslySetInnerHTML:{__html:n}})}const L={announcementBar:"announcementBar_mb4j",announcementBarPlaceholder:"announcementBarPlaceholder_vyr4",announcementBarClose:"announcementBarClose_gvF7",announcementBarContent:"announcementBarContent_xLdY"};function A(){const{announcementBar:e}=(0,k.p)(),{isActive:t,close:n}=(0,w.M)();if(!t)return null;const{backgroundColor:r,textColor:o,isCloseable:i}=e;return(0,u.jsxs)("div",{className:(0,a.A)(g.G.announcementBar.container,L.announcementBar),style:{backgroundColor:r,color:o},role:"banner",children:[i&&(0,u.jsx)("div",{className:L.announcementBarPlaceholder}),(0,u.jsx)(C,{className:L.announcementBarContent}),i&&(0,u.jsx)(_,{onClick:n,className:L.announcementBarClose})]})}var T=n(9876),j=n(3104);var M=n(9532),P=n(5600);const N=r.createContext(null);function O({children:e}){const t=function(){const e=(0,T.M)(),t=(0,P.YL)(),[n,a]=(0,r.useState)(!1),o=null!==t.component,i=(0,M.ZC)(o);return(0,r.useEffect)(()=>{o&&!i&&a(!0)},[o,i]),(0,r.useEffect)(()=>{o?e.shown||a(!0):a(!1)},[e.shown,o]),(0,r.useMemo)(()=>[n,a],[n])}();return(0,u.jsx)(N.Provider,{value:t,children:e})}function R(e){if(e.component){const t=e.component;return(0,u.jsx)(t,{...e.props})}}function B(){const e=(0,r.useContext)(N);if(!e)throw new M.dV("NavbarSecondaryMenuDisplayProvider");const[t,n]=e,a=(0,r.useCallback)(()=>n(!1),[n]),o=(0,P.YL)();return(0,r.useMemo)(()=>({shown:t,hide:a,content:R(o)}),[a,o,t])}function D(e){return parseInt(r.version.split(".")[0],10)<19?{inert:e?"":void 0}:{inert:e}}function F({children:e,inert:t}){return(0,u.jsx)("div",{className:(0,a.A)(g.G.layout.navbar.mobileSidebar.panel,"navbar-sidebar__item menu"),...D(t),children:e})}function I({header:e,primaryMenu:t,secondaryMenu:n}){const{shown:r}=B();return(0,u.jsxs)("div",{className:(0,a.A)(g.G.layout.navbar.mobileSidebar.container,"navbar-sidebar"),children:[e,(0,u.jsxs)("div",{className:(0,a.A)("navbar-sidebar__items",{"navbar-sidebar__items--show-secondary":r}),children:[(0,u.jsx)(F,{inert:r,children:t}),(0,u.jsx)(F,{inert:!r,children:n})]})]})}var z=n(5293),$=n(2303);function U(e){return(0,u.jsx)("svg",{viewBox:"0 0 24 24",width:24,height:24,...e,children:(0,u.jsx)("path",{fill:"currentColor",d:"M12,9c1.65,0,3,1.35,3,3s-1.35,3-3,3s-3-1.35-3-3S10.35,9,12,9 M12,7c-2.76,0-5,2.24-5,5s2.24,5,5,5s5-2.24,5-5 S14.76,7,12,7L12,7z M2,13l2,0c0.55,0,1-0.45,1-1s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S1.45,13,2,13z M20,13l2,0c0.55,0,1-0.45,1-1 s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S19.45,13,20,13z M11,2v2c0,0.55,0.45,1,1,1s1-0.45,1-1V2c0-0.55-0.45-1-1-1S11,1.45,11,2z M11,20v2c0,0.55,0.45,1,1,1s1-0.45,1-1v-2c0-0.55-0.45-1-1-1C11.45,19,11,19.45,11,20z M5.99,4.58c-0.39-0.39-1.03-0.39-1.41,0 c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0s0.39-1.03,0-1.41L5.99,4.58z M18.36,16.95 c-0.39-0.39-1.03-0.39-1.41,0c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0c0.39-0.39,0.39-1.03,0-1.41 L18.36,16.95z M19.42,5.99c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06c-0.39,0.39-0.39,1.03,0,1.41 s1.03,0.39,1.41,0L19.42,5.99z M7.05,18.36c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06 c-0.39,0.39-0.39,1.03,0,1.41s1.03,0.39,1.41,0L7.05,18.36z"})})}function q(e){return(0,u.jsx)("svg",{viewBox:"0 0 24 24",width:24,height:24,...e,children:(0,u.jsx)("path",{fill:"currentColor",d:"M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27C17.45,17.19,14.93,19,12,19 c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36 c-0.98,1.37-2.58,2.26-4.4,2.26c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"})})}function H(e){return(0,u.jsx)("svg",{viewBox:"0 0 24 24",width:24,height:24,...e,children:(0,u.jsx)("path",{fill:"currentColor",d:"m12 21c4.971 0 9-4.029 9-9s-4.029-9-9-9-9 4.029-9 9 4.029 9 9 9zm4.95-13.95c1.313 1.313 2.05 3.093 2.05 4.95s-0.738 3.637-2.05 4.95c-1.313 1.313-3.093 2.05-4.95 2.05v-14c1.857 0 3.637 0.737 4.95 2.05z"})})}const G="toggle_vylO",V="toggleButton_gllP",W="toggleIcon_g3eP",Q="systemToggleIcon_QzmC",K="lightToggleIcon_pyhR",Y="darkToggleIcon_wfgR",X="toggleButtonDisabled_aARS";function Z(e){switch(e){case null:return(0,s.T)({message:"system mode",id:"theme.colorToggle.ariaLabel.mode.system",description:"The name for the system color mode"});case"light":return(0,s.T)({message:"light mode",id:"theme.colorToggle.ariaLabel.mode.light",description:"The name for the light color mode"});case"dark":return(0,s.T)({message:"dark mode",id:"theme.colorToggle.ariaLabel.mode.dark",description:"The name for the dark color mode"});default:throw new Error(`unexpected color mode ${e}`)}}function J(e){return(0,s.T)({message:"Switch between dark and light mode (currently {mode})",id:"theme.colorToggle.ariaLabel",description:"The ARIA label for the color mode toggle"},{mode:Z(e)})}function ee(){return(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)(U,{"aria-hidden":!0,className:(0,a.A)(W,K)}),(0,u.jsx)(q,{"aria-hidden":!0,className:(0,a.A)(W,Y)}),(0,u.jsx)(H,{"aria-hidden":!0,className:(0,a.A)(W,Q)})]})}function te({className:e,buttonClassName:t,respectPrefersColorScheme:n,value:r,onChange:o}){const i=(0,$.A)();return(0,u.jsx)("div",{className:(0,a.A)(G,e),children:(0,u.jsx)("button",{className:(0,a.A)("clean-btn",V,!i&&X,t),type:"button",onClick:()=>o(function(e,t){if(!t)return"dark"===e?"light":"dark";switch(e){case null:return"light";case"light":return"dark";case"dark":return null;default:throw new Error(`unexpected color mode ${e}`)}}(r,n)),disabled:!i,title:Z(r),"aria-label":J(r),children:(0,u.jsx)(ee,{})})})}const ne=r.memo(te),re={darkNavbarColorModeToggle:"darkNavbarColorModeToggle_X3D1"};function ae({className:e}){const t=(0,k.p)().navbar.style,{disableSwitch:n,respectPrefersColorScheme:r}=(0,k.p)().colorMode,{colorModeChoice:a,setColorMode:o}=(0,z.G)();return n?null:(0,u.jsx)(ne,{className:e,buttonClassName:"dark"===t?re.darkNavbarColorModeToggle:void 0,respectPrefersColorScheme:r,value:a,onChange:o})}var oe=n(3465);function ie(){return(0,u.jsx)(oe.A,{className:"navbar__brand",imageClassName:"navbar__logo",titleClassName:"navbar__title text--truncate"})}function le(){const e=(0,T.M)();return(0,u.jsx)("button",{type:"button","aria-label":(0,s.T)({id:"theme.docs.sidebar.closeSidebarButtonAriaLabel",message:"Close navigation bar",description:"The ARIA label for close button of mobile sidebar"}),className:"clean-btn navbar-sidebar__close",onClick:()=>e.toggle(),children:(0,u.jsx)(S,{color:"var(--ifm-color-emphasis-600)"})})}function se(){return(0,u.jsxs)("div",{className:"navbar-sidebar__brand",children:[(0,u.jsx)(ie,{}),(0,u.jsx)(ae,{className:"margin-right--md"}),(0,u.jsx)(le,{})]})}var ce=n(8774),ue=n(6025),de=n(6654);function fe(e,t){return void 0!==e&&void 0!==t&&new RegExp(e,"gi").test(t)}var pe=n(3186);function me({activeBasePath:e,activeBaseRegex:t,to:n,href:r,label:a,html:o,isDropdownLink:i,prependBaseUrlToHref:l,...s}){const c=(0,ue.Ay)(n),d=(0,ue.Ay)(e),f=(0,ue.Ay)(r,{forcePrependBaseUrl:!0}),p=a&&r&&!(0,de.A)(r),m=o?{dangerouslySetInnerHTML:{__html:o}}:{children:(0,u.jsxs)(u.Fragment,{children:[a,p&&(0,u.jsx)(pe.A,{...i&&{width:12,height:12}})]})};return r?(0,u.jsx)(ce.A,{href:l?f:r,...s,...m}):(0,u.jsx)(ce.A,{to:c,isNavLink:!0,...(e||t)&&{isActive:(e,n)=>t?fe(t,n.pathname):n.pathname.startsWith(d)},...s,...m})}function he({className:e,isDropdownItem:t,...n}){return(0,u.jsx)("li",{className:"menu__list-item",children:(0,u.jsx)(me,{className:(0,a.A)("menu__link",e),...n})})}function ge({className:e,isDropdownItem:t=!1,...n}){const r=(0,u.jsx)(me,{className:(0,a.A)(t?"dropdown__link":"navbar__item navbar__link",e),isDropdownLink:t,...n});return t?(0,u.jsx)("li",{children:r}):r}function be({mobile:e=!1,position:t,...n}){const r=e?he:ge;return(0,u.jsx)(r,{...n,activeClassName:n.activeClassName??(e?"menu__link--active":"navbar__link--active")})}var ye=n(1422),ve=n(9169),ke=n(4586);const we="dropdownNavbarItemMobile_J0Sd";function Se(e,t){return e.some(e=>function(e,t){return!!(0,ve.ys)(e.to,t)||!!fe(e.activeBaseRegex,t)||!(!e.activeBasePath||!t.startsWith(e.activeBasePath))}(e,t))}function xe({collapsed:e,onClick:t}){return(0,u.jsx)("button",{"aria-label":e?(0,s.T)({id:"theme.navbar.mobileDropdown.collapseButton.expandAriaLabel",message:"Expand the dropdown",description:"The ARIA label of the button to expand the mobile dropdown navbar item"}):(0,s.T)({id:"theme.navbar.mobileDropdown.collapseButton.collapseAriaLabel",message:"Collapse the dropdown",description:"The ARIA label of the button to collapse the mobile dropdown navbar item"}),"aria-expanded":!e,type:"button",className:"clean-btn menu__caret",onClick:t})}function _e({items:e,className:t,position:n,onClick:o,...i}){const s=function(){const{siteConfig:{baseUrl:e}}=(0,ke.A)(),{pathname:t}=(0,l.zy)();return t.replace(e,"/")}(),c=(0,ve.ys)(i.to,s),d=Se(e,s),{collapsed:f,toggleCollapsed:p}=function({active:e}){const{collapsed:t,toggleCollapsed:n,setCollapsed:a}=(0,ye.u)({initialState:()=>!e});return(0,r.useEffect)(()=>{e&&a(!1)},[e,a]),{collapsed:t,toggleCollapsed:n}}({active:c||d}),m=i.to?void 0:"#";return(0,u.jsxs)("li",{className:(0,a.A)("menu__list-item",{"menu__list-item--collapsed":f}),children:[(0,u.jsxs)("div",{className:(0,a.A)("menu__list-item-collapsible",{"menu__list-item-collapsible--active":c}),children:[(0,u.jsx)(me,{role:"button",className:(0,a.A)(we,"menu__link menu__link--sublist",t),href:m,...i,onClick:e=>{"#"===m&&e.preventDefault(),p()},children:i.children??i.label}),(0,u.jsx)(xe,{collapsed:f,onClick:e=>{e.preventDefault(),p()}})]}),(0,u.jsx)(ye.N,{lazy:!0,as:"ul",className:"menu__list",collapsed:f,children:e.map((e,t)=>(0,r.createElement)(Ie,{mobile:!0,isDropdownItem:!0,onClick:o,activeClassName:"menu__link--active",...e,key:t}))})]})}function Ee({items:e,position:t,className:n,onClick:o,...i}){const l=(0,r.useRef)(null),[s,c]=(0,r.useState)(!1);return(0,r.useEffect)(()=>{const e=e=>{l.current&&!l.current.contains(e.target)&&c(!1)};return document.addEventListener("mousedown",e),document.addEventListener("touchstart",e),document.addEventListener("focusin",e),()=>{document.removeEventListener("mousedown",e),document.removeEventListener("touchstart",e),document.removeEventListener("focusin",e)}},[l]),(0,u.jsxs)("div",{ref:l,className:(0,a.A)("navbar__item","dropdown","dropdown--hoverable",{"dropdown--right":"right"===t,"dropdown--show":s}),children:[(0,u.jsx)(me,{"aria-haspopup":"true","aria-expanded":s,role:"button",href:i.to?void 0:"#",className:(0,a.A)("navbar__link",n),...i,onClick:i.to?void 0:e=>e.preventDefault(),onKeyDown:e=>{"Enter"===e.key&&(e.preventDefault(),c(!s))},children:i.children??i.label}),(0,u.jsx)("ul",{className:"dropdown__menu",children:e.map((e,t)=>(0,r.createElement)(Ie,{isDropdownItem:!0,activeClassName:"dropdown__link--active",...e,key:t}))})]})}function Ce({mobile:e=!1,...t}){const n=e?_e:Ee;return(0,u.jsx)(n,{...t})}var Le=n(2131);function Ae({width:e=20,height:t=20,...n}){return(0,u.jsx)("svg",{viewBox:"0 0 24 24",width:e,height:t,"aria-hidden":!0,...n,children:(0,u.jsx)("path",{fill:"currentColor",d:"M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z"})})}const Te="iconLanguage_nlXk";var je=n(418);const Me={navbarSearchContainer:"navbarSearchContainer_Bca1"};function Pe({children:e,className:t}){return(0,u.jsx)("div",{className:(0,a.A)(t,Me.navbarSearchContainer),children:e})}var Ne=n(4070),Oe=n(6972);var Re=n(3886);function Be({docsPluginId:e,configs:t}){return function(e,t){if(t){const n=new Map(e.map(e=>[e.name,e])),r=(t,r)=>{const a=n.get(t);if(!a)throw new Error(`No docs version exist for name '${t}', please verify your 'docsVersionDropdown' navbar item versions config.\nAvailable version names:\n- ${e.map(e=>`${e.name}`).join("\n- ")}`);return{version:a,label:r?.label??a.label}};return Array.isArray(t)?t.map(e=>r(e,void 0)):Object.entries(t).map(([e,t])=>r(e,t))}return e.map(e=>({version:e,label:e.label}))}((0,Ne.jh)(e),t)}function De(e,t){return t.alternateDocVersions[e.name]??function(e){return e.docs.find(t=>t.id===e.mainDocId)}(e)}const Fe={default:be,localeDropdown:function({mobile:e,dropdownItemsBefore:t,dropdownItemsAfter:n,queryString:r="",...a}){const{i18n:{currentLocale:o,locales:i,localeConfigs:c}}=(0,ke.A)(),d=(0,Le.o)(),{search:f,hash:p}=(0,l.zy)(),m=[...t,...i.map(t=>{const n=`${`pathname://${d.createUrl({locale:t,fullyQualified:!1})}`}${f}${p}${r}`;return{label:c[t].label,lang:c[t].htmlLang,to:n,target:"_self",autoAddBaseUrl:!1,className:t===o?e?"menu__link--active":"dropdown__link--active":""}}),...n],h=e?(0,s.T)({message:"Languages",id:"theme.navbar.mobileLanguageDropdown.label",description:"The label for the mobile language switcher dropdown"}):c[o].label;return(0,u.jsx)(Ce,{...a,mobile:e,label:(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)(Ae,{className:Te}),h]}),items:m})},search:function({mobile:e,className:t}){return e?null:(0,u.jsx)(Pe,{className:t,children:(0,u.jsx)(je.A,{})})},dropdown:Ce,html:function({value:e,className:t,mobile:n=!1,isDropdownItem:r=!1}){const o=r?"li":"div";return(0,u.jsx)(o,{className:(0,a.A)({navbar__item:!n&&!r,"menu__list-item":n},t),dangerouslySetInnerHTML:{__html:e}})},doc:function({docId:e,label:t,docsPluginId:n,...r}){const{activeDoc:a}=(0,Ne.zK)(n),o=(0,Oe.QB)(e,n),i=a?.path===o?.path;return null===o||o.unlisted&&!i?null:(0,u.jsx)(be,{exact:!0,...r,isActive:()=>i||!!a?.sidebar&&a.sidebar===o.sidebar,label:t??o.id,to:o.path})},docSidebar:function({sidebarId:e,label:t,docsPluginId:n,...r}){const{activeDoc:a}=(0,Ne.zK)(n),o=(0,Oe.fW)(e,n).link;if(!o)throw new Error(`DocSidebarNavbarItem: Sidebar with ID "${e}" doesn't have anything to be linked to.`);return(0,u.jsx)(be,{exact:!0,...r,isActive:()=>a?.sidebar===e,label:t??o.label,to:o.path})},docsVersion:function({label:e,to:t,docsPluginId:n,...r}){const a=(0,Oe.Vd)(n)[0],o=e??a.label,i=t??(e=>e.docs.find(t=>t.id===e.mainDocId))(a).path;return(0,u.jsx)(be,{...r,label:o,to:i})},docsVersionDropdown:function({mobile:e,docsPluginId:t,dropdownActiveClassDisabled:n,dropdownItemsBefore:r,dropdownItemsAfter:a,versions:o,...i}){const{search:c,hash:d}=(0,l.zy)(),f=(0,Ne.zK)(t),{savePreferredVersionName:p}=(0,Re.g1)(t),m=Be({docsPluginId:t,configs:o}),h=function({docsPluginId:e,versionItems:t}){return(0,Oe.Vd)(e).map(e=>t.find(t=>t.version===e)).filter(e=>void 0!==e)[0]??t[0]}({docsPluginId:t,versionItems:m}),g=[...r,...m.map(function({version:e,label:t}){return{label:t,to:`${De(e,f).path}${c}${d}`,isActive:()=>e===f.activeVersion,onClick:()=>p(e.name)}}),...a],b=e&&g.length>1?(0,s.T)({id:"theme.navbar.mobileVersionsDropdown.label",message:"Versions",description:"The label for the navbar versions dropdown on mobile view"}):h.label,y=e&&g.length>1?void 0:De(h.version,f).path;return g.length<=1?(0,u.jsx)(be,{...i,mobile:e,label:b,to:y,isActive:n?()=>!1:void 0}):(0,u.jsx)(Ce,{...i,mobile:e,label:b,to:y,items:g,isActive:n?()=>!1:void 0})}};function Ie({type:e,...t}){const n=function(e,t){return e&&"default"!==e?e:"items"in t?"dropdown":"default"}(e,t),r=Fe[n];if(!r)throw new Error(`No NavbarItem component found for type "${e}".`);return(0,u.jsx)(r,{...t})}function ze(){const e=(0,T.M)(),t=(0,k.p)().navbar.items;return(0,u.jsx)("ul",{className:"menu__list",children:t.map((t,n)=>(0,r.createElement)(Ie,{mobile:!0,...t,onClick:()=>e.toggle(),key:n}))})}function $e(e){return(0,u.jsx)("button",{...e,type:"button",className:"clean-btn navbar-sidebar__back",children:(0,u.jsx)(s.A,{id:"theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel",description:"The label of the back button to return to main menu, inside the mobile navbar sidebar secondary menu (notably used to display the docs sidebar)",children:"\u2190 Back to main menu"})})}function Ue(){const e=0===(0,k.p)().navbar.items.length,t=B();return(0,u.jsxs)(u.Fragment,{children:[!e&&(0,u.jsx)($e,{onClick:()=>t.hide()}),t.content]})}function qe(){const e=(0,T.M)();return function(e=!0){(0,r.useEffect)(()=>(document.body.style.overflow=e?"hidden":"visible",()=>{document.body.style.overflow="visible"}),[e])}(e.shown),e.shouldRender?(0,u.jsx)(I,{header:(0,u.jsx)(se,{}),primaryMenu:(0,u.jsx)(ze,{}),secondaryMenu:(0,u.jsx)(Ue,{})}):null}const He={navbarHideable:"navbarHideable_m1mJ",navbarHidden:"navbarHidden_jGov"};function Ge(e){return(0,u.jsx)("div",{role:"presentation",...e,className:(0,a.A)("navbar-sidebar__backdrop",e.className)})}function Ve({children:e}){const{navbar:{hideOnScroll:t,style:n}}=(0,k.p)(),o=(0,T.M)(),{navbarRef:i,isNavbarVisible:l}=function(e){const[t,n]=(0,r.useState)(e),a=(0,r.useRef)(!1),o=(0,r.useRef)(0),i=(0,r.useCallback)(e=>{null!==e&&(o.current=e.getBoundingClientRect().height)},[]);return(0,j.Mq)(({scrollY:t},r)=>{if(!e)return;if(t<o.current)return void n(!0);if(a.current)return void(a.current=!1);const i=r?.scrollY,l=document.documentElement.scrollHeight-o.current,s=window.innerHeight;i&&t>=i?n(!1):t+s<l&&n(!0)}),(0,c.$)(t=>{if(!e)return;const r=t.location.hash;if(r?document.getElementById(r.substring(1)):void 0)return a.current=!0,void n(!1);n(!0)}),{navbarRef:i,isNavbarVisible:t}}(t);return(0,u.jsxs)("nav",{ref:i,"aria-label":(0,s.T)({id:"theme.NavBar.navAriaLabel",message:"Main",description:"The ARIA label for the main navigation"}),className:(0,a.A)(g.G.layout.navbar.container,"navbar","navbar--fixed-top",t&&[He.navbarHideable,!l&&He.navbarHidden],{"navbar--dark":"dark"===n,"navbar--primary":"primary"===n,"navbar-sidebar--show":o.shown}),children:[e,(0,u.jsx)(Ge,{onClick:o.toggle}),(0,u.jsx)(qe,{})]})}var We=n(440);const Qe={errorBoundaryError:"errorBoundaryError_a6uf",errorBoundaryFallback:"errorBoundaryFallback_VBag"};function Ke(e){return(0,u.jsx)("button",{type:"button",...e,children:(0,u.jsx)(s.A,{id:"theme.ErrorPageContent.tryAgain",description:"The label of the button to try again rendering when the React error boundary captures an error",children:"Try again"})})}function Ye({error:e}){const t=(0,We.rA)(e).map(e=>e.message).join("\n\nCause:\n");return(0,u.jsx)("p",{className:Qe.errorBoundaryError,children:t})}class Xe extends r.Component{componentDidCatch(e,t){throw this.props.onError(e,t)}render(){return this.props.children}}const Ze="right";function Je({width:e=30,height:t=30,className:n,...r}){return(0,u.jsx)("svg",{className:n,width:e,height:t,viewBox:"0 0 30 30","aria-hidden":"true",...r,children:(0,u.jsx)("path",{stroke:"currentColor",strokeLinecap:"round",strokeMiterlimit:"10",strokeWidth:"2",d:"M4 7h22M4 15h22M4 23h22"})})}function et(){const{toggle:e,shown:t}=(0,T.M)();return(0,u.jsx)("button",{onClick:e,"aria-label":(0,s.T)({id:"theme.docs.sidebar.toggleSidebarButtonAriaLabel",message:"Toggle navigation bar",description:"The ARIA label for hamburger menu button of mobile navigation"}),"aria-expanded":t,className:"navbar__toggle clean-btn",type:"button",children:(0,u.jsx)(Je,{})})}const tt={colorModeToggle:"colorModeToggle_DEke"};function nt({items:e}){return(0,u.jsx)(u.Fragment,{children:e.map((e,t)=>(0,u.jsx)(Xe,{onError:t=>new Error(`A theme navbar item failed to render.\nPlease double-check the following navbar item (themeConfig.navbar.items) of your Docusaurus config:\n${JSON.stringify(e,null,2)}`,{cause:t}),children:(0,u.jsx)(Ie,{...e})},t))})}function rt({left:e,right:t}){return(0,u.jsxs)("div",{className:"navbar__inner",children:[(0,u.jsx)("div",{className:(0,a.A)(g.G.layout.navbar.containerLeft,"navbar__items"),children:e}),(0,u.jsx)("div",{className:(0,a.A)(g.G.layout.navbar.containerRight,"navbar__items navbar__items--right"),children:t})]})}function at(){const e=(0,T.M)(),t=(0,k.p)().navbar.items,[n,r]=function(e){function t(e){return"left"===(e.position??Ze)}return[e.filter(t),e.filter(e=>!t(e))]}(t),a=t.find(e=>"search"===e.type);return(0,u.jsx)(rt,{left:(0,u.jsxs)(u.Fragment,{children:[!e.disabled&&(0,u.jsx)(et,{}),(0,u.jsx)(ie,{}),(0,u.jsx)(nt,{items:n})]}),right:(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)(nt,{items:r}),(0,u.jsx)(ae,{className:tt.colorModeToggle}),!a&&(0,u.jsx)(Pe,{children:(0,u.jsx)(je.A,{})})]})})}function ot(){return(0,u.jsx)(Ve,{children:(0,u.jsx)(at,{})})}function it({item:e}){const{to:t,href:n,label:r,prependBaseUrlToHref:o,className:i,...l}=e,s=(0,ue.Ay)(t),c=(0,ue.Ay)(n,{forcePrependBaseUrl:!0});return(0,u.jsxs)(ce.A,{className:(0,a.A)("footer__link-item",i),...n?{href:o?c:n}:{to:s},...l,children:[r,n&&!(0,de.A)(n)&&(0,u.jsx)(pe.A,{})]})}function lt({item:e}){return e.html?(0,u.jsx)("li",{className:(0,a.A)("footer__item",e.className),dangerouslySetInnerHTML:{__html:e.html}}):(0,u.jsx)("li",{className:"footer__item",children:(0,u.jsx)(it,{item:e})},e.href??e.to)}function st({column:e}){return(0,u.jsxs)("div",{className:(0,a.A)(g.G.layout.footer.column,"col footer__col",e.className),children:[(0,u.jsx)("div",{className:"footer__title",children:e.title}),(0,u.jsx)("ul",{className:"footer__items clean-list",children:e.items.map((e,t)=>(0,u.jsx)(lt,{item:e},t))})]})}function ct({columns:e}){return(0,u.jsx)("div",{className:"row footer__links",children:e.map((e,t)=>(0,u.jsx)(st,{column:e},t))})}function ut(){return(0,u.jsx)("span",{className:"footer__link-separator",children:"\xb7"})}function dt({item:e}){return e.html?(0,u.jsx)("span",{className:(0,a.A)("footer__link-item",e.className),dangerouslySetInnerHTML:{__html:e.html}}):(0,u.jsx)(it,{item:e})}function ft({links:e}){return(0,u.jsx)("div",{className:"footer__links text--center",children:(0,u.jsx)("div",{className:"footer__links",children:e.map((t,n)=>(0,u.jsxs)(r.Fragment,{children:[(0,u.jsx)(dt,{item:t}),e.length!==n+1&&(0,u.jsx)(ut,{})]},n))})})}function pt({links:e}){return function(e){return"title"in e[0]}(e)?(0,u.jsx)(ct,{columns:e}):(0,u.jsx)(ft,{links:e})}var mt=n(1122);const ht="footerLogoLink_BH7S";function gt({logo:e}){const{withBaseUrl:t}=(0,ue.hH)(),n={light:t(e.src),dark:t(e.srcDark??e.src)};return(0,u.jsx)(mt.A,{className:(0,a.A)("footer__logo",e.className),alt:e.alt,sources:n,width:e.width,height:e.height,style:e.style})}function bt({logo:e}){return e.href?(0,u.jsx)(ce.A,{href:e.href,className:ht,target:e.target,children:(0,u.jsx)(gt,{logo:e})}):(0,u.jsx)(gt,{logo:e})}function yt({copyright:e}){return(0,u.jsx)("div",{className:"footer__copyright",dangerouslySetInnerHTML:{__html:e}})}function vt({style:e,links:t,logo:n,copyright:r}){return(0,u.jsx)("footer",{className:(0,a.A)(g.G.layout.footer.container,"footer",{"footer--dark":"dark"===e}),children:(0,u.jsxs)("div",{className:"container container-fluid",children:[t,(n||r)&&(0,u.jsxs)("div",{className:"footer__bottom text--center",children:[n&&(0,u.jsx)("div",{className:"margin-bottom--sm",children:n}),r]})]})})}function kt(){const{footer:e}=(0,k.p)();if(!e)return null;const{copyright:t,links:n,logo:r,style:a}=e;return(0,u.jsx)(vt,{style:a,links:n&&n.length>0&&(0,u.jsx)(pt,{links:n}),logo:r&&(0,u.jsx)(bt,{logo:r}),copyright:t&&(0,u.jsx)(yt,{copyright:t})})}const wt=r.memo(kt),St=(0,M.fM)([z.a,w.o,j.Tv,Re.VQ,i.Jx,function({children:e}){return(0,u.jsx)(P.y_,{children:(0,u.jsx)(T.e,{children:(0,u.jsx)(O,{children:e})})})}]);function xt({children:e}){return(0,u.jsx)(St,{children:e})}var _t=n(1107);function Et({error:e,tryAgain:t}){return(0,u.jsx)("main",{className:"container margin-vert--xl",children:(0,u.jsx)("div",{className:"row",children:(0,u.jsxs)("div",{className:"col col--6 col--offset-3",children:[(0,u.jsx)(_t.A,{as:"h1",className:"hero__title",children:(0,u.jsx)(s.A,{id:"theme.ErrorPageContent.title",description:"The title of the fallback page when the page crashed",children:"This page crashed."})}),(0,u.jsx)("div",{className:"margin-vert--lg",children:(0,u.jsx)(Ke,{onClick:t,className:"button button--primary shadow--lw"})}),(0,u.jsx)("hr",{}),(0,u.jsx)("div",{className:"margin-vert--md",children:(0,u.jsx)(Ye,{error:e})})]})})})}const Ct={mainWrapper:"mainWrapper_z2l0"};function Lt(e){const{children:t,noFooter:n,wrapperClassName:r,title:l,description:s}=e;return(0,b.J)(),(0,u.jsxs)(xt,{children:[(0,u.jsx)(i.be,{title:l,description:s}),(0,u.jsx)(v,{}),(0,u.jsx)(A,{}),(0,u.jsx)(ot,{}),(0,u.jsx)("div",{id:d,className:(0,a.A)(g.G.layout.main.container,g.G.wrapper.main,Ct.mainWrapper,r),children:(0,u.jsx)(o.A,{fallback:e=>(0,u.jsx)(Et,{...e}),children:t})}),!n&&(0,u.jsx)(wt,{})]})}},1682:(e,t,n)=>{"use strict";function r(e){return Array.from(new Set(e))}function a(e,t){const n={};let r=0;for(const a of e){const e=t(a,r);n[e]??=[],n[e].push(a),r+=1}return n}n.d(t,{$z:()=>a,sb:()=>r})},1765:(e,t,n)=>{"use strict";n.d(t,{My:()=>L,f4:()=>ne});var r,a,o,i,l,s,c,u=n(6540),d=n(4164),f=Object.create,p=Object.defineProperty,m=Object.defineProperties,h=Object.getOwnPropertyDescriptor,g=Object.getOwnPropertyDescriptors,b=Object.getOwnPropertyNames,y=Object.getOwnPropertySymbols,v=Object.getPrototypeOf,k=Object.prototype.hasOwnProperty,w=Object.prototype.propertyIsEnumerable,S=(e,t,n)=>t in e?p(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,x=(e,t)=>{for(var n in t||(t={}))k.call(t,n)&&S(e,n,t[n]);if(y)for(var n of y(t))w.call(t,n)&&S(e,n,t[n]);return e},_=(e,t)=>m(e,g(t)),E=(e,t)=>{var n={};for(var r in e)k.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&y)for(var r of y(e))t.indexOf(r)<0&&w.call(e,r)&&(n[r]=e[r]);return n},C=(r={"../../node_modules/.pnpm/prismjs@1.29.0_patch_hash=vrxx3pzkik6jpmgpayxfjunetu/node_modules/prismjs/prism.js"(e,t){var n=function(){var e=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,t=0,n={},r={util:{encode:function e(t){return t instanceof a?new a(t.type,e(t.content),t.alias):Array.isArray(t)?t.map(e):t.replace(/&/g,"&").replace(/</g,"<").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).slice(8,-1)},objId:function(e){return e.__id||Object.defineProperty(e,"__id",{value:++t}),e.__id},clone:function e(t,n){var a,o;switch(n=n||{},r.util.type(t)){case"Object":if(o=r.util.objId(t),n[o])return n[o];for(var i in a={},n[o]=a,t)t.hasOwnProperty(i)&&(a[i]=e(t[i],n));return a;case"Array":return o=r.util.objId(t),n[o]?n[o]:(a=[],n[o]=a,t.forEach(function(t,r){a[r]=e(t,n)}),a);default:return t}},getLanguage:function(t){for(;t;){var n=e.exec(t.className);if(n)return n[1].toLowerCase();t=t.parentElement}return"none"},setLanguage:function(t,n){t.className=t.className.replace(RegExp(e,"gi"),""),t.classList.add("language-"+n)},isActive:function(e,t,n){for(var r="no-"+t;e;){var a=e.classList;if(a.contains(t))return!0;if(a.contains(r))return!1;e=e.parentElement}return!!n}},languages:{plain:n,plaintext:n,text:n,txt:n,extend:function(e,t){var n=r.util.clone(r.languages[e]);for(var a in t)n[a]=t[a];return n},insertBefore:function(e,t,n,a){var o=(a=a||r.languages)[e],i={};for(var l in o)if(o.hasOwnProperty(l)){if(l==t)for(var s in n)n.hasOwnProperty(s)&&(i[s]=n[s]);n.hasOwnProperty(l)||(i[l]=o[l])}var c=a[e];return a[e]=i,r.languages.DFS(r.languages,function(t,n){n===c&&t!=e&&(this[t]=i)}),i},DFS:function e(t,n,a,o){o=o||{};var i=r.util.objId;for(var l in t)if(t.hasOwnProperty(l)){n.call(t,l,t[l],a||l);var s=t[l],c=r.util.type(s);"Object"!==c||o[i(s)]?"Array"!==c||o[i(s)]||(o[i(s)]=!0,e(s,n,l,o)):(o[i(s)]=!0,e(s,n,null,o))}}},plugins:{},highlight:function(e,t,n){var o={code:e,grammar:t,language:n};if(r.hooks.run("before-tokenize",o),!o.grammar)throw new Error('The language "'+o.language+'" has no grammar.');return o.tokens=r.tokenize(o.code,o.grammar),r.hooks.run("after-tokenize",o),a.stringify(r.util.encode(o.tokens),o.language)},tokenize:function(e,t){var n=t.rest;if(n){for(var r in n)t[r]=n[r];delete t.rest}var a=new l;return s(a,a.head,e),i(e,a,t,a.head,0),function(e){for(var t=[],n=e.head.next;n!==e.tail;)t.push(n.value),n=n.next;return t}(a)},hooks:{all:{},add:function(e,t){var n=r.hooks.all;n[e]=n[e]||[],n[e].push(t)},run:function(e,t){var n=r.hooks.all[e];if(n&&n.length)for(var a,o=0;a=n[o++];)a(t)}},Token:a};function a(e,t,n,r){this.type=e,this.content=t,this.alias=n,this.length=0|(r||"").length}function o(e,t,n,r){e.lastIndex=t;var a=e.exec(n);if(a&&r&&a[1]){var o=a[1].length;a.index+=o,a[0]=a[0].slice(o)}return a}function i(e,t,n,l,u,d){for(var f in n)if(n.hasOwnProperty(f)&&n[f]){var p=n[f];p=Array.isArray(p)?p:[p];for(var m=0;m<p.length;++m){if(d&&d.cause==f+","+m)return;var h=p[m],g=h.inside,b=!!h.lookbehind,y=!!h.greedy,v=h.alias;if(y&&!h.pattern.global){var k=h.pattern.toString().match(/[imsuy]*$/)[0];h.pattern=RegExp(h.pattern.source,k+"g")}for(var w=h.pattern||h,S=l.next,x=u;S!==t.tail&&!(d&&x>=d.reach);x+=S.value.length,S=S.next){var _=S.value;if(t.length>e.length)return;if(!(_ instanceof a)){var E,C=1;if(y){if(!(E=o(w,x,e,b))||E.index>=e.length)break;var L=E.index,A=E.index+E[0].length,T=x;for(T+=S.value.length;L>=T;)T+=(S=S.next).value.length;if(x=T-=S.value.length,S.value instanceof a)continue;for(var j=S;j!==t.tail&&(T<A||"string"==typeof j.value);j=j.next)C++,T+=j.value.length;C--,_=e.slice(x,T),E.index-=x}else if(!(E=o(w,0,_,b)))continue;L=E.index;var M=E[0],P=_.slice(0,L),N=_.slice(L+M.length),O=x+_.length;d&&O>d.reach&&(d.reach=O);var R=S.prev;if(P&&(R=s(t,R,P),x+=P.length),c(t,R,C),S=s(t,R,new a(f,g?r.tokenize(M,g):M,v,M)),N&&s(t,S,N),C>1){var B={cause:f+","+m,reach:O};i(e,t,n,S.prev,x,B),d&&B.reach>d.reach&&(d.reach=B.reach)}}}}}}function l(){var e={value:null,prev:null,next:null},t={value:null,prev:e,next:null};e.next=t,this.head=e,this.tail=t,this.length=0}function s(e,t,n){var r=t.next,a={value:n,prev:t,next:r};return t.next=a,r.prev=a,e.length++,a}function c(e,t,n){for(var r=t.next,a=0;a<n&&r!==e.tail;a++)r=r.next;t.next=r,r.prev=t,e.length-=a}return a.stringify=function e(t,n){if("string"==typeof t)return t;if(Array.isArray(t)){var a="";return t.forEach(function(t){a+=e(t,n)}),a}var o={type:t.type,content:e(t.content,n),tag:"span",classes:["token",t.type],attributes:{},language:n},i=t.alias;i&&(Array.isArray(i)?Array.prototype.push.apply(o.classes,i):o.classes.push(i)),r.hooks.run("wrap",o);var l="";for(var s in o.attributes)l+=" "+s+'="'+(o.attributes[s]||"").replace(/"/g,""")+'"';return"<"+o.tag+' class="'+o.classes.join(" ")+'"'+l+">"+o.content+"</"+o.tag+">"},r}();t.exports=n,n.default=n}},function(){return a||(0,r[b(r)[0]])((a={exports:{}}).exports,a),a.exports}),L=((e,t,n)=>(n=null!=e?f(v(e)):{},((e,t,n,r)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let a of b(t))k.call(e,a)||a===n||p(e,a,{get:()=>t[a],enumerable:!(r=h(t,a))||r.enumerable});return e})(!t&&e&&e.__esModule?n:p(n,"default",{value:e,enumerable:!0}),e)))(C());L.languages.markup={comment:{pattern:/<!--(?:(?!<!--)[\s\S])*?-->/,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/<!DOCTYPE(?:[^>"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|<!--(?:[^-]|-(?!->))*-->)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^<!|>$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern:/<!\[CDATA\[[\s\S]*?\]\]>/i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},L.languages.markup.tag.inside["attr-value"].inside.entity=L.languages.markup.entity,L.languages.markup.doctype.inside["internal-subset"].inside=L.languages.markup,L.hooks.add("wrap",function(e){"entity"===e.type&&(e.attributes.title=e.content.replace(/&/,"&"))}),Object.defineProperty(L.languages.markup.tag,"addInlined",{value:function(e,t){var n;(t=((n=((n={})["language-"+t]={pattern:/(^<!\[CDATA\[)[\s\S]+?(?=\]\]>$)/i,lookbehind:!0,inside:L.languages[t]},n.cdata=/^<!\[CDATA\[|\]\]>$/i,{"included-cdata":{pattern:/<!\[CDATA\[[\s\S]*?\]\]>/i,inside:n}}))["language-"+t]={pattern:/[\s\S]+/,inside:L.languages[t]},{}))[e]={pattern:RegExp(/(<__[^>]*>)(?:<!\[CDATA\[(?:[^\]]|\](?!\]>))*\]\]>|(?!<!\[CDATA\[)[\s\S])*?(?=<\/__>)/.source.replace(/__/g,function(){return e}),"i"),lookbehind:!0,greedy:!0,inside:n},L.languages.insertBefore("markup","cdata",t)}}),Object.defineProperty(L.languages.markup.tag,"addAttribute",{value:function(e,t){L.languages.markup.tag.inside["special-attr"].push({pattern:RegExp(/(^|["'\s])/.source+"(?:"+e+")"+/\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))/.source,"i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[t,"language-"+t],inside:L.languages[t]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),L.languages.html=L.languages.markup,L.languages.mathml=L.languages.markup,L.languages.svg=L.languages.markup,L.languages.xml=L.languages.extend("markup",{}),L.languages.ssml=L.languages.xml,L.languages.atom=L.languages.xml,L.languages.rss=L.languages.xml,o=L,i={pattern:/\\[\\(){}[\]^$+*?|.]/,alias:"escape"},s="(?:[^\\\\-]|"+(l=/\\(?:x[\da-fA-F]{2}|u[\da-fA-F]{4}|u\{[\da-fA-F]+\}|0[0-7]{0,2}|[123][0-7]{2}|c[a-zA-Z]|.)/).source+")",s=RegExp(s+"-"+s),c={pattern:/(<|')[^<>']+(?=[>']$)/,lookbehind:!0,alias:"variable"},o.languages.regex={"char-class":{pattern:/((?:^|[^\\])(?:\\\\)*)\[(?:[^\\\]]|\\[\s\S])*\]/,lookbehind:!0,inside:{"char-class-negation":{pattern:/(^\[)\^/,lookbehind:!0,alias:"operator"},"char-class-punctuation":{pattern:/^\[|\]$/,alias:"punctuation"},range:{pattern:s,inside:{escape:l,"range-punctuation":{pattern:/-/,alias:"operator"}}},"special-escape":i,"char-set":{pattern:/\\[wsd]|\\p\{[^{}]+\}/i,alias:"class-name"},escape:l}},"special-escape":i,"char-set":{pattern:/\.|\\[wsd]|\\p\{[^{}]+\}/i,alias:"class-name"},backreference:[{pattern:/\\(?![123][0-7]{2})[1-9]/,alias:"keyword"},{pattern:/\\k<[^<>']+>/,alias:"keyword",inside:{"group-name":c}}],anchor:{pattern:/[$^]|\\[ABbGZz]/,alias:"function"},escape:l,group:[{pattern:/\((?:\?(?:<[^<>']+>|'[^<>']+'|[>:]|<?[=!]|[idmnsuxU]+(?:-[idmnsuxU]+)?:?))?/,alias:"punctuation",inside:{"group-name":c}},{pattern:/\)/,alias:"punctuation"}],quantifier:{pattern:/(?:[+*?]|\{\d+(?:,\d*)?\})[?+]?/,alias:"number"},alternation:{pattern:/\|/,alias:"keyword"}},L.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/},L.languages.javascript=L.languages.extend("clike",{"class-name":[L.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:{pattern:RegExp(/(^|[^\w$])/.source+"(?:"+/NaN|Infinity/.source+"|"+/0[bB][01]+(?:_[01]+)*n?/.source+"|"+/0[oO][0-7]+(?:_[0-7]+)*n?/.source+"|"+/0[xX][\dA-Fa-f]+(?:_[\dA-Fa-f]+)*n?/.source+"|"+/\d+(?:_\d+)*n/.source+"|"+/(?:\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\.\d+(?:_\d+)*)(?:[Ee][+-]?\d+(?:_\d+)*)?/.source+")"+/(?![\w$])/.source),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),L.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,L.languages.insertBefore("javascript","keyword",{regex:{pattern:RegExp(/((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)/.source+/\//.source+"(?:"+/(?:\[(?:[^\]\\\r\n]|\\.)*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}/.source+"|"+/(?:\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.)*\])*\])*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}v[dgimyus]{0,7}/.source+")"+/(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/.source),lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:L.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:L.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:L.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:L.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:L.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),L.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:L.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),L.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),L.languages.markup&&(L.languages.markup.tag.addInlined("script","javascript"),L.languages.markup.tag.addAttribute(/on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)/.source,"javascript")),L.languages.js=L.languages.javascript,L.languages.actionscript=L.languages.extend("javascript",{keyword:/\b(?:as|break|case|catch|class|const|default|delete|do|dynamic|each|else|extends|final|finally|for|function|get|if|implements|import|in|include|instanceof|interface|internal|is|namespace|native|new|null|override|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|use|var|void|while|with)\b/,operator:/\+\+|--|(?:[+\-*\/%^]|&&?|\|\|?|<<?|>>?>?|[!=]=?)=?|[~?@]/}),L.languages.actionscript["class-name"].alias="function",delete L.languages.actionscript.parameter,delete L.languages.actionscript["literal-property"],L.languages.markup&&L.languages.insertBefore("actionscript","string",{xml:{pattern:/(^|[^.])<\/?\w+(?:\s+[^\s>\/=]+=("|')(?:\\[\s\S]|(?!\2)[^\\])*\2)*\s*\/?>/,lookbehind:!0,inside:L.languages.markup}}),function(e){var t=/#(?!\{).+/,n={pattern:/#\{[^}]+\}/,alias:"variable"};e.languages.coffeescript=e.languages.extend("javascript",{comment:t,string:[{pattern:/'(?:\\[\s\S]|[^\\'])*'/,greedy:!0},{pattern:/"(?:\\[\s\S]|[^\\"])*"/,greedy:!0,inside:{interpolation:n}}],keyword:/\b(?:and|break|by|catch|class|continue|debugger|delete|do|each|else|extend|extends|false|finally|for|if|in|instanceof|is|isnt|let|loop|namespace|new|no|not|null|of|off|on|or|own|return|super|switch|then|this|throw|true|try|typeof|undefined|unless|until|when|while|window|with|yes|yield)\b/,"class-member":{pattern:/@(?!\d)\w+/,alias:"variable"}}),e.languages.insertBefore("coffeescript","comment",{"multiline-comment":{pattern:/###[\s\S]+?###/,alias:"comment"},"block-regex":{pattern:/\/{3}[\s\S]*?\/{3}/,alias:"regex",inside:{comment:t,interpolation:n}}}),e.languages.insertBefore("coffeescript","string",{"inline-javascript":{pattern:/`(?:\\[\s\S]|[^\\`])*`/,inside:{delimiter:{pattern:/^`|`$/,alias:"punctuation"},script:{pattern:/[\s\S]+/,alias:"language-javascript",inside:e.languages.javascript}}},"multiline-string":[{pattern:/'''[\s\S]*?'''/,greedy:!0,alias:"string"},{pattern:/"""[\s\S]*?"""/,greedy:!0,alias:"string",inside:{interpolation:n}}]}),e.languages.insertBefore("coffeescript","keyword",{property:/(?!\d)\w+(?=\s*:(?!:))/}),delete e.languages.coffeescript["template-string"],e.languages.coffee=e.languages.coffeescript}(L),function(e){var t=e.languages.javadoclike={parameter:{pattern:/(^[\t ]*(?:\/{3}|\*|\/\*\*)\s*@(?:arg|arguments|param)\s+)\w+/m,lookbehind:!0},keyword:{pattern:/(^[\t ]*(?:\/{3}|\*|\/\*\*)\s*|\{)@[a-z][a-zA-Z-]+\b/m,lookbehind:!0},punctuation:/[{}]/};Object.defineProperty(t,"addSupport",{value:function(t,n){(t="string"==typeof t?[t]:t).forEach(function(t){var r=function(e){e.inside||(e.inside={}),e.inside.rest=n},a="doc-comment";if(o=e.languages[t]){var o,i=o[a];if((i=i||(o=e.languages.insertBefore(t,"comment",{"doc-comment":{pattern:/(^|[^\\])\/\*\*[^/][\s\S]*?(?:\*\/|$)/,lookbehind:!0,alias:"comment"}}))[a])instanceof RegExp&&(i=o[a]={pattern:i}),Array.isArray(i))for(var l=0,s=i.length;l<s;l++)i[l]instanceof RegExp&&(i[l]={pattern:i[l]}),r(i[l]);else r(i)}})}}),t.addSupport(["java","javascript","php"],t)}(L),function(e){var t=/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;(t=(e.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:RegExp("@[\\w-](?:"+/[^;{\s"']|\s+(?!\s)/.source+"|"+t.source+")*?"+/(?:;|(?=\s*\{))/.source),inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+t.source+"|"+/(?:[^\\\r\n()"']|\\[\s\S])*/.source+")\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+t.source+"$"),alias:"url"}}},selector:{pattern:RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+t.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:t,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},e.languages.css.atrule.inside.rest=e.languages.css,e.languages.markup))&&(t.tag.addInlined("style","css"),t.tag.addAttribute("style","css"))}(L),function(e){var t=/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,n=(t=(e.languages.css.selector={pattern:e.languages.css.selector.pattern,lookbehind:!0,inside:t={"pseudo-element":/:(?:after|before|first-letter|first-line|selection)|::[-\w]+/,"pseudo-class":/:[-\w]+/,class:/\.[-\w]+/,id:/#[-\w]+/,attribute:{pattern:RegExp("\\[(?:[^[\\]\"']|"+t.source+")*\\]"),greedy:!0,inside:{punctuation:/^\[|\]$/,"case-sensitivity":{pattern:/(\s)[si]$/i,lookbehind:!0,alias:"keyword"},namespace:{pattern:/^(\s*)(?:(?!\s)[-*\w\xA0-\uFFFF])*\|(?!=)/,lookbehind:!0,inside:{punctuation:/\|$/}},"attr-name":{pattern:/^(\s*)(?:(?!\s)[-\w\xA0-\uFFFF])+/,lookbehind:!0},"attr-value":[t,{pattern:/(=\s*)(?:(?!\s)[-\w\xA0-\uFFFF])+(?=\s*$)/,lookbehind:!0}],operator:/[|~*^$]?=/}},"n-th":[{pattern:/(\(\s*)[+-]?\d*[\dn](?:\s*[+-]\s*\d+)?(?=\s*\))/,lookbehind:!0,inside:{number:/[\dn]+/,operator:/[+-]/}},{pattern:/(\(\s*)(?:even|odd)(?=\s*\))/i,lookbehind:!0}],combinator:/>|\+|~|\|\|/,punctuation:/[(),]/}},e.languages.css.atrule.inside["selector-function-argument"].inside=t,e.languages.insertBefore("css","property",{variable:{pattern:/(^|[^-\w\xA0-\uFFFF])--(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*/i,lookbehind:!0}}),{pattern:/(\b\d+)(?:%|[a-z]+(?![\w-]))/,lookbehind:!0}),{pattern:/(^|[^\w.-])-?(?:\d+(?:\.\d+)?|\.\d+)/,lookbehind:!0});e.languages.insertBefore("css","function",{operator:{pattern:/(\s)[+\-*\/](?=\s)/,lookbehind:!0},hexcode:{pattern:/\B#[\da-f]{3,8}\b/i,alias:"color"},color:[{pattern:/(^|[^\w-])(?:AliceBlue|AntiqueWhite|Aqua|Aquamarine|Azure|Beige|Bisque|Black|BlanchedAlmond|Blue|BlueViolet|Brown|BurlyWood|CadetBlue|Chartreuse|Chocolate|Coral|CornflowerBlue|Cornsilk|Crimson|Cyan|DarkBlue|DarkCyan|DarkGoldenRod|DarkGr[ae]y|DarkGreen|DarkKhaki|DarkMagenta|DarkOliveGreen|DarkOrange|DarkOrchid|DarkRed|DarkSalmon|DarkSeaGreen|DarkSlateBlue|DarkSlateGr[ae]y|DarkTurquoise|DarkViolet|DeepPink|DeepSkyBlue|DimGr[ae]y|DodgerBlue|FireBrick|FloralWhite|ForestGreen|Fuchsia|Gainsboro|GhostWhite|Gold|GoldenRod|Gr[ae]y|Green|GreenYellow|HoneyDew|HotPink|IndianRed|Indigo|Ivory|Khaki|Lavender|LavenderBlush|LawnGreen|LemonChiffon|LightBlue|LightCoral|LightCyan|LightGoldenRodYellow|LightGr[ae]y|LightGreen|LightPink|LightSalmon|LightSeaGreen|LightSkyBlue|LightSlateGr[ae]y|LightSteelBlue|LightYellow|Lime|LimeGreen|Linen|Magenta|Maroon|MediumAquaMarine|MediumBlue|MediumOrchid|MediumPurple|MediumSeaGreen|MediumSlateBlue|MediumSpringGreen|MediumTurquoise|MediumVioletRed|MidnightBlue|MintCream|MistyRose|Moccasin|NavajoWhite|Navy|OldLace|Olive|OliveDrab|Orange|OrangeRed|Orchid|PaleGoldenRod|PaleGreen|PaleTurquoise|PaleVioletRed|PapayaWhip|PeachPuff|Peru|Pink|Plum|PowderBlue|Purple|RebeccaPurple|Red|RosyBrown|RoyalBlue|SaddleBrown|Salmon|SandyBrown|SeaGreen|SeaShell|Sienna|Silver|SkyBlue|SlateBlue|SlateGr[ae]y|Snow|SpringGreen|SteelBlue|Tan|Teal|Thistle|Tomato|Transparent|Turquoise|Violet|Wheat|White|WhiteSmoke|Yellow|YellowGreen)(?![\w-])/i,lookbehind:!0},{pattern:/\b(?:hsl|rgb)\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*\)\B|\b(?:hsl|rgb)a\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*,\s*(?:0|0?\.\d+|1)\s*\)\B/i,inside:{unit:t,number:n,function:/[\w-]+(?=\()/,punctuation:/[(),]/}}],entity:/\\[\da-f]{1,8}/i,unit:t,number:n})}(L),function(e){var t=/[*&][^\s[\]{},]+/,n=/!(?:<[\w\-%#;/?:@&=+$,.!~*'()[\]]+>|(?:[a-zA-Z\d-]*!)?[\w\-%#;/?:@&=+$.~*'()]+)?/,r="(?:"+n.source+"(?:[ \t]+"+t.source+")?|"+t.source+"(?:[ \t]+"+n.source+")?)",a=/(?:[^\s\x00-\x08\x0e-\x1f!"#%&'*,\-:>?@[\]`{|}\x7f-\x84\x86-\x9f\ud800-\udfff\ufffe\uffff]|[?:-]<PLAIN>)(?:[ \t]*(?:(?![#:])<PLAIN>|:<PLAIN>))*/.source.replace(/<PLAIN>/g,function(){return/[^\s\x00-\x08\x0e-\x1f,[\]{}\x7f-\x84\x86-\x9f\ud800-\udfff\ufffe\uffff]/.source}),o=/"(?:[^"\\\r\n]|\\.)*"|'(?:[^'\\\r\n]|\\.)*'/.source;function i(e,t){t=(t||"").replace(/m/g,"")+"m";var n=/([:\-,[{]\s*(?:\s<<prop>>[ \t]+)?)(?:<<value>>)(?=[ \t]*(?:$|,|\]|\}|(?:[\r\n]\s*)?#))/.source.replace(/<<prop>>/g,function(){return r}).replace(/<<value>>/g,function(){return e});return RegExp(n,t)}e.languages.yaml={scalar:{pattern:RegExp(/([\-:]\s*(?:\s<<prop>>[ \t]+)?[|>])[ \t]*(?:((?:\r?\n|\r)[ \t]+)\S[^\r\n]*(?:\2[^\r\n]+)*)/.source.replace(/<<prop>>/g,function(){return r})),lookbehind:!0,alias:"string"},comment:/#.*/,key:{pattern:RegExp(/((?:^|[:\-,[{\r\n?])[ \t]*(?:<<prop>>[ \t]+)?)<<key>>(?=\s*:\s)/.source.replace(/<<prop>>/g,function(){return r}).replace(/<<key>>/g,function(){return"(?:"+a+"|"+o+")"})),lookbehind:!0,greedy:!0,alias:"atrule"},directive:{pattern:/(^[ \t]*)%.+/m,lookbehind:!0,alias:"important"},datetime:{pattern:i(/\d{4}-\d\d?-\d\d?(?:[tT]|[ \t]+)\d\d?:\d{2}:\d{2}(?:\.\d*)?(?:[ \t]*(?:Z|[-+]\d\d?(?::\d{2})?))?|\d{4}-\d{2}-\d{2}|\d\d?:\d{2}(?::\d{2}(?:\.\d*)?)?/.source),lookbehind:!0,alias:"number"},boolean:{pattern:i(/false|true/.source,"i"),lookbehind:!0,alias:"important"},null:{pattern:i(/null|~/.source,"i"),lookbehind:!0,alias:"important"},string:{pattern:i(o),lookbehind:!0,greedy:!0},number:{pattern:i(/[+-]?(?:0x[\da-f]+|0o[0-7]+|(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?|\.inf|\.nan)/.source,"i"),lookbehind:!0},tag:n,important:t,punctuation:/---|[:[\]{}\-,|>?]|\.\.\./},e.languages.yml=e.languages.yaml}(L),function(e){var t=/(?:\\.|[^\\\n\r]|(?:\n|\r\n?)(?![\r\n]))/.source;function n(e){return e=e.replace(/<inner>/g,function(){return t}),RegExp(/((?:^|[^\\])(?:\\{2})*)/.source+"(?:"+e+")")}var r=/(?:\\.|``(?:[^`\r\n]|`(?!`))+``|`[^`\r\n]+`|[^\\|\r\n`])+/.source,a=/\|?__(?:\|__)+\|?(?:(?:\n|\r\n?)|(?![\s\S]))/.source.replace(/__/g,function(){return r}),o=/\|?[ \t]*:?-{3,}:?[ \t]*(?:\|[ \t]*:?-{3,}:?[ \t]*)+\|?(?:\n|\r\n?)/.source,i=(e.languages.markdown=e.languages.extend("markup",{}),e.languages.insertBefore("markdown","prolog",{"front-matter-block":{pattern:/(^(?:\s*[\r\n])?)---(?!.)[\s\S]*?[\r\n]---(?!.)/,lookbehind:!0,greedy:!0,inside:{punctuation:/^---|---$/,"front-matter":{pattern:/\S+(?:\s+\S+)*/,alias:["yaml","language-yaml"],inside:e.languages.yaml}}},blockquote:{pattern:/^>(?:[\t ]*>)*/m,alias:"punctuation"},table:{pattern:RegExp("^"+a+o+"(?:"+a+")*","m"),inside:{"table-data-rows":{pattern:RegExp("^("+a+o+")(?:"+a+")*$"),lookbehind:!0,inside:{"table-data":{pattern:RegExp(r),inside:e.languages.markdown},punctuation:/\|/}},"table-line":{pattern:RegExp("^("+a+")"+o+"$"),lookbehind:!0,inside:{punctuation:/\||:?-{3,}:?/}},"table-header-row":{pattern:RegExp("^"+a+"$"),inside:{"table-header":{pattern:RegExp(r),alias:"important",inside:e.languages.markdown},punctuation:/\|/}}}},code:[{pattern:/((?:^|\n)[ \t]*\n|(?:^|\r\n?)[ \t]*\r\n?)(?: {4}|\t).+(?:(?:\n|\r\n?)(?: {4}|\t).+)*/,lookbehind:!0,alias:"keyword"},{pattern:/^```[\s\S]*?^```$/m,greedy:!0,inside:{"code-block":{pattern:/^(```.*(?:\n|\r\n?))[\s\S]+?(?=(?:\n|\r\n?)^```$)/m,lookbehind:!0},"code-language":{pattern:/^(```).+/,lookbehind:!0},punctuation:/```/}}],title:[{pattern:/\S.*(?:\n|\r\n?)(?:==+|--+)(?=[ \t]*$)/m,alias:"important",inside:{punctuation:/==+$|--+$/}},{pattern:/(^\s*)#.+/m,lookbehind:!0,alias:"important",inside:{punctuation:/^#+|#+$/}}],hr:{pattern:/(^\s*)([*-])(?:[\t ]*\2){2,}(?=\s*$)/m,lookbehind:!0,alias:"punctuation"},list:{pattern:/(^\s*)(?:[*+-]|\d+\.)(?=[\t ].)/m,lookbehind:!0,alias:"punctuation"},"url-reference":{pattern:/!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:\\.|[^>\\])+>)(?:[\t ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\)))?/,inside:{variable:{pattern:/^(!?\[)[^\]]+/,lookbehind:!0},string:/(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\))$/,punctuation:/^[\[\]!:]|[<>]/},alias:"url"},bold:{pattern:n(/\b__(?:(?!_)<inner>|_(?:(?!_)<inner>)+_)+__\b|\*\*(?:(?!\*)<inner>|\*(?:(?!\*)<inner>)+\*)+\*\*/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^..)[\s\S]+(?=..$)/,lookbehind:!0,inside:{}},punctuation:/\*\*|__/}},italic:{pattern:n(/\b_(?:(?!_)<inner>|__(?:(?!_)<inner>)+__)+_\b|\*(?:(?!\*)<inner>|\*\*(?:(?!\*)<inner>)+\*\*)+\*/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^.)[\s\S]+(?=.$)/,lookbehind:!0,inside:{}},punctuation:/[*_]/}},strike:{pattern:n(/(~~?)(?:(?!~)<inner>)+\2/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^~~?)[\s\S]+(?=\1$)/,lookbehind:!0,inside:{}},punctuation:/~~?/}},"code-snippet":{pattern:/(^|[^\\`])(?:``[^`\r\n]+(?:`[^`\r\n]+)*``(?!`)|`[^`\r\n]+`(?!`))/,lookbehind:!0,greedy:!0,alias:["code","keyword"]},url:{pattern:n(/!?\[(?:(?!\])<inner>)+\](?:\([^\s)]+(?:[\t ]+"(?:\\.|[^"\\])*")?\)|[ \t]?\[(?:(?!\])<inner>)+\])/.source),lookbehind:!0,greedy:!0,inside:{operator:/^!/,content:{pattern:/(^\[)[^\]]+(?=\])/,lookbehind:!0,inside:{}},variable:{pattern:/(^\][ \t]?\[)[^\]]+(?=\]$)/,lookbehind:!0},url:{pattern:/(^\]\()[^\s)]+/,lookbehind:!0},string:{pattern:/(^[ \t]+)"(?:\\.|[^"\\])*"(?=\)$)/,lookbehind:!0}}}}),["url","bold","italic","strike"].forEach(function(t){["url","bold","italic","strike","code-snippet"].forEach(function(n){t!==n&&(e.languages.markdown[t].inside.content.inside[n]=e.languages.markdown[n])})}),e.hooks.add("after-tokenize",function(e){"markdown"!==e.language&&"md"!==e.language||function e(t){if(t&&"string"!=typeof t)for(var n=0,r=t.length;n<r;n++){var a,o=t[n];"code"!==o.type?e(o.content):(a=o.content[1],o=o.content[3],a&&o&&"code-language"===a.type&&"code-block"===o.type&&"string"==typeof a.content&&(a=a.content.replace(/\b#/g,"sharp").replace(/\b\+\+/g,"pp"),a="language-"+(a=(/[a-z][\w-]*/i.exec(a)||[""])[0].toLowerCase()),o.alias?"string"==typeof o.alias?o.alias=[o.alias,a]:o.alias.push(a):o.alias=[a]))}}(e.tokens)}),e.hooks.add("wrap",function(t){if("code-block"===t.type){for(var n="",r=0,a=t.classes.length;r<a;r++){var o=t.classes[r];if(o=/language-(.+)/.exec(o)){n=o[1];break}}var c,u=e.languages[n];u?t.content=e.highlight(t.content.replace(i,"").replace(/&(\w{1,8}|#x?[\da-f]{1,8});/gi,function(e,t){var n;return"#"===(t=t.toLowerCase())[0]?(n="x"===t[1]?parseInt(t.slice(2),16):Number(t.slice(1)),s(n)):l[t]||e}),u,n):n&&"none"!==n&&e.plugins.autoloader&&(c="md-"+(new Date).valueOf()+"-"+Math.floor(1e16*Math.random()),t.attributes.id=c,e.plugins.autoloader.loadLanguages(n,function(){var t=document.getElementById(c);t&&(t.innerHTML=e.highlight(t.textContent,e.languages[n],n))}))}}),RegExp(e.languages.markup.tag.pattern.source,"gi")),l={amp:"&",lt:"<",gt:">",quot:'"'},s=String.fromCodePoint||String.fromCharCode;e.languages.md=e.languages.markdown}(L),L.languages.graphql={comment:/#.*/,description:{pattern:/(?:"""(?:[^"]|(?!""")")*"""|"(?:\\.|[^\\"\r\n])*")(?=\s*[a-z_])/i,greedy:!0,alias:"string",inside:{"language-markdown":{pattern:/(^"(?:"")?)(?!\1)[\s\S]+(?=\1$)/,lookbehind:!0,inside:L.languages.markdown}}},string:{pattern:/"""(?:[^"]|(?!""")")*"""|"(?:\\.|[^\\"\r\n])*"/,greedy:!0},number:/(?:\B-|\b)\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,boolean:/\b(?:false|true)\b/,variable:/\$[a-z_]\w*/i,directive:{pattern:/@[a-z_]\w*/i,alias:"function"},"attr-name":{pattern:/\b[a-z_]\w*(?=\s*(?:\((?:[^()"]|"(?:\\.|[^\\"\r\n])*")*\))?:)/i,greedy:!0},"atom-input":{pattern:/\b[A-Z]\w*Input\b/,alias:"class-name"},scalar:/\b(?:Boolean|Float|ID|Int|String)\b/,constant:/\b[A-Z][A-Z_\d]*\b/,"class-name":{pattern:/(\b(?:enum|implements|interface|on|scalar|type|union)\s+|&\s*|:\s*|\[)[A-Z_]\w*/,lookbehind:!0},fragment:{pattern:/(\bfragment\s+|\.{3}\s*(?!on\b))[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},"definition-mutation":{pattern:/(\bmutation\s+)[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},"definition-query":{pattern:/(\bquery\s+)[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},keyword:/\b(?:directive|enum|extend|fragment|implements|input|interface|mutation|on|query|repeatable|scalar|schema|subscription|type|union)\b/,operator:/[!=|&]|\.{3}/,"property-query":/\w+(?=\s*\()/,object:/\w+(?=\s*\{)/,punctuation:/[!(){}\[\]:=,]/,property:/\w+/},L.hooks.add("after-tokenize",function(e){if("graphql"===e.language)for(var t=e.tokens.filter(function(e){return"string"!=typeof e&&"comment"!==e.type&&"scalar"!==e.type}),n=0;n<t.length;){var r=t[n++];if("keyword"===r.type&&"mutation"===r.content){var a=[];if(d(["definition-mutation","punctuation"])&&"("===u(1).content){n+=2;var o=f(/^\($/,/^\)$/);if(-1===o)continue;for(;n<o;n++){var i=u(0);"variable"===i.type&&(p(i,"variable-input"),a.push(i.content))}n=o+1}if(d(["punctuation","property-query"])&&"{"===u(0).content&&(n++,p(u(0),"property-mutation"),0<a.length)){var l=f(/^\{$/,/^\}$/);if(-1!==l)for(var s=n;s<l;s++){var c=t[s];"variable"===c.type&&0<=a.indexOf(c.content)&&p(c,"variable-input")}}}}function u(e){return t[n+e]}function d(e,t){t=t||0;for(var n=0;n<e.length;n++){var r=u(n+t);if(!r||r.type!==e[n])return}return 1}function f(e,r){for(var a=1,o=n;o<t.length;o++){var i=t[o],l=i.content;if("punctuation"===i.type&&"string"==typeof l)if(e.test(l))a++;else if(r.test(l)&&0===--a)return o}return-1}function p(e,t){var n=e.alias;n?Array.isArray(n)||(e.alias=n=[n]):e.alias=n=[],n.push(t)}}),L.languages.sql={comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|(?:--|\/\/|#).*)/,lookbehind:!0},variable:[{pattern:/@(["'`])(?:\\[\s\S]|(?!\1)[^\\])+\1/,greedy:!0},/@[\w.$]+/],string:{pattern:/(^|[^@\\])("|')(?:\\[\s\S]|(?!\2)[^\\]|\2\2)*\2/,greedy:!0,lookbehind:!0},identifier:{pattern:/(^|[^@\\])`(?:\\[\s\S]|[^`\\]|``)*`/,greedy:!0,lookbehind:!0,inside:{punctuation:/^`|`$/}},function:/\b(?:AVG|COUNT|FIRST|FORMAT|LAST|LCASE|LEN|MAX|MID|MIN|MOD|NOW|ROUND|SUM|UCASE)(?=\s*\()/i,keyword:/\b(?:ACTION|ADD|AFTER|ALGORITHM|ALL|ALTER|ANALYZE|ANY|APPLY|AS|ASC|AUTHORIZATION|AUTO_INCREMENT|BACKUP|BDB|BEGIN|BERKELEYDB|BIGINT|BINARY|BIT|BLOB|BOOL|BOOLEAN|BREAK|BROWSE|BTREE|BULK|BY|CALL|CASCADED?|CASE|CHAIN|CHAR(?:ACTER|SET)?|CHECK(?:POINT)?|CLOSE|CLUSTERED|COALESCE|COLLATE|COLUMNS?|COMMENT|COMMIT(?:TED)?|COMPUTE|CONNECT|CONSISTENT|CONSTRAINT|CONTAINS(?:TABLE)?|CONTINUE|CONVERT|CREATE|CROSS|CURRENT(?:_DATE|_TIME|_TIMESTAMP|_USER)?|CURSOR|CYCLE|DATA(?:BASES?)?|DATE(?:TIME)?|DAY|DBCC|DEALLOCATE|DEC|DECIMAL|DECLARE|DEFAULT|DEFINER|DELAYED|DELETE|DELIMITERS?|DENY|DESC|DESCRIBE|DETERMINISTIC|DISABLE|DISCARD|DISK|DISTINCT|DISTINCTROW|DISTRIBUTED|DO|DOUBLE|DROP|DUMMY|DUMP(?:FILE)?|DUPLICATE|ELSE(?:IF)?|ENABLE|ENCLOSED|END|ENGINE|ENUM|ERRLVL|ERRORS|ESCAPED?|EXCEPT|EXEC(?:UTE)?|EXISTS|EXIT|EXPLAIN|EXTENDED|FETCH|FIELDS|FILE|FILLFACTOR|FIRST|FIXED|FLOAT|FOLLOWING|FOR(?: EACH ROW)?|FORCE|FOREIGN|FREETEXT(?:TABLE)?|FROM|FULL|FUNCTION|GEOMETRY(?:COLLECTION)?|GLOBAL|GOTO|GRANT|GROUP|HANDLER|HASH|HAVING|HOLDLOCK|HOUR|IDENTITY(?:COL|_INSERT)?|IF|IGNORE|IMPORT|INDEX|INFILE|INNER|INNODB|INOUT|INSERT|INT|INTEGER|INTERSECT|INTERVAL|INTO|INVOKER|ISOLATION|ITERATE|JOIN|KEYS?|KILL|LANGUAGE|LAST|LEAVE|LEFT|LEVEL|LIMIT|LINENO|LINES|LINESTRING|LOAD|LOCAL|LOCK|LONG(?:BLOB|TEXT)|LOOP|MATCH(?:ED)?|MEDIUM(?:BLOB|INT|TEXT)|MERGE|MIDDLEINT|MINUTE|MODE|MODIFIES|MODIFY|MONTH|MULTI(?:LINESTRING|POINT|POLYGON)|NATIONAL|NATURAL|NCHAR|NEXT|NO|NONCLUSTERED|NULLIF|NUMERIC|OFF?|OFFSETS?|ON|OPEN(?:DATASOURCE|QUERY|ROWSET)?|OPTIMIZE|OPTION(?:ALLY)?|ORDER|OUT(?:ER|FILE)?|OVER|PARTIAL|PARTITION|PERCENT|PIVOT|PLAN|POINT|POLYGON|PRECEDING|PRECISION|PREPARE|PREV|PRIMARY|PRINT|PRIVILEGES|PROC(?:EDURE)?|PUBLIC|PURGE|QUICK|RAISERROR|READS?|REAL|RECONFIGURE|REFERENCES|RELEASE|RENAME|REPEAT(?:ABLE)?|REPLACE|REPLICATION|REQUIRE|RESIGNAL|RESTORE|RESTRICT|RETURN(?:ING|S)?|REVOKE|RIGHT|ROLLBACK|ROUTINE|ROW(?:COUNT|GUIDCOL|S)?|RTREE|RULE|SAVE(?:POINT)?|SCHEMA|SECOND|SELECT|SERIAL(?:IZABLE)?|SESSION(?:_USER)?|SET(?:USER)?|SHARE|SHOW|SHUTDOWN|SIMPLE|SMALLINT|SNAPSHOT|SOME|SONAME|SQL|START(?:ING)?|STATISTICS|STATUS|STRIPED|SYSTEM_USER|TABLES?|TABLESPACE|TEMP(?:ORARY|TABLE)?|TERMINATED|TEXT(?:SIZE)?|THEN|TIME(?:STAMP)?|TINY(?:BLOB|INT|TEXT)|TOP?|TRAN(?:SACTIONS?)?|TRIGGER|TRUNCATE|TSEQUAL|TYPES?|UNBOUNDED|UNCOMMITTED|UNDEFINED|UNION|UNIQUE|UNLOCK|UNPIVOT|UNSIGNED|UPDATE(?:TEXT)?|USAGE|USE|USER|USING|VALUES?|VAR(?:BINARY|CHAR|CHARACTER|YING)|VIEW|WAITFOR|WARNINGS|WHEN|WHERE|WHILE|WITH(?: ROLLUP|IN)?|WORK|WRITE(?:TEXT)?|YEAR)\b/i,boolean:/\b(?:FALSE|NULL|TRUE)\b/i,number:/\b0x[\da-f]+\b|\b\d+(?:\.\d*)?|\B\.\d+\b/i,operator:/[-+*\/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?|\b(?:AND|BETWEEN|DIV|ILIKE|IN|IS|LIKE|NOT|OR|REGEXP|RLIKE|SOUNDS LIKE|XOR)\b/i,punctuation:/[;[\]()`,.]/},function(e){var t=e.languages.javascript["template-string"],n=t.pattern.source,r=t.inside.interpolation,a=r.inside["interpolation-punctuation"],o=r.pattern.source;function i(t,r){if(e.languages[t])return{pattern:RegExp("((?:"+r+")\\s*)"+n),lookbehind:!0,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},"embedded-code":{pattern:/[\s\S]+/,alias:t}}}}function l(t,n,r){return t={code:t,grammar:n,language:r},e.hooks.run("before-tokenize",t),t.tokens=e.tokenize(t.code,t.grammar),e.hooks.run("after-tokenize",t),t.tokens}function s(t,n,i){var s=e.tokenize(t,{interpolation:{pattern:RegExp(o),lookbehind:!0}}),c=0,u={},d=(s=l(s.map(function(e){if("string"==typeof e)return e;var n,r;for(e=e.content;-1!==t.indexOf((r=c++,n="___"+i.toUpperCase()+"_"+r+"___")););return u[n]=e,n}).join(""),n,i),Object.keys(u));return c=0,function t(n){for(var o=0;o<n.length;o++){if(c>=d.length)return;var i,s,f,p,m,h,g,b=n[o];"string"==typeof b||"string"==typeof b.content?(i=d[c],-1!==(g=(h="string"==typeof b?b:b.content).indexOf(i))&&(++c,s=h.substring(0,g),m=u[i],f=void 0,(p={})["interpolation-punctuation"]=a,3===(p=e.tokenize(m,p)).length&&((f=[1,1]).push.apply(f,l(p[1],e.languages.javascript,"javascript")),p.splice.apply(p,f)),f=new e.Token("interpolation",p,r.alias,m),p=h.substring(g+i.length),m=[],s&&m.push(s),m.push(f),p&&(t(h=[p]),m.push.apply(m,h)),"string"==typeof b?(n.splice.apply(n,[o,1].concat(m)),o+=m.length-1):b.content=m)):(g=b.content,Array.isArray(g)?t(g):t([g]))}}(s),new e.Token(i,s,"language-"+i,t)}e.languages.javascript["template-string"]=[i("css",/\b(?:styled(?:\([^)]*\))?(?:\s*\.\s*\w+(?:\([^)]*\))*)*|css(?:\s*\.\s*(?:global|resolve))?|createGlobalStyle|keyframes)/.source),i("html",/\bhtml|\.\s*(?:inner|outer)HTML\s*\+?=/.source),i("svg",/\bsvg/.source),i("markdown",/\b(?:markdown|md)/.source),i("graphql",/\b(?:gql|graphql(?:\s*\.\s*experimental)?)/.source),i("sql",/\bsql/.source),t].filter(Boolean);var c={javascript:!0,js:!0,typescript:!0,ts:!0,jsx:!0,tsx:!0};function u(e){return"string"==typeof e?e:Array.isArray(e)?e.map(u).join(""):u(e.content)}e.hooks.add("after-tokenize",function(t){t.language in c&&function t(n){for(var r=0,a=n.length;r<a;r++){var o,i,l,c=n[r];"string"!=typeof c&&(o=c.content,Array.isArray(o)?"template-string"===c.type?(c=o[1],3===o.length&&"string"!=typeof c&&"embedded-code"===c.type&&(i=u(c),c=c.alias,c=Array.isArray(c)?c[0]:c,l=e.languages[c])&&(o[1]=s(i,l,c))):t(o):"string"!=typeof o&&t([o]))}}(t.tokens)})}(L),function(e){e.languages.typescript=e.languages.extend("javascript",{"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|type)\s+)(?!keyof\b)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?:\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>)?/,lookbehind:!0,greedy:!0,inside:null},builtin:/\b(?:Array|Function|Promise|any|boolean|console|never|number|string|symbol|unknown)\b/}),e.languages.typescript.keyword.push(/\b(?:abstract|declare|is|keyof|readonly|require)\b/,/\b(?:asserts|infer|interface|module|namespace|type)\b(?=\s*(?:[{_$a-zA-Z\xA0-\uFFFF]|$))/,/\btype\b(?=\s*(?:[\{*]|$))/),delete e.languages.typescript.parameter,delete e.languages.typescript["literal-property"];var t=e.languages.extend("typescript",{});delete t["class-name"],e.languages.typescript["class-name"].inside=t,e.languages.insertBefore("typescript","function",{decorator:{pattern:/@[$\w\xA0-\uFFFF]+/,inside:{at:{pattern:/^@/,alias:"operator"},function:/^[\s\S]+/}},"generic-function":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>(?=\s*\()/,greedy:!0,inside:{function:/^#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:t}}}}),e.languages.ts=e.languages.typescript}(L),function(e){var t=e.languages.javascript,n=/\{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})+\}/.source,r="(@(?:arg|argument|param|property)\\s+(?:"+n+"\\s+)?)";e.languages.jsdoc=e.languages.extend("javadoclike",{parameter:{pattern:RegExp(r+/(?:(?!\s)[$\w\xA0-\uFFFF.])+(?=\s|$)/.source),lookbehind:!0,inside:{punctuation:/\./}}}),e.languages.insertBefore("jsdoc","keyword",{"optional-parameter":{pattern:RegExp(r+/\[(?:(?!\s)[$\w\xA0-\uFFFF.])+(?:=[^[\]]+)?\](?=\s|$)/.source),lookbehind:!0,inside:{parameter:{pattern:/(^\[)[$\w\xA0-\uFFFF\.]+/,lookbehind:!0,inside:{punctuation:/\./}},code:{pattern:/(=)[\s\S]*(?=\]$)/,lookbehind:!0,inside:t,alias:"language-javascript"},punctuation:/[=[\]]/}},"class-name":[{pattern:RegExp(/(@(?:augments|class|extends|interface|memberof!?|template|this|typedef)\s+(?:<TYPE>\s+)?)[A-Z]\w*(?:\.[A-Z]\w*)*/.source.replace(/<TYPE>/g,function(){return n})),lookbehind:!0,inside:{punctuation:/\./}},{pattern:RegExp("(@[a-z]+\\s+)"+n),lookbehind:!0,inside:{string:t.string,number:t.number,boolean:t.boolean,keyword:e.languages.typescript.keyword,operator:/=>|\.\.\.|[&|?:*]/,punctuation:/[.,;=<>{}()[\]]/}}],example:{pattern:/(@example\s+(?!\s))(?:[^@\s]|\s+(?!\s))+?(?=\s*(?:\*\s*)?(?:@\w|\*\/))/,lookbehind:!0,inside:{code:{pattern:/^([\t ]*(?:\*\s*)?)\S.*$/m,lookbehind:!0,inside:t,alias:"language-javascript"}}}}),e.languages.javadoclike.addSupport("javascript",e.languages.jsdoc)}(L),function(e){e.languages.flow=e.languages.extend("javascript",{}),e.languages.insertBefore("flow","keyword",{type:[{pattern:/\b(?:[Bb]oolean|Function|[Nn]umber|[Ss]tring|[Ss]ymbol|any|mixed|null|void)\b/,alias:"class-name"}]}),e.languages.flow["function-variable"].pattern=/(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=\s*(?:function\b|(?:\([^()]*\)(?:\s*:\s*\w+)?|(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/i,delete e.languages.flow.parameter,e.languages.insertBefore("flow","operator",{"flow-punctuation":{pattern:/\{\||\|\}/,alias:"punctuation"}}),Array.isArray(e.languages.flow.keyword)||(e.languages.flow.keyword=[e.languages.flow.keyword]),e.languages.flow.keyword.unshift({pattern:/(^|[^$]\b)(?:Class|declare|opaque|type)\b(?!\$)/,lookbehind:!0},{pattern:/(^|[^$]\B)\$(?:Diff|Enum|Exact|Keys|ObjMap|PropertyType|Record|Shape|Subtype|Supertype|await)\b(?!\$)/,lookbehind:!0})}(L),L.languages.n4js=L.languages.extend("javascript",{keyword:/\b(?:Array|any|boolean|break|case|catch|class|const|constructor|continue|debugger|declare|default|delete|do|else|enum|export|extends|false|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|module|new|null|number|package|private|protected|public|return|set|static|string|super|switch|this|throw|true|try|typeof|var|void|while|with|yield)\b/}),L.languages.insertBefore("n4js","constant",{annotation:{pattern:/@+\w+/,alias:"operator"}}),L.languages.n4jsd=L.languages.n4js,function(e){function t(e,t){return RegExp(e.replace(/<ID>/g,function(){return/(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*/.source}),t)}e.languages.insertBefore("javascript","function-variable",{"method-variable":{pattern:RegExp("(\\.\\s*)"+e.languages.javascript["function-variable"].pattern.source),lookbehind:!0,alias:["function-variable","method","function","property-access"]}}),e.languages.insertBefore("javascript","function",{method:{pattern:RegExp("(\\.\\s*)"+e.languages.javascript.function.source),lookbehind:!0,alias:["function","property-access"]}}),e.languages.insertBefore("javascript","constant",{"known-class-name":[{pattern:/\b(?:(?:Float(?:32|64)|(?:Int|Uint)(?:8|16|32)|Uint8Clamped)?Array|ArrayBuffer|BigInt|Boolean|DataView|Date|Error|Function|Intl|JSON|(?:Weak)?(?:Map|Set)|Math|Number|Object|Promise|Proxy|Reflect|RegExp|String|Symbol|WebAssembly)\b/,alias:"class-name"},{pattern:/\b(?:[A-Z]\w*)Error\b/,alias:"class-name"}]}),e.languages.insertBefore("javascript","keyword",{imports:{pattern:t(/(\bimport\b\s*)(?:<ID>(?:\s*,\s*(?:\*\s*as\s+<ID>|\{[^{}]*\}))?|\*\s*as\s+<ID>|\{[^{}]*\})(?=\s*\bfrom\b)/.source),lookbehind:!0,inside:e.languages.javascript},exports:{pattern:t(/(\bexport\b\s*)(?:\*(?:\s*as\s+<ID>)?(?=\s*\bfrom\b)|\{[^{}]*\})/.source),lookbehind:!0,inside:e.languages.javascript}}),e.languages.javascript.keyword.unshift({pattern:/\b(?:as|default|export|from|import)\b/,alias:"module"},{pattern:/\b(?:await|break|catch|continue|do|else|finally|for|if|return|switch|throw|try|while|yield)\b/,alias:"control-flow"},{pattern:/\bnull\b/,alias:["null","nil"]},{pattern:/\bundefined\b/,alias:"nil"}),e.languages.insertBefore("javascript","operator",{spread:{pattern:/\.{3}/,alias:"operator"},arrow:{pattern:/=>/,alias:"operator"}}),e.languages.insertBefore("javascript","punctuation",{"property-access":{pattern:t(/(\.\s*)#?<ID>/.source),lookbehind:!0},"maybe-class-name":{pattern:/(^|[^$\w\xA0-\uFFFF])[A-Z][$\w\xA0-\uFFFF]+/,lookbehind:!0},dom:{pattern:/\b(?:document|(?:local|session)Storage|location|navigator|performance|window)\b/,alias:"variable"},console:{pattern:/\bconsole(?=\s*\.)/,alias:"class-name"}});for(var n=["function","function-variable","method","method-variable","property-access"],r=0;r<n.length;r++){var a=n[r],o=e.languages.javascript[a];a=(o="RegExp"===e.util.type(o)?e.languages.javascript[a]={pattern:o}:o).inside||{};(o.inside=a)["maybe-class-name"]=/^[A-Z][\s\S]*/}}(L),function(e){var t=e.util.clone(e.languages.javascript),n=/(?:\s|\/\/.*(?!.)|\/\*(?:[^*]|\*(?!\/))\*\/)/.source,r=/(?:\{(?:\{(?:\{[^{}]*\}|[^{}])*\}|[^{}])*\})/.source,a=/(?:\{<S>*\.{3}(?:[^{}]|<BRACES>)*\})/.source;function o(e,t){return e=e.replace(/<S>/g,function(){return n}).replace(/<BRACES>/g,function(){return r}).replace(/<SPREAD>/g,function(){return a}),RegExp(e,t)}function i(t){for(var n=[],r=0;r<t.length;r++){var a=t[r],o=!1;"string"!=typeof a&&("tag"===a.type&&a.content[0]&&"tag"===a.content[0].type?"</"===a.content[0].content[0].content?0<n.length&&n[n.length-1].tagName===l(a.content[0].content[1])&&n.pop():"/>"!==a.content[a.content.length-1].content&&n.push({tagName:l(a.content[0].content[1]),openedBraces:0}):0<n.length&&"punctuation"===a.type&&"{"===a.content?n[n.length-1].openedBraces++:0<n.length&&0<n[n.length-1].openedBraces&&"punctuation"===a.type&&"}"===a.content?n[n.length-1].openedBraces--:o=!0),(o||"string"==typeof a)&&0<n.length&&0===n[n.length-1].openedBraces&&(o=l(a),r<t.length-1&&("string"==typeof t[r+1]||"plain-text"===t[r+1].type)&&(o+=l(t[r+1]),t.splice(r+1,1)),0<r&&("string"==typeof t[r-1]||"plain-text"===t[r-1].type)&&(o=l(t[r-1])+o,t.splice(r-1,1),r--),t[r]=new e.Token("plain-text",o,null,o)),a.content&&"string"!=typeof a.content&&i(a.content)}}a=o(a).source,e.languages.jsx=e.languages.extend("markup",t),e.languages.jsx.tag.pattern=o(/<\/?(?:[\w.:-]+(?:<S>+(?:[\w.:$-]+(?:=(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s{'"/>=]+|<BRACES>))?|<SPREAD>))*<S>*\/?)?>/.source),e.languages.jsx.tag.inside.tag.pattern=/^<\/?[^\s>\/]*/,e.languages.jsx.tag.inside["attr-value"].pattern=/=(?!\{)(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s'">]+)/,e.languages.jsx.tag.inside.tag.inside["class-name"]=/^[A-Z]\w*(?:\.[A-Z]\w*)*$/,e.languages.jsx.tag.inside.comment=t.comment,e.languages.insertBefore("inside","attr-name",{spread:{pattern:o(/<SPREAD>/.source),inside:e.languages.jsx}},e.languages.jsx.tag),e.languages.insertBefore("inside","special-attr",{script:{pattern:o(/=<BRACES>/.source),alias:"language-javascript",inside:{"script-punctuation":{pattern:/^=(?=\{)/,alias:"punctuation"},rest:e.languages.jsx}}},e.languages.jsx.tag);var l=function(e){return e?"string"==typeof e?e:"string"==typeof e.content?e.content:e.content.map(l).join(""):""};e.hooks.add("after-tokenize",function(e){"jsx"!==e.language&&"tsx"!==e.language||i(e.tokens)})}(L),function(e){var t=e.util.clone(e.languages.typescript);(t=(e.languages.tsx=e.languages.extend("jsx",t),delete e.languages.tsx.parameter,delete e.languages.tsx["literal-property"],e.languages.tsx.tag)).pattern=RegExp(/(^|[^\w$]|(?=<\/))/.source+"(?:"+t.pattern.source+")",t.pattern.flags),t.lookbehind=!0}(L),L.languages.swift={comment:{pattern:/(^|[^\\:])(?:\/\/.*|\/\*(?:[^/*]|\/(?!\*)|\*(?!\/)|\/\*(?:[^*]|\*(?!\/))*\*\/)*\*\/)/,lookbehind:!0,greedy:!0},"string-literal":[{pattern:RegExp(/(^|[^"#])/.source+"(?:"+/"(?:\\(?:\((?:[^()]|\([^()]*\))*\)|\r\n|[^(])|[^\\\r\n"])*"/.source+"|"+/"""(?:\\(?:\((?:[^()]|\([^()]*\))*\)|[^(])|[^\\"]|"(?!""))*"""/.source+")"+/(?!["#])/.source),lookbehind:!0,greedy:!0,inside:{interpolation:{pattern:/(\\\()(?:[^()]|\([^()]*\))*(?=\))/,lookbehind:!0,inside:null},"interpolation-punctuation":{pattern:/^\)|\\\($/,alias:"punctuation"},punctuation:/\\(?=[\r\n])/,string:/[\s\S]+/}},{pattern:RegExp(/(^|[^"#])(#+)/.source+"(?:"+/"(?:\\(?:#+\((?:[^()]|\([^()]*\))*\)|\r\n|[^#])|[^\\\r\n])*?"/.source+"|"+/"""(?:\\(?:#+\((?:[^()]|\([^()]*\))*\)|[^#])|[^\\])*?"""/.source+")\\2"),lookbehind:!0,greedy:!0,inside:{interpolation:{pattern:/(\\#+\()(?:[^()]|\([^()]*\))*(?=\))/,lookbehind:!0,inside:null},"interpolation-punctuation":{pattern:/^\)|\\#+\($/,alias:"punctuation"},string:/[\s\S]+/}}],directive:{pattern:RegExp(/#/.source+"(?:"+/(?:elseif|if)\b/.source+"(?:[ \t]*"+/(?:![ \t]*)?(?:\b\w+\b(?:[ \t]*\((?:[^()]|\([^()]*\))*\))?|\((?:[^()]|\([^()]*\))*\))(?:[ \t]*(?:&&|\|\|))?/.source+")+|"+/(?:else|endif)\b/.source+")"),alias:"property",inside:{"directive-name":/^#\w+/,boolean:/\b(?:false|true)\b/,number:/\b\d+(?:\.\d+)*\b/,operator:/!|&&|\|\||[<>]=?/,punctuation:/[(),]/}},literal:{pattern:/#(?:colorLiteral|column|dsohandle|file(?:ID|Literal|Path)?|function|imageLiteral|line)\b/,alias:"constant"},"other-directive":{pattern:/#\w+\b/,alias:"property"},attribute:{pattern:/@\w+/,alias:"atrule"},"function-definition":{pattern:/(\bfunc\s+)\w+/,lookbehind:!0,alias:"function"},label:{pattern:/\b(break|continue)\s+\w+|\b[a-zA-Z_]\w*(?=\s*:\s*(?:for|repeat|while)\b)/,lookbehind:!0,alias:"important"},keyword:/\b(?:Any|Protocol|Self|Type|actor|as|assignment|associatedtype|associativity|async|await|break|case|catch|class|continue|convenience|default|defer|deinit|didSet|do|dynamic|else|enum|extension|fallthrough|fileprivate|final|for|func|get|guard|higherThan|if|import|in|indirect|infix|init|inout|internal|is|isolated|lazy|left|let|lowerThan|mutating|none|nonisolated|nonmutating|open|operator|optional|override|postfix|precedencegroup|prefix|private|protocol|public|repeat|required|rethrows|return|right|safe|self|set|some|static|struct|subscript|super|switch|throw|throws|try|typealias|unowned|unsafe|var|weak|where|while|willSet)\b/,boolean:/\b(?:false|true)\b/,nil:{pattern:/\bnil\b/,alias:"constant"},"short-argument":/\$\d+\b/,omit:{pattern:/\b_\b/,alias:"keyword"},number:/\b(?:[\d_]+(?:\.[\de_]+)?|0x[a-f0-9_]+(?:\.[a-f0-9p_]+)?|0b[01_]+|0o[0-7_]+)\b/i,"class-name":/\b[A-Z](?:[A-Z_\d]*[a-z]\w*)?\b/,function:/\b[a-z_]\w*(?=\s*\()/i,constant:/\b(?:[A-Z_]{2,}|k[A-Z][A-Za-z_]+)\b/,operator:/[-+*/%=!<>&|^~?]+|\.[.\-+*/%=!<>&|^~?]+/,punctuation:/[{}[\]();,.:\\]/},L.languages.swift["string-literal"].forEach(function(e){e.inside.interpolation.inside=L.languages.swift}),function(e){e.languages.kotlin=e.languages.extend("clike",{keyword:{pattern:/(^|[^.])\b(?:abstract|actual|annotation|as|break|by|catch|class|companion|const|constructor|continue|crossinline|data|do|dynamic|else|enum|expect|external|final|finally|for|fun|get|if|import|in|infix|init|inline|inner|interface|internal|is|lateinit|noinline|null|object|open|operator|out|override|package|private|protected|public|reified|return|sealed|set|super|suspend|tailrec|this|throw|to|try|typealias|val|var|vararg|when|where|while)\b/,lookbehind:!0},function:[{pattern:/(?:`[^\r\n`]+`|\b\w+)(?=\s*\()/,greedy:!0},{pattern:/(\.)(?:`[^\r\n`]+`|\w+)(?=\s*\{)/,lookbehind:!0,greedy:!0}],number:/\b(?:0[xX][\da-fA-F]+(?:_[\da-fA-F]+)*|0[bB][01]+(?:_[01]+)*|\d+(?:_\d+)*(?:\.\d+(?:_\d+)*)?(?:[eE][+-]?\d+(?:_\d+)*)?[fFL]?)\b/,operator:/\+[+=]?|-[-=>]?|==?=?|!(?:!|==?)?|[\/*%<>]=?|[?:]:?|\.\.|&&|\|\||\b(?:and|inv|or|shl|shr|ushr|xor)\b/}),delete e.languages.kotlin["class-name"];var t={"interpolation-punctuation":{pattern:/^\$\{?|\}$/,alias:"punctuation"},expression:{pattern:/[\s\S]+/,inside:e.languages.kotlin}};e.languages.insertBefore("kotlin","string",{"string-literal":[{pattern:/"""(?:[^$]|\$(?:(?!\{)|\{[^{}]*\}))*?"""/,alias:"multiline",inside:{interpolation:{pattern:/\$(?:[a-z_]\w*|\{[^{}]*\})/i,inside:t},string:/[\s\S]+/}},{pattern:/"(?:[^"\\\r\n$]|\\.|\$(?:(?!\{)|\{[^{}]*\}))*"/,alias:"singleline",inside:{interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$(?:[a-z_]\w*|\{[^{}]*\})/i,lookbehind:!0,inside:t},string:/[\s\S]+/}}],char:{pattern:/'(?:[^'\\\r\n]|\\(?:.|u[a-fA-F0-9]{0,4}))'/,greedy:!0}}),delete e.languages.kotlin.string,e.languages.insertBefore("kotlin","keyword",{annotation:{pattern:/\B@(?:\w+:)?(?:[A-Z]\w*|\[[^\]]+\])/,alias:"builtin"}}),e.languages.insertBefore("kotlin","function",{label:{pattern:/\b\w+@|@\w+\b/,alias:"symbol"}}),e.languages.kt=e.languages.kotlin,e.languages.kts=e.languages.kotlin}(L),L.languages.c=L.languages.extend("clike",{comment:{pattern:/\/\/(?:[^\r\n\\]|\\(?:\r\n?|\n|(?![\r\n])))*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},string:{pattern:/"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/,greedy:!0},"class-name":{pattern:/(\b(?:enum|struct)\s+(?:__attribute__\s*\(\([\s\S]*?\)\)\s*)?)\w+|\b[a-z]\w*_t\b/,lookbehind:!0},keyword:/\b(?:_Alignas|_Alignof|_Atomic|_Bool|_Complex|_Generic|_Imaginary|_Noreturn|_Static_assert|_Thread_local|__attribute__|asm|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|inline|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|typeof|union|unsigned|void|volatile|while)\b/,function:/\b[a-z_]\w*(?=\s*\()/i,number:/(?:\b0x(?:[\da-f]+(?:\.[\da-f]*)?|\.[\da-f]+)(?:p[+-]?\d+)?|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?)[ful]{0,4}/i,operator:/>>=?|<<=?|->|([-+&|:])\1|[?:~]|[-+*/%&|^!=<>]=?/}),L.languages.insertBefore("c","string",{char:{pattern:/'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n]){0,32}'/,greedy:!0}}),L.languages.insertBefore("c","string",{macro:{pattern:/(^[\t ]*)#\s*[a-z](?:[^\r\n\\/]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|\\(?:\r\n|[\s\S]))*/im,lookbehind:!0,greedy:!0,alias:"property",inside:{string:[{pattern:/^(#\s*include\s*)<[^>]+>/,lookbehind:!0},L.languages.c.string],char:L.languages.c.char,comment:L.languages.c.comment,"macro-name":[{pattern:/(^#\s*define\s+)\w+\b(?!\()/i,lookbehind:!0},{pattern:/(^#\s*define\s+)\w+\b(?=\()/i,lookbehind:!0,alias:"function"}],directive:{pattern:/^(#\s*)[a-z]+/,lookbehind:!0,alias:"keyword"},"directive-hash":/^#/,punctuation:/##|\\(?=[\r\n])/,expression:{pattern:/\S[\s\S]*/,inside:L.languages.c}}}}),L.languages.insertBefore("c","function",{constant:/\b(?:EOF|NULL|SEEK_CUR|SEEK_END|SEEK_SET|__DATE__|__FILE__|__LINE__|__TIMESTAMP__|__TIME__|__func__|stderr|stdin|stdout)\b/}),delete L.languages.c.boolean,L.languages.objectivec=L.languages.extend("c",{string:{pattern:/@?"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/,greedy:!0},keyword:/\b(?:asm|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|in|inline|int|long|register|return|self|short|signed|sizeof|static|struct|super|switch|typedef|typeof|union|unsigned|void|volatile|while)\b|(?:@interface|@end|@implementation|@protocol|@class|@public|@protected|@private|@property|@try|@catch|@finally|@throw|@synthesize|@dynamic|@selector)\b/,operator:/-[->]?|\+\+?|!=?|<<?=?|>>?=?|==?|&&?|\|\|?|[~^%?*\/@]/}),delete L.languages.objectivec["class-name"],L.languages.objc=L.languages.objectivec,L.languages.reason=L.languages.extend("clike",{string:{pattern:/"(?:\\(?:\r\n|[\s\S])|[^\\\r\n"])*"/,greedy:!0},"class-name":/\b[A-Z]\w*/,keyword:/\b(?:and|as|assert|begin|class|constraint|do|done|downto|else|end|exception|external|for|fun|function|functor|if|in|include|inherit|initializer|lazy|let|method|module|mutable|new|nonrec|object|of|open|or|private|rec|sig|struct|switch|then|to|try|type|val|virtual|when|while|with)\b/,operator:/\.{3}|:[:=]|\|>|->|=(?:==?|>)?|<=?|>=?|[|^?'#!~`]|[+\-*\/]\.?|\b(?:asr|land|lor|lsl|lsr|lxor|mod)\b/}),L.languages.insertBefore("reason","class-name",{char:{pattern:/'(?:\\x[\da-f]{2}|\\o[0-3][0-7][0-7]|\\\d{3}|\\.|[^'\\\r\n])'/,greedy:!0},constructor:/\b[A-Z]\w*\b(?!\s*\.)/,label:{pattern:/\b[a-z]\w*(?=::)/,alias:"symbol"}}),delete L.languages.reason.function,function(e){for(var t=/\/\*(?:[^*/]|\*(?!\/)|\/(?!\*)|<self>)*\*\//.source,n=0;n<2;n++)t=t.replace(/<self>/g,function(){return t});t=t.replace(/<self>/g,function(){return/[^\s\S]/.source}),e.languages.rust={comment:[{pattern:RegExp(/(^|[^\\])/.source+t),lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/b?"(?:\\[\s\S]|[^\\"])*"|b?r(#*)"(?:[^"]|"(?!\1))*"\1/,greedy:!0},char:{pattern:/b?'(?:\\(?:x[0-7][\da-fA-F]|u\{(?:[\da-fA-F]_*){1,6}\}|.)|[^\\\r\n\t'])'/,greedy:!0},attribute:{pattern:/#!?\[(?:[^\[\]"]|"(?:\\[\s\S]|[^\\"])*")*\]/,greedy:!0,alias:"attr-name",inside:{string:null}},"closure-params":{pattern:/([=(,:]\s*|\bmove\s*)\|[^|]*\||\|[^|]*\|(?=\s*(?:\{|->))/,lookbehind:!0,greedy:!0,inside:{"closure-punctuation":{pattern:/^\||\|$/,alias:"punctuation"},rest:null}},"lifetime-annotation":{pattern:/'\w+/,alias:"symbol"},"fragment-specifier":{pattern:/(\$\w+:)[a-z]+/,lookbehind:!0,alias:"punctuation"},variable:/\$\w+/,"function-definition":{pattern:/(\bfn\s+)\w+/,lookbehind:!0,alias:"function"},"type-definition":{pattern:/(\b(?:enum|struct|trait|type|union)\s+)\w+/,lookbehind:!0,alias:"class-name"},"module-declaration":[{pattern:/(\b(?:crate|mod)\s+)[a-z][a-z_\d]*/,lookbehind:!0,alias:"namespace"},{pattern:/(\b(?:crate|self|super)\s*)::\s*[a-z][a-z_\d]*\b(?:\s*::(?:\s*[a-z][a-z_\d]*\s*::)*)?/,lookbehind:!0,alias:"namespace",inside:{punctuation:/::/}}],keyword:[/\b(?:Self|abstract|as|async|await|become|box|break|const|continue|crate|do|dyn|else|enum|extern|final|fn|for|if|impl|in|let|loop|macro|match|mod|move|mut|override|priv|pub|ref|return|self|static|struct|super|trait|try|type|typeof|union|unsafe|unsized|use|virtual|where|while|yield)\b/,/\b(?:bool|char|f(?:32|64)|[ui](?:8|16|32|64|128|size)|str)\b/],function:/\b[a-z_]\w*(?=\s*(?:::\s*<|\())/,macro:{pattern:/\b\w+!/,alias:"property"},constant:/\b[A-Z_][A-Z_\d]+\b/,"class-name":/\b[A-Z]\w*\b/,namespace:{pattern:/(?:\b[a-z][a-z_\d]*\s*::\s*)*\b[a-z][a-z_\d]*\s*::(?!\s*<)/,inside:{punctuation:/::/}},number:/\b(?:0x[\dA-Fa-f](?:_?[\dA-Fa-f])*|0o[0-7](?:_?[0-7])*|0b[01](?:_?[01])*|(?:(?:\d(?:_?\d)*)?\.)?\d(?:_?\d)*(?:[Ee][+-]?\d+)?)(?:_?(?:f32|f64|[iu](?:8|16|32|64|size)?))?\b/,boolean:/\b(?:false|true)\b/,punctuation:/->|\.\.=|\.{1,3}|::|[{}[\];(),:]/,operator:/[-+*\/%!^]=?|=[=>]?|&[&=]?|\|[|=]?|<<?=?|>>?=?|[@?]/},e.languages.rust["closure-params"].inside.rest=e.languages.rust,e.languages.rust.attribute.inside.string=e.languages.rust.string}(L),L.languages.go=L.languages.extend("clike",{string:{pattern:/(^|[^\\])"(?:\\.|[^"\\\r\n])*"|`[^`]*`/,lookbehind:!0,greedy:!0},keyword:/\b(?:break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go(?:to)?|if|import|interface|map|package|range|return|select|struct|switch|type|var)\b/,boolean:/\b(?:_|false|iota|nil|true)\b/,number:[/\b0(?:b[01_]+|o[0-7_]+)i?\b/i,/\b0x(?:[a-f\d_]+(?:\.[a-f\d_]*)?|\.[a-f\d_]+)(?:p[+-]?\d+(?:_\d+)*)?i?(?!\w)/i,/(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.\d[\d_]*)(?:e[+-]?[\d_]+)?i?(?!\w)/i],operator:/[*\/%^!=]=?|\+[=+]?|-[=-]?|\|[=|]?|&(?:=|&|\^=?)?|>(?:>=?|=)?|<(?:<=?|=|-)?|:=|\.\.\./,builtin:/\b(?:append|bool|byte|cap|close|complex|complex(?:64|128)|copy|delete|error|float(?:32|64)|u?int(?:8|16|32|64)?|imag|len|make|new|panic|print(?:ln)?|real|recover|rune|string|uintptr)\b/}),L.languages.insertBefore("go","string",{char:{pattern:/'(?:\\.|[^'\\\r\n]){0,10}'/,greedy:!0}}),delete L.languages.go["class-name"],function(e){var t=/\b(?:alignas|alignof|asm|auto|bool|break|case|catch|char|char16_t|char32_t|char8_t|class|co_await|co_return|co_yield|compl|concept|const|const_cast|consteval|constexpr|constinit|continue|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|final|float|for|friend|goto|if|import|inline|int|int16_t|int32_t|int64_t|int8_t|long|module|mutable|namespace|new|noexcept|nullptr|operator|override|private|protected|public|register|reinterpret_cast|requires|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|uint16_t|uint32_t|uint64_t|uint8_t|union|unsigned|using|virtual|void|volatile|wchar_t|while)\b/,n=/\b(?!<keyword>)\w+(?:\s*\.\s*\w+)*\b/.source.replace(/<keyword>/g,function(){return t.source});e.languages.cpp=e.languages.extend("c",{"class-name":[{pattern:RegExp(/(\b(?:class|concept|enum|struct|typename)\s+)(?!<keyword>)\w+/.source.replace(/<keyword>/g,function(){return t.source})),lookbehind:!0},/\b[A-Z]\w*(?=\s*::\s*\w+\s*\()/,/\b[A-Z_]\w*(?=\s*::\s*~\w+\s*\()/i,/\b\w+(?=\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>\s*::\s*\w+\s*\()/],keyword:t,number:{pattern:/(?:\b0b[01']+|\b0x(?:[\da-f']+(?:\.[\da-f']*)?|\.[\da-f']+)(?:p[+-]?[\d']+)?|(?:\b[\d']+(?:\.[\d']*)?|\B\.[\d']+)(?:e[+-]?[\d']+)?)[ful]{0,4}/i,greedy:!0},operator:/>>=?|<<=?|->|--|\+\+|&&|\|\||[?:~]|<=>|[-+*/%&|^!=<>]=?|\b(?:and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\b/,boolean:/\b(?:false|true)\b/}),e.languages.insertBefore("cpp","string",{module:{pattern:RegExp(/(\b(?:import|module)\s+)/.source+"(?:"+/"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|<[^<>\r\n]*>/.source+"|"+/<mod-name>(?:\s*:\s*<mod-name>)?|:\s*<mod-name>/.source.replace(/<mod-name>/g,function(){return n})+")"),lookbehind:!0,greedy:!0,inside:{string:/^[<"][\s\S]+/,operator:/:/,punctuation:/\./}},"raw-string":{pattern:/R"([^()\\ ]{0,16})\([\s\S]*?\)\1"/,alias:"string",greedy:!0}}),e.languages.insertBefore("cpp","keyword",{"generic-function":{pattern:/\b(?!operator\b)[a-z_]\w*\s*<(?:[^<>]|<[^<>]*>)*>(?=\s*\()/i,inside:{function:/^\w+/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:e.languages.cpp}}}}),e.languages.insertBefore("cpp","operator",{"double-colon":{pattern:/::/,alias:"punctuation"}}),e.languages.insertBefore("cpp","class-name",{"base-clause":{pattern:/(\b(?:class|struct)\s+\w+\s*:\s*)[^;{}"'\s]+(?:\s+[^;{}"'\s]+)*(?=\s*[;{])/,lookbehind:!0,greedy:!0,inside:e.languages.extend("cpp",{})}}),e.languages.insertBefore("inside","double-colon",{"class-name":/\b[a-z_]\w*\b(?!\s*::)/i},e.languages.cpp["base-clause"])}(L),L.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0,greedy:!0},"string-interpolation":{pattern:/(?:f|fr|rf)(?:("""|''')[\s\S]*?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:\{\{)*)\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}])+\})+\})+\}/,lookbehind:!0,inside:{"format-spec":{pattern:/(:)[^:(){}]+(?=\}$)/,lookbehind:!0},"conversion-option":{pattern:/![sra](?=[:}]$)/,alias:"punctuation"},rest:null}},string:/[\s\S]+/}},"triple-quoted-string":{pattern:/(?:[rub]|br|rb)?("""|''')[\s\S]*?\1/i,greedy:!0,alias:"string"},string:{pattern:/(?:[rub]|br|rb)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,greedy:!0},function:{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^[\t ]*)@\w+(?:\.\w+)*/m,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:_(?=\s*:)|and|as|assert|async|await|break|case|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|match|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,boolean:/\b(?:False|None|True)\b/,number:/\b0(?:b(?:_?[01])+|o(?:_?[0-7])+|x(?:_?[a-f0-9])+)\b|(?:\b\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\B\.\d+(?:_\d+)*)(?:e[+-]?\d+(?:_\d+)*)?j?(?!\w)/i,operator:/[-+%=]=?|!=|:=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},L.languages.python["string-interpolation"].inside.interpolation.inside.rest=L.languages.python,L.languages.py=L.languages.python,L.languages.json={property:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?=\s*:)/,lookbehind:!0,greedy:!0},string:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?!\s*:)/,lookbehind:!0,greedy:!0},comment:{pattern:/\/\/.*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},number:/-?\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,punctuation:/[{}[\],]/,operator:/:/,boolean:/\b(?:false|true)\b/,null:{pattern:/\bnull\b/,alias:"keyword"}},L.languages.webmanifest=L.languages.json;((e,t)=>{for(var n in t)p(e,n,{get:t[n],enumerable:!0})})({},{dracula:()=>A,duotoneDark:()=>T,duotoneLight:()=>j,github:()=>M,gruvboxMaterialDark:()=>Q,gruvboxMaterialLight:()=>K,jettwaveDark:()=>H,jettwaveLight:()=>G,nightOwl:()=>P,nightOwlLight:()=>N,oceanicNext:()=>B,okaidia:()=>D,oneDark:()=>V,oneLight:()=>W,palenight:()=>F,shadesOfPurple:()=>I,synthwave84:()=>z,ultramin:()=>$,vsDark:()=>U,vsLight:()=>q});var A={plain:{color:"#F8F8F2",backgroundColor:"#282A36"},styles:[{types:["prolog","constant","builtin"],style:{color:"rgb(189, 147, 249)"}},{types:["inserted","function"],style:{color:"rgb(80, 250, 123)"}},{types:["deleted"],style:{color:"rgb(255, 85, 85)"}},{types:["changed"],style:{color:"rgb(255, 184, 108)"}},{types:["punctuation","symbol"],style:{color:"rgb(248, 248, 242)"}},{types:["string","char","tag","selector"],style:{color:"rgb(255, 121, 198)"}},{types:["keyword","variable"],style:{color:"rgb(189, 147, 249)",fontStyle:"italic"}},{types:["comment"],style:{color:"rgb(98, 114, 164)"}},{types:["attr-name"],style:{color:"rgb(241, 250, 140)"}}]},T={plain:{backgroundColor:"#2a2734",color:"#9a86fd"},styles:[{types:["comment","prolog","doctype","cdata","punctuation"],style:{color:"#6c6783"}},{types:["namespace"],style:{opacity:.7}},{types:["tag","operator","number"],style:{color:"#e09142"}},{types:["property","function"],style:{color:"#9a86fd"}},{types:["tag-id","selector","atrule-id"],style:{color:"#eeebff"}},{types:["attr-name"],style:{color:"#c4b9fe"}},{types:["boolean","string","entity","url","attr-value","keyword","control","directive","unit","statement","regex","atrule","placeholder","variable"],style:{color:"#ffcc99"}},{types:["deleted"],style:{textDecorationLine:"line-through"}},{types:["inserted"],style:{textDecorationLine:"underline"}},{types:["italic"],style:{fontStyle:"italic"}},{types:["important","bold"],style:{fontWeight:"bold"}},{types:["important"],style:{color:"#c4b9fe"}}]},j={plain:{backgroundColor:"#faf8f5",color:"#728fcb"},styles:[{types:["comment","prolog","doctype","cdata","punctuation"],style:{color:"#b6ad9a"}},{types:["namespace"],style:{opacity:.7}},{types:["tag","operator","number"],style:{color:"#063289"}},{types:["property","function"],style:{color:"#b29762"}},{types:["tag-id","selector","atrule-id"],style:{color:"#2d2006"}},{types:["attr-name"],style:{color:"#896724"}},{types:["boolean","string","entity","url","attr-value","keyword","control","directive","unit","statement","regex","atrule"],style:{color:"#728fcb"}},{types:["placeholder","variable"],style:{color:"#93abdc"}},{types:["deleted"],style:{textDecorationLine:"line-through"}},{types:["inserted"],style:{textDecorationLine:"underline"}},{types:["italic"],style:{fontStyle:"italic"}},{types:["important","bold"],style:{fontWeight:"bold"}},{types:["important"],style:{color:"#896724"}}]},M={plain:{color:"#393A34",backgroundColor:"#f6f8fa"},styles:[{types:["comment","prolog","doctype","cdata"],style:{color:"#999988",fontStyle:"italic"}},{types:["namespace"],style:{opacity:.7}},{types:["string","attr-value"],style:{color:"#e3116c"}},{types:["punctuation","operator"],style:{color:"#393A34"}},{types:["entity","url","symbol","number","boolean","variable","constant","property","regex","inserted"],style:{color:"#36acaa"}},{types:["atrule","keyword","attr-name","selector"],style:{color:"#00a4db"}},{types:["function","deleted","tag"],style:{color:"#d73a49"}},{types:["function-variable"],style:{color:"#6f42c1"}},{types:["tag","selector","keyword"],style:{color:"#00009f"}}]},P={plain:{color:"#d6deeb",backgroundColor:"#011627"},styles:[{types:["changed"],style:{color:"rgb(162, 191, 252)",fontStyle:"italic"}},{types:["deleted"],style:{color:"rgba(239, 83, 80, 0.56)",fontStyle:"italic"}},{types:["inserted","attr-name"],style:{color:"rgb(173, 219, 103)",fontStyle:"italic"}},{types:["comment"],style:{color:"rgb(99, 119, 119)",fontStyle:"italic"}},{types:["string","url"],style:{color:"rgb(173, 219, 103)"}},{types:["variable"],style:{color:"rgb(214, 222, 235)"}},{types:["number"],style:{color:"rgb(247, 140, 108)"}},{types:["builtin","char","constant","function"],style:{color:"rgb(130, 170, 255)"}},{types:["punctuation"],style:{color:"rgb(199, 146, 234)"}},{types:["selector","doctype"],style:{color:"rgb(199, 146, 234)",fontStyle:"italic"}},{types:["class-name"],style:{color:"rgb(255, 203, 139)"}},{types:["tag","operator","keyword"],style:{color:"rgb(127, 219, 202)"}},{types:["boolean"],style:{color:"rgb(255, 88, 116)"}},{types:["property"],style:{color:"rgb(128, 203, 196)"}},{types:["namespace"],style:{color:"rgb(178, 204, 214)"}}]},N={plain:{color:"#403f53",backgroundColor:"#FBFBFB"},styles:[{types:["changed"],style:{color:"rgb(162, 191, 252)",fontStyle:"italic"}},{types:["deleted"],style:{color:"rgba(239, 83, 80, 0.56)",fontStyle:"italic"}},{types:["inserted","attr-name"],style:{color:"rgb(72, 118, 214)",fontStyle:"italic"}},{types:["comment"],style:{color:"rgb(152, 159, 177)",fontStyle:"italic"}},{types:["string","builtin","char","constant","url"],style:{color:"rgb(72, 118, 214)"}},{types:["variable"],style:{color:"rgb(201, 103, 101)"}},{types:["number"],style:{color:"rgb(170, 9, 130)"}},{types:["punctuation"],style:{color:"rgb(153, 76, 195)"}},{types:["function","selector","doctype"],style:{color:"rgb(153, 76, 195)",fontStyle:"italic"}},{types:["class-name"],style:{color:"rgb(17, 17, 17)"}},{types:["tag"],style:{color:"rgb(153, 76, 195)"}},{types:["operator","property","keyword","namespace"],style:{color:"rgb(12, 150, 155)"}},{types:["boolean"],style:{color:"rgb(188, 84, 84)"}}]},O="#c5a5c5",R="#8dc891",B={plain:{backgroundColor:"#282c34",color:"#ffffff"},styles:[{types:["attr-name"],style:{color:O}},{types:["attr-value"],style:{color:R}},{types:["comment","block-comment","prolog","doctype","cdata","shebang"],style:{color:"#999999"}},{types:["property","number","function-name","constant","symbol","deleted"],style:{color:"#5a9bcf"}},{types:["boolean"],style:{color:"#ff8b50"}},{types:["tag"],style:{color:"#fc929e"}},{types:["string"],style:{color:R}},{types:["punctuation"],style:{color:R}},{types:["selector","char","builtin","inserted"],style:{color:"#D8DEE9"}},{types:["function"],style:{color:"#79b6f2"}},{types:["operator","entity","url","variable"],style:{color:"#d7deea"}},{types:["keyword"],style:{color:O}},{types:["atrule","class-name"],style:{color:"#FAC863"}},{types:["important"],style:{fontWeight:"400"}},{types:["bold"],style:{fontWeight:"bold"}},{types:["italic"],style:{fontStyle:"italic"}},{types:["namespace"],style:{opacity:.7}}]},D={plain:{color:"#f8f8f2",backgroundColor:"#272822"},styles:[{types:["changed"],style:{color:"rgb(162, 191, 252)",fontStyle:"italic"}},{types:["deleted"],style:{color:"#f92672",fontStyle:"italic"}},{types:["inserted"],style:{color:"rgb(173, 219, 103)",fontStyle:"italic"}},{types:["comment"],style:{color:"#8292a2",fontStyle:"italic"}},{types:["string","url"],style:{color:"#a6e22e"}},{types:["variable"],style:{color:"#f8f8f2"}},{types:["number"],style:{color:"#ae81ff"}},{types:["builtin","char","constant","function","class-name"],style:{color:"#e6db74"}},{types:["punctuation"],style:{color:"#f8f8f2"}},{types:["selector","doctype"],style:{color:"#a6e22e",fontStyle:"italic"}},{types:["tag","operator","keyword"],style:{color:"#66d9ef"}},{types:["boolean"],style:{color:"#ae81ff"}},{types:["namespace"],style:{color:"rgb(178, 204, 214)",opacity:.7}},{types:["tag","property"],style:{color:"#f92672"}},{types:["attr-name"],style:{color:"#a6e22e !important"}},{types:["doctype"],style:{color:"#8292a2"}},{types:["rule"],style:{color:"#e6db74"}}]},F={plain:{color:"#bfc7d5",backgroundColor:"#292d3e"},styles:[{types:["comment"],style:{color:"rgb(105, 112, 152)",fontStyle:"italic"}},{types:["string","inserted"],style:{color:"rgb(195, 232, 141)"}},{types:["number"],style:{color:"rgb(247, 140, 108)"}},{types:["builtin","char","constant","function"],style:{color:"rgb(130, 170, 255)"}},{types:["punctuation","selector"],style:{color:"rgb(199, 146, 234)"}},{types:["variable"],style:{color:"rgb(191, 199, 213)"}},{types:["class-name","attr-name"],style:{color:"rgb(255, 203, 107)"}},{types:["tag","deleted"],style:{color:"rgb(255, 85, 114)"}},{types:["operator"],style:{color:"rgb(137, 221, 255)"}},{types:["boolean"],style:{color:"rgb(255, 88, 116)"}},{types:["keyword"],style:{fontStyle:"italic"}},{types:["doctype"],style:{color:"rgb(199, 146, 234)",fontStyle:"italic"}},{types:["namespace"],style:{color:"rgb(178, 204, 214)"}},{types:["url"],style:{color:"rgb(221, 221, 221)"}}]},I={plain:{color:"#9EFEFF",backgroundColor:"#2D2A55"},styles:[{types:["changed"],style:{color:"rgb(255, 238, 128)"}},{types:["deleted"],style:{color:"rgba(239, 83, 80, 0.56)"}},{types:["inserted"],style:{color:"rgb(173, 219, 103)"}},{types:["comment"],style:{color:"rgb(179, 98, 255)",fontStyle:"italic"}},{types:["punctuation"],style:{color:"rgb(255, 255, 255)"}},{types:["constant"],style:{color:"rgb(255, 98, 140)"}},{types:["string","url"],style:{color:"rgb(165, 255, 144)"}},{types:["variable"],style:{color:"rgb(255, 238, 128)"}},{types:["number","boolean"],style:{color:"rgb(255, 98, 140)"}},{types:["attr-name"],style:{color:"rgb(255, 180, 84)"}},{types:["keyword","operator","property","namespace","tag","selector","doctype"],style:{color:"rgb(255, 157, 0)"}},{types:["builtin","char","constant","function","class-name"],style:{color:"rgb(250, 208, 0)"}}]},z={plain:{backgroundColor:"linear-gradient(to bottom, #2a2139 75%, #34294f)",backgroundImage:"#34294f",color:"#f92aad",textShadow:"0 0 2px #100c0f, 0 0 5px #dc078e33, 0 0 10px #fff3"},styles:[{types:["comment","block-comment","prolog","doctype","cdata"],style:{color:"#495495",fontStyle:"italic"}},{types:["punctuation"],style:{color:"#ccc"}},{types:["tag","attr-name","namespace","number","unit","hexcode","deleted"],style:{color:"#e2777a"}},{types:["property","selector"],style:{color:"#72f1b8",textShadow:"0 0 2px #100c0f, 0 0 10px #257c5575, 0 0 35px #21272475"}},{types:["function-name"],style:{color:"#6196cc"}},{types:["boolean","selector-id","function"],style:{color:"#fdfdfd",textShadow:"0 0 2px #001716, 0 0 3px #03edf975, 0 0 5px #03edf975, 0 0 8px #03edf975"}},{types:["class-name","maybe-class-name","builtin"],style:{color:"#fff5f6",textShadow:"0 0 2px #000, 0 0 10px #fc1f2c75, 0 0 5px #fc1f2c75, 0 0 25px #fc1f2c75"}},{types:["constant","symbol"],style:{color:"#f92aad",textShadow:"0 0 2px #100c0f, 0 0 5px #dc078e33, 0 0 10px #fff3"}},{types:["important","atrule","keyword","selector-class"],style:{color:"#f4eee4",textShadow:"0 0 2px #393a33, 0 0 8px #f39f0575, 0 0 2px #f39f0575"}},{types:["string","char","attr-value","regex","variable"],style:{color:"#f87c32"}},{types:["parameter"],style:{fontStyle:"italic"}},{types:["entity","url"],style:{color:"#67cdcc"}},{types:["operator"],style:{color:"ffffffee"}},{types:["important","bold"],style:{fontWeight:"bold"}},{types:["italic"],style:{fontStyle:"italic"}},{types:["entity"],style:{cursor:"help"}},{types:["inserted"],style:{color:"green"}}]},$={plain:{color:"#282a2e",backgroundColor:"#ffffff"},styles:[{types:["comment"],style:{color:"rgb(197, 200, 198)"}},{types:["string","number","builtin","variable"],style:{color:"rgb(150, 152, 150)"}},{types:["class-name","function","tag","attr-name"],style:{color:"rgb(40, 42, 46)"}}]},U={plain:{color:"#9CDCFE",backgroundColor:"#1E1E1E"},styles:[{types:["prolog"],style:{color:"rgb(0, 0, 128)"}},{types:["comment"],style:{color:"rgb(106, 153, 85)"}},{types:["builtin","changed","keyword","interpolation-punctuation"],style:{color:"rgb(86, 156, 214)"}},{types:["number","inserted"],style:{color:"rgb(181, 206, 168)"}},{types:["constant"],style:{color:"rgb(100, 102, 149)"}},{types:["attr-name","variable"],style:{color:"rgb(156, 220, 254)"}},{types:["deleted","string","attr-value","template-punctuation"],style:{color:"rgb(206, 145, 120)"}},{types:["selector"],style:{color:"rgb(215, 186, 125)"}},{types:["tag"],style:{color:"rgb(78, 201, 176)"}},{types:["tag"],languages:["markup"],style:{color:"rgb(86, 156, 214)"}},{types:["punctuation","operator"],style:{color:"rgb(212, 212, 212)"}},{types:["punctuation"],languages:["markup"],style:{color:"#808080"}},{types:["function"],style:{color:"rgb(220, 220, 170)"}},{types:["class-name"],style:{color:"rgb(78, 201, 176)"}},{types:["char"],style:{color:"rgb(209, 105, 105)"}}]},q={plain:{color:"#000000",backgroundColor:"#ffffff"},styles:[{types:["comment"],style:{color:"rgb(0, 128, 0)"}},{types:["builtin"],style:{color:"rgb(0, 112, 193)"}},{types:["number","variable","inserted"],style:{color:"rgb(9, 134, 88)"}},{types:["operator"],style:{color:"rgb(0, 0, 0)"}},{types:["constant","char"],style:{color:"rgb(129, 31, 63)"}},{types:["tag"],style:{color:"rgb(128, 0, 0)"}},{types:["attr-name"],style:{color:"rgb(255, 0, 0)"}},{types:["deleted","string"],style:{color:"rgb(163, 21, 21)"}},{types:["changed","punctuation"],style:{color:"rgb(4, 81, 165)"}},{types:["function","keyword"],style:{color:"rgb(0, 0, 255)"}},{types:["class-name"],style:{color:"rgb(38, 127, 153)"}}]},H={plain:{color:"#f8fafc",backgroundColor:"#011627"},styles:[{types:["prolog"],style:{color:"#000080"}},{types:["comment"],style:{color:"#6A9955"}},{types:["builtin","changed","keyword","interpolation-punctuation"],style:{color:"#569CD6"}},{types:["number","inserted"],style:{color:"#B5CEA8"}},{types:["constant"],style:{color:"#f8fafc"}},{types:["attr-name","variable"],style:{color:"#9CDCFE"}},{types:["deleted","string","attr-value","template-punctuation"],style:{color:"#cbd5e1"}},{types:["selector"],style:{color:"#D7BA7D"}},{types:["tag"],style:{color:"#0ea5e9"}},{types:["tag"],languages:["markup"],style:{color:"#0ea5e9"}},{types:["punctuation","operator"],style:{color:"#D4D4D4"}},{types:["punctuation"],languages:["markup"],style:{color:"#808080"}},{types:["function"],style:{color:"#7dd3fc"}},{types:["class-name"],style:{color:"#0ea5e9"}},{types:["char"],style:{color:"#D16969"}}]},G={plain:{color:"#0f172a",backgroundColor:"#f1f5f9"},styles:[{types:["prolog"],style:{color:"#000080"}},{types:["comment"],style:{color:"#6A9955"}},{types:["builtin","changed","keyword","interpolation-punctuation"],style:{color:"#0c4a6e"}},{types:["number","inserted"],style:{color:"#B5CEA8"}},{types:["constant"],style:{color:"#0f172a"}},{types:["attr-name","variable"],style:{color:"#0c4a6e"}},{types:["deleted","string","attr-value","template-punctuation"],style:{color:"#64748b"}},{types:["selector"],style:{color:"#D7BA7D"}},{types:["tag"],style:{color:"#0ea5e9"}},{types:["tag"],languages:["markup"],style:{color:"#0ea5e9"}},{types:["punctuation","operator"],style:{color:"#475569"}},{types:["punctuation"],languages:["markup"],style:{color:"#808080"}},{types:["function"],style:{color:"#0e7490"}},{types:["class-name"],style:{color:"#0ea5e9"}},{types:["char"],style:{color:"#D16969"}}]},V={plain:{backgroundColor:"hsl(220, 13%, 18%)",color:"hsl(220, 14%, 71%)",textShadow:"0 1px rgba(0, 0, 0, 0.3)"},styles:[{types:["comment","prolog","cdata"],style:{color:"hsl(220, 10%, 40%)"}},{types:["doctype","punctuation","entity"],style:{color:"hsl(220, 14%, 71%)"}},{types:["attr-name","class-name","maybe-class-name","boolean","constant","number","atrule"],style:{color:"hsl(29, 54%, 61%)"}},{types:["keyword"],style:{color:"hsl(286, 60%, 67%)"}},{types:["property","tag","symbol","deleted","important"],style:{color:"hsl(355, 65%, 65%)"}},{types:["selector","string","char","builtin","inserted","regex","attr-value"],style:{color:"hsl(95, 38%, 62%)"}},{types:["variable","operator","function"],style:{color:"hsl(207, 82%, 66%)"}},{types:["url"],style:{color:"hsl(187, 47%, 55%)"}},{types:["deleted"],style:{textDecorationLine:"line-through"}},{types:["inserted"],style:{textDecorationLine:"underline"}},{types:["italic"],style:{fontStyle:"italic"}},{types:["important","bold"],style:{fontWeight:"bold"}},{types:["important"],style:{color:"hsl(220, 14%, 71%)"}}]},W={plain:{backgroundColor:"hsl(230, 1%, 98%)",color:"hsl(230, 8%, 24%)"},styles:[{types:["comment","prolog","cdata"],style:{color:"hsl(230, 4%, 64%)"}},{types:["doctype","punctuation","entity"],style:{color:"hsl(230, 8%, 24%)"}},{types:["attr-name","class-name","boolean","constant","number","atrule"],style:{color:"hsl(35, 99%, 36%)"}},{types:["keyword"],style:{color:"hsl(301, 63%, 40%)"}},{types:["property","tag","symbol","deleted","important"],style:{color:"hsl(5, 74%, 59%)"}},{types:["selector","string","char","builtin","inserted","regex","attr-value","punctuation"],style:{color:"hsl(119, 34%, 47%)"}},{types:["variable","operator","function"],style:{color:"hsl(221, 87%, 60%)"}},{types:["url"],style:{color:"hsl(198, 99%, 37%)"}},{types:["deleted"],style:{textDecorationLine:"line-through"}},{types:["inserted"],style:{textDecorationLine:"underline"}},{types:["italic"],style:{fontStyle:"italic"}},{types:["important","bold"],style:{fontWeight:"bold"}},{types:["important"],style:{color:"hsl(230, 8%, 24%)"}}]},Q={plain:{color:"#ebdbb2",backgroundColor:"#292828"},styles:[{types:["imports","class-name","maybe-class-name","constant","doctype","builtin","function"],style:{color:"#d8a657"}},{types:["property-access"],style:{color:"#7daea3"}},{types:["tag"],style:{color:"#e78a4e"}},{types:["attr-name","char","url","regex"],style:{color:"#a9b665"}},{types:["attr-value","string"],style:{color:"#89b482"}},{types:["comment","prolog","cdata","operator","inserted"],style:{color:"#a89984"}},{types:["delimiter","boolean","keyword","selector","important","atrule","property","variable","deleted"],style:{color:"#ea6962"}},{types:["entity","number","symbol"],style:{color:"#d3869b"}}]},K={plain:{color:"#654735",backgroundColor:"#f9f5d7"},styles:[{types:["delimiter","boolean","keyword","selector","important","atrule","property","variable","deleted"],style:{color:"#af2528"}},{types:["imports","class-name","maybe-class-name","constant","doctype","builtin"],style:{color:"#b4730e"}},{types:["string","attr-value"],style:{color:"#477a5b"}},{types:["property-access"],style:{color:"#266b79"}},{types:["function","attr-name","char","url"],style:{color:"#72761e"}},{types:["tag"],style:{color:"#b94c07"}},{types:["comment","prolog","cdata","operator","inserted"],style:{color:"#a89984"}},{types:["entity","number","symbol"],style:{color:"#924f79"}}]},Y=/\r\n|\r|\n/,X=e=>{0===e.length?e.push({types:["plain"],content:"\n",empty:!0}):1===e.length&&""===e[0].content&&(e[0].content="\n",e[0].empty=!0)},Z=(e,t)=>{const n=e.length;return n>0&&e[n-1]===t?e:e.concat(t)},J=e=>{const t=[[]],n=[e],r=[0],a=[e.length];let o=0,i=0,l=[];const s=[l];for(;i>-1;){for(;(o=r[i]++)<a[i];){let e,c=t[i];const u=n[i][o];if("string"==typeof u?(c=i>0?c:["plain"],e=u):(c=Z(c,u.type),u.alias&&(c=Z(c,u.alias)),e=u.content),"string"!=typeof e){i++,t.push(c),n.push(e),r.push(0),a.push(e.length);continue}const d=e.split(Y),f=d.length;l.push({types:c,content:d[0]});for(let t=1;t<f;t++)X(l),s.push(l=[]),l.push({types:c,content:d[t]})}i--,t.pop(),n.pop(),r.pop(),a.pop()}return X(l),s},ee=(e,t)=>{const{plain:n}=e,r=e.styles.reduce((e,n)=>{const{languages:r,style:a}=n;return r&&!r.includes(t)||n.types.forEach(t=>{const n=x(x({},e[t]),a);e[t]=n}),e},{});return r.root=n,r.plain=_(x({},n),{backgroundColor:void 0}),r},te=({children:e,language:t,code:n,theme:r,prism:a})=>{const o=t.toLowerCase(),i=ee(r,o),l=(e=>(0,u.useCallback)(t=>{var n=t,{className:r,style:a,line:o}=n,i=E(n,["className","style","line"]);const l=_(x({},i),{className:(0,d.A)("token-line",r)});return"object"==typeof e&&"plain"in e&&(l.style=e.plain),"object"==typeof a&&(l.style=x(x({},l.style||{}),a)),l},[e]))(i),s=(e=>{const t=(0,u.useCallback)(({types:t,empty:n})=>{if(null!=e)return 1===t.length&&"plain"===t[0]?null!=n?{display:"inline-block"}:void 0:1===t.length&&null!=n?e[t[0]]:Object.assign(null!=n?{display:"inline-block"}:{},...t.map(t=>e[t]))},[e]);return(0,u.useCallback)(e=>{var n=e,{token:r,className:a,style:o}=n,i=E(n,["token","className","style"]);const l=_(x({},i),{className:(0,d.A)("token",...r.types,a),children:r.content,style:t(r)});return null!=o&&(l.style=x(x({},l.style||{}),o)),l},[t])})(i),c=(({prism:e,code:t,grammar:n,language:r})=>(0,u.useMemo)(()=>{if(null==n)return J([t]);const a={code:t,grammar:n,language:r,tokens:[]};return e.hooks.run("before-tokenize",a),a.tokens=e.tokenize(t,n),e.hooks.run("after-tokenize",a),J(a.tokens)},[t,n,r,e]))({prism:a,language:o,code:n,grammar:a.languages[o]});return e({tokens:c,className:`prism-code language-${o}`,style:null!=i?i.root:{},getLineProps:l,getTokenProps:s})},ne=e=>(0,u.createElement)(te,_(x({},e),{prism:e.prism||L,theme:e.theme||U,code:e.code,language:e.language}))},2131:(e,t,n)=>{"use strict";n.d(t,{o:()=>i});var r=n(4586),a=n(6347),o=n(440);function i(){const{siteConfig:{baseUrl:e,url:t,trailingSlash:n},i18n:{defaultLocale:i,currentLocale:l}}=(0,r.A)(),{pathname:s}=(0,a.zy)(),c=(0,o.Ks)(s,{trailingSlash:n,baseUrl:e}),u=l===i?e:e.replace(`/${l}/`,"/"),d=c.replace(e,"");return{createUrl:function({locale:e,fullyQualified:n}){return`${n?t:""}${function(e){return e===i?`${u}`:`${u}${e}/`}(e)}${d}`}}}},2303:(e,t,n)=>{"use strict";n.d(t,{A:()=>o});var r=n(6540),a=n(6125);function o(){return(0,r.useContext)(a.o)}},2566:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.addPrefix=function(e,t){return e.startsWith(t)?e:`${t}${e}`},t.removeSuffix=function(e,t){if(""===t)return e;return e.endsWith(t)?e.slice(0,-t.length):e},t.addSuffix=function(e,t){return e.endsWith(t)?e:`${e}${t}`},t.removePrefix=function(e,t){return e.startsWith(t)?e.slice(t.length):e}},2654:e=>{"use strict";e.exports={}},2694:(e,t,n)=>{"use strict";var r=n(6925);function a(){}function o(){}o.resetWarningCache=a,e.exports=function(){function e(e,t,n,a,o,i){if(i!==r){var l=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw l.name="Invariant Violation",l}}function t(){return e}e.isRequired=e;var n={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:o,resetWarningCache:a};return n.PropTypes=n,n}},2799:(e,t)=>{"use strict";var n="function"==typeof Symbol&&Symbol.for,r=n?Symbol.for("react.element"):60103,a=n?Symbol.for("react.portal"):60106,o=n?Symbol.for("react.fragment"):60107,i=n?Symbol.for("react.strict_mode"):60108,l=n?Symbol.for("react.profiler"):60114,s=n?Symbol.for("react.provider"):60109,c=n?Symbol.for("react.context"):60110,u=n?Symbol.for("react.async_mode"):60111,d=n?Symbol.for("react.concurrent_mode"):60111,f=n?Symbol.for("react.forward_ref"):60112,p=n?Symbol.for("react.suspense"):60113,m=n?Symbol.for("react.suspense_list"):60120,h=n?Symbol.for("react.memo"):60115,g=n?Symbol.for("react.lazy"):60116,b=n?Symbol.for("react.block"):60121,y=n?Symbol.for("react.fundamental"):60117,v=n?Symbol.for("react.responder"):60118,k=n?Symbol.for("react.scope"):60119;function w(e){if("object"==typeof e&&null!==e){var t=e.$$typeof;switch(t){case r:switch(e=e.type){case u:case d:case o:case l:case i:case p:return e;default:switch(e=e&&e.$$typeof){case c:case f:case g:case h:case s:return e;default:return t}}case a:return t}}}function S(e){return w(e)===d}t.AsyncMode=u,t.ConcurrentMode=d,t.ContextConsumer=c,t.ContextProvider=s,t.Element=r,t.ForwardRef=f,t.Fragment=o,t.Lazy=g,t.Memo=h,t.Portal=a,t.Profiler=l,t.StrictMode=i,t.Suspense=p,t.isAsyncMode=function(e){return S(e)||w(e)===u},t.isConcurrentMode=S,t.isContextConsumer=function(e){return w(e)===c},t.isContextProvider=function(e){return w(e)===s},t.isElement=function(e){return"object"==typeof e&&null!==e&&e.$$typeof===r},t.isForwardRef=function(e){return w(e)===f},t.isFragment=function(e){return w(e)===o},t.isLazy=function(e){return w(e)===g},t.isMemo=function(e){return w(e)===h},t.isPortal=function(e){return w(e)===a},t.isProfiler=function(e){return w(e)===l},t.isStrictMode=function(e){return w(e)===i},t.isSuspense=function(e){return w(e)===p},t.isValidElementType=function(e){return"string"==typeof e||"function"==typeof e||e===o||e===d||e===l||e===i||e===p||e===m||"object"==typeof e&&null!==e&&(e.$$typeof===g||e.$$typeof===h||e.$$typeof===s||e.$$typeof===c||e.$$typeof===f||e.$$typeof===y||e.$$typeof===v||e.$$typeof===k||e.$$typeof===b)},t.typeOf=w},2831:(e,t,n)=>{"use strict";n.d(t,{u:()=>i,v:()=>l});var r=n(6347),a=n(8168),o=n(6540);function i(e,t,n){return void 0===n&&(n=[]),e.some(function(e){var a=e.path?(0,r.B6)(t,e):n.length?n[n.length-1].match:r.Ix.computeRootMatch(t);return a&&(n.push({route:e,match:a}),e.routes&&i(e.routes,t,n)),a}),n}function l(e,t,n){return void 0===t&&(t={}),void 0===n&&(n={}),e?o.createElement(r.dO,n,e.map(function(e,n){return o.createElement(r.qh,{key:e.key||n,path:e.path,exact:e.exact,strict:e.strict,render:function(n){return e.render?e.render((0,a.A)({},n,{},t,{route:e})):o.createElement(e.component,(0,a.A)({},n,t,{route:e}))}})})):null}},2833:e=>{e.exports=function(e,t,n,r){var a=n?n.call(r,e,t):void 0;if(void 0!==a)return!!a;if(e===t)return!0;if("object"!=typeof e||!e||"object"!=typeof t||!t)return!1;var o=Object.keys(e),i=Object.keys(t);if(o.length!==i.length)return!1;for(var l=Object.prototype.hasOwnProperty.bind(t),s=0;s<o.length;s++){var c=o[s];if(!l(c))return!1;var u=e[c],d=t[c];if(!1===(a=n?n.call(r,u,d,c):void 0)||void 0===a&&u!==d)return!1}return!0}},2892:(e,t,n)=>{"use strict";function r(e,t){return r=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e},r(e,t)}function a(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,r(e,t)}n.d(t,{A:()=>a})},2983:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.addTrailingSlash=a,t.default=function(e,t){const{trailingSlash:n,baseUrl:r}=t;if(e.startsWith("#"))return e;if(void 0===n)return e;const[i]=e.split(/[#?]/),l="/"===i||i===r?i:(s=i,c=n,c?a(s):o(s));var s,c;return e.replace(i,l)},t.addLeadingSlash=function(e){return(0,r.addPrefix)(e,"/")},t.removeTrailingSlash=o;const r=n(2566);function a(e){return e.endsWith("/")?e:`${e}/`}function o(e){return(0,r.removeSuffix)(e,"/")}},3001:(e,t,n)=>{"use strict";n.r(t)},3025:(e,t,n)=>{"use strict";n.d(t,{n:()=>l,r:()=>s});var r=n(6540),a=n(9532),o=n(4848);const i=r.createContext(null);function l({children:e,version:t}){return(0,o.jsx)(i.Provider,{value:t,children:e})}function s(){const e=(0,r.useContext)(i);if(null===e)throw new a.dV("DocsVersionProvider");return e}},3102:(e,t,n)=>{"use strict";n.d(t,{W:()=>i,o:()=>o});var r=n(6540),a=n(4848);const o=r.createContext(null);function i({children:e,value:t}){const n=r.useContext(o),i=(0,r.useMemo)(()=>function({parent:e,value:t}){if(!e){if(!t)throw new Error("Unexpected: no Docusaurus route context found");if(!("plugin"in t))throw new Error("Unexpected: Docusaurus topmost route context has no `plugin` attribute");return t}const n={...e.data,...t?.data};return{plugin:e.plugin,data:n}}({parent:n,value:t}),[n,t]);return(0,a.jsx)(o.Provider,{value:i,children:e})}},3104:(e,t,n)=>{"use strict";n.d(t,{Mq:()=>f,Tv:()=>c,gk:()=>p});var r=n(6540),a=n(8193),o=n(2303),i=(n(205),n(9532)),l=n(4848);const s=r.createContext(void 0);function c({children:e}){const t=function(){const e=(0,r.useRef)(!0);return(0,r.useMemo)(()=>({scrollEventsEnabledRef:e,enableScrollEvents:()=>{e.current=!0},disableScrollEvents:()=>{e.current=!1}}),[])}();return(0,l.jsx)(s.Provider,{value:t,children:e})}function u(){const e=(0,r.useContext)(s);if(null==e)throw new i.dV("ScrollControllerProvider");return e}const d=()=>a.A.canUseDOM?{scrollX:window.pageXOffset,scrollY:window.pageYOffset}:null;function f(e,t=[]){const{scrollEventsEnabledRef:n}=u(),a=(0,r.useRef)(d()),o=(0,i._q)(e);(0,r.useEffect)(()=>{const e=()=>{if(!n.current)return;const e=d();o(e,a.current),a.current=e},t={passive:!0};return e(),window.addEventListener("scroll",e,t),()=>window.removeEventListener("scroll",e,t)},[o,n,...t])}function p(){const e=(0,r.useRef)(null),t=(0,o.A)()&&"smooth"===getComputedStyle(document.documentElement).scrollBehavior;return{startScroll:n=>{e.current=t?function(e){return window.scrollTo({top:e,behavior:"smooth"}),()=>{}}(n):function(e){let t=null;const n=document.documentElement.scrollTop>e;return function r(){const a=document.documentElement.scrollTop;(n&&a>e||!n&&a<e)&&(t=requestAnimationFrame(r),window.scrollTo(0,Math.floor(.85*(a-e))+e))}(),()=>t&&cancelAnimationFrame(t)}(n)},cancelScroll:()=>e.current?.()}}},3109:(e,t,n)=>{"use strict";function r(){return window.matchMedia("(prefers-reduced-motion: reduce)").matches}n.d(t,{O:()=>r})},3157:(e,t,n)=>{var r={"./":8722};function a(e){var t=o(e);return n(t)}function o(e){if(!n.o(r,e)){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}return r[e]}a.keys=function(){return Object.keys(r)},a.resolve=o,e.exports=a,a.id=3157},3186:(e,t,n)=>{"use strict";n.d(t,{A:()=>i});n(6540);const r={iconExternalLink:"iconExternalLink_nPIU"};var a=n(4848);const o="#theme-svg-external-link";function i({width:e=13.5,height:t=13.5}){return(0,a.jsx)("svg",{width:e,height:t,"aria-hidden":"true",className:r.iconExternalLink,children:(0,a.jsx)("use",{href:o})})}},3259:(e,t,n)=>{"use strict";function r(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,e.__proto__=t}function a(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function o(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(){return i=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},i.apply(this,arguments)}var l=n(6540),s=[],c=[];var u=l.createContext(null);function d(e){var t=e(),n={loading:!0,loaded:null,error:null};return n.promise=t.then(function(e){return n.loading=!1,n.loaded=e,e}).catch(function(e){throw n.loading=!1,n.error=e,e}),n}function f(e){var t={loading:!1,loaded:{},error:null},n=[];try{Object.keys(e).forEach(function(r){var a=d(e[r]);a.loading?t.loading=!0:(t.loaded[r]=a.loaded,t.error=a.error),n.push(a.promise),a.promise.then(function(e){t.loaded[r]=e}).catch(function(e){t.error=e})})}catch(r){t.error=r}return t.promise=Promise.all(n).then(function(e){return t.loading=!1,e}).catch(function(e){throw t.loading=!1,e}),t}function p(e,t){return l.createElement((n=e)&&n.__esModule?n.default:n,t);var n}function m(e,t){var d,f;if(!t.loading)throw new Error("react-loadable requires a `loading` component");var m=i({loader:null,loading:null,delay:200,timeout:null,render:p,webpack:null,modules:null},t),h=null;function g(){return h||(h=e(m.loader)),h.promise}return s.push(g),"function"==typeof m.webpack&&c.push(function(){if((0,m.webpack)().every(function(e){return void 0!==e&&void 0!==n.m[e]}))return g()}),f=d=function(t){function n(n){var r;return o(a(a(r=t.call(this,n)||this)),"retry",function(){r.setState({error:null,loading:!0,timedOut:!1}),h=e(m.loader),r._loadModule()}),g(),r.state={error:h.error,pastDelay:!1,timedOut:!1,loading:h.loading,loaded:h.loaded},r}r(n,t),n.preload=function(){return g()};var i=n.prototype;return i.UNSAFE_componentWillMount=function(){this._loadModule()},i.componentDidMount=function(){this._mounted=!0},i._loadModule=function(){var e=this;if(this.context&&Array.isArray(m.modules)&&m.modules.forEach(function(t){e.context.report(t)}),h.loading){var t=function(t){e._mounted&&e.setState(t)};"number"==typeof m.delay&&(0===m.delay?this.setState({pastDelay:!0}):this._delay=setTimeout(function(){t({pastDelay:!0})},m.delay)),"number"==typeof m.timeout&&(this._timeout=setTimeout(function(){t({timedOut:!0})},m.timeout));var n=function(){t({error:h.error,loaded:h.loaded,loading:h.loading}),e._clearTimeouts()};h.promise.then(function(){return n(),null}).catch(function(e){return n(),null})}},i.componentWillUnmount=function(){this._mounted=!1,this._clearTimeouts()},i._clearTimeouts=function(){clearTimeout(this._delay),clearTimeout(this._timeout)},i.render=function(){return this.state.loading||this.state.error?l.createElement(m.loading,{isLoading:this.state.loading,pastDelay:this.state.pastDelay,timedOut:this.state.timedOut,error:this.state.error,retry:this.retry}):this.state.loaded?m.render(this.state.loaded,this.props):null},n}(l.Component),o(d,"contextType",u),f}function h(e){return m(d,e)}h.Map=function(e){if("function"!=typeof e.render)throw new Error("LoadableMap requires a `render(loaded, props)` function");return m(f,e)};var g=function(e){function t(){return e.apply(this,arguments)||this}return r(t,e),t.prototype.render=function(){return l.createElement(u.Provider,{value:{report:this.props.report}},l.Children.only(this.props.children))},t}(l.Component);function b(e){for(var t=[];e.length;){var n=e.pop();t.push(n())}return Promise.all(t).then(function(){if(e.length)return b(e)})}h.Capture=g,h.preloadAll=function(){return new Promise(function(e,t){b(s).then(e,t)})},h.preloadReady=function(){return new Promise(function(e,t){b(c).then(e,e)})},e.exports=h},3427:(e,t,n)=>{"use strict";n.d(t,{A:()=>i});var r=n(6540);n(4848);const a=r.createContext({collectAnchor:()=>{},collectLink:()=>{}}),o=()=>(0,r.useContext)(a);function i(){return o()}},3465:(e,t,n)=>{"use strict";n.d(t,{A:()=>u});n(6540);var r=n(8774),a=n(6025),o=n(4586),i=n(6342),l=n(1122),s=n(4848);function c({logo:e,alt:t,imageClassName:n}){const r={light:(0,a.Ay)(e.src),dark:(0,a.Ay)(e.srcDark||e.src)},o=(0,s.jsx)(l.A,{className:e.className,sources:r,height:e.height,width:e.width,alt:t,style:e.style});return n?(0,s.jsx)("div",{className:n,children:o}):o}function u(e){const{siteConfig:{title:t}}=(0,o.A)(),{navbar:{title:n,logo:l}}=(0,i.p)(),{imageClassName:u,titleClassName:d,...f}=e,p=(0,a.Ay)(l?.href||"/"),m=n?"":t,h=l?.alt??m;return(0,s.jsxs)(r.A,{to:p,...f,...l?.target&&{target:l.target},children:[l&&(0,s.jsx)(c,{logo:l,alt:h,imageClassName:u}),null!=n&&(0,s.jsx)("b",{className:d,children:n})]})}},3886:(e,t,n)=>{"use strict";n.d(t,{VQ:()=>g,g1:()=>y});var r=n(6540),a=n(4070),o=n(7065),i=n(6342),l=n(679),s=n(9532),c=n(4848);const u=e=>`docs-preferred-version-${e}`,d={save:(e,t,n)=>{(0,l.Wf)(u(e),{persistence:t}).set(n)},read:(e,t)=>(0,l.Wf)(u(e),{persistence:t}).get(),clear:(e,t)=>{(0,l.Wf)(u(e),{persistence:t}).del()}},f=e=>Object.fromEntries(e.map(e=>[e,{preferredVersionName:null}]));const p=r.createContext(null);function m(){const e=(0,a.Gy)(),t=(0,i.p)().docs.versionPersistence,n=(0,r.useMemo)(()=>Object.keys(e),[e]),[o,l]=(0,r.useState)(()=>f(n));(0,r.useEffect)(()=>{l(function({pluginIds:e,versionPersistence:t,allDocsData:n}){function r(e){const r=d.read(e,t);return n[e].versions.some(e=>e.name===r)?{preferredVersionName:r}:(d.clear(e,t),{preferredVersionName:null})}return Object.fromEntries(e.map(e=>[e,r(e)]))}({allDocsData:e,versionPersistence:t,pluginIds:n}))},[e,t,n]);return[o,(0,r.useMemo)(()=>({savePreferredVersion:function(e,n){d.save(e,t,n),l(t=>({...t,[e]:{preferredVersionName:n}}))}}),[t])]}function h({children:e}){const t=m();return(0,c.jsx)(p.Provider,{value:t,children:e})}function g({children:e}){return(0,c.jsx)(h,{children:e})}function b(){const e=(0,r.useContext)(p);if(!e)throw new s.dV("DocsPreferredVersionContextProvider");return e}function y(e=o.W){const t=(0,a.ht)(e),[n,i]=b(),{preferredVersionName:l}=n[e];return{preferredVersion:t.versions.find(e=>e.name===l)??null,savePreferredVersionName:(0,r.useCallback)(t=>{i.savePreferredVersion(e,t)},[i,e])}}},4054:e=>{"use strict";e.exports=JSON.parse('{"/BharatMLStack/blog-c07":{"__comp":"a6aa9e1f","__context":{"plugin":"36994c47"},"sidebar":"814f3328","items":[{"content":"4dd73b28"},{"content":"769c1945"},{"content":"845957d4"},{"content":"3039fa8c"},{"content":"1a4fe2b7"},{"content":"a1ba6e62"}],"__props":"f994c8da"},"/BharatMLStack/blog/archive-dde":{"__comp":"9e4087bc","__context":{"plugin":"36994c47"},"__props":"6479fb86"},"/BharatMLStack/blog/authors-f47":{"__comp":"621db11d","__context":{"data":{"blogMetadata":"acecf23e"},"plugin":"36994c47"},"sidebar":"814f3328","__props":"2d865531"},"/BharatMLStack/blog/building-meeshos-mlplatform-3fa":{"__comp":"ccc49370","__context":{"data":{"blogMetadata":"acecf23e"},"plugin":"36994c47"},"sidebar":"814f3328","content":"3c208a5b"},"/BharatMLStack/blog/building-meeshos-mlplatform-lessons-from-first-gen-a84":{"__comp":"ccc49370","__context":{"data":{"blogMetadata":"acecf23e"},"plugin":"36994c47"},"sidebar":"814f3328","content":"23d02069"},"/BharatMLStack/blog/episodic-memory-for-agents-c4f":{"__comp":"ccc49370","__context":{"data":{"blogMetadata":"acecf23e"},"plugin":"36994c47"},"sidebar":"814f3328","content":"4b01b88a"},"/BharatMLStack/blog/llm-inference-optimization-sub-sec-latency-403":{"__comp":"ccc49370","__context":{"data":{"blogMetadata":"acecf23e"},"plugin":"36994c47"},"sidebar":"814f3328","content":"d9861b0f"},"/BharatMLStack/blog/multi-engine-llm-inferencing-platform-b26":{"__comp":"ccc49370","__context":{"data":{"blogMetadata":"acecf23e"},"plugin":"36994c47"},"sidebar":"814f3328","content":"d853e668"},"/BharatMLStack/blog/scaling-model-inference-and-embedding-search-d48":{"__comp":"ccc49370","__context":{"data":{"blogMetadata":"acecf23e"},"plugin":"36994c47"},"sidebar":"814f3328","content":"2303959d"},"/BharatMLStack/blog/tags-8af":{"__comp":"01a85c17","__context":{"plugin":"36994c47"},"sidebar":"814f3328","__props":"7fa80e1c"},"/BharatMLStack/blog/tags/ai-agents-2cc":{"__comp":"6875c492","__context":{"plugin":"36994c47"},"sidebar":"814f3328","items":[{"content":"4dd73b28"}],"__props":"c31e69d4"},"/BharatMLStack/blog/tags/architecture-584":{"__comp":"6875c492","__context":{"plugin":"36994c47"},"sidebar":"814f3328","items":[{"content":"4dd73b28"}],"__props":"982cae12"},"/BharatMLStack/blog/tags/bharatmlstack-c39":{"__comp":"6875c492","__context":{"plugin":"36994c47"},"sidebar":"814f3328","items":[{"content":"769c1945"},{"content":"845957d4"},{"content":"3039fa8c"},{"content":"1a4fe2b7"}],"__props":"aaabe254"},"/BharatMLStack/blog/tags/embedding-search-f63":{"__comp":"6875c492","__context":{"plugin":"36994c47"},"sidebar":"814f3328","items":[{"content":"3039fa8c"}],"__props":"be9e6e2d"},"/BharatMLStack/blog/tags/episodic-memory-97a":{"__comp":"6875c492","__context":{"plugin":"36994c47"},"sidebar":"814f3328","items":[{"content":"4dd73b28"}],"__props":"a2d4c71d"},"/BharatMLStack/blog/tags/inferflow-3d0":{"__comp":"6875c492","__context":{"plugin":"36994c47"},"sidebar":"814f3328","items":[{"content":"1a4fe2b7"}],"__props":"93f344c7"},"/BharatMLStack/blog/tags/interaction-store-709":{"__comp":"6875c492","__context":{"plugin":"36994c47"},"sidebar":"814f3328","items":[{"content":"1a4fe2b7"},{"content":"a1ba6e62"}],"__props":"3980073a"},"/BharatMLStack/blog/tags/llm-bdb":{"__comp":"6875c492","__context":{"plugin":"36994c47"},"sidebar":"814f3328","items":[{"content":"4dd73b28"},{"content":"769c1945"},{"content":"845957d4"}],"__props":"adb039a4"},"/BharatMLStack/blog/tags/meesho-bb0":{"__comp":"6875c492","__context":{"plugin":"36994c47"},"sidebar":"814f3328","items":[{"content":"769c1945"},{"content":"845957d4"},{"content":"3039fa8c"},{"content":"1a4fe2b7"},{"content":"a1ba6e62"}],"__props":"1a64de69"},"/BharatMLStack/blog/tags/memory-eff":{"__comp":"6875c492","__context":{"plugin":"36994c47"},"sidebar":"814f3328","items":[{"content":"4dd73b28"}],"__props":"0dae2a8b"},"/BharatMLStack/blog/tags/mlplatform-e7a":{"__comp":"6875c492","__context":{"plugin":"36994c47"},"sidebar":"814f3328","items":[{"content":"769c1945"},{"content":"845957d4"},{"content":"3039fa8c"},{"content":"1a4fe2b7"},{"content":"a1ba6e62"}],"__props":"479eb034"},"/BharatMLStack/blog/tags/model-inference-e08":{"__comp":"6875c492","__context":{"plugin":"36994c47"},"sidebar":"814f3328","items":[{"content":"3039fa8c"}],"__props":"08daf6b6"},"/BharatMLStack/blog/tags/online-feature-store-7fc":{"__comp":"6875c492","__context":{"plugin":"36994c47"},"sidebar":"814f3328","items":[{"content":"a1ba6e62"}],"__props":"3e1c5046"},"/BharatMLStack/blog/tags/tensorrt-llm-e6b":{"__comp":"6875c492","__context":{"plugin":"36994c47"},"sidebar":"814f3328","items":[{"content":"769c1945"},{"content":"845957d4"}],"__props":"99009a21"},"/BharatMLStack/blog/tags/vllm-c0a":{"__comp":"6875c492","__context":{"plugin":"36994c47"},"sidebar":"814f3328","items":[{"content":"769c1945"},{"content":"845957d4"}],"__props":"6bb91276"},"/BharatMLStack/markdown-page-747":{"__comp":"1f391b9e","__context":{"plugin":"a7456010"},"content":"393be207"},"/BharatMLStack/-e34":{"__comp":"c4f5d8e4","__context":{"plugin":"a7456010"},"config":"5e9f5e1a"},"/BharatMLStack/-634":{"__comp":"5e95c892","__context":{"plugin":"aba21aa0"}},"/BharatMLStack/-069":{"__comp":"a7bd4aaa","__props":"4137b431"},"/BharatMLStack/-289":{"__comp":"a94703ab"},"/BharatMLStack/category/go-sdk-6b0":{"__comp":"14eb3368","__props":"c7b64fcc"},"/BharatMLStack/category/inferflow-e9f":{"__comp":"14eb3368","__props":"8dd2df60"},"/BharatMLStack/category/numerix-703":{"__comp":"14eb3368","__props":"50899a24"},"/BharatMLStack/category/online-feature-store-7ee":{"__comp":"14eb3368","__props":"8ac6191a"},"/BharatMLStack/category/predator-549":{"__comp":"14eb3368","__props":"74783256"},"/BharatMLStack/category/python-sdk-1fd":{"__comp":"14eb3368","__props":"44d1c015"},"/BharatMLStack/category/quick-start-dff":{"__comp":"14eb3368","__props":"14064408"},"/BharatMLStack/category/sdks-532":{"__comp":"14eb3368","__props":"616111d3"},"/BharatMLStack/category/skye-1b4":{"__comp":"14eb3368","__props":"4d1a2db0"},"/BharatMLStack/category/trufflebox-ui-5f5":{"__comp":"14eb3368","__props":"fcf4f6ca"},"/BharatMLStack/inferflow/v1.0.0-675":{"__comp":"17896441","content":"ae7a6e8a"},"/BharatMLStack/inferflow/v1.0.0/architecture-46b":{"__comp":"17896441","content":"252a9097"},"/BharatMLStack/inferflow/v1.0.0/configuration-4ef":{"__comp":"17896441","content":"9d13045e"},"/BharatMLStack/inferflow/v1.0.0/functionalities-65a":{"__comp":"17896441","content":"0a89f5c9"},"/BharatMLStack/inferflow/v1.0.0/release-notes-cce":{"__comp":"17896441","content":"9aed321e"},"/BharatMLStack/intro-eef":{"__comp":"17896441","content":"0e384e19"},"/BharatMLStack/numerix/v1.0.0-7c7":{"__comp":"17896441","content":"b0267ac9"},"/BharatMLStack/numerix/v1.0.0/architecture-4d9":{"__comp":"17896441","content":"4df0e30b"},"/BharatMLStack/numerix/v1.0.0/benchmarks-eae":{"__comp":"17896441","content":"e8202a51"},"/BharatMLStack/numerix/v1.0.0/functionalities-f7b":{"__comp":"17896441","content":"2c62ead1"},"/BharatMLStack/numerix/v1.0.0/release-notes-77b":{"__comp":"17896441","content":"8ea48c46"},"/BharatMLStack/online-feature-store/v1.0.0-b5e":{"__comp":"17896441","content":"340c7c5f"},"/BharatMLStack/online-feature-store/v1.0.0/architecture-0af":{"__comp":"17896441","content":"e66382f6"},"/BharatMLStack/online-feature-store/v1.0.0/benchmarks-889":{"__comp":"17896441","content":"67d4782a"},"/BharatMLStack/online-feature-store/v1.0.0/data-formats-46e":{"__comp":"17896441","content":"4caa95bf"},"/BharatMLStack/online-feature-store/v1.0.0/functionalities-415":{"__comp":"17896441","content":"c4822c4f"},"/BharatMLStack/online-feature-store/v1.0.0/release-notes-36c":{"__comp":"17896441","content":"d152284c"},"/BharatMLStack/predator/v1.0.0-93a":{"__comp":"17896441","content":"d01bc907"},"/BharatMLStack/predator/v1.0.0/architecture-618":{"__comp":"17896441","content":"23a1b8fc"},"/BharatMLStack/predator/v1.0.0/functionalities-094":{"__comp":"17896441","content":"e8321834"},"/BharatMLStack/predator/v1.0.0/release-notes-d81":{"__comp":"17896441","content":"bba9e323"},"/BharatMLStack/quick-start/v1.0.0-726":{"__comp":"17896441","content":"bf2864cf"},"/BharatMLStack/quick-start/v1.0.0/quick-start-b19":{"__comp":"17896441","content":"0fff8dc8"},"/BharatMLStack/sdks/go/v1.0.0-ccc":{"__comp":"17896441","content":"bcee635f"},"/BharatMLStack/sdks/go/v1.0.0/feature_client-1df":{"__comp":"17896441","content":"4af50aac"},"/BharatMLStack/sdks/python/v1.0.0-60c":{"__comp":"17896441","content":"c621f852"},"/BharatMLStack/sdks/python/v1.0.0/grpc_feature_client-9dc":{"__comp":"17896441","content":"0413d9af"},"/BharatMLStack/sdks/python/v1.0.0/spark_feature_push_client-1bc":{"__comp":"17896441","content":"ac51638e"},"/BharatMLStack/skye/v1.0.0-29b":{"__comp":"17896441","content":"3650a837"},"/BharatMLStack/skye/v1.0.0/architecture-ef9":{"__comp":"17896441","content":"56eef1be"},"/BharatMLStack/skye/v1.0.0/functionalities-d7c":{"__comp":"17896441","content":"9796f4b8"},"/BharatMLStack/skye/v1.0.0/release-notes-139":{"__comp":"17896441","content":"bd5b7851"},"/BharatMLStack/trufflebox-ui/v1.0.0-a9d":{"__comp":"17896441","content":"df502808"},"/BharatMLStack/trufflebox-ui/v1.0.0/userguide-65e":{"__comp":"17896441","content":"176d210f"}}')},4070:(e,t,n)=>{"use strict";n.d(t,{zK:()=>h,vT:()=>f,Gy:()=>u,HW:()=>g,ht:()=>d,r7:()=>m,jh:()=>p});var r=n(6347),a=n(4586),o=n(7065);function i(e,t={}){const n=function(){const{globalData:e}=(0,a.A)();return e}()[e];if(!n&&t.failfast)throw new Error(`Docusaurus plugin global data not found for "${e}" plugin.`);return n}const l=e=>e.versions.find(e=>e.isLast);function s(e,t){const n=function(e,t){return[...e.versions].sort((e,t)=>e.path===t.path?0:e.path.includes(t.path)?-1:t.path.includes(e.path)?1:0).find(e=>!!(0,r.B6)(t,{path:e.path,exact:!1,strict:!1}))}(e,t),a=n?.docs.find(e=>!!(0,r.B6)(t,{path:e.path,exact:!0,strict:!1}));return{activeVersion:n,activeDoc:a,alternateDocVersions:a?function(t){const n={};return e.versions.forEach(e=>{e.docs.forEach(r=>{r.id===t&&(n[e.name]=r)})}),n}(a.id):{}}}const c={},u=()=>i("docusaurus-plugin-content-docs")??c,d=e=>{try{return function(e,t=o.W,n={}){const r=i(e),a=r?.[t];if(!a&&n.failfast)throw new Error(`Docusaurus plugin global data not found for "${e}" plugin with id "${t}".`);return a}("docusaurus-plugin-content-docs",e,{failfast:!0})}catch(t){throw new Error("You are using a feature of the Docusaurus docs plugin, but this plugin does not seem to be enabled"+("Default"===e?"":` (pluginId=${e}`),{cause:t})}};function f(e={}){const t=u(),{pathname:n}=(0,r.zy)();return function(e,t,n={}){const a=Object.entries(e).sort((e,t)=>t[1].path.localeCompare(e[1].path)).find(([,e])=>!!(0,r.B6)(t,{path:e.path,exact:!1,strict:!1})),o=a?{pluginId:a[0],pluginData:a[1]}:void 0;if(!o&&n.failfast)throw new Error(`Can't find active docs plugin for "${t}" pathname, while it was expected to be found. Maybe you tried to use a docs feature that can only be used on a docs-related page? Existing docs plugin paths are: ${Object.values(e).map(e=>e.path).join(", ")}`);return o}(t,n,e)}function p(e){return d(e).versions}function m(e){const t=d(e);return l(t)}function h(e){const t=d(e),{pathname:n}=(0,r.zy)();return s(t,n)}function g(e){const t=d(e),{pathname:n}=(0,r.zy)();return function(e,t){const n=l(e);return{latestDocSuggestion:s(e,t).alternateDocVersions[n.name],latestVersionSuggestion:n}}(t,n)}},4090:(e,t,n)=>{"use strict";n.d(t,{w:()=>a,J:()=>o});var r=n(6540);const a="navigation-with-keyboard";function o(){(0,r.useEffect)(()=>{function e(e){"keydown"===e.type&&"Tab"===e.key&&document.body.classList.add(a),"mousedown"===e.type&&document.body.classList.remove(a)}return document.addEventListener("keydown",e),document.addEventListener("mousedown",e),()=>{document.body.classList.remove(a),document.removeEventListener("keydown",e),document.removeEventListener("mousedown",e)}},[])}},4146:(e,t,n)=>{"use strict";var r=n(4363),a={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},o={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},i={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},l={};function s(e){return r.isMemo(e)?i:l[e.$$typeof]||a}l[r.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},l[r.Memo]=i;var c=Object.defineProperty,u=Object.getOwnPropertyNames,d=Object.getOwnPropertySymbols,f=Object.getOwnPropertyDescriptor,p=Object.getPrototypeOf,m=Object.prototype;e.exports=function e(t,n,r){if("string"!=typeof n){if(m){var a=p(n);a&&a!==m&&e(t,a,r)}var i=u(n);d&&(i=i.concat(d(n)));for(var l=s(t),h=s(n),g=0;g<i.length;++g){var b=i[g];if(!(o[b]||r&&r[b]||h&&h[b]||l&&l[b])){var y=f(n,b);try{c(t,b,y)}catch(v){}}}}return t}},4164:(e,t,n)=>{"use strict";function r(e){var t,n,a="";if("string"==typeof e||"number"==typeof e)a+=e;else if("object"==typeof e)if(Array.isArray(e)){var o=e.length;for(t=0;t<o;t++)e[t]&&(n=r(e[t]))&&(a&&(a+=" "),a+=n)}else for(n in e)e[n]&&(a&&(a+=" "),a+=n);return a}n.d(t,{A:()=>a});const a=function(){for(var e,t,n=0,a="",o=arguments.length;n<o;n++)(e=arguments[n])&&(t=r(e))&&(a&&(a+=" "),a+=t);return a}},4363:(e,t,n)=>{"use strict";e.exports=n(2799)},4477:(e,t)=>{"use strict";function n(e,t){var n=e.length;e.push(t);e:for(;0<n;){var r=n-1>>>1,a=e[r];if(!(0<o(a,t)))break e;e[r]=t,e[n]=a,n=r}}function r(e){return 0===e.length?null:e[0]}function a(e){if(0===e.length)return null;var t=e[0],n=e.pop();if(n!==t){e[0]=n;e:for(var r=0,a=e.length,i=a>>>1;r<i;){var l=2*(r+1)-1,s=e[l],c=l+1,u=e[c];if(0>o(s,n))c<a&&0>o(u,s)?(e[r]=u,e[c]=n,r=c):(e[r]=s,e[l]=n,r=l);else{if(!(c<a&&0>o(u,n)))break e;e[r]=u,e[c]=n,r=c}}}return t}function o(e,t){var n=e.sortIndex-t.sortIndex;return 0!==n?n:e.id-t.id}if(t.unstable_now=void 0,"object"==typeof performance&&"function"==typeof performance.now){var i=performance;t.unstable_now=function(){return i.now()}}else{var l=Date,s=l.now();t.unstable_now=function(){return l.now()-s}}var c=[],u=[],d=1,f=null,p=3,m=!1,h=!1,g=!1,b=!1,y="function"==typeof setTimeout?setTimeout:null,v="function"==typeof clearTimeout?clearTimeout:null,k="undefined"!=typeof setImmediate?setImmediate:null;function w(e){for(var t=r(u);null!==t;){if(null===t.callback)a(u);else{if(!(t.startTime<=e))break;a(u),t.sortIndex=t.expirationTime,n(c,t)}t=r(u)}}function S(e){if(g=!1,w(e),!h)if(null!==r(c))h=!0,_||(_=!0,x());else{var t=r(u);null!==t&&P(S,t.startTime-e)}}var x,_=!1,E=-1,C=5,L=-1;function A(){return!!b||!(t.unstable_now()-L<C)}function T(){if(b=!1,_){var e=t.unstable_now();L=e;var n=!0;try{e:{h=!1,g&&(g=!1,v(E),E=-1),m=!0;var o=p;try{t:{for(w(e),f=r(c);null!==f&&!(f.expirationTime>e&&A());){var i=f.callback;if("function"==typeof i){f.callback=null,p=f.priorityLevel;var l=i(f.expirationTime<=e);if(e=t.unstable_now(),"function"==typeof l){f.callback=l,w(e),n=!0;break t}f===r(c)&&a(c),w(e)}else a(c);f=r(c)}if(null!==f)n=!0;else{var s=r(u);null!==s&&P(S,s.startTime-e),n=!1}}break e}finally{f=null,p=o,m=!1}n=void 0}}finally{n?x():_=!1}}}if("function"==typeof k)x=function(){k(T)};else if("undefined"!=typeof MessageChannel){var j=new MessageChannel,M=j.port2;j.port1.onmessage=T,x=function(){M.postMessage(null)}}else x=function(){y(T,0)};function P(e,n){E=y(function(){e(t.unstable_now())},n)}t.unstable_IdlePriority=5,t.unstable_ImmediatePriority=1,t.unstable_LowPriority=4,t.unstable_NormalPriority=3,t.unstable_Profiling=null,t.unstable_UserBlockingPriority=2,t.unstable_cancelCallback=function(e){e.callback=null},t.unstable_forceFrameRate=function(e){0>e||125<e?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):C=0<e?Math.floor(1e3/e):5},t.unstable_getCurrentPriorityLevel=function(){return p},t.unstable_next=function(e){switch(p){case 1:case 2:case 3:var t=3;break;default:t=p}var n=p;p=t;try{return e()}finally{p=n}},t.unstable_requestPaint=function(){b=!0},t.unstable_runWithPriority=function(e,t){switch(e){case 1:case 2:case 3:case 4:case 5:break;default:e=3}var n=p;p=e;try{return t()}finally{p=n}},t.unstable_scheduleCallback=function(e,a,o){var i=t.unstable_now();switch("object"==typeof o&&null!==o?o="number"==typeof(o=o.delay)&&0<o?i+o:i:o=i,e){case 1:var l=-1;break;case 2:l=250;break;case 5:l=1073741823;break;case 4:l=1e4;break;default:l=5e3}return e={id:d++,callback:a,priorityLevel:e,startTime:o,expirationTime:l=o+l,sortIndex:-1},o>i?(e.sortIndex=o,n(u,e),null===r(c)&&e===r(u)&&(g?(v(E),E=-1):g=!0,P(S,o-i))):(e.sortIndex=l,n(c,e),h||m||(h=!0,_||(_=!0,x()))),e},t.unstable_shouldYield=A,t.unstable_wrapCallback=function(e){var t=p;return function(){var n=p;p=t;try{return e.apply(this,arguments)}finally{p=n}}}},4563:(e,t,n)=>{"use strict";n.d(t,{AL:()=>u,s$:()=>d});var r=n(6540),a=n(4586),o=n(6803),i=n(9532),l=n(4848);const s=({title:e,siteTitle:t,titleDelimiter:n})=>{const r=e?.trim();return r&&r!==t?`${r} ${n} ${t}`:t},c=(0,r.createContext)(null);function u({formatter:e,children:t}){return(0,l.jsx)(c.Provider,{value:e,children:t})}function d(){const e=function(){const e=(0,r.useContext)(c);if(null===e)throw new i.dV("TitleFormatterProvider");return e}(),{siteConfig:t}=(0,a.A)(),{title:n,titleDelimiter:l}=t,{plugin:u}=(0,o.A)();return{format:t=>e({title:t,siteTitle:n,titleDelimiter:l,plugin:u,defaultFormatter:s})}}},4581:(e,t,n)=>{"use strict";n.d(t,{l:()=>l});var r=n(6540),a=n(8193);const o={desktop:"desktop",mobile:"mobile",ssr:"ssr"},i=996;function l({desktopBreakpoint:e=i}={}){const[t,n]=(0,r.useState)(()=>"ssr");return(0,r.useEffect)(()=>{function t(){n(function(e){if(!a.A.canUseDOM)throw new Error("getWindowSize() should only be called after React hydration");return window.innerWidth>e?o.desktop:o.mobile}(e))}return t(),window.addEventListener("resize",t),()=>{window.removeEventListener("resize",t)}},[e]),t}},4586:(e,t,n)=>{"use strict";n.d(t,{A:()=>o});var r=n(6540),a=n(6988);function o(){return(0,r.useContext)(a.o)}},4625:(e,t,n)=>{"use strict";n.d(t,{I9:()=>d,Kd:()=>u,N_:()=>b,k2:()=>k});var r=n(6347),a=n(2892),o=n(6540),i=n(1513),l=n(8168),s=n(8587),c=n(1561),u=function(e){function t(){for(var t,n=arguments.length,r=new Array(n),a=0;a<n;a++)r[a]=arguments[a];return(t=e.call.apply(e,[this].concat(r))||this).history=(0,i.zR)(t.props),t}return(0,a.A)(t,e),t.prototype.render=function(){return o.createElement(r.Ix,{history:this.history,children:this.props.children})},t}(o.Component);var d=function(e){function t(){for(var t,n=arguments.length,r=new Array(n),a=0;a<n;a++)r[a]=arguments[a];return(t=e.call.apply(e,[this].concat(r))||this).history=(0,i.TM)(t.props),t}return(0,a.A)(t,e),t.prototype.render=function(){return o.createElement(r.Ix,{history:this.history,children:this.props.children})},t}(o.Component);var f=function(e,t){return"function"==typeof e?e(t):e},p=function(e,t){return"string"==typeof e?(0,i.yJ)(e,null,null,t):e},m=function(e){return e},h=o.forwardRef;void 0===h&&(h=m);var g=h(function(e,t){var n=e.innerRef,r=e.navigate,a=e.onClick,i=(0,s.A)(e,["innerRef","navigate","onClick"]),c=i.target,u=(0,l.A)({},i,{onClick:function(e){try{a&&a(e)}catch(t){throw e.preventDefault(),t}e.defaultPrevented||0!==e.button||c&&"_self"!==c||function(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}(e)||(e.preventDefault(),r())}});return u.ref=m!==h&&t||n,o.createElement("a",u)});var b=h(function(e,t){var n=e.component,a=void 0===n?g:n,u=e.replace,d=e.to,b=e.innerRef,y=(0,s.A)(e,["component","replace","to","innerRef"]);return o.createElement(r.XZ.Consumer,null,function(e){e||(0,c.A)(!1);var n=e.history,r=p(f(d,e.location),e.location),s=r?n.createHref(r):"",g=(0,l.A)({},y,{href:s,navigate:function(){var t=f(d,e.location),r=(0,i.AO)(e.location)===(0,i.AO)(p(t));(u||r?n.replace:n.push)(t)}});return m!==h?g.ref=t||b:g.innerRef=b,o.createElement(a,g)})}),y=function(e){return e},v=o.forwardRef;void 0===v&&(v=y);var k=v(function(e,t){var n=e["aria-current"],a=void 0===n?"page":n,i=e.activeClassName,u=void 0===i?"active":i,d=e.activeStyle,m=e.className,h=e.exact,g=e.isActive,k=e.location,w=e.sensitive,S=e.strict,x=e.style,_=e.to,E=e.innerRef,C=(0,s.A)(e,["aria-current","activeClassName","activeStyle","className","exact","isActive","location","sensitive","strict","style","to","innerRef"]);return o.createElement(r.XZ.Consumer,null,function(e){e||(0,c.A)(!1);var n=k||e.location,i=p(f(_,n),n),s=i.pathname,L=s&&s.replace(/([.+*?=^!:${}()[\]|/\\])/g,"\\$1"),A=L?(0,r.B6)(n.pathname,{path:L,exact:h,sensitive:w,strict:S}):null,T=!!(g?g(A,n):A),j="function"==typeof m?m(T):m,M="function"==typeof x?x(T):x;T&&(j=function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return t.filter(function(e){return e}).join(" ")}(j,u),M=(0,l.A)({},M,d));var P=(0,l.A)({"aria-current":T&&a||null,className:j,style:M,to:i},C);return y!==v?P.ref=t||E:P.innerRef=E,o.createElement(b,P)})})},4634:e=>{e.exports=Array.isArray||function(e){return"[object Array]"==Object.prototype.toString.call(e)}},4784:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r={title:"BharatMLStack",tagline:"BharatMLStack is a comprehensive, production-ready machine learning infrastructure platform designed to democratize ML capabilities across India and beyond. Our mission is to provide a robust, scalable, and accessible ML stack that empowers organizations to build, deploy, and manage machine learning solutions at massive scale.",favicon:"img/favicon.ico",future:{v4:{removeLegacyPostBuildHeadAttribute:!0,useCssCascadeLayers:!0},experimental_faster:{swcJsLoader:!1,swcJsMinimizer:!1,swcHtmlMinimizer:!1,lightningCssMinimizer:!1,mdxCrossCompilerCache:!1,rspackBundler:!1,rspackPersistentCache:!1,ssgWorkerThreads:!1},experimental_storage:{type:"localStorage",namespace:!1},experimental_router:"browser"},url:"https://meesho.github.io",baseUrl:"/BharatMLStack/",organizationName:"Meesho Ltd.",projectName:"BharatMLStack",onBrokenLinks:"throw",onBrokenMarkdownLinks:"warn",i18n:{defaultLocale:"en",locales:["en"],path:"i18n",localeConfigs:{}},presets:[["classic",{docs:{sidebarPath:"./sidebars.js",editUrl:"https://github.com/Meesho/BharatMLStack/tree/main/docs",routeBasePath:"/"},blog:{blogSidebarCount:"ALL",showReadingTime:!0,feedOptions:{type:["rss","atom"],xslt:!0},editUrl:"https://github.com/Meesho/BharatMLStack/tree/main/docs",onInlineTags:"warn",onInlineAuthors:"warn",onUntruncatedBlogPosts:"warn"},theme:{customCss:"./src/css/custom.css"}}]],themeConfig:{image:"img/docusaurus-social-card.jpg",colorMode:{defaultMode:"dark",respectPrefersColorScheme:!0,disableSwitch:!1},navbar:{title:"BharatMLStack",items:[{type:"docSidebar",sidebarId:"tutorialSidebar",position:"left",label:"Docs"},{to:"/blog",label:"Blog",position:"left"},{href:"https://github.com/Meesho/BharatMLStack",label:"GitHub",position:"right"}],hideOnScroll:!1},footer:{style:"dark",links:[{title:"Community",items:[{label:"Github Discussions",href:"https://github.com/Meesho/BharatMLStack/discussions"},{label:"Discord",href:"https://discord.gg/XkT7XsV2AU"}]},{title:"More",items:[{label:"Blog",to:"/blog"},{label:"GitHub",href:"https://github.com/Meesho/BharatMLStack"}]}],copyright:"Copyright \xa9 2026 Meesho Ltd. Built with Docusaurus."},prism:{theme:{plain:{color:"#393A34",backgroundColor:"#f6f8fa"},styles:[{types:["comment","prolog","doctype","cdata"],style:{color:"#999988",fontStyle:"italic"}},{types:["namespace"],style:{opacity:.7}},{types:["string","attr-value"],style:{color:"#e3116c"}},{types:["punctuation","operator"],style:{color:"#393A34"}},{types:["entity","url","symbol","number","boolean","variable","constant","property","regex","inserted"],style:{color:"#36acaa"}},{types:["atrule","keyword","attr-name","selector"],style:{color:"#00a4db"}},{types:["function","deleted","tag"],style:{color:"#d73a49"}},{types:["function-variable"],style:{color:"#6f42c1"}},{types:["tag","selector","keyword"],style:{color:"#00009f"}}]},darkTheme:{plain:{color:"#F8F8F2",backgroundColor:"#282A36"},styles:[{types:["prolog","constant","builtin"],style:{color:"rgb(189, 147, 249)"}},{types:["inserted","function"],style:{color:"rgb(80, 250, 123)"}},{types:["deleted"],style:{color:"rgb(255, 85, 85)"}},{types:["changed"],style:{color:"rgb(255, 184, 108)"}},{types:["punctuation","symbol"],style:{color:"rgb(248, 248, 242)"}},{types:["string","char","tag","selector"],style:{color:"rgb(255, 121, 198)"}},{types:["keyword","variable"],style:{color:"rgb(189, 147, 249)",fontStyle:"italic"}},{types:["comment"],style:{color:"rgb(98, 114, 164)"}},{types:["attr-name"],style:{color:"rgb(241, 250, 140)"}}]},additionalLanguages:[],magicComments:[{className:"theme-code-block-highlighted-line",line:"highlight-next-line",block:{start:"highlight-start",end:"highlight-end"}}]},docs:{versionPersistence:"localStorage",sidebar:{hideable:!1,autoCollapseCategories:!1}},blog:{sidebar:{groupByYear:!0}},metadata:[],tableOfContents:{minHeadingLevel:2,maxHeadingLevel:3}},baseUrlIssueBanner:!0,onBrokenAnchors:"warn",onDuplicateRoutes:"warn",staticDirectories:["static"],customFields:{},plugins:[],themes:[],scripts:[],headTags:[],stylesheets:[],clientModules:[],titleDelimiter:"|",noIndex:!1,markdown:{format:"mdx",mermaid:!1,mdx1Compat:{comments:!0,admonitions:!0,headingIds:!0},anchors:{maintainCase:!1}}}},4848:(e,t,n)=>{"use strict";e.exports=n(9698)},5041:(e,t,n)=>{"use strict";n.d(t,{M:()=>h,o:()=>m});var r=n(6540),a=n(2303),o=n(679),i=n(9532),l=n(6342),s=n(4848);const c=(0,o.Wf)("docusaurus.announcement.dismiss"),u=(0,o.Wf)("docusaurus.announcement.id"),d=()=>"true"===c.get(),f=e=>c.set(String(e)),p=r.createContext(null);function m({children:e}){const t=function(){const{announcementBar:e}=(0,l.p)(),t=(0,a.A)(),[n,o]=(0,r.useState)(()=>!!t&&d());(0,r.useEffect)(()=>{o(d())},[]);const i=(0,r.useCallback)(()=>{f(!0),o(!0)},[]);return(0,r.useEffect)(()=>{if(!e)return;const{id:t}=e;let n=u.get();"annoucement-bar"===n&&(n="announcement-bar");const r=t!==n;u.set(t),r&&f(!1),!r&&d()||o(!1)},[e]),(0,r.useMemo)(()=>({isActive:!!e&&!n,close:i}),[e,n,i])}();return(0,s.jsx)(p.Provider,{value:t,children:e})}function h(){const e=(0,r.useContext)(p);if(!e)throw new i.dV("AnnouncementBarProvider");return e}},5062:(e,t,n)=>{"use strict";n.d(t,{$:()=>i});var r=n(6540),a=n(6347),o=n(9532);function i(e){const t=(0,a.zy)(),n=(0,o.ZC)(t),i=(0,o._q)(e);(0,r.useEffect)(()=>{n&&t!==n&&i({location:t,previousLocation:n})},[i,t,n])}},5260:(e,t,n)=>{"use strict";n.d(t,{A:()=>o});n(6540);var r=n(545),a=n(4848);function o(e){return(0,a.jsx)(r.mg,{...e})}},5293:(e,t,n)=>{"use strict";n.d(t,{G:()=>k,a:()=>v});var r=n(6540),a=n(9532),o=n(679),i=n(6342),l=n(4848);function s(){return window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}function c(e){return function(e,t){const n=window.matchMedia(e);return n.addEventListener("change",t),()=>n.removeEventListener("change",t)}("(prefers-color-scheme: dark)",()=>e(s()))}const u=r.createContext(void 0),d=(0,o.Wf)("theme"),f="system",p=e=>"dark"===e?"dark":"light",m=e=>null===e||e===f?null:p(e),h={get:()=>p(document.documentElement.getAttribute("data-theme")),set:e=>{document.documentElement.setAttribute("data-theme",p(e))}},g={get:()=>m(document.documentElement.getAttribute("data-theme-choice")),set:e=>{document.documentElement.setAttribute("data-theme-choice",m(e)??f)}},b=e=>{null===e?d.del():d.set(p(e))};function y(){const{colorMode:{defaultMode:e,disableSwitch:t,respectPrefersColorScheme:n}}=(0,i.p)(),{colorMode:a,setColorModeState:o,colorModeChoice:l,setColorModeChoiceState:u}=function(){const{colorMode:{defaultMode:e}}=(0,i.p)(),[t,n]=(0,r.useState)(e),[a,o]=(0,r.useState)(null);return(0,r.useEffect)(()=>{n(h.get()),o(g.get())},[]),{colorMode:t,setColorModeState:n,colorModeChoice:a,setColorModeChoiceState:o}}();(0,r.useEffect)(()=>{t&&d.del()},[t]);const f=(0,r.useCallback)((t,r={})=>{const{persist:a=!0}=r;if(null===t){const t=n?s():e;h.set(t),o(t),g.set(null),u(null)}else h.set(t),g.set(t),o(t),u(t);a&&b(t)},[o,u,n,e]);return(0,r.useEffect)(()=>d.listen(e=>{f(m(e.newValue))}),[f]),(0,r.useEffect)(()=>{if(null===l&&n)return c(e=>{o(e),h.set(e)})},[n,l,o]),(0,r.useMemo)(()=>({colorMode:a,colorModeChoice:l,setColorMode:f,get isDarkTheme(){return"dark"===a},setLightTheme(){f("light")},setDarkTheme(){f("dark")}}),[a,l,f])}function v({children:e}){const t=y();return(0,l.jsx)(u.Provider,{value:t,children:e})}function k(){const e=(0,r.useContext)(u);if(null==e)throw new a.dV("ColorModeProvider","Please see https://docusaurus.io/docs/api/themes/configuration#use-color-mode.");return e}},5338:(e,t,n)=>{"use strict";!function e(){if("undefined"!=typeof __REACT_DEVTOOLS_GLOBAL_HOOK__&&"function"==typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE)try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(t){console.error(t)}}(),e.exports=n(1247)},5500:(e,t,n)=>{"use strict";n.d(t,{Jx:()=>b,be:()=>m,e3:()=>g});var r=n(6540),a=n(4164),o=n(5260),i=n(6803),l=n(6025),s=n(4563),c=n(4848);function u({title:e}){const t=(0,s.s$)().format(e);return(0,c.jsxs)(o.A,{children:[(0,c.jsx)("title",{children:t}),(0,c.jsx)("meta",{property:"og:title",content:t})]})}function d({description:e}){return(0,c.jsxs)(o.A,{children:[(0,c.jsx)("meta",{name:"description",content:e}),(0,c.jsx)("meta",{property:"og:description",content:e})]})}function f({image:e}){const{withBaseUrl:t}=(0,l.hH)(),n=t(e,{absolute:!0});return(0,c.jsxs)(o.A,{children:[(0,c.jsx)("meta",{property:"og:image",content:n}),(0,c.jsx)("meta",{name:"twitter:image",content:n})]})}function p({keywords:e}){return(0,c.jsx)(o.A,{children:(0,c.jsx)("meta",{name:"keywords",content:Array.isArray(e)?e.join(","):e})})}function m({title:e,description:t,keywords:n,image:r,children:a}){return(0,c.jsxs)(c.Fragment,{children:[e&&(0,c.jsx)(u,{title:e}),t&&(0,c.jsx)(d,{description:t}),n&&(0,c.jsx)(p,{keywords:n}),r&&(0,c.jsx)(f,{image:r}),a&&(0,c.jsx)(o.A,{children:a})]})}const h=r.createContext(void 0);function g({className:e,children:t}){const n=r.useContext(h),i=(0,a.A)(n,e);return(0,c.jsxs)(h.Provider,{value:i,children:[(0,c.jsx)(o.A,{children:(0,c.jsx)("html",{className:i})}),t]})}function b({children:e}){const t=(0,i.A)(),n=`plugin-${t.plugin.name.replace(/docusaurus-(?:plugin|theme)-(?:content-)?/gi,"")}`;const r=`plugin-id-${t.plugin.id}`;return(0,c.jsx)(g,{className:(0,a.A)(n,r),children:e})}},5556:(e,t,n)=>{e.exports=n(2694)()},5600:(e,t,n)=>{"use strict";n.d(t,{GX:()=>c,YL:()=>s,y_:()=>l});var r=n(6540),a=n(9532),o=n(4848);const i=r.createContext(null);function l({children:e}){const t=(0,r.useState)({component:null,props:null});return(0,o.jsx)(i.Provider,{value:t,children:e})}function s(){const e=(0,r.useContext)(i);if(!e)throw new a.dV("NavbarSecondaryMenuContentProvider");return e[0]}function c({component:e,props:t}){const n=(0,r.useContext)(i);if(!n)throw new a.dV("NavbarSecondaryMenuContentProvider");const[,o]=n,l=(0,a.Be)(t);return(0,r.useEffect)(()=>{o({component:e,props:l})},[o,e,l]),(0,r.useEffect)(()=>()=>o({component:null,props:null}),[o]),null}},5947:function(e,t,n){var r,a;r=function(){var e,t,n={version:"0.2.0"},r=n.settings={minimum:.08,easing:"ease",positionUsing:"",speed:200,trickle:!0,trickleRate:.02,trickleSpeed:800,showSpinner:!0,barSelector:'[role="bar"]',spinnerSelector:'[role="spinner"]',parent:"body",template:'<div class="bar" role="bar"><div class="peg"></div></div><div class="spinner" role="spinner"><div class="spinner-icon"></div></div>'};function a(e,t,n){return e<t?t:e>n?n:e}function o(e){return 100*(-1+e)}function i(e,t,n){var a;return(a="translate3d"===r.positionUsing?{transform:"translate3d("+o(e)+"%,0,0)"}:"translate"===r.positionUsing?{transform:"translate("+o(e)+"%,0)"}:{"margin-left":o(e)+"%"}).transition="all "+t+"ms "+n,a}n.configure=function(e){var t,n;for(t in e)void 0!==(n=e[t])&&e.hasOwnProperty(t)&&(r[t]=n);return this},n.status=null,n.set=function(e){var t=n.isStarted();e=a(e,r.minimum,1),n.status=1===e?null:e;var o=n.render(!t),c=o.querySelector(r.barSelector),u=r.speed,d=r.easing;return o.offsetWidth,l(function(t){""===r.positionUsing&&(r.positionUsing=n.getPositioningCSS()),s(c,i(e,u,d)),1===e?(s(o,{transition:"none",opacity:1}),o.offsetWidth,setTimeout(function(){s(o,{transition:"all "+u+"ms linear",opacity:0}),setTimeout(function(){n.remove(),t()},u)},u)):setTimeout(t,u)}),this},n.isStarted=function(){return"number"==typeof n.status},n.start=function(){n.status||n.set(0);var e=function(){setTimeout(function(){n.status&&(n.trickle(),e())},r.trickleSpeed)};return r.trickle&&e(),this},n.done=function(e){return e||n.status?n.inc(.3+.5*Math.random()).set(1):this},n.inc=function(e){var t=n.status;return t?("number"!=typeof e&&(e=(1-t)*a(Math.random()*t,.1,.95)),t=a(t+e,0,.994),n.set(t)):n.start()},n.trickle=function(){return n.inc(Math.random()*r.trickleRate)},e=0,t=0,n.promise=function(r){return r&&"resolved"!==r.state()?(0===t&&n.start(),e++,t++,r.always(function(){0===--t?(e=0,n.done()):n.set((e-t)/e)}),this):this},n.render=function(e){if(n.isRendered())return document.getElementById("nprogress");u(document.documentElement,"nprogress-busy");var t=document.createElement("div");t.id="nprogress",t.innerHTML=r.template;var a,i=t.querySelector(r.barSelector),l=e?"-100":o(n.status||0),c=document.querySelector(r.parent);return s(i,{transition:"all 0 linear",transform:"translate3d("+l+"%,0,0)"}),r.showSpinner||(a=t.querySelector(r.spinnerSelector))&&p(a),c!=document.body&&u(c,"nprogress-custom-parent"),c.appendChild(t),t},n.remove=function(){d(document.documentElement,"nprogress-busy"),d(document.querySelector(r.parent),"nprogress-custom-parent");var e=document.getElementById("nprogress");e&&p(e)},n.isRendered=function(){return!!document.getElementById("nprogress")},n.getPositioningCSS=function(){var e=document.body.style,t="WebkitTransform"in e?"Webkit":"MozTransform"in e?"Moz":"msTransform"in e?"ms":"OTransform"in e?"O":"";return t+"Perspective"in e?"translate3d":t+"Transform"in e?"translate":"margin"};var l=function(){var e=[];function t(){var n=e.shift();n&&n(t)}return function(n){e.push(n),1==e.length&&t()}}(),s=function(){var e=["Webkit","O","Moz","ms"],t={};function n(e){return e.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,function(e,t){return t.toUpperCase()})}function r(t){var n=document.body.style;if(t in n)return t;for(var r,a=e.length,o=t.charAt(0).toUpperCase()+t.slice(1);a--;)if((r=e[a]+o)in n)return r;return t}function a(e){return e=n(e),t[e]||(t[e]=r(e))}function o(e,t,n){t=a(t),e.style[t]=n}return function(e,t){var n,r,a=arguments;if(2==a.length)for(n in t)void 0!==(r=t[n])&&t.hasOwnProperty(n)&&o(e,n,r);else o(e,a[1],a[2])}}();function c(e,t){return("string"==typeof e?e:f(e)).indexOf(" "+t+" ")>=0}function u(e,t){var n=f(e),r=n+t;c(n,t)||(e.className=r.substring(1))}function d(e,t){var n,r=f(e);c(e,t)&&(n=r.replace(" "+t+" "," "),e.className=n.substring(1,n.length-1))}function f(e){return(" "+(e.className||"")+" ").replace(/\s+/gi," ")}function p(e){e&&e.parentNode&&e.parentNode.removeChild(e)}return n},void 0===(a="function"==typeof r?r.call(t,n,t,e):r)||(e.exports=a)},6025:(e,t,n)=>{"use strict";n.d(t,{Ay:()=>l,hH:()=>i});var r=n(6540),a=n(4586),o=n(6654);function i(){const{siteConfig:e}=(0,a.A)(),{baseUrl:t,url:n}=e,i=e.future.experimental_router,l=(0,r.useCallback)((e,r)=>function({siteUrl:e,baseUrl:t,url:n,options:{forcePrependBaseUrl:r=!1,absolute:a=!1}={},router:i}){if(!n||n.startsWith("#")||(0,o.z)(n))return n;if("hash"===i)return n.startsWith("/")?`.${n}`:`./${n}`;if(r)return t+n.replace(/^\//,"");if(n===t.replace(/\/$/,""))return t;const l=n.startsWith(t)?n:t+n.replace(/^\//,"");return a?e+l:l}({siteUrl:n,baseUrl:t,url:e,options:r,router:i}),[n,t,i]);return{withBaseUrl:l}}function l(e,t={}){const{withBaseUrl:n}=i();return n(e,t)}},6125:(e,t,n)=>{"use strict";n.d(t,{o:()=>o,x:()=>i});var r=n(6540),a=n(4848);const o=r.createContext(!1);function i({children:e}){const[t,n]=(0,r.useState)(!1);return(0,r.useEffect)(()=>{n(!0)},[]),(0,a.jsx)(o.Provider,{value:t,children:e})}},6134:(e,t,n)=>{"use strict";var r=n(1765),a=n(4784);!function(e){const{themeConfig:{prism:t}}=a.default,{additionalLanguages:r}=t,o=globalThis.Prism;globalThis.Prism=e,r.forEach(e=>{"php"===e&&n(9700),n(8692)(`./prism-${e}`)}),delete globalThis.Prism,void 0!==o&&(globalThis.Prism=e)}(r.My)},6221:(e,t,n)=>{"use strict";var r=n(6540);function a(e){var t="https://react.dev/errors/"+e;if(1<arguments.length){t+="?args[]="+encodeURIComponent(arguments[1]);for(var n=2;n<arguments.length;n++)t+="&args[]="+encodeURIComponent(arguments[n])}return"Minified React error #"+e+"; visit "+t+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}function o(){}var i={d:{f:o,r:function(){throw Error(a(522))},D:o,C:o,L:o,m:o,X:o,S:o,M:o},p:0,findDOMNode:null},l=Symbol.for("react.portal");var s=r.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;function c(e,t){return"font"===e?"":"string"==typeof t?"use-credentials"===t?t:"":void 0}t.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=i,t.createPortal=function(e,t){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:null;if(!t||1!==t.nodeType&&9!==t.nodeType&&11!==t.nodeType)throw Error(a(299));return function(e,t,n){var r=3<arguments.length&&void 0!==arguments[3]?arguments[3]:null;return{$$typeof:l,key:null==r?null:""+r,children:e,containerInfo:t,implementation:n}}(e,t,null,n)},t.flushSync=function(e){var t=s.T,n=i.p;try{if(s.T=null,i.p=2,e)return e()}finally{s.T=t,i.p=n,i.d.f()}},t.preconnect=function(e,t){"string"==typeof e&&(t?t="string"==typeof(t=t.crossOrigin)?"use-credentials"===t?t:"":void 0:t=null,i.d.C(e,t))},t.prefetchDNS=function(e){"string"==typeof e&&i.d.D(e)},t.preinit=function(e,t){if("string"==typeof e&&t&&"string"==typeof t.as){var n=t.as,r=c(n,t.crossOrigin),a="string"==typeof t.integrity?t.integrity:void 0,o="string"==typeof t.fetchPriority?t.fetchPriority:void 0;"style"===n?i.d.S(e,"string"==typeof t.precedence?t.precedence:void 0,{crossOrigin:r,integrity:a,fetchPriority:o}):"script"===n&&i.d.X(e,{crossOrigin:r,integrity:a,fetchPriority:o,nonce:"string"==typeof t.nonce?t.nonce:void 0})}},t.preinitModule=function(e,t){if("string"==typeof e)if("object"==typeof t&&null!==t){if(null==t.as||"script"===t.as){var n=c(t.as,t.crossOrigin);i.d.M(e,{crossOrigin:n,integrity:"string"==typeof t.integrity?t.integrity:void 0,nonce:"string"==typeof t.nonce?t.nonce:void 0})}}else null==t&&i.d.M(e)},t.preload=function(e,t){if("string"==typeof e&&"object"==typeof t&&null!==t&&"string"==typeof t.as){var n=t.as,r=c(n,t.crossOrigin);i.d.L(e,n,{crossOrigin:r,integrity:"string"==typeof t.integrity?t.integrity:void 0,nonce:"string"==typeof t.nonce?t.nonce:void 0,type:"string"==typeof t.type?t.type:void 0,fetchPriority:"string"==typeof t.fetchPriority?t.fetchPriority:void 0,referrerPolicy:"string"==typeof t.referrerPolicy?t.referrerPolicy:void 0,imageSrcSet:"string"==typeof t.imageSrcSet?t.imageSrcSet:void 0,imageSizes:"string"==typeof t.imageSizes?t.imageSizes:void 0,media:"string"==typeof t.media?t.media:void 0})}},t.preloadModule=function(e,t){if("string"==typeof e)if(t){var n=c(t.as,t.crossOrigin);i.d.m(e,{as:"string"==typeof t.as&&"script"!==t.as?t.as:void 0,crossOrigin:n,integrity:"string"==typeof t.integrity?t.integrity:void 0})}else i.d.m(e)},t.requestFormReset=function(e){i.d.r(e)},t.unstable_batchedUpdates=function(e,t){return e(t)},t.useFormState=function(e,t,n){return s.H.useFormState(e,t,n)},t.useFormStatus=function(){return s.H.useHostTransitionStatus()},t.version="19.1.1"},6294:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(5947),a=n.n(r);a().configure({showSpinner:!1});const o={onRouteUpdate({location:e,previousLocation:t}){if(t&&e.pathname!==t.pathname){const e=window.setTimeout(()=>{a().start()},200);return()=>window.clearTimeout(e)}},onRouteDidUpdate(){a().done()}}},6342:(e,t,n)=>{"use strict";n.d(t,{p:()=>a});var r=n(4586);function a(){return(0,r.A)().siteConfig.themeConfig}},6347:(e,t,n)=>{"use strict";n.d(t,{B6:()=>x,Ix:()=>v,W6:()=>P,XZ:()=>y,dO:()=>j,qh:()=>_,zy:()=>N});var r=n(2892),a=n(6540),o=n(5556),i=n.n(o),l=n(1513),s=n(1561),c=n(8168),u=n(8505),d=n.n(u),f=(n(4363),n(8587)),p=(n(4146),1073741823),m="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:void 0!==n.g?n.g:{};var h=a.createContext||function(e,t){var n,o,l="__create-react-context-"+function(){var e="__global_unique_id__";return m[e]=(m[e]||0)+1}()+"__",s=function(e){function n(){for(var t,n,r,a=arguments.length,o=new Array(a),i=0;i<a;i++)o[i]=arguments[i];return(t=e.call.apply(e,[this].concat(o))||this).emitter=(n=t.props.value,r=[],{on:function(e){r.push(e)},off:function(e){r=r.filter(function(t){return t!==e})},get:function(){return n},set:function(e,t){n=e,r.forEach(function(e){return e(n,t)})}}),t}(0,r.A)(n,e);var a=n.prototype;return a.getChildContext=function(){var e;return(e={})[l]=this.emitter,e},a.componentWillReceiveProps=function(e){if(this.props.value!==e.value){var n,r=this.props.value,a=e.value;((o=r)===(i=a)?0!==o||1/o==1/i:o!=o&&i!=i)?n=0:(n="function"==typeof t?t(r,a):p,0!==(n|=0)&&this.emitter.set(e.value,n))}var o,i},a.render=function(){return this.props.children},n}(a.Component);s.childContextTypes=((n={})[l]=i().object.isRequired,n);var c=function(t){function n(){for(var e,n=arguments.length,r=new Array(n),a=0;a<n;a++)r[a]=arguments[a];return(e=t.call.apply(t,[this].concat(r))||this).observedBits=void 0,e.state={value:e.getValue()},e.onUpdate=function(t,n){0!==((0|e.observedBits)&n)&&e.setState({value:e.getValue()})},e}(0,r.A)(n,t);var a=n.prototype;return a.componentWillReceiveProps=function(e){var t=e.observedBits;this.observedBits=null==t?p:t},a.componentDidMount=function(){this.context[l]&&this.context[l].on(this.onUpdate);var e=this.props.observedBits;this.observedBits=null==e?p:e},a.componentWillUnmount=function(){this.context[l]&&this.context[l].off(this.onUpdate)},a.getValue=function(){return this.context[l]?this.context[l].get():e},a.render=function(){return(e=this.props.children,Array.isArray(e)?e[0]:e)(this.state.value);var e},n}(a.Component);return c.contextTypes=((o={})[l]=i().object,o),{Provider:s,Consumer:c}},g=function(e){var t=h();return t.displayName=e,t},b=g("Router-History"),y=g("Router"),v=function(e){function t(t){var n;return(n=e.call(this,t)||this).state={location:t.history.location},n._isMounted=!1,n._pendingLocation=null,t.staticContext||(n.unlisten=t.history.listen(function(e){n._pendingLocation=e})),n}(0,r.A)(t,e),t.computeRootMatch=function(e){return{path:"/",url:"/",params:{},isExact:"/"===e}};var n=t.prototype;return n.componentDidMount=function(){var e=this;this._isMounted=!0,this.unlisten&&this.unlisten(),this.props.staticContext||(this.unlisten=this.props.history.listen(function(t){e._isMounted&&e.setState({location:t})})),this._pendingLocation&&this.setState({location:this._pendingLocation})},n.componentWillUnmount=function(){this.unlisten&&(this.unlisten(),this._isMounted=!1,this._pendingLocation=null)},n.render=function(){return a.createElement(y.Provider,{value:{history:this.props.history,location:this.state.location,match:t.computeRootMatch(this.state.location.pathname),staticContext:this.props.staticContext}},a.createElement(b.Provider,{children:this.props.children||null,value:this.props.history}))},t}(a.Component);a.Component;a.Component;var k={},w=1e4,S=0;function x(e,t){void 0===t&&(t={}),("string"==typeof t||Array.isArray(t))&&(t={path:t});var n=t,r=n.path,a=n.exact,o=void 0!==a&&a,i=n.strict,l=void 0!==i&&i,s=n.sensitive,c=void 0!==s&&s;return[].concat(r).reduce(function(t,n){if(!n&&""!==n)return null;if(t)return t;var r=function(e,t){var n=""+t.end+t.strict+t.sensitive,r=k[n]||(k[n]={});if(r[e])return r[e];var a=[],o={regexp:d()(e,a,t),keys:a};return S<w&&(r[e]=o,S++),o}(n,{end:o,strict:l,sensitive:c}),a=r.regexp,i=r.keys,s=a.exec(e);if(!s)return null;var u=s[0],f=s.slice(1),p=e===u;return o&&!p?null:{path:n,url:"/"===n&&""===u?"/":u,isExact:p,params:i.reduce(function(e,t,n){return e[t.name]=f[n],e},{})}},null)}var _=function(e){function t(){return e.apply(this,arguments)||this}return(0,r.A)(t,e),t.prototype.render=function(){var e=this;return a.createElement(y.Consumer,null,function(t){t||(0,s.A)(!1);var n=e.props.location||t.location,r=e.props.computedMatch?e.props.computedMatch:e.props.path?x(n.pathname,e.props):t.match,o=(0,c.A)({},t,{location:n,match:r}),i=e.props,l=i.children,u=i.component,d=i.render;return Array.isArray(l)&&function(e){return 0===a.Children.count(e)}(l)&&(l=null),a.createElement(y.Provider,{value:o},o.match?l?"function"==typeof l?l(o):l:u?a.createElement(u,o):d?d(o):null:"function"==typeof l?l(o):null)})},t}(a.Component);function E(e){return"/"===e.charAt(0)?e:"/"+e}function C(e,t){if(!e)return t;var n=E(e);return 0!==t.pathname.indexOf(n)?t:(0,c.A)({},t,{pathname:t.pathname.substr(n.length)})}function L(e){return"string"==typeof e?e:(0,l.AO)(e)}function A(e){return function(){(0,s.A)(!1)}}function T(){}a.Component;var j=function(e){function t(){return e.apply(this,arguments)||this}return(0,r.A)(t,e),t.prototype.render=function(){var e=this;return a.createElement(y.Consumer,null,function(t){t||(0,s.A)(!1);var n,r,o=e.props.location||t.location;return a.Children.forEach(e.props.children,function(e){if(null==r&&a.isValidElement(e)){n=e;var i=e.props.path||e.props.from;r=i?x(o.pathname,(0,c.A)({},e.props,{path:i})):t.match}}),r?a.cloneElement(n,{location:o,computedMatch:r}):null})},t}(a.Component);var M=a.useContext;function P(){return M(b)}function N(){return M(y).location}},6540:(e,t,n)=>{"use strict";e.exports=n(9869)},6654:(e,t,n)=>{"use strict";function r(e){return/^(?:\w*:|\/\/)/.test(e)}function a(e){return void 0!==e&&!r(e)}n.d(t,{A:()=>a,z:()=>r})},6803:(e,t,n)=>{"use strict";n.d(t,{A:()=>o});var r=n(6540),a=n(3102);function o(){const e=r.useContext(a.o);if(!e)throw new Error("Unexpected: no Docusaurus route context found");return e}},6921:(e,t,n)=>{"use strict";n.d(t,{A:()=>a});const r=e=>"object"==typeof e&&!!e&&Object.keys(e).length>0;function a(e){const t={};return function e(n,a){Object.entries(n).forEach(([n,o])=>{const i=a?`${a}.${n}`:n;r(o)?e(o,i):t[i]=o})}(e),t}},6925:e=>{"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},6969:e=>{e.exports&&(e.exports={core:{meta:{path:"components/prism-core.js",option:"mandatory"},core:"Core"},themes:{meta:{path:"themes/{id}.css",link:"index.html?theme={id}",exclusive:!0},prism:{title:"Default",option:"default"},"prism-dark":"Dark","prism-funky":"Funky","prism-okaidia":{title:"Okaidia",owner:"ocodia"},"prism-twilight":{title:"Twilight",owner:"remybach"},"prism-coy":{title:"Coy",owner:"tshedor"},"prism-solarizedlight":{title:"Solarized Light",owner:"hectormatos2011 "},"prism-tomorrow":{title:"Tomorrow Night",owner:"Rosey"}},languages:{meta:{path:"components/prism-{id}",noCSS:!0,examplesPath:"examples/prism-{id}",addCheckAll:!0},markup:{title:"Markup",alias:["html","xml","svg","mathml","ssml","atom","rss"],aliasTitles:{html:"HTML",xml:"XML",svg:"SVG",mathml:"MathML",ssml:"SSML",atom:"Atom",rss:"RSS"},option:"default"},css:{title:"CSS",option:"default",modify:"markup"},clike:{title:"C-like",option:"default"},javascript:{title:"JavaScript",require:"clike",modify:"markup",optional:"regex",alias:"js",option:"default"},abap:{title:"ABAP",owner:"dellagustin"},abnf:{title:"ABNF",owner:"RunDevelopment"},actionscript:{title:"ActionScript",require:"javascript",modify:"markup",owner:"Golmote"},ada:{title:"Ada",owner:"Lucretia"},agda:{title:"Agda",owner:"xy-ren"},al:{title:"AL",owner:"RunDevelopment"},antlr4:{title:"ANTLR4",alias:"g4",owner:"RunDevelopment"},apacheconf:{title:"Apache Configuration",owner:"GuiTeK"},apex:{title:"Apex",require:["clike","sql"],owner:"RunDevelopment"},apl:{title:"APL",owner:"ngn"},applescript:{title:"AppleScript",owner:"Golmote"},aql:{title:"AQL",owner:"RunDevelopment"},arduino:{title:"Arduino",require:"cpp",alias:"ino",owner:"dkern"},arff:{title:"ARFF",owner:"Golmote"},armasm:{title:"ARM Assembly",alias:"arm-asm",owner:"RunDevelopment"},arturo:{title:"Arturo",alias:"art",optional:["bash","css","javascript","markup","markdown","sql"],owner:"drkameleon"},asciidoc:{alias:"adoc",title:"AsciiDoc",owner:"Golmote"},aspnet:{title:"ASP.NET (C#)",require:["markup","csharp"],owner:"nauzilus"},asm6502:{title:"6502 Assembly",owner:"kzurawel"},asmatmel:{title:"Atmel AVR Assembly",owner:"cerkit"},autohotkey:{title:"AutoHotkey",owner:"aviaryan"},autoit:{title:"AutoIt",owner:"Golmote"},avisynth:{title:"AviSynth",alias:"avs",owner:"Zinfidel"},"avro-idl":{title:"Avro IDL",alias:"avdl",owner:"RunDevelopment"},awk:{title:"AWK",alias:"gawk",aliasTitles:{gawk:"GAWK"},owner:"RunDevelopment"},bash:{title:"Bash",alias:["sh","shell"],aliasTitles:{sh:"Shell",shell:"Shell"},owner:"zeitgeist87"},basic:{title:"BASIC",owner:"Golmote"},batch:{title:"Batch",owner:"Golmote"},bbcode:{title:"BBcode",alias:"shortcode",aliasTitles:{shortcode:"Shortcode"},owner:"RunDevelopment"},bbj:{title:"BBj",owner:"hyyan"},bicep:{title:"Bicep",owner:"johnnyreilly"},birb:{title:"Birb",require:"clike",owner:"Calamity210"},bison:{title:"Bison",require:"c",owner:"Golmote"},bnf:{title:"BNF",alias:"rbnf",aliasTitles:{rbnf:"RBNF"},owner:"RunDevelopment"},bqn:{title:"BQN",owner:"yewscion"},brainfuck:{title:"Brainfuck",owner:"Golmote"},brightscript:{title:"BrightScript",owner:"RunDevelopment"},bro:{title:"Bro",owner:"wayward710"},bsl:{title:"BSL (1C:Enterprise)",alias:"oscript",aliasTitles:{oscript:"OneScript"},owner:"Diversus23"},c:{title:"C",require:"clike",owner:"zeitgeist87"},csharp:{title:"C#",require:"clike",alias:["cs","dotnet"],owner:"mvalipour"},cpp:{title:"C++",require:"c",owner:"zeitgeist87"},cfscript:{title:"CFScript",require:"clike",alias:"cfc",owner:"mjclemente"},chaiscript:{title:"ChaiScript",require:["clike","cpp"],owner:"RunDevelopment"},cil:{title:"CIL",owner:"sbrl"},cilkc:{title:"Cilk/C",require:"c",alias:"cilk-c",owner:"OpenCilk"},cilkcpp:{title:"Cilk/C++",require:"cpp",alias:["cilk-cpp","cilk"],owner:"OpenCilk"},clojure:{title:"Clojure",owner:"troglotit"},cmake:{title:"CMake",owner:"mjrogozinski"},cobol:{title:"COBOL",owner:"RunDevelopment"},coffeescript:{title:"CoffeeScript",require:"javascript",alias:"coffee",owner:"R-osey"},concurnas:{title:"Concurnas",alias:"conc",owner:"jasontatton"},csp:{title:"Content-Security-Policy",owner:"ScottHelme"},cooklang:{title:"Cooklang",owner:"ahue"},coq:{title:"Coq",owner:"RunDevelopment"},crystal:{title:"Crystal",require:"ruby",owner:"MakeNowJust"},"css-extras":{title:"CSS Extras",require:"css",modify:"css",owner:"milesj"},csv:{title:"CSV",owner:"RunDevelopment"},cue:{title:"CUE",owner:"RunDevelopment"},cypher:{title:"Cypher",owner:"RunDevelopment"},d:{title:"D",require:"clike",owner:"Golmote"},dart:{title:"Dart",require:"clike",owner:"Golmote"},dataweave:{title:"DataWeave",owner:"machaval"},dax:{title:"DAX",owner:"peterbud"},dhall:{title:"Dhall",owner:"RunDevelopment"},diff:{title:"Diff",owner:"uranusjr"},django:{title:"Django/Jinja2",require:"markup-templating",alias:"jinja2",owner:"romanvm"},"dns-zone-file":{title:"DNS zone file",owner:"RunDevelopment",alias:"dns-zone"},docker:{title:"Docker",alias:"dockerfile",owner:"JustinBeckwith"},dot:{title:"DOT (Graphviz)",alias:"gv",optional:"markup",owner:"RunDevelopment"},ebnf:{title:"EBNF",owner:"RunDevelopment"},editorconfig:{title:"EditorConfig",owner:"osipxd"},eiffel:{title:"Eiffel",owner:"Conaclos"},ejs:{title:"EJS",require:["javascript","markup-templating"],owner:"RunDevelopment",alias:"eta",aliasTitles:{eta:"Eta"}},elixir:{title:"Elixir",owner:"Golmote"},elm:{title:"Elm",owner:"zwilias"},etlua:{title:"Embedded Lua templating",require:["lua","markup-templating"],owner:"RunDevelopment"},erb:{title:"ERB",require:["ruby","markup-templating"],owner:"Golmote"},erlang:{title:"Erlang",owner:"Golmote"},"excel-formula":{title:"Excel Formula",alias:["xlsx","xls"],owner:"RunDevelopment"},fsharp:{title:"F#",require:"clike",owner:"simonreynolds7"},factor:{title:"Factor",owner:"catb0t"},false:{title:"False",owner:"edukisto"},"firestore-security-rules":{title:"Firestore security rules",require:"clike",owner:"RunDevelopment"},flow:{title:"Flow",require:"javascript",owner:"Golmote"},fortran:{title:"Fortran",owner:"Golmote"},ftl:{title:"FreeMarker Template Language",require:"markup-templating",owner:"RunDevelopment"},gml:{title:"GameMaker Language",alias:"gamemakerlanguage",require:"clike",owner:"LiarOnce"},gap:{title:"GAP (CAS)",owner:"RunDevelopment"},gcode:{title:"G-code",owner:"RunDevelopment"},gdscript:{title:"GDScript",owner:"RunDevelopment"},gedcom:{title:"GEDCOM",owner:"Golmote"},gettext:{title:"gettext",alias:"po",owner:"RunDevelopment"},gherkin:{title:"Gherkin",owner:"hason"},git:{title:"Git",owner:"lgiraudel"},glsl:{title:"GLSL",require:"c",owner:"Golmote"},gn:{title:"GN",alias:"gni",owner:"RunDevelopment"},"linker-script":{title:"GNU Linker Script",alias:"ld",owner:"RunDevelopment"},go:{title:"Go",require:"clike",owner:"arnehormann"},"go-module":{title:"Go module",alias:"go-mod",owner:"RunDevelopment"},gradle:{title:"Gradle",require:"clike",owner:"zeabdelkhalek-badido18"},graphql:{title:"GraphQL",optional:"markdown",owner:"Golmote"},groovy:{title:"Groovy",require:"clike",owner:"robfletcher"},haml:{title:"Haml",require:"ruby",optional:["css","css-extras","coffeescript","erb","javascript","less","markdown","scss","textile"],owner:"Golmote"},handlebars:{title:"Handlebars",require:"markup-templating",alias:["hbs","mustache"],aliasTitles:{mustache:"Mustache"},owner:"Golmote"},haskell:{title:"Haskell",alias:"hs",owner:"bholst"},haxe:{title:"Haxe",require:"clike",optional:"regex",owner:"Golmote"},hcl:{title:"HCL",owner:"outsideris"},hlsl:{title:"HLSL",require:"c",owner:"RunDevelopment"},hoon:{title:"Hoon",owner:"matildepark"},http:{title:"HTTP",optional:["csp","css","hpkp","hsts","javascript","json","markup","uri"],owner:"danielgtaylor"},hpkp:{title:"HTTP Public-Key-Pins",owner:"ScottHelme"},hsts:{title:"HTTP Strict-Transport-Security",owner:"ScottHelme"},ichigojam:{title:"IchigoJam",owner:"BlueCocoa"},icon:{title:"Icon",owner:"Golmote"},"icu-message-format":{title:"ICU Message Format",owner:"RunDevelopment"},idris:{title:"Idris",alias:"idr",owner:"KeenS",require:"haskell"},ignore:{title:".ignore",owner:"osipxd",alias:["gitignore","hgignore","npmignore"],aliasTitles:{gitignore:".gitignore",hgignore:".hgignore",npmignore:".npmignore"}},inform7:{title:"Inform 7",owner:"Golmote"},ini:{title:"Ini",owner:"aviaryan"},io:{title:"Io",owner:"AlesTsurko"},j:{title:"J",owner:"Golmote"},java:{title:"Java",require:"clike",owner:"sherblot"},javadoc:{title:"JavaDoc",require:["markup","java","javadoclike"],modify:"java",optional:"scala",owner:"RunDevelopment"},javadoclike:{title:"JavaDoc-like",modify:["java","javascript","php"],owner:"RunDevelopment"},javastacktrace:{title:"Java stack trace",owner:"RunDevelopment"},jexl:{title:"Jexl",owner:"czosel"},jolie:{title:"Jolie",require:"clike",owner:"thesave"},jq:{title:"JQ",owner:"RunDevelopment"},jsdoc:{title:"JSDoc",require:["javascript","javadoclike","typescript"],modify:"javascript",optional:["actionscript","coffeescript"],owner:"RunDevelopment"},"js-extras":{title:"JS Extras",require:"javascript",modify:"javascript",optional:["actionscript","coffeescript","flow","n4js","typescript"],owner:"RunDevelopment"},json:{title:"JSON",alias:"webmanifest",aliasTitles:{webmanifest:"Web App Manifest"},owner:"CupOfTea696"},json5:{title:"JSON5",require:"json",owner:"RunDevelopment"},jsonp:{title:"JSONP",require:"json",owner:"RunDevelopment"},jsstacktrace:{title:"JS stack trace",owner:"sbrl"},"js-templates":{title:"JS Templates",require:"javascript",modify:"javascript",optional:["css","css-extras","graphql","markdown","markup","sql"],owner:"RunDevelopment"},julia:{title:"Julia",owner:"cdagnino"},keepalived:{title:"Keepalived Configure",owner:"dev-itsheng"},keyman:{title:"Keyman",owner:"mcdurdin"},kotlin:{title:"Kotlin",alias:["kt","kts"],aliasTitles:{kts:"Kotlin Script"},require:"clike",owner:"Golmote"},kumir:{title:"KuMir (\u041a\u0443\u041c\u0438\u0440)",alias:"kum",owner:"edukisto"},kusto:{title:"Kusto",owner:"RunDevelopment"},latex:{title:"LaTeX",alias:["tex","context"],aliasTitles:{tex:"TeX",context:"ConTeXt"},owner:"japborst"},latte:{title:"Latte",require:["clike","markup-templating","php"],owner:"nette"},less:{title:"Less",require:"css",optional:"css-extras",owner:"Golmote"},lilypond:{title:"LilyPond",require:"scheme",alias:"ly",owner:"RunDevelopment"},liquid:{title:"Liquid",require:"markup-templating",owner:"cinhtau"},lisp:{title:"Lisp",alias:["emacs","elisp","emacs-lisp"],owner:"JuanCaicedo"},livescript:{title:"LiveScript",owner:"Golmote"},llvm:{title:"LLVM IR",owner:"porglezomp"},log:{title:"Log file",optional:"javastacktrace",owner:"RunDevelopment"},lolcode:{title:"LOLCODE",owner:"Golmote"},lua:{title:"Lua",owner:"Golmote"},magma:{title:"Magma (CAS)",owner:"RunDevelopment"},makefile:{title:"Makefile",owner:"Golmote"},markdown:{title:"Markdown",require:"markup",optional:"yaml",alias:"md",owner:"Golmote"},"markup-templating":{title:"Markup templating",require:"markup",owner:"Golmote"},mata:{title:"Mata",owner:"RunDevelopment"},matlab:{title:"MATLAB",owner:"Golmote"},maxscript:{title:"MAXScript",owner:"RunDevelopment"},mel:{title:"MEL",owner:"Golmote"},mermaid:{title:"Mermaid",owner:"RunDevelopment"},metafont:{title:"METAFONT",owner:"LaeriExNihilo"},mizar:{title:"Mizar",owner:"Golmote"},mongodb:{title:"MongoDB",owner:"airs0urce",require:"javascript"},monkey:{title:"Monkey",owner:"Golmote"},moonscript:{title:"MoonScript",alias:"moon",owner:"RunDevelopment"},n1ql:{title:"N1QL",owner:"TMWilds"},n4js:{title:"N4JS",require:"javascript",optional:"jsdoc",alias:"n4jsd",owner:"bsmith-n4"},"nand2tetris-hdl":{title:"Nand To Tetris HDL",owner:"stephanmax"},naniscript:{title:"Naninovel Script",owner:"Elringus",alias:"nani"},nasm:{title:"NASM",owner:"rbmj"},neon:{title:"NEON",owner:"nette"},nevod:{title:"Nevod",owner:"nezaboodka"},nginx:{title:"nginx",owner:"volado"},nim:{title:"Nim",owner:"Golmote"},nix:{title:"Nix",owner:"Golmote"},nsis:{title:"NSIS",owner:"idleberg"},objectivec:{title:"Objective-C",require:"c",alias:"objc",owner:"uranusjr"},ocaml:{title:"OCaml",owner:"Golmote"},odin:{title:"Odin",owner:"edukisto"},opencl:{title:"OpenCL",require:"c",modify:["c","cpp"],owner:"Milania1"},openqasm:{title:"OpenQasm",alias:"qasm",owner:"RunDevelopment"},oz:{title:"Oz",owner:"Golmote"},parigp:{title:"PARI/GP",owner:"Golmote"},parser:{title:"Parser",require:"markup",owner:"Golmote"},pascal:{title:"Pascal",alias:"objectpascal",aliasTitles:{objectpascal:"Object Pascal"},owner:"Golmote"},pascaligo:{title:"Pascaligo",owner:"DefinitelyNotAGoat"},psl:{title:"PATROL Scripting Language",owner:"bertysentry"},pcaxis:{title:"PC-Axis",alias:"px",owner:"RunDevelopment"},peoplecode:{title:"PeopleCode",alias:"pcode",owner:"RunDevelopment"},perl:{title:"Perl",owner:"Golmote"},php:{title:"PHP",require:"markup-templating",owner:"milesj"},phpdoc:{title:"PHPDoc",require:["php","javadoclike"],modify:"php",owner:"RunDevelopment"},"php-extras":{title:"PHP Extras",require:"php",modify:"php",owner:"milesj"},"plant-uml":{title:"PlantUML",alias:"plantuml",owner:"RunDevelopment"},plsql:{title:"PL/SQL",require:"sql",owner:"Golmote"},powerquery:{title:"PowerQuery",alias:["pq","mscript"],owner:"peterbud"},powershell:{title:"PowerShell",owner:"nauzilus"},processing:{title:"Processing",require:"clike",owner:"Golmote"},prolog:{title:"Prolog",owner:"Golmote"},promql:{title:"PromQL",owner:"arendjr"},properties:{title:".properties",owner:"Golmote"},protobuf:{title:"Protocol Buffers",require:"clike",owner:"just-boris"},pug:{title:"Pug",require:["markup","javascript"],optional:["coffeescript","ejs","handlebars","less","livescript","markdown","scss","stylus","twig"],owner:"Golmote"},puppet:{title:"Puppet",owner:"Golmote"},pure:{title:"Pure",optional:["c","cpp","fortran"],owner:"Golmote"},purebasic:{title:"PureBasic",require:"clike",alias:"pbfasm",owner:"HeX0R101"},purescript:{title:"PureScript",require:"haskell",alias:"purs",owner:"sriharshachilakapati"},python:{title:"Python",alias:"py",owner:"multipetros"},qsharp:{title:"Q#",require:"clike",alias:"qs",owner:"fedonman"},q:{title:"Q (kdb+ database)",owner:"Golmote"},qml:{title:"QML",require:"javascript",owner:"RunDevelopment"},qore:{title:"Qore",require:"clike",owner:"temnroegg"},r:{title:"R",owner:"Golmote"},racket:{title:"Racket",require:"scheme",alias:"rkt",owner:"RunDevelopment"},cshtml:{title:"Razor C#",alias:"razor",require:["markup","csharp"],optional:["css","css-extras","javascript","js-extras"],owner:"RunDevelopment"},jsx:{title:"React JSX",require:["markup","javascript"],optional:["jsdoc","js-extras","js-templates"],owner:"vkbansal"},tsx:{title:"React TSX",require:["jsx","typescript"]},reason:{title:"Reason",require:"clike",owner:"Golmote"},regex:{title:"Regex",owner:"RunDevelopment"},rego:{title:"Rego",owner:"JordanSh"},renpy:{title:"Ren'py",alias:"rpy",owner:"HyuchiaDiego"},rescript:{title:"ReScript",alias:"res",owner:"vmarcosp"},rest:{title:"reST (reStructuredText)",owner:"Golmote"},rip:{title:"Rip",owner:"ravinggenius"},roboconf:{title:"Roboconf",owner:"Golmote"},robotframework:{title:"Robot Framework",alias:"robot",owner:"RunDevelopment"},ruby:{title:"Ruby",require:"clike",alias:"rb",owner:"samflores"},rust:{title:"Rust",owner:"Golmote"},sas:{title:"SAS",optional:["groovy","lua","sql"],owner:"Golmote"},sass:{title:"Sass (Sass)",require:"css",optional:"css-extras",owner:"Golmote"},scss:{title:"Sass (SCSS)",require:"css",optional:"css-extras",owner:"MoOx"},scala:{title:"Scala",require:"java",owner:"jozic"},scheme:{title:"Scheme",owner:"bacchus123"},"shell-session":{title:"Shell session",require:"bash",alias:["sh-session","shellsession"],owner:"RunDevelopment"},smali:{title:"Smali",owner:"RunDevelopment"},smalltalk:{title:"Smalltalk",owner:"Golmote"},smarty:{title:"Smarty",require:"markup-templating",optional:"php",owner:"Golmote"},sml:{title:"SML",alias:"smlnj",aliasTitles:{smlnj:"SML/NJ"},owner:"RunDevelopment"},solidity:{title:"Solidity (Ethereum)",alias:"sol",require:"clike",owner:"glachaud"},"solution-file":{title:"Solution file",alias:"sln",owner:"RunDevelopment"},soy:{title:"Soy (Closure Template)",require:"markup-templating",owner:"Golmote"},sparql:{title:"SPARQL",require:"turtle",owner:"Triply-Dev",alias:"rq"},"splunk-spl":{title:"Splunk SPL",owner:"RunDevelopment"},sqf:{title:"SQF: Status Quo Function (Arma 3)",require:"clike",owner:"RunDevelopment"},sql:{title:"SQL",owner:"multipetros"},squirrel:{title:"Squirrel",require:"clike",owner:"RunDevelopment"},stan:{title:"Stan",owner:"RunDevelopment"},stata:{title:"Stata Ado",require:["mata","java","python"],owner:"RunDevelopment"},iecst:{title:"Structured Text (IEC 61131-3)",owner:"serhioromano"},stylus:{title:"Stylus",owner:"vkbansal"},supercollider:{title:"SuperCollider",alias:"sclang",owner:"RunDevelopment"},swift:{title:"Swift",owner:"chrischares"},systemd:{title:"Systemd configuration file",owner:"RunDevelopment"},"t4-templating":{title:"T4 templating",owner:"RunDevelopment"},"t4-cs":{title:"T4 Text Templates (C#)",require:["t4-templating","csharp"],alias:"t4",owner:"RunDevelopment"},"t4-vb":{title:"T4 Text Templates (VB)",require:["t4-templating","vbnet"],owner:"RunDevelopment"},tap:{title:"TAP",owner:"isaacs",require:"yaml"},tcl:{title:"Tcl",owner:"PeterChaplin"},tt2:{title:"Template Toolkit 2",require:["clike","markup-templating"],owner:"gflohr"},textile:{title:"Textile",require:"markup",optional:"css",owner:"Golmote"},toml:{title:"TOML",owner:"RunDevelopment"},tremor:{title:"Tremor",alias:["trickle","troy"],owner:"darach",aliasTitles:{trickle:"trickle",troy:"troy"}},turtle:{title:"Turtle",alias:"trig",aliasTitles:{trig:"TriG"},owner:"jakubklimek"},twig:{title:"Twig",require:"markup-templating",owner:"brandonkelly"},typescript:{title:"TypeScript",require:"javascript",optional:"js-templates",alias:"ts",owner:"vkbansal"},typoscript:{title:"TypoScript",alias:"tsconfig",aliasTitles:{tsconfig:"TSConfig"},owner:"dkern"},unrealscript:{title:"UnrealScript",alias:["uscript","uc"],owner:"RunDevelopment"},uorazor:{title:"UO Razor Script",owner:"jaseowns"},uri:{title:"URI",alias:"url",aliasTitles:{url:"URL"},owner:"RunDevelopment"},v:{title:"V",require:"clike",owner:"taggon"},vala:{title:"Vala",require:"clike",optional:"regex",owner:"TemplarVolk"},vbnet:{title:"VB.Net",require:"basic",owner:"Bigsby"},velocity:{title:"Velocity",require:"markup",owner:"Golmote"},verilog:{title:"Verilog",owner:"a-rey"},vhdl:{title:"VHDL",owner:"a-rey"},vim:{title:"vim",owner:"westonganger"},"visual-basic":{title:"Visual Basic",alias:["vb","vba"],aliasTitles:{vba:"VBA"},owner:"Golmote"},warpscript:{title:"WarpScript",owner:"RunDevelopment"},wasm:{title:"WebAssembly",owner:"Golmote"},"web-idl":{title:"Web IDL",alias:"webidl",owner:"RunDevelopment"},wgsl:{title:"WGSL",owner:"Dr4gonthree"},wiki:{title:"Wiki markup",require:"markup",owner:"Golmote"},wolfram:{title:"Wolfram language",alias:["mathematica","nb","wl"],aliasTitles:{mathematica:"Mathematica",nb:"Mathematica Notebook"},owner:"msollami"},wren:{title:"Wren",owner:"clsource"},xeora:{title:"Xeora",require:"markup",alias:"xeoracube",aliasTitles:{xeoracube:"XeoraCube"},owner:"freakmaxi"},"xml-doc":{title:"XML doc (.net)",require:"markup",modify:["csharp","fsharp","vbnet"],owner:"RunDevelopment"},xojo:{title:"Xojo (REALbasic)",owner:"Golmote"},xquery:{title:"XQuery",require:"markup",owner:"Golmote"},yaml:{title:"YAML",alias:"yml",owner:"hason"},yang:{title:"YANG",owner:"RunDevelopment"},zig:{title:"Zig",owner:"RunDevelopment"}},plugins:{meta:{path:"plugins/{id}/prism-{id}",link:"plugins/{id}/"},"line-highlight":{title:"Line Highlight",description:"Highlights specific lines and/or line ranges."},"line-numbers":{title:"Line Numbers",description:"Line number at the beginning of code lines.",owner:"kuba-kubula"},"show-invisibles":{title:"Show Invisibles",description:"Show hidden characters such as tabs and line breaks.",optional:["autolinker","data-uri-highlight"]},autolinker:{title:"Autolinker",description:"Converts URLs and emails in code to clickable links. Parses Markdown links in comments."},wpd:{title:"WebPlatform Docs",description:'Makes tokens link to <a href="https://webplatform.github.io/docs/">WebPlatform.org documentation</a>. The links open in a new tab.'},"custom-class":{title:"Custom Class",description:"This plugin allows you to prefix Prism's default classes (<code>.comment</code> can become <code>.namespace--comment</code>) or replace them with your defined ones (like <code>.editor__comment</code>). You can even add new classes.",owner:"dvkndn",noCSS:!0},"file-highlight":{title:"File Highlight",description:"Fetch external files and highlight them with Prism. Used on the Prism website itself.",noCSS:!0},"show-language":{title:"Show Language",description:"Display the highlighted language in code blocks (inline code does not show the label).",owner:"nauzilus",noCSS:!0,require:"toolbar"},"jsonp-highlight":{title:"JSONP Highlight",description:"Fetch content with JSONP and highlight some interesting content (e.g. GitHub/Gists or Bitbucket API).",noCSS:!0,owner:"nauzilus"},"highlight-keywords":{title:"Highlight Keywords",description:"Adds special CSS classes for each keyword for fine-grained highlighting.",owner:"vkbansal",noCSS:!0},"remove-initial-line-feed":{title:"Remove initial line feed",description:"Removes the initial line feed in code blocks.",owner:"Golmote",noCSS:!0},"inline-color":{title:"Inline color",description:"Adds a small inline preview for colors in style sheets.",require:"css-extras",owner:"RunDevelopment"},previewers:{title:"Previewers",description:"Previewers for angles, colors, gradients, easing and time.",require:"css-extras",owner:"Golmote"},autoloader:{title:"Autoloader",description:"Automatically loads the needed languages to highlight the code blocks.",owner:"Golmote",noCSS:!0},"keep-markup":{title:"Keep Markup",description:"Prevents custom markup from being dropped out during highlighting.",owner:"Golmote",optional:"normalize-whitespace",noCSS:!0},"command-line":{title:"Command Line",description:"Display a command line with a prompt and, optionally, the output/response from the commands.",owner:"chriswells0"},"unescaped-markup":{title:"Unescaped Markup",description:"Write markup without having to escape anything."},"normalize-whitespace":{title:"Normalize Whitespace",description:"Supports multiple operations to normalize whitespace in code blocks.",owner:"zeitgeist87",optional:"unescaped-markup",noCSS:!0},"data-uri-highlight":{title:"Data-URI Highlight",description:"Highlights data-URI contents.",owner:"Golmote",noCSS:!0},toolbar:{title:"Toolbar",description:"Attach a toolbar for plugins to easily register buttons on the top of a code block.",owner:"mAAdhaTTah"},"copy-to-clipboard":{title:"Copy to Clipboard Button",description:"Add a button that copies the code block to the clipboard when clicked.",owner:"mAAdhaTTah",require:"toolbar",noCSS:!0},"download-button":{title:"Download Button",description:"A button in the toolbar of a code block adding a convenient way to download a code file.",owner:"Golmote",require:"toolbar",noCSS:!0},"match-braces":{title:"Match braces",description:"Highlights matching braces.",owner:"RunDevelopment"},"diff-highlight":{title:"Diff Highlight",description:"Highlights the code inside diff blocks.",owner:"RunDevelopment",require:"diff"},"filter-highlight-all":{title:"Filter highlightAll",description:"Filters the elements the <code>highlightAll</code> and <code>highlightAllUnder</code> methods actually highlight.",owner:"RunDevelopment",noCSS:!0},treeview:{title:"Treeview",description:"A language with special styles to highlight file system tree structures.",owner:"Golmote"}}})},6972:(e,t,n)=>{"use strict";n.d(t,{$S:()=>m,B5:()=>C,Nr:()=>p,OF:()=>S,QB:()=>E,Vd:()=>x,Y:()=>k,a4:()=>h,cC:()=>f,d1:()=>L,fW:()=>_,w8:()=>y});var r=n(6540),a=n(6347),o=n(2831),i=n(4070),l=n(9169),s=n(1682),c=n(3886),u=n(3025),d=n(609);function f(e){const t=(0,u.r)();if(!e)return;const n=t.docs[e];if(!n)throw new Error(`no version doc found by id=${e}`);return n}function p(e){return"link"!==e.type||e.unlisted?"category"===e.type?function(e){if(e.href&&!e.linkUnlisted)return e.href;for(const t of e.items){const e=p(t);if(e)return e}}(e):void 0:e.href}function m(){const{pathname:e}=(0,a.zy)(),t=(0,d.t)();if(!t)throw new Error("Unexpected: cant find current sidebar in context");const n=w({sidebarItems:t.items,pathname:e,onlyCategories:!0}).slice(-1)[0];if(!n)throw new Error(`${e} is not associated with a category. useCurrentSidebarCategory() should only be used on category index pages.`);return n}function h(){const{pathname:e}=(0,a.zy)(),t=(0,d.t)();if(!t)throw new Error("Unexpected: cant find current sidebar in context");const n=w({sidebarItems:t.items,pathname:e,onlyCategories:!0}).slice(-1)[0];return n?.items??t.items}const g=(e,t)=>void 0!==e&&(0,l.ys)(e,t),b=(e,t)=>e.some(e=>y(e,t));function y(e,t){return"link"===e.type?g(e.href,t):"category"===e.type&&(g(e.href,t)||b(e.items,t))}function v(e,t){switch(e.type){case"category":return y(e,t)||void 0!==e.href&&!e.linkUnlisted||e.items.some(e=>v(e,t));case"link":return!e.unlisted||y(e,t);default:return!0}}function k(e,t){return(0,r.useMemo)(()=>e.filter(e=>v(e,t)),[e,t])}function w({sidebarItems:e,pathname:t,onlyCategories:n=!1}){const r=[];return function e(a){for(const o of a)if("category"===o.type&&((0,l.ys)(o.href,t)||e(o.items))||"link"===o.type&&(0,l.ys)(o.href,t)){return n&&"category"!==o.type||r.unshift(o),!0}return!1}(e),r}function S(){const e=(0,d.t)(),{pathname:t}=(0,a.zy)(),n=(0,i.vT)()?.pluginData.breadcrumbs;return!1!==n&&e?w({sidebarItems:e.items,pathname:t}):null}function x(e){const{activeVersion:t}=(0,i.zK)(e),{preferredVersion:n}=(0,c.g1)(e),a=(0,i.r7)(e);return(0,r.useMemo)(()=>(0,s.sb)([t,n,a].filter(Boolean)),[t,n,a])}function _(e,t){const n=x(t);return(0,r.useMemo)(()=>{const t=n.flatMap(e=>e.sidebars?Object.entries(e.sidebars):[]),r=t.find(t=>t[0]===e);if(!r)throw new Error(`Can't find any sidebar with id "${e}" in version${n.length>1?"s":""} ${n.map(e=>e.name).join(", ")}".\nAvailable sidebar ids are:\n- ${t.map(e=>e[0]).join("\n- ")}`);return r[1]},[e,n])}function E(e,t){const n=x(t);return(0,r.useMemo)(()=>{const t=n.flatMap(e=>e.docs),r=t.find(t=>t.id===e);if(!r){if(n.flatMap(e=>e.draftIds).includes(e))return null;throw new Error(`Couldn't find any doc with id "${e}" in version${n.length>1?"s":""} "${n.map(e=>e.name).join(", ")}".\nAvailable doc ids are:\n- ${(0,s.sb)(t.map(e=>e.id)).join("\n- ")}`)}return r},[e,n])}function C({route:e}){const t=(0,a.zy)(),n=(0,u.r)(),r=e.routes,i=r.find(e=>(0,a.B6)(t.pathname,e));if(!i)return null;const l=i.sidebar,s=l?n.docsSidebars[l]:void 0;return{docElement:(0,o.v)(r),sidebarName:l,sidebarItems:s}}function L(e){return e.filter(e=>!("category"===e.type||"link"===e.type)||!!p(e))}},6988:(e,t,n)=>{"use strict";n.d(t,{o:()=>d,l:()=>f});var r=n(6540),a=n(4784);const o=JSON.parse('{"docusaurus-plugin-content-docs":{"default":{"path":"/BharatMLStack/","versions":[{"name":"current","label":"Next","isLast":true,"path":"/BharatMLStack/","mainDocId":"intro","docs":[{"id":"inferflow/v1.0.0/architecture","path":"/BharatMLStack/inferflow/v1.0.0/architecture","sidebar":"tutorialSidebar"},{"id":"inferflow/v1.0.0/configuration","path":"/BharatMLStack/inferflow/v1.0.0/configuration","sidebar":"tutorialSidebar"},{"id":"inferflow/v1.0.0/functionalities","path":"/BharatMLStack/inferflow/v1.0.0/functionalities","sidebar":"tutorialSidebar"},{"id":"inferflow/v1.0.0/index","path":"/BharatMLStack/inferflow/v1.0.0","sidebar":"tutorialSidebar"},{"id":"inferflow/v1.0.0/release-notes","path":"/BharatMLStack/inferflow/v1.0.0/release-notes","sidebar":"tutorialSidebar"},{"id":"intro","path":"/BharatMLStack/intro","sidebar":"tutorialSidebar"},{"id":"numerix/v1.0.0/architecture","path":"/BharatMLStack/numerix/v1.0.0/architecture","sidebar":"tutorialSidebar"},{"id":"numerix/v1.0.0/benchmarks","path":"/BharatMLStack/numerix/v1.0.0/benchmarks","sidebar":"tutorialSidebar"},{"id":"numerix/v1.0.0/functionalities","path":"/BharatMLStack/numerix/v1.0.0/functionalities","sidebar":"tutorialSidebar"},{"id":"numerix/v1.0.0/index","path":"/BharatMLStack/numerix/v1.0.0","sidebar":"tutorialSidebar"},{"id":"numerix/v1.0.0/release-notes","path":"/BharatMLStack/numerix/v1.0.0/release-notes","sidebar":"tutorialSidebar"},{"id":"online-feature-store/v1.0.0/architecture","path":"/BharatMLStack/online-feature-store/v1.0.0/architecture","sidebar":"tutorialSidebar"},{"id":"online-feature-store/v1.0.0/benchmarks","path":"/BharatMLStack/online-feature-store/v1.0.0/benchmarks","sidebar":"tutorialSidebar"},{"id":"online-feature-store/v1.0.0/data-formats","path":"/BharatMLStack/online-feature-store/v1.0.0/data-formats","sidebar":"tutorialSidebar"},{"id":"online-feature-store/v1.0.0/functionalities","path":"/BharatMLStack/online-feature-store/v1.0.0/functionalities","sidebar":"tutorialSidebar"},{"id":"online-feature-store/v1.0.0/index","path":"/BharatMLStack/online-feature-store/v1.0.0","sidebar":"tutorialSidebar"},{"id":"online-feature-store/v1.0.0/release-notes","path":"/BharatMLStack/online-feature-store/v1.0.0/release-notes","sidebar":"tutorialSidebar"},{"id":"predator/v1.0.0/architecture","path":"/BharatMLStack/predator/v1.0.0/architecture","sidebar":"tutorialSidebar"},{"id":"predator/v1.0.0/functionalities","path":"/BharatMLStack/predator/v1.0.0/functionalities","sidebar":"tutorialSidebar"},{"id":"predator/v1.0.0/index","path":"/BharatMLStack/predator/v1.0.0","sidebar":"tutorialSidebar"},{"id":"predator/v1.0.0/release-notes","path":"/BharatMLStack/predator/v1.0.0/release-notes","sidebar":"tutorialSidebar"},{"id":"quick-start/v1.0.0/index","path":"/BharatMLStack/quick-start/v1.0.0","sidebar":"tutorialSidebar"},{"id":"quick-start/v1.0.0/quick-start","path":"/BharatMLStack/quick-start/v1.0.0/quick-start","sidebar":"tutorialSidebar"},{"id":"sdks/go/v1.0.0/feature_client","path":"/BharatMLStack/sdks/go/v1.0.0/feature_client","sidebar":"tutorialSidebar"},{"id":"sdks/go/v1.0.0/index","path":"/BharatMLStack/sdks/go/v1.0.0","sidebar":"tutorialSidebar"},{"id":"sdks/python/v1.0.0/grpc_feature_client","path":"/BharatMLStack/sdks/python/v1.0.0/grpc_feature_client","sidebar":"tutorialSidebar"},{"id":"sdks/python/v1.0.0/index","path":"/BharatMLStack/sdks/python/v1.0.0","sidebar":"tutorialSidebar"},{"id":"sdks/python/v1.0.0/spark_feature_push_client","path":"/BharatMLStack/sdks/python/v1.0.0/spark_feature_push_client","sidebar":"tutorialSidebar"},{"id":"skye/v1.0.0/architecture","path":"/BharatMLStack/skye/v1.0.0/architecture","sidebar":"tutorialSidebar"},{"id":"skye/v1.0.0/functionalities","path":"/BharatMLStack/skye/v1.0.0/functionalities","sidebar":"tutorialSidebar"},{"id":"skye/v1.0.0/index","path":"/BharatMLStack/skye/v1.0.0","sidebar":"tutorialSidebar"},{"id":"skye/v1.0.0/release-notes","path":"/BharatMLStack/skye/v1.0.0/release-notes","sidebar":"tutorialSidebar"},{"id":"trufflebox-ui/v1.0.0/index","path":"/BharatMLStack/trufflebox-ui/v1.0.0","sidebar":"tutorialSidebar"},{"id":"trufflebox-ui/v1.0.0/userguide","path":"/BharatMLStack/trufflebox-ui/v1.0.0/userguide","sidebar":"tutorialSidebar"},{"id":"/category/online-feature-store","path":"/BharatMLStack/category/online-feature-store","sidebar":"tutorialSidebar"},{"id":"/category/inferflow","path":"/BharatMLStack/category/inferflow","sidebar":"tutorialSidebar"},{"id":"/category/quick-start","path":"/BharatMLStack/category/quick-start","sidebar":"tutorialSidebar"},{"id":"/category/trufflebox-ui","path":"/BharatMLStack/category/trufflebox-ui","sidebar":"tutorialSidebar"},{"id":"/category/sdks","path":"/BharatMLStack/category/sdks","sidebar":"tutorialSidebar"},{"id":"/category/go-sdk","path":"/BharatMLStack/category/go-sdk","sidebar":"tutorialSidebar"},{"id":"/category/python-sdk","path":"/BharatMLStack/category/python-sdk","sidebar":"tutorialSidebar"},{"id":"/category/skye","path":"/BharatMLStack/category/skye","sidebar":"tutorialSidebar"},{"id":"/category/numerix","path":"/BharatMLStack/category/numerix","sidebar":"tutorialSidebar"},{"id":"/category/predator","path":"/BharatMLStack/category/predator","sidebar":"tutorialSidebar"}],"draftIds":[],"sidebars":{"tutorialSidebar":{"link":{"path":"/BharatMLStack/intro","label":"intro"}}}}],"breadcrumbs":true}}}'),i=JSON.parse('{"defaultLocale":"en","locales":["en"],"path":"i18n","currentLocale":"en","localeConfigs":{"en":{"label":"English","direction":"ltr","htmlLang":"en","calendar":"gregory","path":"en"}}}');var l=n(2654);const s=JSON.parse('{"docusaurusVersion":"3.8.1","siteVersion":"0.0.0","pluginVersions":{"docusaurus-plugin-css-cascade-layers":{"type":"package","name":"@docusaurus/plugin-css-cascade-layers","version":"3.8.1"},"docusaurus-plugin-content-docs":{"type":"package","name":"@docusaurus/plugin-content-docs","version":"3.8.1"},"docusaurus-plugin-content-blog":{"type":"package","name":"@docusaurus/plugin-content-blog","version":"3.8.1"},"docusaurus-plugin-content-pages":{"type":"package","name":"@docusaurus/plugin-content-pages","version":"3.8.1"},"docusaurus-plugin-sitemap":{"type":"package","name":"@docusaurus/plugin-sitemap","version":"3.8.1"},"docusaurus-plugin-svgr":{"type":"package","name":"@docusaurus/plugin-svgr","version":"3.8.1"},"docusaurus-theme-classic":{"type":"package","name":"@docusaurus/theme-classic","version":"3.8.1"}}}');var c=n(4848);const u={siteConfig:a.default,siteMetadata:s,globalData:o,i18n:i,codeTranslations:l},d=r.createContext(u);function f({children:e}){return(0,c.jsx)(d.Provider,{value:u,children:e})}},7065:(e,t,n)=>{"use strict";n.d(t,{W:()=>r});const r="default"},7489:(e,t,n)=>{"use strict";n.d(t,{A:()=>h});var r=n(6540),a=n(8193),o=n(5260),i=n(440),l=n(1656),s=n(3102),c=n(4848);function u({error:e,tryAgain:t}){return(0,c.jsxs)("div",{style:{display:"flex",flexDirection:"column",justifyContent:"center",alignItems:"flex-start",minHeight:"100vh",width:"100%",maxWidth:"80ch",fontSize:"20px",margin:"0 auto",padding:"1rem"},children:[(0,c.jsx)("h1",{style:{fontSize:"3rem"},children:"This page crashed"}),(0,c.jsx)("button",{type:"button",onClick:t,style:{margin:"1rem 0",fontSize:"2rem",cursor:"pointer",borderRadius:20,padding:"1rem"},children:"Try again"}),(0,c.jsx)(d,{error:e})]})}function d({error:e}){const t=(0,i.rA)(e).map(e=>e.message).join("\n\nCause:\n");return(0,c.jsx)("p",{style:{whiteSpace:"pre-wrap"},children:t})}function f({children:e}){return(0,c.jsx)(s.W,{value:{plugin:{name:"docusaurus-core-error-boundary",id:"default"}},children:e})}function p({error:e,tryAgain:t}){return(0,c.jsx)(f,{children:(0,c.jsxs)(h,{fallback:()=>(0,c.jsx)(u,{error:e,tryAgain:t}),children:[(0,c.jsx)(o.A,{children:(0,c.jsx)("title",{children:"Page Error"})}),(0,c.jsx)(l.A,{children:(0,c.jsx)(u,{error:e,tryAgain:t})})]})})}const m=e=>(0,c.jsx)(p,{...e});class h extends r.Component{constructor(e){super(e),this.state={error:null}}componentDidCatch(e){a.A.canUseDOM&&this.setState({error:e})}render(){const{children:e}=this.props,{error:t}=this.state;if(t){const e={error:t,tryAgain:()=>this.setState({error:null})};return(this.props.fallback??m)(e)}return e??null}}},7559:(e,t,n)=>{"use strict";n.d(t,{G:()=>r});const r={page:{blogListPage:"blog-list-page",blogPostPage:"blog-post-page",blogTagsListPage:"blog-tags-list-page",blogTagPostListPage:"blog-tags-post-list-page",blogAuthorsListPage:"blog-authors-list-page",blogAuthorsPostsPage:"blog-authors-posts-page",docsDocPage:"docs-doc-page",docsTagsListPage:"docs-tags-list-page",docsTagDocListPage:"docs-tags-doc-list-page",mdxPage:"mdx-page"},wrapper:{main:"main-wrapper",blogPages:"blog-wrapper",docsPages:"docs-wrapper",mdxPages:"mdx-wrapper"},common:{editThisPage:"theme-edit-this-page",lastUpdated:"theme-last-updated",backToTopButton:"theme-back-to-top-button",codeBlock:"theme-code-block",admonition:"theme-admonition",unlistedBanner:"theme-unlisted-banner",draftBanner:"theme-draft-banner",admonitionType:e=>`theme-admonition-${e}`},announcementBar:{container:"theme-announcement-bar"},layout:{navbar:{container:"theme-layout-navbar",containerLeft:"theme-layout-navbar-left",containerRight:"theme-layout-navbar-right",mobileSidebar:{container:"theme-layout-navbar-sidebar",panel:"theme-layout-navbar-sidebar-panel"}},main:{container:"theme-layout-main"},footer:{container:"theme-layout-footer",column:"theme-layout-footer-column"}},docs:{docVersionBanner:"theme-doc-version-banner",docVersionBadge:"theme-doc-version-badge",docBreadcrumbs:"theme-doc-breadcrumbs",docMarkdown:"theme-doc-markdown",docTocMobile:"theme-doc-toc-mobile",docTocDesktop:"theme-doc-toc-desktop",docFooter:"theme-doc-footer",docFooterTagsRow:"theme-doc-footer-tags-row",docFooterEditMetaRow:"theme-doc-footer-edit-meta-row",docSidebarContainer:"theme-doc-sidebar-container",docSidebarMenu:"theme-doc-sidebar-menu",docSidebarItemCategory:"theme-doc-sidebar-item-category",docSidebarItemLink:"theme-doc-sidebar-item-link",docSidebarItemCategoryLevel:e=>`theme-doc-sidebar-item-category-level-${e}`,docSidebarItemLinkLevel:e=>`theme-doc-sidebar-item-link-level-${e}`},blog:{blogFooterTagsRow:"theme-blog-footer-tags-row",blogFooterEditMetaRow:"theme-blog-footer-edit-meta-row"},pages:{pageFooterEditMetaRow:"theme-pages-footer-edit-meta-row"}}},8168:(e,t,n)=>{"use strict";function r(){return r=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)({}).hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},r.apply(null,arguments)}n.d(t,{A:()=>r})},8193:(e,t,n)=>{"use strict";n.d(t,{A:()=>a});const r="undefined"!=typeof window&&"document"in window&&"createElement"in window.document,a={canUseDOM:r,canUseEventListeners:r&&("addEventListener"in window||"attachEvent"in window),canUseIntersectionObserver:r&&"IntersectionObserver"in window,canUseViewport:r&&"screen"in window}},8328:(e,t,n)=>{"use strict";n.d(t,{A:()=>f});n(6540);var r=n(3259),a=n.n(r),o=n(4054);const i={"01a85c17":[()=>Promise.all([n.e(1869),n.e(8209)]).then(n.bind(n,9158)),"@theme/BlogTagsListPage",9158],"0413d9af":[()=>n.e(9919).then(n.bind(n,7114)),"@site/docs/sdks/python/v1.0.0/grpc_feature_client.md",7114],"08daf6b6":[()=>n.e(1686).then(n.t.bind(n,848,19)),"@generated/docusaurus-plugin-content-blog/default/p/bharat-ml-stack-blog-tags-model-inference-44e.json",848],"0a89f5c9":[()=>n.e(7508).then(n.bind(n,5641)),"@site/docs/inferflow/v1.0.0/functionalities.md",5641],"0dae2a8b":[()=>n.e(2092).then(n.t.bind(n,1335,19)),"@generated/docusaurus-plugin-content-blog/default/p/bharat-ml-stack-blog-tags-memory-4a5.json",1335],"0e384e19":[()=>n.e(3976).then(n.bind(n,2053)),"@site/docs/intro.md",2053],"0fff8dc8":[()=>n.e(9596).then(n.bind(n,5958)),"@site/docs/quick-start/v1.0.0/quick-start.md",5958],14064408:[()=>n.e(4582).then(n.t.bind(n,9416,19)),"@generated/docusaurus-plugin-content-docs/default/p/bharat-ml-stack-category-quick-start-b0e.json",9416],"14eb3368":[()=>Promise.all([n.e(1869),n.e(6969)]).then(n.bind(n,5847)),"@theme/DocCategoryGeneratedIndexPage",5847],"176d210f":[()=>n.e(6100).then(n.bind(n,753)),"@site/docs/trufflebox-ui/v1.0.0/userguide.md",753],17896441:[()=>Promise.all([n.e(1869),n.e(6870),n.e(8401)]).then(n.bind(n,833)),"@theme/DocItem",833],"1a4fe2b7":[()=>n.e(6027).then(n.bind(n,1691)),"@site/blog/bharatmlstack-history/building-meeshos-mlplatform-lessons-from-first-gen/index.md?truncated=true",1691],"1a64de69":[()=>n.e(3645).then(n.t.bind(n,1694,19)),"@generated/docusaurus-plugin-content-blog/default/p/bharat-ml-stack-blog-tags-meesho-214.json",1694],"1f391b9e":[()=>Promise.all([n.e(1869),n.e(6870),n.e(6061)]).then(n.bind(n,7973)),"@theme/MDXPage",7973],"2303959d":[()=>n.e(2576).then(n.bind(n,9494)),"@site/blog/bharatmlstack-history/scaling-model-inference-and-embedding-search/index.md",9494],"23a1b8fc":[()=>n.e(8439).then(n.bind(n,211)),"@site/docs/predator/v1.0.0/architecture.md",211],"23d02069":[()=>n.e(3239).then(n.bind(n,2377)),"@site/blog/bharatmlstack-history/building-meeshos-mlplatform-lessons-from-first-gen/index.md",2377],"252a9097":[()=>n.e(4424).then(n.bind(n,248)),"@site/docs/inferflow/v1.0.0/architecture.md",248],"2c62ead1":[()=>n.e(5801).then(n.bind(n,1688)),"@site/docs/numerix/v1.0.0/functionalities.md",1688],"2d865531":[()=>n.e(9197).then(n.t.bind(n,4153,19)),"@generated/docusaurus-plugin-content-blog/default/p/bharat-ml-stack-blog-authors-eb6.json",4153],"3039fa8c":[()=>n.e(1966).then(n.bind(n,474)),"@site/blog/bharatmlstack-history/scaling-model-inference-and-embedding-search/index.md?truncated=true",474],"340c7c5f":[()=>Promise.all([n.e(1869),n.e(74)]).then(n.bind(n,7642)),"@site/docs/online-feature-store/v1.0.0/index.md",7642],"3650a837":[()=>Promise.all([n.e(1869),n.e(1782)]).then(n.bind(n,4927)),"@site/docs/skye/v1.0.0/index.md",4927],"36994c47":[()=>n.e(9858).then(n.t.bind(n,5516,19)),"@generated/docusaurus-plugin-content-blog/default/__plugin.json",5516],"393be207":[()=>n.e(4134).then(n.bind(n,591)),"@site/src/pages/markdown-page.md",591],"3980073a":[()=>n.e(940).then(n.t.bind(n,3840,19)),"@generated/docusaurus-plugin-content-blog/default/p/bharat-ml-stack-blog-tags-interaction-store-62d.json",3840],"3c208a5b":[()=>n.e(7795).then(n.bind(n,1467)),"@site/blog/bharatmlstack-history/building-meeshos-mlplatform-from-chaos-to-cutting-edge/index.md",1467],"3e1c5046":[()=>n.e(690).then(n.t.bind(n,8750,19)),"@generated/docusaurus-plugin-content-blog/default/p/bharat-ml-stack-blog-tags-online-feature-store-e01.json",8750],"4137b431":[()=>n.e(6054).then(n.t.bind(n,4019,19)),"@generated/docusaurus-plugin-content-docs/default/p/bharat-ml-stack-aad.json",4019],"44d1c015":[()=>n.e(1065).then(n.t.bind(n,6725,19)),"@generated/docusaurus-plugin-content-docs/default/p/bharat-ml-stack-category-python-sdk-f96.json",6725],"479eb034":[()=>n.e(5425).then(n.t.bind(n,9341,19)),"@generated/docusaurus-plugin-content-blog/default/p/bharat-ml-stack-blog-tags-mlplatform-b63.json",9341],"4af50aac":[()=>n.e(1964).then(n.bind(n,6220)),"@site/docs/sdks/go/v1.0.0/feature_client.md",6220],"4b01b88a":[()=>n.e(9095).then(n.bind(n,1737)),"@site/blog/bharatmlstack-history/episodic-memory-for-agents/index.md",1737],"4caa95bf":[()=>n.e(2344).then(n.bind(n,9584)),"@site/docs/online-feature-store/v1.0.0/data-formats.md",9584],"4d1a2db0":[()=>n.e(8241).then(n.t.bind(n,2062,19)),"@generated/docusaurus-plugin-content-docs/default/p/bharat-ml-stack-category-skye-872.json",2062],"4dd73b28":[()=>n.e(4797).then(n.bind(n,4043)),"@site/blog/bharatmlstack-history/episodic-memory-for-agents/index.md?truncated=true",4043],"4df0e30b":[()=>n.e(2379).then(n.bind(n,9680)),"@site/docs/numerix/v1.0.0/architecture.md",9680],"50899a24":[()=>n.e(1009).then(n.t.bind(n,1008,19)),"@generated/docusaurus-plugin-content-docs/default/p/bharat-ml-stack-category-numerix-843.json",1008],"56eef1be":[()=>n.e(1508).then(n.bind(n,9390)),"@site/docs/skye/v1.0.0/architecture.md",9390],"5e95c892":[()=>n.e(9647).then(n.bind(n,9502)),"@theme/DocsRoot",9502],"5e9f5e1a":[()=>Promise.resolve().then(n.bind(n,4784)),"@generated/docusaurus.config",4784],"616111d3":[()=>n.e(9158).then(n.t.bind(n,9470,19)),"@generated/docusaurus-plugin-content-docs/default/p/bharat-ml-stack-category-sdks-291.json",9470],"621db11d":[()=>Promise.all([n.e(1869),n.e(7518),n.e(4212)]).then(n.bind(n,3250)),"@theme/Blog/Pages/BlogAuthorsListPage",3250],"6479fb86":[()=>n.e(5579).then(n.t.bind(n,3751,19)),"@generated/docusaurus-plugin-content-blog/default/p/bharat-ml-stack-blog-archive-553.json",3751],"67d4782a":[()=>n.e(8588).then(n.bind(n,8769)),"@site/docs/online-feature-store/v1.0.0/benchmarks.md",8769],"6875c492":[()=>Promise.all([n.e(1869),n.e(6870),n.e(7518),n.e(4813)]).then(n.bind(n,3069)),"@theme/BlogTagsPostsPage",3069],"6bb91276":[()=>n.e(9226).then(n.t.bind(n,8758,19)),"@generated/docusaurus-plugin-content-blog/default/p/bharat-ml-stack-blog-tags-vllm-9b4.json",8758],74783256:[()=>n.e(7813).then(n.t.bind(n,1566,19)),"@generated/docusaurus-plugin-content-docs/default/p/bharat-ml-stack-category-predator-a7f.json",1566],"769c1945":[()=>n.e(6267).then(n.bind(n,7879)),"@site/blog/bharatmlstack-history/llm-inference-optimization/index.md?truncated=true",7879],"7fa80e1c":[()=>n.e(3322).then(n.t.bind(n,9189,19)),"@generated/docusaurus-plugin-content-blog/default/p/bharat-ml-stack-blog-tags-853.json",9189],"814f3328":[()=>n.e(7472).then(n.t.bind(n,5513,19)),"~blog/default/blog-post-list-prop-default.json",5513],"845957d4":[()=>n.e(9).then(n.bind(n,9354)),"@site/blog/bharatmlstack-history/llm-inferencing-platform/index.md?truncated=true",9354],"8ac6191a":[()=>n.e(8465).then(n.t.bind(n,4540,19)),"@generated/docusaurus-plugin-content-docs/default/p/bharat-ml-stack-category-online-feature-store-8eb.json",4540],"8dd2df60":[()=>n.e(1537).then(n.t.bind(n,3359,19)),"@generated/docusaurus-plugin-content-docs/default/p/bharat-ml-stack-category-inferflow-541.json",3359],"8ea48c46":[()=>n.e(9824).then(n.bind(n,7956)),"@site/docs/numerix/v1.0.0/release-notes.md",7956],"93f344c7":[()=>n.e(4416).then(n.t.bind(n,5232,19)),"@generated/docusaurus-plugin-content-blog/default/p/bharat-ml-stack-blog-tags-inferflow-dc1.json",5232],"9796f4b8":[()=>n.e(5430).then(n.bind(n,9352)),"@site/docs/skye/v1.0.0/functionalities.md",9352],"982cae12":[()=>n.e(7290).then(n.t.bind(n,5394,19)),"@generated/docusaurus-plugin-content-blog/default/p/bharat-ml-stack-blog-tags-architecture-2b1.json",5394],"99009a21":[()=>n.e(4064).then(n.t.bind(n,1161,19)),"@generated/docusaurus-plugin-content-blog/default/p/bharat-ml-stack-blog-tags-tensorrt-llm-ee4.json",1161],"9aed321e":[()=>n.e(2951).then(n.bind(n,9059)),"@site/docs/inferflow/v1.0.0/release-notes.md",9059],"9d13045e":[()=>n.e(8014).then(n.bind(n,7791)),"@site/docs/inferflow/v1.0.0/configuration.md",7791],"9e4087bc":[()=>n.e(2711).then(n.bind(n,9331)),"@theme/BlogArchivePage",9331],a1ba6e62:[()=>n.e(4197).then(n.bind(n,9037)),"@site/blog/bharatmlstack-history/building-meeshos-mlplatform-from-chaos-to-cutting-edge/index.md?truncated=true",9037],a2d4c71d:[()=>n.e(8643).then(n.t.bind(n,2209,19)),"@generated/docusaurus-plugin-content-blog/default/p/bharat-ml-stack-blog-tags-episodic-memory-a8d.json",2209],a6aa9e1f:[()=>Promise.all([n.e(1869),n.e(6870),n.e(7518),n.e(7643)]).then(n.bind(n,5124)),"@theme/BlogListPage",5124],a7456010:[()=>n.e(1235).then(n.t.bind(n,8552,19)),"@generated/docusaurus-plugin-content-pages/default/__plugin.json",8552],a7bd4aaa:[()=>n.e(7098).then(n.bind(n,1723)),"@theme/DocVersionRoot",1723],a94703ab:[()=>Promise.all([n.e(1869),n.e(9048)]).then(n.bind(n,1377)),"@theme/DocRoot",1377],aaabe254:[()=>n.e(770).then(n.t.bind(n,1982,19)),"@generated/docusaurus-plugin-content-blog/default/p/bharat-ml-stack-blog-tags-bharatmlstack-205.json",1982],aba21aa0:[()=>n.e(5742).then(n.t.bind(n,7093,19)),"@generated/docusaurus-plugin-content-docs/default/__plugin.json",7093],ac51638e:[()=>n.e(9473).then(n.bind(n,6692)),"@site/docs/sdks/python/v1.0.0/spark_feature_push_client.md",6692],acecf23e:[()=>n.e(1903).then(n.t.bind(n,1912,19)),"~blog/default/blogMetadata-default.json",1912],adb039a4:[()=>n.e(7609).then(n.t.bind(n,926,19)),"@generated/docusaurus-plugin-content-blog/default/p/bharat-ml-stack-blog-tags-llm-025.json",926],ae7a6e8a:[()=>Promise.all([n.e(1869),n.e(8591)]).then(n.bind(n,8670)),"@site/docs/inferflow/v1.0.0/index.md",8670],b0267ac9:[()=>Promise.all([n.e(1869),n.e(1965)]).then(n.bind(n,2604)),"@site/docs/numerix/v1.0.0/index.md",2604],bba9e323:[()=>n.e(6812).then(n.bind(n,2486)),"@site/docs/predator/v1.0.0/release-notes.md",2486],bcee635f:[()=>Promise.all([n.e(1869),n.e(4164)]).then(n.bind(n,4459)),"@site/docs/sdks/go/v1.0.0/index.md",4459],bd5b7851:[()=>n.e(6063).then(n.bind(n,9042)),"@site/docs/skye/v1.0.0/release-notes.md",9042],be9e6e2d:[()=>n.e(7871).then(n.t.bind(n,3405,19)),"@generated/docusaurus-plugin-content-blog/default/p/bharat-ml-stack-blog-tags-embedding-search-672.json",3405],bf2864cf:[()=>Promise.all([n.e(1869),n.e(9688)]).then(n.bind(n,3969)),"@site/docs/quick-start/v1.0.0/index.md",3969],c31e69d4:[()=>n.e(3212).then(n.t.bind(n,915,19)),"@generated/docusaurus-plugin-content-blog/default/p/bharat-ml-stack-blog-tags-ai-agents-d97.json",915],c4822c4f:[()=>n.e(1915).then(n.bind(n,3649)),"@site/docs/online-feature-store/v1.0.0/functionalities.md",3649],c4f5d8e4:[()=>Promise.all([n.e(1869),n.e(2634)]).then(n.bind(n,1459)),"@site/src/pages/index.js",1459],c621f852:[()=>Promise.all([n.e(1869),n.e(8276)]).then(n.bind(n,3338)),"@site/docs/sdks/python/v1.0.0/index.md",3338],c7b64fcc:[()=>n.e(8933).then(n.t.bind(n,9997,19)),"@generated/docusaurus-plugin-content-docs/default/p/bharat-ml-stack-category-go-sdk-b5b.json",9997],ccc49370:[()=>Promise.all([n.e(1869),n.e(6870),n.e(7518),n.e(3249)]).then(n.bind(n,3858)),"@theme/BlogPostPage",3858],d01bc907:[()=>Promise.all([n.e(1869),n.e(8593)]).then(n.bind(n,7383)),"@site/docs/predator/v1.0.0/index.md",7383],d152284c:[()=>n.e(1606).then(n.bind(n,5876)),"@site/docs/online-feature-store/v1.0.0/release-notes.md",5876],d853e668:[()=>n.e(3068).then(n.bind(n,7062)),"@site/blog/bharatmlstack-history/llm-inferencing-platform/index.md",7062],d9861b0f:[()=>n.e(9795).then(n.bind(n,9957)),"@site/blog/bharatmlstack-history/llm-inference-optimization/index.md",9957],df502808:[()=>Promise.all([n.e(1869),n.e(6088)]).then(n.bind(n,5074)),"@site/docs/trufflebox-ui/v1.0.0/index.md",5074],e66382f6:[()=>n.e(1405).then(n.bind(n,9563)),"@site/docs/online-feature-store/v1.0.0/architecture.md",9563],e8202a51:[()=>n.e(2771).then(n.bind(n,1185)),"@site/docs/numerix/v1.0.0/benchmarks.md",1185],e8321834:[()=>n.e(149).then(n.bind(n,5842)),"@site/docs/predator/v1.0.0/functionalities.md",5842],f994c8da:[()=>n.e(1999).then(n.t.bind(n,38,19)),"@generated/docusaurus-plugin-content-blog/default/p/bharat-ml-stack-blog-7a3.json",38],fcf4f6ca:[()=>n.e(7720).then(n.t.bind(n,4041,19)),"@generated/docusaurus-plugin-content-docs/default/p/bharat-ml-stack-category-trufflebox-ui-b39.json",4041]};var l=n(4848);function s({error:e,retry:t,pastDelay:n}){return e?(0,l.jsxs)("div",{style:{textAlign:"center",color:"#fff",backgroundColor:"#fa383e",borderColor:"#fa383e",borderStyle:"solid",borderRadius:"0.25rem",borderWidth:"1px",boxSizing:"border-box",display:"block",padding:"1rem",flex:"0 0 50%",marginLeft:"25%",marginRight:"25%",marginTop:"5rem",maxWidth:"50%",width:"100%"},children:[(0,l.jsx)("p",{children:String(e)}),(0,l.jsx)("div",{children:(0,l.jsx)("button",{type:"button",onClick:t,children:"Retry"})})]}):n?(0,l.jsx)("div",{style:{display:"flex",justifyContent:"center",alignItems:"center",height:"100vh"},children:(0,l.jsx)("svg",{id:"loader",style:{width:128,height:110,position:"absolute",top:"calc(100vh - 64%)"},viewBox:"0 0 45 45",xmlns:"http://www.w3.org/2000/svg",stroke:"#61dafb",children:(0,l.jsxs)("g",{fill:"none",fillRule:"evenodd",transform:"translate(1 1)",strokeWidth:"2",children:[(0,l.jsxs)("circle",{cx:"22",cy:"22",r:"6",strokeOpacity:"0",children:[(0,l.jsx)("animate",{attributeName:"r",begin:"1.5s",dur:"3s",values:"6;22",calcMode:"linear",repeatCount:"indefinite"}),(0,l.jsx)("animate",{attributeName:"stroke-opacity",begin:"1.5s",dur:"3s",values:"1;0",calcMode:"linear",repeatCount:"indefinite"}),(0,l.jsx)("animate",{attributeName:"stroke-width",begin:"1.5s",dur:"3s",values:"2;0",calcMode:"linear",repeatCount:"indefinite"})]}),(0,l.jsxs)("circle",{cx:"22",cy:"22",r:"6",strokeOpacity:"0",children:[(0,l.jsx)("animate",{attributeName:"r",begin:"3s",dur:"3s",values:"6;22",calcMode:"linear",repeatCount:"indefinite"}),(0,l.jsx)("animate",{attributeName:"stroke-opacity",begin:"3s",dur:"3s",values:"1;0",calcMode:"linear",repeatCount:"indefinite"}),(0,l.jsx)("animate",{attributeName:"stroke-width",begin:"3s",dur:"3s",values:"2;0",calcMode:"linear",repeatCount:"indefinite"})]}),(0,l.jsx)("circle",{cx:"22",cy:"22",r:"8",children:(0,l.jsx)("animate",{attributeName:"r",begin:"0s",dur:"1.5s",values:"6;1;2;3;4;5;6",calcMode:"linear",repeatCount:"indefinite"})})]})})}):null}var c=n(6921),u=n(3102);function d(e,t){if("*"===e)return a()({loading:s,loader:()=>n.e(2237).then(n.bind(n,2237)),modules:["@theme/NotFound"],webpack:()=>[2237],render(e,t){const n=e.default;return(0,l.jsx)(u.W,{value:{plugin:{name:"native",id:"default"}},children:(0,l.jsx)(n,{...t})})}});const r=o[`${e}-${t}`],d={},f=[],p=[],m=(0,c.A)(r);return Object.entries(m).forEach(([e,t])=>{const n=i[t];n&&(d[e]=n[0],f.push(n[1]),p.push(n[2]))}),a().Map({loading:s,loader:d,modules:f,webpack:()=>p,render(t,n){const a=JSON.parse(JSON.stringify(r));Object.entries(t).forEach(([t,n])=>{const r=n.default;if(!r)throw new Error(`The page component at ${e} doesn't have a default export. This makes it impossible to render anything. Consider default-exporting a React component.`);"object"!=typeof r&&"function"!=typeof r||Object.keys(n).filter(e=>"default"!==e).forEach(e=>{r[e]=n[e]});let o=a;const i=t.split(".");i.slice(0,-1).forEach(e=>{o=o[e]}),o[i[i.length-1]]=r});const o=a.__comp;delete a.__comp;const i=a.__context;delete a.__context;const s=a.__props;return delete a.__props,(0,l.jsx)(u.W,{value:i,children:(0,l.jsx)(o,{...a,...s,...n})})}})}const f=[{path:"/BharatMLStack/blog",component:d("/BharatMLStack/blog","c07"),exact:!0},{path:"/BharatMLStack/blog/archive",component:d("/BharatMLStack/blog/archive","dde"),exact:!0},{path:"/BharatMLStack/blog/authors",component:d("/BharatMLStack/blog/authors","f47"),exact:!0},{path:"/BharatMLStack/blog/building-meeshos-mlplatform",component:d("/BharatMLStack/blog/building-meeshos-mlplatform","3fa"),exact:!0},{path:"/BharatMLStack/blog/building-meeshos-mlplatform-lessons-from-first-gen",component:d("/BharatMLStack/blog/building-meeshos-mlplatform-lessons-from-first-gen","a84"),exact:!0},{path:"/BharatMLStack/blog/episodic-memory-for-agents",component:d("/BharatMLStack/blog/episodic-memory-for-agents","c4f"),exact:!0},{path:"/BharatMLStack/blog/llm-inference-optimization-sub-sec-latency",component:d("/BharatMLStack/blog/llm-inference-optimization-sub-sec-latency","403"),exact:!0},{path:"/BharatMLStack/blog/multi-engine-llm-inferencing-platform",component:d("/BharatMLStack/blog/multi-engine-llm-inferencing-platform","b26"),exact:!0},{path:"/BharatMLStack/blog/scaling-model-inference-and-embedding-search",component:d("/BharatMLStack/blog/scaling-model-inference-and-embedding-search","d48"),exact:!0},{path:"/BharatMLStack/blog/tags",component:d("/BharatMLStack/blog/tags","8af"),exact:!0},{path:"/BharatMLStack/blog/tags/ai-agents",component:d("/BharatMLStack/blog/tags/ai-agents","2cc"),exact:!0},{path:"/BharatMLStack/blog/tags/architecture",component:d("/BharatMLStack/blog/tags/architecture","584"),exact:!0},{path:"/BharatMLStack/blog/tags/bharatmlstack",component:d("/BharatMLStack/blog/tags/bharatmlstack","c39"),exact:!0},{path:"/BharatMLStack/blog/tags/embedding-search",component:d("/BharatMLStack/blog/tags/embedding-search","f63"),exact:!0},{path:"/BharatMLStack/blog/tags/episodic-memory",component:d("/BharatMLStack/blog/tags/episodic-memory","97a"),exact:!0},{path:"/BharatMLStack/blog/tags/inferflow",component:d("/BharatMLStack/blog/tags/inferflow","3d0"),exact:!0},{path:"/BharatMLStack/blog/tags/interaction-store",component:d("/BharatMLStack/blog/tags/interaction-store","709"),exact:!0},{path:"/BharatMLStack/blog/tags/llm",component:d("/BharatMLStack/blog/tags/llm","bdb"),exact:!0},{path:"/BharatMLStack/blog/tags/meesho",component:d("/BharatMLStack/blog/tags/meesho","bb0"),exact:!0},{path:"/BharatMLStack/blog/tags/memory",component:d("/BharatMLStack/blog/tags/memory","eff"),exact:!0},{path:"/BharatMLStack/blog/tags/mlplatform",component:d("/BharatMLStack/blog/tags/mlplatform","e7a"),exact:!0},{path:"/BharatMLStack/blog/tags/model-inference",component:d("/BharatMLStack/blog/tags/model-inference","e08"),exact:!0},{path:"/BharatMLStack/blog/tags/online-feature-store",component:d("/BharatMLStack/blog/tags/online-feature-store","7fc"),exact:!0},{path:"/BharatMLStack/blog/tags/tensorrt-llm",component:d("/BharatMLStack/blog/tags/tensorrt-llm","e6b"),exact:!0},{path:"/BharatMLStack/blog/tags/vllm",component:d("/BharatMLStack/blog/tags/vllm","c0a"),exact:!0},{path:"/BharatMLStack/markdown-page",component:d("/BharatMLStack/markdown-page","747"),exact:!0},{path:"/BharatMLStack/",component:d("/BharatMLStack/","e34"),exact:!0},{path:"/BharatMLStack/",component:d("/BharatMLStack/","634"),routes:[{path:"/BharatMLStack/",component:d("/BharatMLStack/","069"),routes:[{path:"/BharatMLStack/",component:d("/BharatMLStack/","289"),routes:[{path:"/BharatMLStack/category/go-sdk",component:d("/BharatMLStack/category/go-sdk","6b0"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/category/inferflow",component:d("/BharatMLStack/category/inferflow","e9f"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/category/numerix",component:d("/BharatMLStack/category/numerix","703"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/category/online-feature-store",component:d("/BharatMLStack/category/online-feature-store","7ee"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/category/predator",component:d("/BharatMLStack/category/predator","549"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/category/python-sdk",component:d("/BharatMLStack/category/python-sdk","1fd"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/category/quick-start",component:d("/BharatMLStack/category/quick-start","dff"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/category/sdks",component:d("/BharatMLStack/category/sdks","532"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/category/skye",component:d("/BharatMLStack/category/skye","1b4"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/category/trufflebox-ui",component:d("/BharatMLStack/category/trufflebox-ui","5f5"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/inferflow/v1.0.0",component:d("/BharatMLStack/inferflow/v1.0.0","675"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/inferflow/v1.0.0/architecture",component:d("/BharatMLStack/inferflow/v1.0.0/architecture","46b"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/inferflow/v1.0.0/configuration",component:d("/BharatMLStack/inferflow/v1.0.0/configuration","4ef"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/inferflow/v1.0.0/functionalities",component:d("/BharatMLStack/inferflow/v1.0.0/functionalities","65a"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/inferflow/v1.0.0/release-notes",component:d("/BharatMLStack/inferflow/v1.0.0/release-notes","cce"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/intro",component:d("/BharatMLStack/intro","eef"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/numerix/v1.0.0",component:d("/BharatMLStack/numerix/v1.0.0","7c7"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/numerix/v1.0.0/architecture",component:d("/BharatMLStack/numerix/v1.0.0/architecture","4d9"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/numerix/v1.0.0/benchmarks",component:d("/BharatMLStack/numerix/v1.0.0/benchmarks","eae"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/numerix/v1.0.0/functionalities",component:d("/BharatMLStack/numerix/v1.0.0/functionalities","f7b"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/numerix/v1.0.0/release-notes",component:d("/BharatMLStack/numerix/v1.0.0/release-notes","77b"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/online-feature-store/v1.0.0",component:d("/BharatMLStack/online-feature-store/v1.0.0","b5e"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/online-feature-store/v1.0.0/architecture",component:d("/BharatMLStack/online-feature-store/v1.0.0/architecture","0af"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/online-feature-store/v1.0.0/benchmarks",component:d("/BharatMLStack/online-feature-store/v1.0.0/benchmarks","889"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/online-feature-store/v1.0.0/data-formats",component:d("/BharatMLStack/online-feature-store/v1.0.0/data-formats","46e"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/online-feature-store/v1.0.0/functionalities",component:d("/BharatMLStack/online-feature-store/v1.0.0/functionalities","415"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/online-feature-store/v1.0.0/release-notes",component:d("/BharatMLStack/online-feature-store/v1.0.0/release-notes","36c"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/predator/v1.0.0",component:d("/BharatMLStack/predator/v1.0.0","93a"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/predator/v1.0.0/architecture",component:d("/BharatMLStack/predator/v1.0.0/architecture","618"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/predator/v1.0.0/functionalities",component:d("/BharatMLStack/predator/v1.0.0/functionalities","094"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/predator/v1.0.0/release-notes",component:d("/BharatMLStack/predator/v1.0.0/release-notes","d81"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/quick-start/v1.0.0",component:d("/BharatMLStack/quick-start/v1.0.0","726"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/quick-start/v1.0.0/quick-start",component:d("/BharatMLStack/quick-start/v1.0.0/quick-start","b19"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/sdks/go/v1.0.0",component:d("/BharatMLStack/sdks/go/v1.0.0","ccc"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/sdks/go/v1.0.0/feature_client",component:d("/BharatMLStack/sdks/go/v1.0.0/feature_client","1df"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/sdks/python/v1.0.0",component:d("/BharatMLStack/sdks/python/v1.0.0","60c"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/sdks/python/v1.0.0/grpc_feature_client",component:d("/BharatMLStack/sdks/python/v1.0.0/grpc_feature_client","9dc"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/sdks/python/v1.0.0/spark_feature_push_client",component:d("/BharatMLStack/sdks/python/v1.0.0/spark_feature_push_client","1bc"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/skye/v1.0.0",component:d("/BharatMLStack/skye/v1.0.0","29b"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/skye/v1.0.0/architecture",component:d("/BharatMLStack/skye/v1.0.0/architecture","ef9"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/skye/v1.0.0/functionalities",component:d("/BharatMLStack/skye/v1.0.0/functionalities","d7c"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/skye/v1.0.0/release-notes",component:d("/BharatMLStack/skye/v1.0.0/release-notes","139"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/trufflebox-ui/v1.0.0",component:d("/BharatMLStack/trufflebox-ui/v1.0.0","a9d"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/trufflebox-ui/v1.0.0/userguide",component:d("/BharatMLStack/trufflebox-ui/v1.0.0/userguide","65e"),exact:!0,sidebar:"tutorialSidebar"}]}]}]},{path:"*",component:d("*")}]},8380:e=>{"use strict";var t=function(){var e=function(){};function t(e,t){Array.isArray(e)?e.forEach(t):null!=e&&t(e,0)}function n(e){for(var t={},n=0,r=e.length;n<r;n++)t[e[n]]=!0;return t}function r(e){var n={},r=[];function a(r,o){if(!(r in n)){o.push(r);var i=o.indexOf(r);if(i<o.length-1)throw new Error("Circular dependency: "+o.slice(i).join(" -> "));var l={},s=e[r];if(s){function c(t){if(!(t in e))throw new Error(r+" depends on an unknown component "+t);if(!(t in l))for(var i in a(t,o),l[t]=!0,n[t])l[i]=!0}t(s.require,c),t(s.optional,c),t(s.modify,c)}n[r]=l,o.pop()}}return function(e){var t=n[e];return t||(a(e,r),t=n[e]),t}}function a(e){for(var t in e)return!0;return!1}return function(o,i,l){var s=function(e){var t={};for(var n in e){var r=e[n];for(var a in r)if("meta"!=a){var o=r[a];t[a]="string"==typeof o?{title:o}:o}}return t}(o),c=function(e){var n;return function(r){if(r in e)return r;if(!n)for(var a in n={},e){var o=e[a];t(o&&o.alias,function(t){if(t in n)throw new Error(t+" cannot be alias for both "+a+" and "+n[t]);if(t in e)throw new Error(t+" cannot be alias of "+a+" because it is a component.");n[t]=a})}return n[r]||r}}(s);i=i.map(c),l=(l||[]).map(c);var u=n(i),d=n(l);i.forEach(function e(n){var r=s[n];t(r&&r.require,function(t){t in d||(u[t]=!0,e(t))})});for(var f,p=r(s),m=u;a(m);){for(var h in f={},m){var g=s[h];t(g&&g.modify,function(e){e in d&&(f[e]=!0)})}for(var b in d)if(!(b in u))for(var y in p(b))if(y in u){f[b]=!0;break}for(var v in m=f)u[v]=!0}var k={getIds:function(){var e=[];return k.load(function(t){e.push(t)}),e},load:function(t,n){return function(t,n,r,a){var o=a?a.series:void 0,i=a?a.parallel:e,l={},s={};function c(e){if(e in l)return l[e];s[e]=!0;var a,u=[];for(var d in t(e))d in n&&u.push(d);if(0===u.length)a=r(e);else{var f=i(u.map(function(e){var t=c(e);return delete s[e],t}));o?a=o(f,function(){return r(e)}):r(e)}return l[e]=a}for(var u in n)c(u);var d=[];for(var f in s)d.push(l[f]);return i(d)}(p,u,t,n)}};return k}}();e.exports=t},8505:(e,t,n)=>{var r=n(4634);e.exports=h,e.exports.parse=o,e.exports.compile=function(e,t){return c(o(e,t),t)},e.exports.tokensToFunction=c,e.exports.tokensToRegExp=m;var a=new RegExp(["(\\\\.)","([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?|(\\*))"].join("|"),"g");function o(e,t){for(var n,r=[],o=0,l=0,s="",c=t&&t.delimiter||"/";null!=(n=a.exec(e));){var u=n[0],f=n[1],p=n.index;if(s+=e.slice(l,p),l=p+u.length,f)s+=f[1];else{var m=e[l],h=n[2],g=n[3],b=n[4],y=n[5],v=n[6],k=n[7];s&&(r.push(s),s="");var w=null!=h&&null!=m&&m!==h,S="+"===v||"*"===v,x="?"===v||"*"===v,_=h||c,E=b||y,C=h||("string"==typeof r[r.length-1]?r[r.length-1]:"");r.push({name:g||o++,prefix:h||"",delimiter:_,optional:x,repeat:S,partial:w,asterisk:!!k,pattern:E?d(E):k?".*":i(_,C)})}}return l<e.length&&(s+=e.substr(l)),s&&r.push(s),r}function i(e,t){return!t||t.indexOf(e)>-1?"[^"+u(e)+"]+?":u(t)+"|(?:(?!"+u(t)+")[^"+u(e)+"])+?"}function l(e){return encodeURI(e).replace(/[\/?#]/g,function(e){return"%"+e.charCodeAt(0).toString(16).toUpperCase()})}function s(e){return encodeURI(e).replace(/[?#]/g,function(e){return"%"+e.charCodeAt(0).toString(16).toUpperCase()})}function c(e,t){for(var n=new Array(e.length),a=0;a<e.length;a++)"object"==typeof e[a]&&(n[a]=new RegExp("^(?:"+e[a].pattern+")$",p(t)));return function(t,a){for(var o="",i=t||{},c=(a||{}).pretty?l:encodeURIComponent,u=0;u<e.length;u++){var d=e[u];if("string"!=typeof d){var f,p=i[d.name];if(null==p){if(d.optional){d.partial&&(o+=d.prefix);continue}throw new TypeError('Expected "'+d.name+'" to be defined')}if(r(p)){if(!d.repeat)throw new TypeError('Expected "'+d.name+'" to not repeat, but received `'+JSON.stringify(p)+"`");if(0===p.length){if(d.optional)continue;throw new TypeError('Expected "'+d.name+'" to not be empty')}for(var m=0;m<p.length;m++){if(f=c(p[m]),!n[u].test(f))throw new TypeError('Expected all "'+d.name+'" to match "'+d.pattern+'", but received `'+JSON.stringify(f)+"`");o+=(0===m?d.prefix:d.delimiter)+f}}else{if(f=d.asterisk?s(p):c(p),!n[u].test(f))throw new TypeError('Expected "'+d.name+'" to match "'+d.pattern+'", but received "'+f+'"');o+=d.prefix+f}}else o+=d}return o}}function u(e){return e.replace(/([.+*?=^!:${}()[\]|\/\\])/g,"\\$1")}function d(e){return e.replace(/([=!:$\/()])/g,"\\$1")}function f(e,t){return e.keys=t,e}function p(e){return e&&e.sensitive?"":"i"}function m(e,t,n){r(t)||(n=t||n,t=[]);for(var a=(n=n||{}).strict,o=!1!==n.end,i="",l=0;l<e.length;l++){var s=e[l];if("string"==typeof s)i+=u(s);else{var c=u(s.prefix),d="(?:"+s.pattern+")";t.push(s),s.repeat&&(d+="(?:"+c+d+")*"),i+=d=s.optional?s.partial?c+"("+d+")?":"(?:"+c+"("+d+"))?":c+"("+d+")"}}var m=u(n.delimiter||"/"),h=i.slice(-m.length)===m;return a||(i=(h?i.slice(0,-m.length):i)+"(?:"+m+"(?=$))?"),i+=o?"$":a&&h?"":"(?="+m+"|$)",f(new RegExp("^"+i,p(n)),t)}function h(e,t,n){return r(t)||(n=t||n,t=[]),n=n||{},e instanceof RegExp?function(e,t){var n=e.source.match(/\((?!\?)/g);if(n)for(var r=0;r<n.length;r++)t.push({name:r,prefix:null,delimiter:null,optional:!1,repeat:!1,partial:!1,asterisk:!1,pattern:null});return f(e,t)}(e,t):r(e)?function(e,t,n){for(var r=[],a=0;a<e.length;a++)r.push(h(e[a],t,n).source);return f(new RegExp("(?:"+r.join("|")+")",p(n)),t)}(e,t,n):function(e,t,n){return m(o(e,n),t,n)}(e,t,n)}},8587:(e,t,n)=>{"use strict";function r(e,t){if(null==e)return{};var n={};for(var r in e)if({}.hasOwnProperty.call(e,r)){if(-1!==t.indexOf(r))continue;n[r]=e[r]}return n}n.d(t,{A:()=>r})},8692:(e,t,n)=>{var r={"./":8722};function a(e){var t=o(e);return n(t)}function o(e){if(!n.o(r,e)){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}return r[e]}a.keys=function(){return Object.keys(r)},a.resolve=o,e.exports=a,a.id=8692},8722:(e,t,n)=>{const r=n(6969),a=n(8380),o=new Set;function i(e){void 0===e?e=Object.keys(r.languages).filter(e=>"meta"!=e):Array.isArray(e)||(e=[e]);const t=[...o,...Object.keys(Prism.languages)];a(r,e,t).load(e=>{if(!(e in r.languages))return void(i.silent||console.warn("Language does not exist: "+e));const t="./prism-"+e;delete n.c[n(3157).resolve(t)],delete Prism.languages[e],n(3157)(t),o.add(e)})}i.silent=!1,e.exports=i},8774:(e,t,n)=>{"use strict";n.d(t,{A:()=>p});var r=n(6540),a=n(4625),o=n(440),i=n(4586),l=n(6654),s=n(8193),c=n(3427),u=n(6025),d=n(4848);function f({isNavLink:e,to:t,href:n,activeClassName:f,isActive:p,"data-noBrokenLinkCheck":m,autoAddBaseUrl:h=!0,...g},b){const{siteConfig:y}=(0,i.A)(),{trailingSlash:v,baseUrl:k}=y,w=y.future.experimental_router,{withBaseUrl:S}=(0,u.hH)(),x=(0,c.A)(),_=(0,r.useRef)(null);(0,r.useImperativeHandle)(b,()=>_.current);const E=t||n;const C=(0,l.A)(E),L=E?.replace("pathname://","");let A=void 0!==L?(T=L,h&&(e=>e.startsWith("/"))(T)?S(T):T):void 0;var T;"hash"===w&&A?.startsWith("./")&&(A=A?.slice(1)),A&&C&&(A=(0,o.Ks)(A,{trailingSlash:v,baseUrl:k}));const j=(0,r.useRef)(!1),M=e?a.k2:a.N_,P=s.A.canUseIntersectionObserver,N=(0,r.useRef)(),O=()=>{j.current||null==A||(window.docusaurus.preload(A),j.current=!0)};(0,r.useEffect)(()=>(!P&&C&&s.A.canUseDOM&&null!=A&&window.docusaurus.prefetch(A),()=>{P&&N.current&&N.current.disconnect()}),[N,A,P,C]);const R=A?.startsWith("#")??!1,B=!g.target||"_self"===g.target,D=!A||!C||!B||R&&"hash"!==w;m||!R&&D||x.collectLink(A),g.id&&x.collectAnchor(g.id);const F={};return D?(0,d.jsx)("a",{ref:_,href:A,...E&&!C&&{target:"_blank",rel:"noopener noreferrer"},...g,...F}):(0,d.jsx)(M,{...g,onMouseEnter:O,onTouchStart:O,innerRef:e=>{_.current=e,P&&e&&C&&(N.current=new window.IntersectionObserver(t=>{t.forEach(t=>{e===t.target&&(t.isIntersecting||t.intersectionRatio>0)&&(N.current.unobserve(e),N.current.disconnect(),null!=A&&window.docusaurus.prefetch(A))})}),N.current.observe(e))},to:A,...e&&{isActive:p,activeClassName:f},...F})}const p=r.forwardRef(f)},9169:(e,t,n)=>{"use strict";n.d(t,{Dt:()=>l,ys:()=>i});var r=n(6540),a=n(8328),o=n(4586);function i(e,t){const n=e=>(!e||e.endsWith("/")?e:`${e}/`)?.toLowerCase();return n(e)===n(t)}function l(){const{baseUrl:e}=(0,o.A)().siteConfig;return(0,r.useMemo)(()=>function({baseUrl:e,routes:t}){function n(t){return t.path===e&&!0===t.exact}function r(t){return t.path===e&&!t.exact}return function e(t){if(0===t.length)return;return t.find(n)||e(t.filter(r).flatMap(e=>e.routes??[]))}(t)}({routes:a.A,baseUrl:e}),[e])}},9462:(e,t,n)=>{"use strict";var r=n(6540),a=n(5338),o=n(545),i=n(4625),l=n(4784),s=n(8193);const c=[n(3001),n(119),n(6134),n(6294),n(1043)];var u=n(8328),d=n(6347),f=n(2831),p=n(4848);function m({children:e}){return(0,p.jsxs)(p.Fragment,{children:[(0,p.jsxs)("div",{className:"gradient-bg-global",children:[(0,p.jsx)("div",{className:"gradient-orb-global orb-global-1"}),(0,p.jsx)("div",{className:"gradient-orb-global orb-global-2"}),(0,p.jsx)("div",{className:"gradient-orb-global orb-global-3"})]}),e]})}var h=n(4563);const g=e=>e.defaultFormatter(e);function b({children:e}){return(0,p.jsx)(h.AL,{formatter:g,children:e})}function y({children:e}){return(0,p.jsx)(b,{children:e})}var v=n(5260),k=n(4586),w=n(6025),S=n(6342),x=n(5500),_=n(2131),E=n(4090);var C=n(440),L=n(1463);function A(){const{i18n:{currentLocale:e,defaultLocale:t,localeConfigs:n}}=(0,k.A)(),r=(0,_.o)(),a=n[e].htmlLang,o=e=>e.replace("-","_");return(0,p.jsxs)(v.A,{children:[Object.entries(n).map(([e,{htmlLang:t}])=>(0,p.jsx)("link",{rel:"alternate",href:r.createUrl({locale:e,fullyQualified:!0}),hrefLang:t},e)),(0,p.jsx)("link",{rel:"alternate",href:r.createUrl({locale:t,fullyQualified:!0}),hrefLang:"x-default"}),(0,p.jsx)("meta",{property:"og:locale",content:o(a)}),Object.values(n).filter(e=>a!==e.htmlLang).map(e=>(0,p.jsx)("meta",{property:"og:locale:alternate",content:o(e.htmlLang)},`meta-og-${e.htmlLang}`))]})}function T({permalink:e}){const{siteConfig:{url:t}}=(0,k.A)(),n=function(){const{siteConfig:{url:e,baseUrl:t,trailingSlash:n}}=(0,k.A)(),{pathname:r}=(0,d.zy)();return e+(0,C.Ks)((0,w.Ay)(r),{trailingSlash:n,baseUrl:t})}(),r=e?`${t}${e}`:n;return(0,p.jsxs)(v.A,{children:[(0,p.jsx)("meta",{property:"og:url",content:r}),(0,p.jsx)("link",{rel:"canonical",href:r})]})}function j(){const{i18n:{currentLocale:e}}=(0,k.A)(),{metadata:t,image:n}=(0,S.p)();return(0,p.jsxs)(p.Fragment,{children:[(0,p.jsxs)(v.A,{children:[(0,p.jsx)("meta",{name:"twitter:card",content:"summary_large_image"}),(0,p.jsx)("body",{className:E.w})]}),n&&(0,p.jsx)(x.be,{image:n}),(0,p.jsx)(T,{}),(0,p.jsx)(A,{}),(0,p.jsx)(L.A,{tag:"default",locale:e}),(0,p.jsx)(v.A,{children:t.map((e,t)=>(0,p.jsx)("meta",{...e},t))})]})}const M=new Map;var P=n(6125),N=n(6988),O=n(205);function R(e,...t){const n=c.map(n=>{const r=n.default?.[e]??n[e];return r?.(...t)});return()=>n.forEach(e=>e?.())}const B=function({children:e,location:t,previousLocation:n}){return(0,O.A)(()=>{n!==t&&(!function({location:e,previousLocation:t}){if(!t)return;const n=e.pathname===t.pathname,r=e.hash===t.hash,a=e.search===t.search;if(n&&r&&!a)return;const{hash:o}=e;if(o){const e=decodeURIComponent(o.substring(1)),t=document.getElementById(e);t?.scrollIntoView()}else window.scrollTo(0,0)}({location:t,previousLocation:n}),R("onRouteDidUpdate",{previousLocation:n,location:t}))},[n,t]),e};function D(e){const t=Array.from(new Set([e,decodeURI(e)])).map(e=>(0,f.u)(u.A,e)).flat();return Promise.all(t.map(e=>e.route.component.preload?.()))}class F extends r.Component{previousLocation;routeUpdateCleanupCb;constructor(e){super(e),this.previousLocation=null,this.routeUpdateCleanupCb=s.A.canUseDOM?R("onRouteUpdate",{previousLocation:null,location:this.props.location}):()=>{},this.state={nextRouteHasLoaded:!0}}shouldComponentUpdate(e,t){if(e.location===this.props.location)return t.nextRouteHasLoaded;const n=e.location;return this.previousLocation=this.props.location,this.setState({nextRouteHasLoaded:!1}),this.routeUpdateCleanupCb=R("onRouteUpdate",{previousLocation:this.previousLocation,location:n}),D(n.pathname).then(()=>{this.routeUpdateCleanupCb(),this.setState({nextRouteHasLoaded:!0})}).catch(e=>{console.warn(e),window.location.reload()}),!1}render(){const{children:e,location:t}=this.props;return(0,p.jsx)(B,{previousLocation:this.previousLocation,location:t,children:(0,p.jsx)(d.qh,{location:t,render:()=>e})})}}const I=F,z="__docusaurus-base-url-issue-banner-suggestion-container";function $(e){return`\ndocument.addEventListener('DOMContentLoaded', function maybeInsertBanner() {\n var shouldInsert = typeof window['docusaurus'] === 'undefined';\n shouldInsert && insertBanner();\n});\n\nfunction insertBanner() {\n var bannerContainer = document.createElement('div');\n bannerContainer.id = '__docusaurus-base-url-issue-banner-container';\n var bannerHtml = ${JSON.stringify(function(e){return`\n<div id="__docusaurus-base-url-issue-banner" style="border: thick solid red; background-color: rgb(255, 230, 179); margin: 20px; padding: 20px; font-size: 20px;">\n <p style="font-weight: bold; font-size: 30px;">Your Docusaurus site did not load properly.</p>\n <p>A very common reason is a wrong site <a href="https://docusaurus.io/docs/docusaurus.config.js/#baseUrl" style="font-weight: bold;">baseUrl configuration</a>.</p>\n <p>Current configured baseUrl = <span style="font-weight: bold; color: red;">${e}</span> ${"/"===e?" (default value)":""}</p>\n <p>We suggest trying baseUrl = <span id="${z}" style="font-weight: bold; color: green;"></span></p>\n</div>\n`}(e)).replace(/</g,"\\<")};\n bannerContainer.innerHTML = bannerHtml;\n document.body.prepend(bannerContainer);\n var suggestionContainer = document.getElementById('${z}');\n var actualHomePagePath = window.location.pathname;\n var suggestedBaseUrl = actualHomePagePath.substr(-1) === '/'\n ? actualHomePagePath\n : actualHomePagePath + '/';\n suggestionContainer.innerHTML = suggestedBaseUrl;\n}\n`}function U(){const{siteConfig:{baseUrl:e}}=(0,k.A)();return(0,p.jsx)(p.Fragment,{children:!s.A.canUseDOM&&(0,p.jsx)(v.A,{children:(0,p.jsx)("script",{children:$(e)})})})}function q(){const{siteConfig:{baseUrl:e,baseUrlIssueBanner:t}}=(0,k.A)(),{pathname:n}=(0,d.zy)();return t&&n===e?(0,p.jsx)(U,{}):null}function H(){const{siteConfig:{favicon:e,title:t,noIndex:n},i18n:{currentLocale:r,localeConfigs:a}}=(0,k.A)(),o=(0,w.Ay)(e),{htmlLang:i,direction:l}=a[r];return(0,p.jsxs)(v.A,{children:[(0,p.jsx)("html",{lang:i,dir:l}),(0,p.jsx)("title",{children:t}),(0,p.jsx)("meta",{property:"og:title",content:t}),(0,p.jsx)("meta",{name:"viewport",content:"width=device-width, initial-scale=1.0"}),n&&(0,p.jsx)("meta",{name:"robots",content:"noindex, nofollow"}),e&&(0,p.jsx)("link",{rel:"icon",href:o})]})}var G=n(7489),V=n(2303);function W(){const e=(0,V.A)();return(0,p.jsx)(v.A,{children:(0,p.jsx)("html",{"data-has-hydrated":e})})}const Q=(0,f.v)(u.A);function K(){const e=function(e){if(M.has(e.pathname))return{...e,pathname:M.get(e.pathname)};if((0,f.u)(u.A,e.pathname).some(({route:e})=>!0===e.exact))return M.set(e.pathname,e.pathname),e;const t=e.pathname.trim().replace(/(?:\/index)?\.html$/,"")||"/";return M.set(e.pathname,t),{...e,pathname:t}}((0,d.zy)());return(0,p.jsx)(I,{location:e,children:Q})}function Y(){return(0,p.jsx)(G.A,{children:(0,p.jsx)(N.l,{children:(0,p.jsxs)(P.x,{children:[(0,p.jsx)(m,{children:(0,p.jsxs)(y,{children:[(0,p.jsx)(H,{}),(0,p.jsx)(j,{}),(0,p.jsx)(q,{}),(0,p.jsx)(K,{})]})}),(0,p.jsx)(W,{})]})})})}var X=n(4054);const Z=function(e){try{return document.createElement("link").relList.supports(e)}catch{return!1}}("prefetch")?function(e){return new Promise((t,n)=>{if("undefined"==typeof document)return void n();const r=document.createElement("link");r.setAttribute("rel","prefetch"),r.setAttribute("href",e),r.onload=()=>t(),r.onerror=()=>n();const a=document.getElementsByTagName("head")[0]??document.getElementsByName("script")[0]?.parentNode;a?.appendChild(r)})}:function(e){return new Promise((t,n)=>{const r=new XMLHttpRequest;r.open("GET",e,!0),r.withCredentials=!0,r.onload=()=>{200===r.status?t():n()},r.send(null)})};var J=n(6921);const ee=new Set,te=new Set,ne=()=>navigator.connection?.effectiveType.includes("2g")||navigator.connection?.saveData,re={prefetch:e=>{if(!(e=>!ne()&&!te.has(e)&&!ee.has(e))(e))return!1;ee.add(e);const t=(0,f.u)(u.A,e).flatMap(e=>{return t=e.route.path,Object.entries(X).filter(([e])=>e.replace(/-[^-]+$/,"")===t).flatMap(([,e])=>Object.values((0,J.A)(e)));var t});return Promise.all(t.map(e=>{const t=n.gca(e);return t&&!t.includes("undefined")?Z(t).catch(()=>{}):Promise.resolve()}))},preload:e=>!!(e=>!ne()&&!te.has(e))(e)&&(te.add(e),D(e))},ae=Object.freeze(re);function oe({children:e}){return"hash"===l.default.future.experimental_router?(0,p.jsx)(i.I9,{children:e}):(0,p.jsx)(i.Kd,{children:e})}const ie=Boolean(!0);if(s.A.canUseDOM){window.docusaurus=ae;const e=document.getElementById("__docusaurus"),t=(0,p.jsx)(o.vd,{children:(0,p.jsx)(oe,{children:(0,p.jsx)(Y,{})})}),n=(e,t)=>{console.error("Docusaurus React Root onRecoverableError:",e,t)},i=()=>{if(window.docusaurusRoot)window.docusaurusRoot.render(t);else if(ie)window.docusaurusRoot=a.hydrateRoot(e,t,{onRecoverableError:n});else{const r=a.createRoot(e,{onRecoverableError:n});r.render(t),window.docusaurusRoot=r}};D(window.location.pathname).then(()=>{(0,r.startTransition)(i)})}},9532:(e,t,n)=>{"use strict";n.d(t,{Be:()=>c,ZC:()=>l,_q:()=>i,dV:()=>s,fM:()=>u});var r=n(6540),a=n(205),o=n(4848);function i(e){const t=(0,r.useRef)(e);return(0,a.A)(()=>{t.current=e},[e]),(0,r.useCallback)((...e)=>t.current(...e),[])}function l(e){const t=(0,r.useRef)();return(0,a.A)(()=>{t.current=e}),t.current}class s extends Error{constructor(e,t){super(),this.name="ReactContextError",this.message=`Hook ${this.stack?.split("\n")[1]?.match(/at (?:\w+\.)?(?<name>\w+)/)?.groups.name??""} is called outside the <${e}>. ${t??""}`}}function c(e){const t=Object.entries(e);return t.sort((e,t)=>e[0].localeCompare(t[0])),(0,r.useMemo)(()=>e,t.flat())}function u(e){return({children:t})=>(0,o.jsx)(o.Fragment,{children:e.reduceRight((e,t)=>(0,o.jsx)(t,{children:e}),t)})}},9698:(e,t)=>{"use strict";var n=Symbol.for("react.transitional.element"),r=Symbol.for("react.fragment");function a(e,t,r){var a=null;if(void 0!==r&&(a=""+r),void 0!==t.key&&(a=""+t.key),"key"in t)for(var o in r={},t)"key"!==o&&(r[o]=t[o]);else r=t;return t=r.ref,{$$typeof:n,type:e,key:a,ref:void 0!==t?t:null,props:r}}t.Fragment=r,t.jsx=a,t.jsxs=a},9700:()=>{!function(e){function t(e,t){return"___"+e.toUpperCase()+t+"___"}Object.defineProperties(e.languages["markup-templating"]={},{buildPlaceholders:{value:function(n,r,a,o){if(n.language===r){var i=n.tokenStack=[];n.code=n.code.replace(a,function(e){if("function"==typeof o&&!o(e))return e;for(var a,l=i.length;-1!==n.code.indexOf(a=t(r,l));)++l;return i[l]=e,a}),n.grammar=e.languages.markup}}},tokenizePlaceholders:{value:function(n,r){if(n.language===r&&n.tokenStack){n.grammar=e.languages[r];var a=0,o=Object.keys(n.tokenStack);!function i(l){for(var s=0;s<l.length&&!(a>=o.length);s++){var c=l[s];if("string"==typeof c||c.content&&"string"==typeof c.content){var u=o[a],d=n.tokenStack[u],f="string"==typeof c?c:c.content,p=t(r,u),m=f.indexOf(p);if(m>-1){++a;var h=f.substring(0,m),g=new e.Token(r,e.tokenize(d,n.grammar),"language-"+r,d),b=f.substring(m+p.length),y=[];h&&y.push.apply(y,i([h])),y.push(g),b&&y.push.apply(y,i([b])),"string"==typeof c?l.splice.apply(l,[s,1].concat(y)):c.content=y}}else c.content&&i(c.content)}return l}(n.tokens)}}}})}(Prism)},9869:(e,t)=>{"use strict";var n=Symbol.for("react.transitional.element"),r=Symbol.for("react.portal"),a=Symbol.for("react.fragment"),o=Symbol.for("react.strict_mode"),i=Symbol.for("react.profiler"),l=Symbol.for("react.consumer"),s=Symbol.for("react.context"),c=Symbol.for("react.forward_ref"),u=Symbol.for("react.suspense"),d=Symbol.for("react.memo"),f=Symbol.for("react.lazy"),p=Symbol.iterator;var m={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},h=Object.assign,g={};function b(e,t,n){this.props=e,this.context=t,this.refs=g,this.updater=n||m}function y(){}function v(e,t,n){this.props=e,this.context=t,this.refs=g,this.updater=n||m}b.prototype.isReactComponent={},b.prototype.setState=function(e,t){if("object"!=typeof e&&"function"!=typeof e&&null!=e)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")},b.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")},y.prototype=b.prototype;var k=v.prototype=new y;k.constructor=v,h(k,b.prototype),k.isPureReactComponent=!0;var w=Array.isArray,S={H:null,A:null,T:null,S:null,V:null},x=Object.prototype.hasOwnProperty;function _(e,t,r,a,o,i){return r=i.ref,{$$typeof:n,type:e,key:t,ref:void 0!==r?r:null,props:i}}function E(e){return"object"==typeof e&&null!==e&&e.$$typeof===n}var C=/\/+/g;function L(e,t){return"object"==typeof e&&null!==e&&null!=e.key?(n=""+e.key,r={"=":"=0",":":"=2"},"$"+n.replace(/[=:]/g,function(e){return r[e]})):t.toString(36);var n,r}function A(){}function T(e,t,a,o,i){var l=typeof e;"undefined"!==l&&"boolean"!==l||(e=null);var s,c,u=!1;if(null===e)u=!0;else switch(l){case"bigint":case"string":case"number":u=!0;break;case"object":switch(e.$$typeof){case n:case r:u=!0;break;case f:return T((u=e._init)(e._payload),t,a,o,i)}}if(u)return i=i(e),u=""===o?"."+L(e,0):o,w(i)?(a="",null!=u&&(a=u.replace(C,"$&/")+"/"),T(i,t,a,"",function(e){return e})):null!=i&&(E(i)&&(s=i,c=a+(null==i.key||e&&e.key===i.key?"":(""+i.key).replace(C,"$&/")+"/")+u,i=_(s.type,c,void 0,0,0,s.props)),t.push(i)),1;u=0;var d,m=""===o?".":o+":";if(w(e))for(var h=0;h<e.length;h++)u+=T(o=e[h],t,a,l=m+L(o,h),i);else if("function"==typeof(h=null===(d=e)||"object"!=typeof d?null:"function"==typeof(d=p&&d[p]||d["@@iterator"])?d:null))for(e=h.call(e),h=0;!(o=e.next()).done;)u+=T(o=o.value,t,a,l=m+L(o,h++),i);else if("object"===l){if("function"==typeof e.then)return T(function(e){switch(e.status){case"fulfilled":return e.value;case"rejected":throw e.reason;default:switch("string"==typeof e.status?e.then(A,A):(e.status="pending",e.then(function(t){"pending"===e.status&&(e.status="fulfilled",e.value=t)},function(t){"pending"===e.status&&(e.status="rejected",e.reason=t)})),e.status){case"fulfilled":return e.value;case"rejected":throw e.reason}}throw e}(e),t,a,o,i);throw t=String(e),Error("Objects are not valid as a React child (found: "+("[object Object]"===t?"object with keys {"+Object.keys(e).join(", ")+"}":t)+"). If you meant to render a collection of children, use an array instead.")}return u}function j(e,t,n){if(null==e)return e;var r=[],a=0;return T(e,r,"","",function(e){return t.call(n,e,a++)}),r}function M(e){if(-1===e._status){var t=e._result;(t=t()).then(function(t){0!==e._status&&-1!==e._status||(e._status=1,e._result=t)},function(t){0!==e._status&&-1!==e._status||(e._status=2,e._result=t)}),-1===e._status&&(e._status=0,e._result=t)}if(1===e._status)return e._result.default;throw e._result}var P="function"==typeof reportError?reportError:function(e){if("object"==typeof window&&"function"==typeof window.ErrorEvent){var t=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:"object"==typeof e&&null!==e&&"string"==typeof e.message?String(e.message):String(e),error:e});if(!window.dispatchEvent(t))return}else if("object"==typeof process&&"function"==typeof process.emit)return void process.emit("uncaughtException",e);console.error(e)};function N(){}t.Children={map:j,forEach:function(e,t,n){j(e,function(){t.apply(this,arguments)},n)},count:function(e){var t=0;return j(e,function(){t++}),t},toArray:function(e){return j(e,function(e){return e})||[]},only:function(e){if(!E(e))throw Error("React.Children.only expected to receive a single React element child.");return e}},t.Component=b,t.Fragment=a,t.Profiler=i,t.PureComponent=v,t.StrictMode=o,t.Suspense=u,t.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=S,t.__COMPILER_RUNTIME={__proto__:null,c:function(e){return S.H.useMemoCache(e)}},t.cache=function(e){return function(){return e.apply(null,arguments)}},t.cloneElement=function(e,t,n){if(null==e)throw Error("The argument must be a React element, but you passed "+e+".");var r=h({},e.props),a=e.key;if(null!=t)for(o in void 0!==t.ref&&void 0,void 0!==t.key&&(a=""+t.key),t)!x.call(t,o)||"key"===o||"__self"===o||"__source"===o||"ref"===o&&void 0===t.ref||(r[o]=t[o]);var o=arguments.length-2;if(1===o)r.children=n;else if(1<o){for(var i=Array(o),l=0;l<o;l++)i[l]=arguments[l+2];r.children=i}return _(e.type,a,void 0,0,0,r)},t.createContext=function(e){return(e={$$typeof:s,_currentValue:e,_currentValue2:e,_threadCount:0,Provider:null,Consumer:null}).Provider=e,e.Consumer={$$typeof:l,_context:e},e},t.createElement=function(e,t,n){var r,a={},o=null;if(null!=t)for(r in void 0!==t.key&&(o=""+t.key),t)x.call(t,r)&&"key"!==r&&"__self"!==r&&"__source"!==r&&(a[r]=t[r]);var i=arguments.length-2;if(1===i)a.children=n;else if(1<i){for(var l=Array(i),s=0;s<i;s++)l[s]=arguments[s+2];a.children=l}if(e&&e.defaultProps)for(r in i=e.defaultProps)void 0===a[r]&&(a[r]=i[r]);return _(e,o,void 0,0,0,a)},t.createRef=function(){return{current:null}},t.forwardRef=function(e){return{$$typeof:c,render:e}},t.isValidElement=E,t.lazy=function(e){return{$$typeof:f,_payload:{_status:-1,_result:e},_init:M}},t.memo=function(e,t){return{$$typeof:d,type:e,compare:void 0===t?null:t}},t.startTransition=function(e){var t=S.T,n={};S.T=n;try{var r=e(),a=S.S;null!==a&&a(n,r),"object"==typeof r&&null!==r&&"function"==typeof r.then&&r.then(N,P)}catch(o){P(o)}finally{S.T=t}},t.unstable_useCacheRefresh=function(){return S.H.useCacheRefresh()},t.use=function(e){return S.H.use(e)},t.useActionState=function(e,t,n){return S.H.useActionState(e,t,n)},t.useCallback=function(e,t){return S.H.useCallback(e,t)},t.useContext=function(e){return S.H.useContext(e)},t.useDebugValue=function(){},t.useDeferredValue=function(e,t){return S.H.useDeferredValue(e,t)},t.useEffect=function(e,t,n){var r=S.H;if("function"==typeof n)throw Error("useEffect CRUD overload is not enabled in this build of React.");return r.useEffect(e,t)},t.useId=function(){return S.H.useId()},t.useImperativeHandle=function(e,t,n){return S.H.useImperativeHandle(e,t,n)},t.useInsertionEffect=function(e,t){return S.H.useInsertionEffect(e,t)},t.useLayoutEffect=function(e,t){return S.H.useLayoutEffect(e,t)},t.useMemo=function(e,t){return S.H.useMemo(e,t)},t.useOptimistic=function(e,t){return S.H.useOptimistic(e,t)},t.useReducer=function(e,t,n){return S.H.useReducer(e,t,n)},t.useRef=function(e){return S.H.useRef(e)},t.useState=function(e){return S.H.useState(e)},t.useSyncExternalStore=function(e,t,n){return S.H.useSyncExternalStore(e,t,n)},t.useTransition=function(){return S.H.useTransition()},t.version="19.1.1"},9876:(e,t,n)=>{"use strict";n.d(t,{e:()=>m,M:()=>h});var r=n(6540),a=n(5600),o=n(4581),i=n(6347),l=n(9532);function s(e){!function(e){const t=(0,i.W6)(),n=(0,l._q)(e);(0,r.useEffect)(()=>t.block((e,t)=>n(e,t)),[t,n])}((t,n)=>{if("POP"===n)return e(t,n)})}var c=n(6342),u=n(4848);const d=r.createContext(void 0);function f(){const e=function(){const e=(0,a.YL)(),{items:t}=(0,c.p)().navbar;return 0===t.length&&!e.component}(),t=(0,o.l)(),n=!e&&"mobile"===t,[i,l]=(0,r.useState)(!1),s=(0,r.useCallback)(()=>{l(e=>!e)},[]);return(0,r.useEffect)(()=>{"desktop"===t&&l(!1)},[t]),(0,r.useMemo)(()=>({disabled:e,shouldRender:n,toggle:s,shown:i}),[e,n,s,i])}function p({handler:e}){return s(e),null}function m({children:e}){const t=f();return(0,u.jsxs)(u.Fragment,{children:[t.shown&&(0,u.jsx)(p,{handler:()=>(t.toggle(),!1)}),(0,u.jsx)(d.Provider,{value:t,children:e})]})}function h(){const e=r.useContext(d);if(void 0===e)throw new l.dV("NavbarMobileSidebarProvider");return e}},9982:(e,t,n)=>{"use strict";e.exports=n(4477)}},e=>{e.O(0,[1869],()=>{return t=9462,e(e.s=t);var t});e.O()}]); \ No newline at end of file diff --git a/docs/assets/js/main.7cb3cce5.js.LICENSE.txt b/docs/assets/js/main.6f8db0ca.js.LICENSE.txt similarity index 100% rename from docs/assets/js/main.7cb3cce5.js.LICENSE.txt rename to docs/assets/js/main.6f8db0ca.js.LICENSE.txt diff --git a/docs/assets/js/main.7cb3cce5.js b/docs/assets/js/main.7cb3cce5.js deleted file mode 100644 index a17aa70d..00000000 --- a/docs/assets/js/main.7cb3cce5.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! For license information please see main.7cb3cce5.js.LICENSE.txt */ -(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[8792],{115:e=>{var t="undefined"!=typeof Element,n="function"==typeof Map,r="function"==typeof Set,a="function"==typeof ArrayBuffer&&!!ArrayBuffer.isView;function o(e,i){if(e===i)return!0;if(e&&i&&"object"==typeof e&&"object"==typeof i){if(e.constructor!==i.constructor)return!1;var l,s,u,c;if(Array.isArray(e)){if((l=e.length)!=i.length)return!1;for(s=l;0!==s--;)if(!o(e[s],i[s]))return!1;return!0}if(n&&e instanceof Map&&i instanceof Map){if(e.size!==i.size)return!1;for(c=e.entries();!(s=c.next()).done;)if(!i.has(s.value[0]))return!1;for(c=e.entries();!(s=c.next()).done;)if(!o(s.value[1],i.get(s.value[0])))return!1;return!0}if(r&&e instanceof Set&&i instanceof Set){if(e.size!==i.size)return!1;for(c=e.entries();!(s=c.next()).done;)if(!i.has(s.value[0]))return!1;return!0}if(a&&ArrayBuffer.isView(e)&&ArrayBuffer.isView(i)){if((l=e.length)!=i.length)return!1;for(s=l;0!==s--;)if(e[s]!==i[s])return!1;return!0}if(e.constructor===RegExp)return e.source===i.source&&e.flags===i.flags;if(e.valueOf!==Object.prototype.valueOf&&"function"==typeof e.valueOf&&"function"==typeof i.valueOf)return e.valueOf()===i.valueOf();if(e.toString!==Object.prototype.toString&&"function"==typeof e.toString&&"function"==typeof i.toString)return e.toString()===i.toString();if((l=(u=Object.keys(e)).length)!==Object.keys(i).length)return!1;for(s=l;0!==s--;)if(!Object.prototype.hasOwnProperty.call(i,u[s]))return!1;if(t&&e instanceof Element)return!1;for(s=l;0!==s--;)if(("_owner"!==u[s]&&"__v"!==u[s]&&"__o"!==u[s]||!e.$$typeof)&&!o(e[u[s]],i[u[s]]))return!1;return!0}return e!=e&&i!=i}e.exports=function(e,t){try{return o(e,t)}catch(n){if((n.message||"").match(/stack|recursion/i))return console.warn("react-fast-compare cannot handle circular refs"),!1;throw n}}},119:(e,t,n)=>{"use strict";n.r(t)},205:(e,t,n)=>{"use strict";n.d(t,{A:()=>a});var r=n(6540);const a=n(8193).A.canUseDOM?r.useLayoutEffect:r.useEffect},253:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getErrorCausalChain=function e(t){if(t.cause)return[t,...e(t.cause)];return[t]}},311:e=>{"use strict";e.exports=function(e,t,n,r,a,o,i,l){if(!e){var s;if(void 0===t)s=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var u=[n,r,a,o,i,l],c=0;(s=new Error(t.replace(/%s/g,(function(){return u[c++]})))).name="Invariant Violation"}throw s.framesToPop=1,s}}},418:(e,t,n)=>{"use strict";n.d(t,{A:()=>r});const r=()=>null},440:(e,t,n)=>{"use strict";t.rA=t.Ks=t.LU=void 0;const r=n(1635);t.LU="__blog-post-container";var a=n(2983);Object.defineProperty(t,"Ks",{enumerable:!0,get:function(){return r.__importDefault(a).default}});var o=n(2566);var i=n(253);Object.defineProperty(t,"rA",{enumerable:!0,get:function(){return i.getErrorCausalChain}})},545:(e,t,n)=>{"use strict";n.d(t,{mg:()=>J,vd:()=>G});var r=n(6540),a=n(5556),o=n.n(a),i=n(115),l=n.n(i),s=n(311),u=n.n(s),c=n(2833),d=n.n(c);function f(){return f=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},f.apply(this,arguments)}function p(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,m(e,t)}function m(e,t){return m=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e},m(e,t)}function h(e,t){if(null==e)return{};var n,r,a={},o=Object.keys(e);for(r=0;r<o.length;r++)t.indexOf(n=o[r])>=0||(a[n]=e[n]);return a}var g={BASE:"base",BODY:"body",HEAD:"head",HTML:"html",LINK:"link",META:"meta",NOSCRIPT:"noscript",SCRIPT:"script",STYLE:"style",TITLE:"title",FRAGMENT:"Symbol(react.fragment)"},y={rel:["amphtml","canonical","alternate"]},b={type:["application/ld+json"]},v={charset:"",name:["robots","description"],property:["og:type","og:title","og:url","og:image","og:image:alt","og:description","twitter:url","twitter:title","twitter:description","twitter:image","twitter:image:alt","twitter:card","twitter:site"]},w=Object.keys(g).map((function(e){return g[e]})),k={accesskey:"accessKey",charset:"charSet",class:"className",contenteditable:"contentEditable",contextmenu:"contextMenu","http-equiv":"httpEquiv",itemprop:"itemProp",tabindex:"tabIndex"},S=Object.keys(k).reduce((function(e,t){return e[k[t]]=t,e}),{}),x=function(e,t){for(var n=e.length-1;n>=0;n-=1){var r=e[n];if(Object.prototype.hasOwnProperty.call(r,t))return r[t]}return null},_=function(e){var t=x(e,g.TITLE),n=x(e,"titleTemplate");if(Array.isArray(t)&&(t=t.join("")),n&&t)return n.replace(/%s/g,(function(){return t}));var r=x(e,"defaultTitle");return t||r||void 0},E=function(e){return x(e,"onChangeClientState")||function(){}},C=function(e,t){return t.filter((function(t){return void 0!==t[e]})).map((function(t){return t[e]})).reduce((function(e,t){return f({},e,t)}),{})},A=function(e,t){return t.filter((function(e){return void 0!==e[g.BASE]})).map((function(e){return e[g.BASE]})).reverse().reduce((function(t,n){if(!t.length)for(var r=Object.keys(n),a=0;a<r.length;a+=1){var o=r[a].toLowerCase();if(-1!==e.indexOf(o)&&n[o])return t.concat(n)}return t}),[])},T=function(e,t,n){var r={};return n.filter((function(t){return!!Array.isArray(t[e])||(void 0!==t[e]&&console&&"function"==typeof console.warn&&console.warn("Helmet: "+e+' should be of type "Array". Instead found type "'+typeof t[e]+'"'),!1)})).map((function(t){return t[e]})).reverse().reduce((function(e,n){var a={};n.filter((function(e){for(var n,o=Object.keys(e),i=0;i<o.length;i+=1){var l=o[i],s=l.toLowerCase();-1===t.indexOf(s)||"rel"===n&&"canonical"===e[n].toLowerCase()||"rel"===s&&"stylesheet"===e[s].toLowerCase()||(n=s),-1===t.indexOf(l)||"innerHTML"!==l&&"cssText"!==l&&"itemprop"!==l||(n=l)}if(!n||!e[n])return!1;var u=e[n].toLowerCase();return r[n]||(r[n]={}),a[n]||(a[n]={}),!r[n][u]&&(a[n][u]=!0,!0)})).reverse().forEach((function(t){return e.push(t)}));for(var o=Object.keys(a),i=0;i<o.length;i+=1){var l=o[i],s=f({},r[l],a[l]);r[l]=s}return e}),[]).reverse()},L=function(e,t){if(Array.isArray(e)&&e.length)for(var n=0;n<e.length;n+=1)if(e[n][t])return!0;return!1},j=function(e){return Array.isArray(e)?e.join(""):e},P=function(e,t){return Array.isArray(e)?e.reduce((function(e,n){return function(e,t){for(var n=Object.keys(e),r=0;r<n.length;r+=1)if(t[n[r]]&&t[n[r]].includes(e[n[r]]))return!0;return!1}(n,t)?e.priority.push(n):e.default.push(n),e}),{priority:[],default:[]}):{default:e}},N=function(e,t){var n;return f({},e,((n={})[t]=void 0,n))},O=[g.NOSCRIPT,g.SCRIPT,g.STYLE],R=function(e,t){return void 0===t&&(t=!0),!1===t?String(e):String(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")},M=function(e){return Object.keys(e).reduce((function(t,n){var r=void 0!==e[n]?n+'="'+e[n]+'"':""+n;return t?t+" "+r:r}),"")},D=function(e,t){return void 0===t&&(t={}),Object.keys(e).reduce((function(t,n){return t[k[n]||n]=e[n],t}),t)},F=function(e,t){return t.map((function(t,n){var a,o=((a={key:n})["data-rh"]=!0,a);return Object.keys(t).forEach((function(e){var n=k[e]||e;"innerHTML"===n||"cssText"===n?o.dangerouslySetInnerHTML={__html:t.innerHTML||t.cssText}:o[n]=t[e]})),r.createElement(e,o)}))},I=function(e,t,n){switch(e){case g.TITLE:return{toComponent:function(){return n=t.titleAttributes,(a={key:e=t.title})["data-rh"]=!0,o=D(n,a),[r.createElement(g.TITLE,o,e)];var e,n,a,o},toString:function(){return function(e,t,n,r){var a=M(n),o=j(t);return a?"<"+e+' data-rh="true" '+a+">"+R(o,r)+"</"+e+">":"<"+e+' data-rh="true">'+R(o,r)+"</"+e+">"}(e,t.title,t.titleAttributes,n)}};case"bodyAttributes":case"htmlAttributes":return{toComponent:function(){return D(t)},toString:function(){return M(t)}};default:return{toComponent:function(){return F(e,t)},toString:function(){return function(e,t,n){return t.reduce((function(t,r){var a=Object.keys(r).filter((function(e){return!("innerHTML"===e||"cssText"===e)})).reduce((function(e,t){var a=void 0===r[t]?t:t+'="'+R(r[t],n)+'"';return e?e+" "+a:a}),""),o=r.innerHTML||r.cssText||"",i=-1===O.indexOf(e);return t+"<"+e+' data-rh="true" '+a+(i?"/>":">"+o+"</"+e+">")}),"")}(e,t,n)}}}},B=function(e){var t=e.baseTag,n=e.bodyAttributes,r=e.encode,a=e.htmlAttributes,o=e.noscriptTags,i=e.styleTags,l=e.title,s=void 0===l?"":l,u=e.titleAttributes,c=e.linkTags,d=e.metaTags,f=e.scriptTags,p={toComponent:function(){},toString:function(){return""}};if(e.prioritizeSeoTags){var m=function(e){var t=e.linkTags,n=e.scriptTags,r=e.encode,a=P(e.metaTags,v),o=P(t,y),i=P(n,b);return{priorityMethods:{toComponent:function(){return[].concat(F(g.META,a.priority),F(g.LINK,o.priority),F(g.SCRIPT,i.priority))},toString:function(){return I(g.META,a.priority,r)+" "+I(g.LINK,o.priority,r)+" "+I(g.SCRIPT,i.priority,r)}},metaTags:a.default,linkTags:o.default,scriptTags:i.default}}(e);p=m.priorityMethods,c=m.linkTags,d=m.metaTags,f=m.scriptTags}return{priority:p,base:I(g.BASE,t,r),bodyAttributes:I("bodyAttributes",n,r),htmlAttributes:I("htmlAttributes",a,r),link:I(g.LINK,c,r),meta:I(g.META,d,r),noscript:I(g.NOSCRIPT,o,r),script:I(g.SCRIPT,f,r),style:I(g.STYLE,i,r),title:I(g.TITLE,{title:s,titleAttributes:u},r)}},z=[],$=function(e,t){var n=this;void 0===t&&(t="undefined"!=typeof document),this.instances=[],this.value={setHelmet:function(e){n.context.helmet=e},helmetInstances:{get:function(){return n.canUseDOM?z:n.instances},add:function(e){(n.canUseDOM?z:n.instances).push(e)},remove:function(e){var t=(n.canUseDOM?z:n.instances).indexOf(e);(n.canUseDOM?z:n.instances).splice(t,1)}}},this.context=e,this.canUseDOM=t,t||(e.helmet=B({baseTag:[],bodyAttributes:{},encodeSpecialCharacters:!0,htmlAttributes:{},linkTags:[],metaTags:[],noscriptTags:[],scriptTags:[],styleTags:[],title:"",titleAttributes:{}}))},U=r.createContext({}),q=o().shape({setHelmet:o().func,helmetInstances:o().shape({get:o().func,add:o().func,remove:o().func})}),H="undefined"!=typeof document,G=function(e){function t(n){var r;return(r=e.call(this,n)||this).helmetData=new $(r.props.context,t.canUseDOM),r}return p(t,e),t.prototype.render=function(){return r.createElement(U.Provider,{value:this.helmetData.value},this.props.children)},t}(r.Component);G.canUseDOM=H,G.propTypes={context:o().shape({helmet:o().shape()}),children:o().node.isRequired},G.defaultProps={context:{}},G.displayName="HelmetProvider";var V=function(e,t){var n,r=document.head||document.querySelector(g.HEAD),a=r.querySelectorAll(e+"[data-rh]"),o=[].slice.call(a),i=[];return t&&t.length&&t.forEach((function(t){var r=document.createElement(e);for(var a in t)Object.prototype.hasOwnProperty.call(t,a)&&("innerHTML"===a?r.innerHTML=t.innerHTML:"cssText"===a?r.styleSheet?r.styleSheet.cssText=t.cssText:r.appendChild(document.createTextNode(t.cssText)):r.setAttribute(a,void 0===t[a]?"":t[a]));r.setAttribute("data-rh","true"),o.some((function(e,t){return n=t,r.isEqualNode(e)}))?o.splice(n,1):i.push(r)})),o.forEach((function(e){return e.parentNode.removeChild(e)})),i.forEach((function(e){return r.appendChild(e)})),{oldTags:o,newTags:i}},W=function(e,t){var n=document.getElementsByTagName(e)[0];if(n){for(var r=n.getAttribute("data-rh"),a=r?r.split(","):[],o=[].concat(a),i=Object.keys(t),l=0;l<i.length;l+=1){var s=i[l],u=t[s]||"";n.getAttribute(s)!==u&&n.setAttribute(s,u),-1===a.indexOf(s)&&a.push(s);var c=o.indexOf(s);-1!==c&&o.splice(c,1)}for(var d=o.length-1;d>=0;d-=1)n.removeAttribute(o[d]);a.length===o.length?n.removeAttribute("data-rh"):n.getAttribute("data-rh")!==i.join(",")&&n.setAttribute("data-rh",i.join(","))}},Q=function(e,t){var n=e.baseTag,r=e.htmlAttributes,a=e.linkTags,o=e.metaTags,i=e.noscriptTags,l=e.onChangeClientState,s=e.scriptTags,u=e.styleTags,c=e.title,d=e.titleAttributes;W(g.BODY,e.bodyAttributes),W(g.HTML,r),function(e,t){void 0!==e&&document.title!==e&&(document.title=j(e)),W(g.TITLE,t)}(c,d);var f={baseTag:V(g.BASE,n),linkTags:V(g.LINK,a),metaTags:V(g.META,o),noscriptTags:V(g.NOSCRIPT,i),scriptTags:V(g.SCRIPT,s),styleTags:V(g.STYLE,u)},p={},m={};Object.keys(f).forEach((function(e){var t=f[e],n=t.newTags,r=t.oldTags;n.length&&(p[e]=n),r.length&&(m[e]=f[e].oldTags)})),t&&t(),l(e,p,m)},K=null,Y=function(e){function t(){for(var t,n=arguments.length,r=new Array(n),a=0;a<n;a++)r[a]=arguments[a];return(t=e.call.apply(e,[this].concat(r))||this).rendered=!1,t}p(t,e);var n=t.prototype;return n.shouldComponentUpdate=function(e){return!d()(e,this.props)},n.componentDidUpdate=function(){this.emitChange()},n.componentWillUnmount=function(){this.props.context.helmetInstances.remove(this),this.emitChange()},n.emitChange=function(){var e,t,n=this.props.context,r=n.setHelmet,a=null,o=(e=n.helmetInstances.get().map((function(e){var t=f({},e.props);return delete t.context,t})),{baseTag:A(["href"],e),bodyAttributes:C("bodyAttributes",e),defer:x(e,"defer"),encode:x(e,"encodeSpecialCharacters"),htmlAttributes:C("htmlAttributes",e),linkTags:T(g.LINK,["rel","href"],e),metaTags:T(g.META,["name","charset","http-equiv","property","itemprop"],e),noscriptTags:T(g.NOSCRIPT,["innerHTML"],e),onChangeClientState:E(e),scriptTags:T(g.SCRIPT,["src","innerHTML"],e),styleTags:T(g.STYLE,["cssText"],e),title:_(e),titleAttributes:C("titleAttributes",e),prioritizeSeoTags:L(e,"prioritizeSeoTags")});G.canUseDOM?(t=o,K&&cancelAnimationFrame(K),t.defer?K=requestAnimationFrame((function(){Q(t,(function(){K=null}))})):(Q(t),K=null)):B&&(a=B(o)),r(a)},n.init=function(){this.rendered||(this.rendered=!0,this.props.context.helmetInstances.add(this),this.emitChange())},n.render=function(){return this.init(),null},t}(r.Component);Y.propTypes={context:q.isRequired},Y.displayName="HelmetDispatcher";var X=["children"],Z=["children"],J=function(e){function t(){return e.apply(this,arguments)||this}p(t,e);var n=t.prototype;return n.shouldComponentUpdate=function(e){return!l()(N(this.props,"helmetData"),N(e,"helmetData"))},n.mapNestedChildrenToProps=function(e,t){if(!t)return null;switch(e.type){case g.SCRIPT:case g.NOSCRIPT:return{innerHTML:t};case g.STYLE:return{cssText:t};default:throw new Error("<"+e.type+" /> elements are self-closing and can not contain children. Refer to our API for more information.")}},n.flattenArrayTypeChildren=function(e){var t,n=e.child,r=e.arrayTypeChildren;return f({},r,((t={})[n.type]=[].concat(r[n.type]||[],[f({},e.newChildProps,this.mapNestedChildrenToProps(n,e.nestedChildren))]),t))},n.mapObjectTypeChildren=function(e){var t,n,r=e.child,a=e.newProps,o=e.newChildProps,i=e.nestedChildren;switch(r.type){case g.TITLE:return f({},a,((t={})[r.type]=i,t.titleAttributes=f({},o),t));case g.BODY:return f({},a,{bodyAttributes:f({},o)});case g.HTML:return f({},a,{htmlAttributes:f({},o)});default:return f({},a,((n={})[r.type]=f({},o),n))}},n.mapArrayTypeChildrenToProps=function(e,t){var n=f({},t);return Object.keys(e).forEach((function(t){var r;n=f({},n,((r={})[t]=e[t],r))})),n},n.warnOnInvalidChildren=function(e,t){return u()(w.some((function(t){return e.type===t})),"function"==typeof e.type?"You may be attempting to nest <Helmet> components within each other, which is not allowed. Refer to our API for more information.":"Only elements types "+w.join(", ")+" are allowed. Helmet does not support rendering <"+e.type+"> elements. Refer to our API for more information."),u()(!t||"string"==typeof t||Array.isArray(t)&&!t.some((function(e){return"string"!=typeof e})),"Helmet expects a string as a child of <"+e.type+">. Did you forget to wrap your children in braces? ( <"+e.type+">{``}</"+e.type+"> ) Refer to our API for more information."),!0},n.mapChildrenToProps=function(e,t){var n=this,a={};return r.Children.forEach(e,(function(e){if(e&&e.props){var r=e.props,o=r.children,i=h(r,X),l=Object.keys(i).reduce((function(e,t){return e[S[t]||t]=i[t],e}),{}),s=e.type;switch("symbol"==typeof s?s=s.toString():n.warnOnInvalidChildren(e,o),s){case g.FRAGMENT:t=n.mapChildrenToProps(o,t);break;case g.LINK:case g.META:case g.NOSCRIPT:case g.SCRIPT:case g.STYLE:a=n.flattenArrayTypeChildren({child:e,arrayTypeChildren:a,newChildProps:l,nestedChildren:o});break;default:t=n.mapObjectTypeChildren({child:e,newProps:t,newChildProps:l,nestedChildren:o})}}})),this.mapArrayTypeChildrenToProps(a,t)},n.render=function(){var e=this.props,t=e.children,n=h(e,Z),a=f({},n),o=n.helmetData;return t&&(a=this.mapChildrenToProps(t,a)),!o||o instanceof $||(o=new $(o.context,o.instances)),o?r.createElement(Y,f({},a,{context:o.value,helmetData:void 0})):r.createElement(U.Consumer,null,(function(e){return r.createElement(Y,f({},a,{context:e}))}))},t}(r.Component);J.propTypes={base:o().object,bodyAttributes:o().object,children:o().oneOfType([o().arrayOf(o().node),o().node]),defaultTitle:o().string,defer:o().bool,encodeSpecialCharacters:o().bool,htmlAttributes:o().object,link:o().arrayOf(o().object),meta:o().arrayOf(o().object),noscript:o().arrayOf(o().object),onChangeClientState:o().func,script:o().arrayOf(o().object),style:o().arrayOf(o().object),title:o().string,titleAttributes:o().object,titleTemplate:o().string,prioritizeSeoTags:o().bool,helmetData:o().object},J.defaultProps={defer:!0,encodeSpecialCharacters:!0,prioritizeSeoTags:!1},J.displayName="Helmet"},609:(e,t,n)=>{"use strict";n.d(t,{V:()=>s,t:()=>u});var r=n(6540),a=n(9532),o=n(4848);const i=Symbol("EmptyContext"),l=r.createContext(i);function s({children:e,name:t,items:n}){const a=(0,r.useMemo)((()=>t&&n?{name:t,items:n}:null),[t,n]);return(0,o.jsx)(l.Provider,{value:a,children:e})}function u(){const e=(0,r.useContext)(l);if(e===i)throw new a.dV("DocsSidebarProvider");return e}},679:(e,t,n)=>{"use strict";n.d(t,{Wf:()=>u});n(6540);const r=JSON.parse('{"N":"localStorage","M":""}'),a=r.N;function o({key:e,oldValue:t,newValue:n,storage:r}){if(t===n)return;const a=document.createEvent("StorageEvent");a.initStorageEvent("storage",!1,!1,e,t,n,window.location.href,r),window.dispatchEvent(a)}function i(e=a){if("undefined"==typeof window)throw new Error("Browser storage is not available on Node.js/Docusaurus SSR process.");if("none"===e)return null;try{return window[e]}catch(n){return t=n,l||(console.warn("Docusaurus browser storage is not available.\nPossible reasons: running Docusaurus in an iframe, in an incognito browser session, or using too strict browser privacy settings.",t),l=!0),null}var t}let l=!1;const s={get:()=>null,set:()=>{},del:()=>{},listen:()=>()=>{}};function u(e,t){const n=`${e}${r.M}`;if("undefined"==typeof window)return function(e){function t(){throw new Error(`Illegal storage API usage for storage key "${e}".\nDocusaurus storage APIs are not supposed to be called on the server-rendering process.\nPlease only call storage APIs in effects and event handlers.`)}return{get:t,set:t,del:t,listen:t}}(n);const a=i(t?.persistence);return null===a?s:{get:()=>{try{return a.getItem(n)}catch(e){return console.error(`Docusaurus storage error, can't get key=${n}`,e),null}},set:e=>{try{const t=a.getItem(n);a.setItem(n,e),o({key:n,oldValue:t,newValue:e,storage:a})}catch(t){console.error(`Docusaurus storage error, can't set ${n}=${e}`,t)}},del:()=>{try{const e=a.getItem(n);a.removeItem(n),o({key:n,oldValue:e,newValue:null,storage:a})}catch(e){console.error(`Docusaurus storage error, can't delete key=${n}`,e)}},listen:e=>{try{const t=t=>{t.storageArea===a&&t.key===n&&e(t)};return window.addEventListener("storage",t),()=>window.removeEventListener("storage",t)}catch(t){return console.error(`Docusaurus storage error, can't listen for changes of key=${n}`,t),()=>{}}}}}},961:(e,t,n)=>{"use strict";!function e(){if("undefined"!=typeof __REACT_DEVTOOLS_GLOBAL_HOOK__&&"function"==typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE)try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(t){console.error(t)}}(),e.exports=n(6221)},1043:(e,t,n)=>{"use strict";n.r(t)},1107:(e,t,n)=>{"use strict";n.d(t,{A:()=>c});n(6540);var r=n(4164),a=n(1312),o=n(6342),i=n(8774),l=n(3427);const s={anchorWithStickyNavbar:"anchorWithStickyNavbar_LWe7",anchorWithHideOnScrollNavbar:"anchorWithHideOnScrollNavbar_WYt5"};var u=n(4848);function c({as:e,id:t,...n}){const c=(0,l.A)(),{navbar:{hideOnScroll:d}}=(0,o.p)();if("h1"===e||!t)return(0,u.jsx)(e,{...n,id:void 0});c.collectAnchor(t);const f=(0,a.T)({id:"theme.common.headingLinkTitle",message:"Direct link to {heading}",description:"Title for link to heading"},{heading:"string"==typeof n.children?n.children:t});return(0,u.jsxs)(e,{...n,className:(0,r.A)("anchor",d?s.anchorWithHideOnScrollNavbar:s.anchorWithStickyNavbar,n.className),id:t,children:[n.children,(0,u.jsx)(i.A,{className:"hash-link",to:`#${t}`,"aria-label":f,title:f,children:"\u200b"})]})}},1122:(e,t,n)=>{"use strict";n.d(t,{A:()=>c});var r=n(6540),a=n(4164),o=n(2303),i=n(5293);const l={themedComponent:"themedComponent_mlkZ","themedComponent--light":"themedComponent--light_NVdE","themedComponent--dark":"themedComponent--dark_xIcU"};var s=n(4848);function u({className:e,children:t}){const n=(0,o.A)(),{colorMode:u}=(0,i.G)();return(0,s.jsx)(s.Fragment,{children:(n?"dark"===u?["dark"]:["light"]:["light","dark"]).map((n=>{const o=t({theme:n,className:(0,a.A)(e,l.themedComponent,l[`themedComponent--${n}`])});return(0,s.jsx)(r.Fragment,{children:o},n)}))})}function c(e){const{sources:t,className:n,alt:r,...a}=e;return(0,s.jsx)(u,{className:n,children:({theme:e,className:n})=>(0,s.jsx)("img",{src:t[e],alt:r,className:n,...a})})}},1247:(e,t,n)=>{"use strict";var r=n(9982),a=n(6540),o=n(961);function i(e){var t="https://react.dev/errors/"+e;if(1<arguments.length){t+="?args[]="+encodeURIComponent(arguments[1]);for(var n=2;n<arguments.length;n++)t+="&args[]="+encodeURIComponent(arguments[n])}return"Minified React error #"+e+"; visit "+t+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}function l(e){return!(!e||1!==e.nodeType&&9!==e.nodeType&&11!==e.nodeType)}function s(e){var t=e,n=e;if(e.alternate)for(;t.return;)t=t.return;else{e=t;do{!!(4098&(t=e).flags)&&(n=t.return),e=t.return}while(e)}return 3===t.tag?n:null}function u(e){if(13===e.tag){var t=e.memoizedState;if(null===t&&(null!==(e=e.alternate)&&(t=e.memoizedState)),null!==t)return t.dehydrated}return null}function c(e){if(s(e)!==e)throw Error(i(188))}function d(e){var t=e.tag;if(5===t||26===t||27===t||6===t)return e;for(e=e.child;null!==e;){if(null!==(t=d(e)))return t;e=e.sibling}return null}var f=Object.assign,p=Symbol.for("react.element"),m=Symbol.for("react.transitional.element"),h=Symbol.for("react.portal"),g=Symbol.for("react.fragment"),y=Symbol.for("react.strict_mode"),b=Symbol.for("react.profiler"),v=Symbol.for("react.provider"),w=Symbol.for("react.consumer"),k=Symbol.for("react.context"),S=Symbol.for("react.forward_ref"),x=Symbol.for("react.suspense"),_=Symbol.for("react.suspense_list"),E=Symbol.for("react.memo"),C=Symbol.for("react.lazy");Symbol.for("react.scope");var A=Symbol.for("react.activity");Symbol.for("react.legacy_hidden"),Symbol.for("react.tracing_marker");var T=Symbol.for("react.memo_cache_sentinel");Symbol.for("react.view_transition");var L=Symbol.iterator;function j(e){return null===e||"object"!=typeof e?null:"function"==typeof(e=L&&e[L]||e["@@iterator"])?e:null}var P=Symbol.for("react.client.reference");function N(e){if(null==e)return null;if("function"==typeof e)return e.$$typeof===P?null:e.displayName||e.name||null;if("string"==typeof e)return e;switch(e){case g:return"Fragment";case b:return"Profiler";case y:return"StrictMode";case x:return"Suspense";case _:return"SuspenseList";case A:return"Activity"}if("object"==typeof e)switch(e.$$typeof){case h:return"Portal";case k:return(e.displayName||"Context")+".Provider";case w:return(e._context.displayName||"Context")+".Consumer";case S:var t=e.render;return(e=e.displayName)||(e=""!==(e=t.displayName||t.name||"")?"ForwardRef("+e+")":"ForwardRef"),e;case E:return null!==(t=e.displayName||null)?t:N(e.type)||"Memo";case C:t=e._payload,e=e._init;try{return N(e(t))}catch(n){}}return null}var O=Array.isArray,R=a.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,M=o.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,D={pending:!1,data:null,method:null,action:null},F=[],I=-1;function B(e){return{current:e}}function z(e){0>I||(e.current=F[I],F[I]=null,I--)}function $(e,t){I++,F[I]=e.current,e.current=t}var U=B(null),q=B(null),H=B(null),G=B(null);function V(e,t){switch($(H,t),$(q,e),$(U,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?ad(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)e=od(t=ad(t),e);else switch(e){case"svg":e=1;break;case"math":e=2;break;default:e=0}}z(U),$(U,e)}function W(){z(U),z(q),z(H)}function Q(e){null!==e.memoizedState&&$(G,e);var t=U.current,n=od(t,e.type);t!==n&&($(q,e),$(U,n))}function K(e){q.current===e&&(z(U),z(q)),G.current===e&&(z(G),Qd._currentValue=D)}var Y=Object.prototype.hasOwnProperty,X=r.unstable_scheduleCallback,Z=r.unstable_cancelCallback,J=r.unstable_shouldYield,ee=r.unstable_requestPaint,te=r.unstable_now,ne=r.unstable_getCurrentPriorityLevel,re=r.unstable_ImmediatePriority,ae=r.unstable_UserBlockingPriority,oe=r.unstable_NormalPriority,ie=r.unstable_LowPriority,le=r.unstable_IdlePriority,se=r.log,ue=r.unstable_setDisableYieldValue,ce=null,de=null;function fe(e){if("function"==typeof se&&ue(e),de&&"function"==typeof de.setStrictMode)try{de.setStrictMode(ce,e)}catch(t){}}var pe=Math.clz32?Math.clz32:function(e){return 0===(e>>>=0)?32:31-(me(e)/he|0)|0},me=Math.log,he=Math.LN2;var ge=256,ye=4194304;function be(e){var t=42&e;if(0!==t)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return 4194048&e;case 4194304:case 8388608:case 16777216:case 33554432:return 62914560&e;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function ve(e,t,n){var r=e.pendingLanes;if(0===r)return 0;var a=0,o=e.suspendedLanes,i=e.pingedLanes;e=e.warmLanes;var l=134217727&r;return 0!==l?0!==(r=l&~o)?a=be(r):0!==(i&=l)?a=be(i):n||0!==(n=l&~e)&&(a=be(n)):0!==(l=r&~o)?a=be(l):0!==i?a=be(i):n||0!==(n=r&~e)&&(a=be(n)),0===a?0:0!==t&&t!==a&&0===(t&o)&&((o=a&-a)>=(n=t&-t)||32===o&&4194048&n)?t:a}function we(e,t){return 0===(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)}function ke(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;default:return-1}}function Se(){var e=ge;return!(4194048&(ge<<=1))&&(ge=256),e}function xe(){var e=ye;return!(62914560&(ye<<=1))&&(ye=4194304),e}function _e(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function Ee(e,t){e.pendingLanes|=t,268435456!==t&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function Ce(e,t,n){e.pendingLanes|=t,e.suspendedLanes&=~t;var r=31-pe(t);e.entangledLanes|=t,e.entanglements[r]=1073741824|e.entanglements[r]|4194090&n}function Ae(e,t){var n=e.entangledLanes|=t;for(e=e.entanglements;n;){var r=31-pe(n),a=1<<r;a&t|e[r]&t&&(e[r]|=t),n&=~a}}function Te(e){switch(e){case 2:e=1;break;case 8:e=4;break;case 32:e=16;break;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:e=128;break;case 268435456:e=134217728;break;default:e=0}return e}function Le(e){return 2<(e&=-e)?8<e?134217727&e?32:268435456:8:2}function je(){var e=M.p;return 0!==e?e:void 0===(e=window.event)?32:cf(e.type)}var Pe=Math.random().toString(36).slice(2),Ne="__reactFiber$"+Pe,Oe="__reactProps$"+Pe,Re="__reactContainer$"+Pe,Me="__reactEvents$"+Pe,De="__reactListeners$"+Pe,Fe="__reactHandles$"+Pe,Ie="__reactResources$"+Pe,Be="__reactMarker$"+Pe;function ze(e){delete e[Ne],delete e[Oe],delete e[Me],delete e[De],delete e[Fe]}function $e(e){var t=e[Ne];if(t)return t;for(var n=e.parentNode;n;){if(t=n[Re]||n[Ne]){if(n=t.alternate,null!==t.child||null!==n&&null!==n.child)for(e=vd(e);null!==e;){if(n=e[Ne])return n;e=vd(e)}return t}n=(e=n).parentNode}return null}function Ue(e){if(e=e[Ne]||e[Re]){var t=e.tag;if(5===t||6===t||13===t||26===t||27===t||3===t)return e}return null}function qe(e){var t=e.tag;if(5===t||26===t||27===t||6===t)return e.stateNode;throw Error(i(33))}function He(e){var t=e[Ie];return t||(t=e[Ie]={hoistableStyles:new Map,hoistableScripts:new Map}),t}function Ge(e){e[Be]=!0}var Ve=new Set,We={};function Qe(e,t){Ke(e,t),Ke(e+"Capture",t)}function Ke(e,t){for(We[e]=t,e=0;e<t.length;e++)Ve.add(t[e])}var Ye,Xe,Ze=RegExp("^[:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD][:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD\\-.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040]*$"),Je={},et={};function tt(e,t,n){if(a=t,Y.call(et,a)||!Y.call(Je,a)&&(Ze.test(a)?et[a]=!0:(Je[a]=!0,0)))if(null===n)e.removeAttribute(t);else{switch(typeof n){case"undefined":case"function":case"symbol":return void e.removeAttribute(t);case"boolean":var r=t.toLowerCase().slice(0,5);if("data-"!==r&&"aria-"!==r)return void e.removeAttribute(t)}e.setAttribute(t,""+n)}var a}function nt(e,t,n){if(null===n)e.removeAttribute(t);else{switch(typeof n){case"undefined":case"function":case"symbol":case"boolean":return void e.removeAttribute(t)}e.setAttribute(t,""+n)}}function rt(e,t,n,r){if(null===r)e.removeAttribute(n);else{switch(typeof r){case"undefined":case"function":case"symbol":case"boolean":return void e.removeAttribute(n)}e.setAttributeNS(t,n,""+r)}}function at(e){if(void 0===Ye)try{throw Error()}catch(n){var t=n.stack.trim().match(/\n( *(at )?)/);Ye=t&&t[1]||"",Xe=-1<n.stack.indexOf("\n at")?" (<anonymous>)":-1<n.stack.indexOf("@")?"@unknown:0:0":""}return"\n"+Ye+e+Xe}var ot=!1;function it(e,t){if(!e||ot)return"";ot=!0;var n=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var r={DetermineComponentFrameRoot:function(){try{if(t){var n=function(){throw Error()};if(Object.defineProperty(n.prototype,"props",{set:function(){throw Error()}}),"object"==typeof Reflect&&Reflect.construct){try{Reflect.construct(n,[])}catch(a){var r=a}Reflect.construct(e,[],n)}else{try{n.call()}catch(o){r=o}e.call(n.prototype)}}else{try{throw Error()}catch(i){r=i}(n=e())&&"function"==typeof n.catch&&n.catch((function(){}))}}catch(l){if(l&&r&&"string"==typeof l.stack)return[l.stack,r.stack]}return[null,null]}};r.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var a=Object.getOwnPropertyDescriptor(r.DetermineComponentFrameRoot,"name");a&&a.configurable&&Object.defineProperty(r.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var o=r.DetermineComponentFrameRoot(),i=o[0],l=o[1];if(i&&l){var s=i.split("\n"),u=l.split("\n");for(a=r=0;r<s.length&&!s[r].includes("DetermineComponentFrameRoot");)r++;for(;a<u.length&&!u[a].includes("DetermineComponentFrameRoot");)a++;if(r===s.length||a===u.length)for(r=s.length-1,a=u.length-1;1<=r&&0<=a&&s[r]!==u[a];)a--;for(;1<=r&&0<=a;r--,a--)if(s[r]!==u[a]){if(1!==r||1!==a)do{if(r--,0>--a||s[r]!==u[a]){var c="\n"+s[r].replace(" at new "," at ");return e.displayName&&c.includes("<anonymous>")&&(c=c.replace("<anonymous>",e.displayName)),c}}while(1<=r&&0<=a);break}}}finally{ot=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:"")?at(n):""}function lt(e){switch(e.tag){case 26:case 27:case 5:return at(e.type);case 16:return at("Lazy");case 13:return at("Suspense");case 19:return at("SuspenseList");case 0:case 15:return it(e.type,!1);case 11:return it(e.type.render,!1);case 1:return it(e.type,!0);case 31:return at("Activity");default:return""}}function st(e){try{var t="";do{t+=lt(e),e=e.return}while(e);return t}catch(n){return"\nError generating stack: "+n.message+"\n"+n.stack}}function ut(e){switch(typeof e){case"bigint":case"boolean":case"number":case"string":case"undefined":case"object":return e;default:return""}}function ct(e){var t=e.type;return(e=e.nodeName)&&"input"===e.toLowerCase()&&("checkbox"===t||"radio"===t)}function dt(e){e._valueTracker||(e._valueTracker=function(e){var t=ct(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&void 0!==n&&"function"==typeof n.get&&"function"==typeof n.set){var a=n.get,o=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return a.call(this)},set:function(e){r=""+e,o.call(this,e)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(e){r=""+e},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}(e))}function ft(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=ct(e)?e.checked?"true":"false":e.value),(e=r)!==n&&(t.setValue(e),!0)}function pt(e){if(void 0===(e=e||("undefined"!=typeof document?document:void 0)))return null;try{return e.activeElement||e.body}catch(t){return e.body}}var mt=/[\n"\\]/g;function ht(e){return e.replace(mt,(function(e){return"\\"+e.charCodeAt(0).toString(16)+" "}))}function gt(e,t,n,r,a,o,i,l){e.name="",null!=i&&"function"!=typeof i&&"symbol"!=typeof i&&"boolean"!=typeof i?e.type=i:e.removeAttribute("type"),null!=t?"number"===i?(0===t&&""===e.value||e.value!=t)&&(e.value=""+ut(t)):e.value!==""+ut(t)&&(e.value=""+ut(t)):"submit"!==i&&"reset"!==i||e.removeAttribute("value"),null!=t?bt(e,i,ut(t)):null!=n?bt(e,i,ut(n)):null!=r&&e.removeAttribute("value"),null==a&&null!=o&&(e.defaultChecked=!!o),null!=a&&(e.checked=a&&"function"!=typeof a&&"symbol"!=typeof a),null!=l&&"function"!=typeof l&&"symbol"!=typeof l&&"boolean"!=typeof l?e.name=""+ut(l):e.removeAttribute("name")}function yt(e,t,n,r,a,o,i,l){if(null!=o&&"function"!=typeof o&&"symbol"!=typeof o&&"boolean"!=typeof o&&(e.type=o),null!=t||null!=n){if(("submit"===o||"reset"===o)&&null==t)return;n=null!=n?""+ut(n):"",t=null!=t?""+ut(t):n,l||t===e.value||(e.value=t),e.defaultValue=t}r="function"!=typeof(r=null!=r?r:a)&&"symbol"!=typeof r&&!!r,e.checked=l?e.checked:!!r,e.defaultChecked=!!r,null!=i&&"function"!=typeof i&&"symbol"!=typeof i&&"boolean"!=typeof i&&(e.name=i)}function bt(e,t,n){"number"===t&&pt(e.ownerDocument)===e||e.defaultValue===""+n||(e.defaultValue=""+n)}function vt(e,t,n,r){if(e=e.options,t){t={};for(var a=0;a<n.length;a++)t["$"+n[a]]=!0;for(n=0;n<e.length;n++)a=t.hasOwnProperty("$"+e[n].value),e[n].selected!==a&&(e[n].selected=a),a&&r&&(e[n].defaultSelected=!0)}else{for(n=""+ut(n),t=null,a=0;a<e.length;a++){if(e[a].value===n)return e[a].selected=!0,void(r&&(e[a].defaultSelected=!0));null!==t||e[a].disabled||(t=e[a])}null!==t&&(t.selected=!0)}}function wt(e,t,n){null==t||((t=""+ut(t))!==e.value&&(e.value=t),null!=n)?e.defaultValue=null!=n?""+ut(n):"":e.defaultValue!==t&&(e.defaultValue=t)}function kt(e,t,n,r){if(null==t){if(null!=r){if(null!=n)throw Error(i(92));if(O(r)){if(1<r.length)throw Error(i(93));r=r[0]}n=r}null==n&&(n=""),t=n}n=ut(t),e.defaultValue=n,(r=e.textContent)===n&&""!==r&&null!==r&&(e.value=r)}function St(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&3===n.nodeType)return void(n.nodeValue=t)}e.textContent=t}var xt=new Set("animationIterationCount aspectRatio borderImageOutset borderImageSlice borderImageWidth boxFlex boxFlexGroup boxOrdinalGroup columnCount columns flex flexGrow flexPositive flexShrink flexNegative flexOrder gridArea gridRow gridRowEnd gridRowSpan gridRowStart gridColumn gridColumnEnd gridColumnSpan gridColumnStart fontWeight lineClamp lineHeight opacity order orphans scale tabSize widows zIndex zoom fillOpacity floodOpacity stopOpacity strokeDasharray strokeDashoffset strokeMiterlimit strokeOpacity strokeWidth MozAnimationIterationCount MozBoxFlex MozBoxFlexGroup MozLineClamp msAnimationIterationCount msFlex msZoom msFlexGrow msFlexNegative msFlexOrder msFlexPositive msFlexShrink msGridColumn msGridColumnSpan msGridRow msGridRowSpan WebkitAnimationIterationCount WebkitBoxFlex WebKitBoxFlexGroup WebkitBoxOrdinalGroup WebkitColumnCount WebkitColumns WebkitFlex WebkitFlexGrow WebkitFlexPositive WebkitFlexShrink WebkitLineClamp".split(" "));function _t(e,t,n){var r=0===t.indexOf("--");null==n||"boolean"==typeof n||""===n?r?e.setProperty(t,""):"float"===t?e.cssFloat="":e[t]="":r?e.setProperty(t,n):"number"!=typeof n||0===n||xt.has(t)?"float"===t?e.cssFloat=n:e[t]=(""+n).trim():e[t]=n+"px"}function Et(e,t,n){if(null!=t&&"object"!=typeof t)throw Error(i(62));if(e=e.style,null!=n){for(var r in n)!n.hasOwnProperty(r)||null!=t&&t.hasOwnProperty(r)||(0===r.indexOf("--")?e.setProperty(r,""):"float"===r?e.cssFloat="":e[r]="");for(var a in t)r=t[a],t.hasOwnProperty(a)&&n[a]!==r&&_t(e,a,r)}else for(var o in t)t.hasOwnProperty(o)&&_t(e,o,t[o])}function Ct(e){if(-1===e.indexOf("-"))return!1;switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var At=new Map([["acceptCharset","accept-charset"],["htmlFor","for"],["httpEquiv","http-equiv"],["crossOrigin","crossorigin"],["accentHeight","accent-height"],["alignmentBaseline","alignment-baseline"],["arabicForm","arabic-form"],["baselineShift","baseline-shift"],["capHeight","cap-height"],["clipPath","clip-path"],["clipRule","clip-rule"],["colorInterpolation","color-interpolation"],["colorInterpolationFilters","color-interpolation-filters"],["colorProfile","color-profile"],["colorRendering","color-rendering"],["dominantBaseline","dominant-baseline"],["enableBackground","enable-background"],["fillOpacity","fill-opacity"],["fillRule","fill-rule"],["floodColor","flood-color"],["floodOpacity","flood-opacity"],["fontFamily","font-family"],["fontSize","font-size"],["fontSizeAdjust","font-size-adjust"],["fontStretch","font-stretch"],["fontStyle","font-style"],["fontVariant","font-variant"],["fontWeight","font-weight"],["glyphName","glyph-name"],["glyphOrientationHorizontal","glyph-orientation-horizontal"],["glyphOrientationVertical","glyph-orientation-vertical"],["horizAdvX","horiz-adv-x"],["horizOriginX","horiz-origin-x"],["imageRendering","image-rendering"],["letterSpacing","letter-spacing"],["lightingColor","lighting-color"],["markerEnd","marker-end"],["markerMid","marker-mid"],["markerStart","marker-start"],["overlinePosition","overline-position"],["overlineThickness","overline-thickness"],["paintOrder","paint-order"],["panose-1","panose-1"],["pointerEvents","pointer-events"],["renderingIntent","rendering-intent"],["shapeRendering","shape-rendering"],["stopColor","stop-color"],["stopOpacity","stop-opacity"],["strikethroughPosition","strikethrough-position"],["strikethroughThickness","strikethrough-thickness"],["strokeDasharray","stroke-dasharray"],["strokeDashoffset","stroke-dashoffset"],["strokeLinecap","stroke-linecap"],["strokeLinejoin","stroke-linejoin"],["strokeMiterlimit","stroke-miterlimit"],["strokeOpacity","stroke-opacity"],["strokeWidth","stroke-width"],["textAnchor","text-anchor"],["textDecoration","text-decoration"],["textRendering","text-rendering"],["transformOrigin","transform-origin"],["underlinePosition","underline-position"],["underlineThickness","underline-thickness"],["unicodeBidi","unicode-bidi"],["unicodeRange","unicode-range"],["unitsPerEm","units-per-em"],["vAlphabetic","v-alphabetic"],["vHanging","v-hanging"],["vIdeographic","v-ideographic"],["vMathematical","v-mathematical"],["vectorEffect","vector-effect"],["vertAdvY","vert-adv-y"],["vertOriginX","vert-origin-x"],["vertOriginY","vert-origin-y"],["wordSpacing","word-spacing"],["writingMode","writing-mode"],["xmlnsXlink","xmlns:xlink"],["xHeight","x-height"]]),Tt=/^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*:/i;function Lt(e){return Tt.test(""+e)?"javascript:throw new Error('React has blocked a javascript: URL as a security precaution.')":e}var jt=null;function Pt(e){return(e=e.target||e.srcElement||window).correspondingUseElement&&(e=e.correspondingUseElement),3===e.nodeType?e.parentNode:e}var Nt=null,Ot=null;function Rt(e){var t=Ue(e);if(t&&(e=t.stateNode)){var n=e[Oe]||null;e:switch(e=t.stateNode,t.type){case"input":if(gt(e,n.value,n.defaultValue,n.defaultValue,n.checked,n.defaultChecked,n.type,n.name),t=n.name,"radio"===n.type&&null!=t){for(n=e;n.parentNode;)n=n.parentNode;for(n=n.querySelectorAll('input[name="'+ht(""+t)+'"][type="radio"]'),t=0;t<n.length;t++){var r=n[t];if(r!==e&&r.form===e.form){var a=r[Oe]||null;if(!a)throw Error(i(90));gt(r,a.value,a.defaultValue,a.defaultValue,a.checked,a.defaultChecked,a.type,a.name)}}for(t=0;t<n.length;t++)(r=n[t]).form===e.form&&ft(r)}break e;case"textarea":wt(e,n.value,n.defaultValue);break e;case"select":null!=(t=n.value)&&vt(e,!!n.multiple,t,!1)}}}var Mt=!1;function Dt(e,t,n){if(Mt)return e(t,n);Mt=!0;try{return e(t)}finally{if(Mt=!1,(null!==Nt||null!==Ot)&&($u(),Nt&&(t=Nt,e=Ot,Ot=Nt=null,Rt(t),e)))for(t=0;t<e.length;t++)Rt(e[t])}}function Ft(e,t){var n=e.stateNode;if(null===n)return null;var r=n[Oe]||null;if(null===r)return null;n=r[t];e:switch(t){case"onClick":case"onClickCapture":case"onDoubleClick":case"onDoubleClickCapture":case"onMouseDown":case"onMouseDownCapture":case"onMouseMove":case"onMouseMoveCapture":case"onMouseUp":case"onMouseUpCapture":case"onMouseEnter":(r=!r.disabled)||(r=!("button"===(e=e.type)||"input"===e||"select"===e||"textarea"===e)),e=!r;break e;default:e=!1}if(e)return null;if(n&&"function"!=typeof n)throw Error(i(231,t,typeof n));return n}var It=!("undefined"==typeof window||void 0===window.document||void 0===window.document.createElement),Bt=!1;if(It)try{var zt={};Object.defineProperty(zt,"passive",{get:function(){Bt=!0}}),window.addEventListener("test",zt,zt),window.removeEventListener("test",zt,zt)}catch(Rf){Bt=!1}var $t=null,Ut=null,qt=null;function Ht(){if(qt)return qt;var e,t,n=Ut,r=n.length,a="value"in $t?$t.value:$t.textContent,o=a.length;for(e=0;e<r&&n[e]===a[e];e++);var i=r-e;for(t=1;t<=i&&n[r-t]===a[o-t];t++);return qt=a.slice(e,1<t?1-t:void 0)}function Gt(e){var t=e.keyCode;return"charCode"in e?0===(e=e.charCode)&&13===t&&(e=13):e=t,10===e&&(e=13),32<=e||13===e?e:0}function Vt(){return!0}function Wt(){return!1}function Qt(e){function t(t,n,r,a,o){for(var i in this._reactName=t,this._targetInst=r,this.type=n,this.nativeEvent=a,this.target=o,this.currentTarget=null,e)e.hasOwnProperty(i)&&(t=e[i],this[i]=t?t(a):a[i]);return this.isDefaultPrevented=(null!=a.defaultPrevented?a.defaultPrevented:!1===a.returnValue)?Vt:Wt,this.isPropagationStopped=Wt,this}return f(t.prototype,{preventDefault:function(){this.defaultPrevented=!0;var e=this.nativeEvent;e&&(e.preventDefault?e.preventDefault():"unknown"!=typeof e.returnValue&&(e.returnValue=!1),this.isDefaultPrevented=Vt)},stopPropagation:function(){var e=this.nativeEvent;e&&(e.stopPropagation?e.stopPropagation():"unknown"!=typeof e.cancelBubble&&(e.cancelBubble=!0),this.isPropagationStopped=Vt)},persist:function(){},isPersistent:Vt}),t}var Kt,Yt,Xt,Zt={eventPhase:0,bubbles:0,cancelable:0,timeStamp:function(e){return e.timeStamp||Date.now()},defaultPrevented:0,isTrusted:0},Jt=Qt(Zt),en=f({},Zt,{view:0,detail:0}),tn=Qt(en),nn=f({},en,{screenX:0,screenY:0,clientX:0,clientY:0,pageX:0,pageY:0,ctrlKey:0,shiftKey:0,altKey:0,metaKey:0,getModifierState:mn,button:0,buttons:0,relatedTarget:function(e){return void 0===e.relatedTarget?e.fromElement===e.srcElement?e.toElement:e.fromElement:e.relatedTarget},movementX:function(e){return"movementX"in e?e.movementX:(e!==Xt&&(Xt&&"mousemove"===e.type?(Kt=e.screenX-Xt.screenX,Yt=e.screenY-Xt.screenY):Yt=Kt=0,Xt=e),Kt)},movementY:function(e){return"movementY"in e?e.movementY:Yt}}),rn=Qt(nn),an=Qt(f({},nn,{dataTransfer:0})),on=Qt(f({},en,{relatedTarget:0})),ln=Qt(f({},Zt,{animationName:0,elapsedTime:0,pseudoElement:0})),sn=Qt(f({},Zt,{clipboardData:function(e){return"clipboardData"in e?e.clipboardData:window.clipboardData}})),un=Qt(f({},Zt,{data:0})),cn={Esc:"Escape",Spacebar:" ",Left:"ArrowLeft",Up:"ArrowUp",Right:"ArrowRight",Down:"ArrowDown",Del:"Delete",Win:"OS",Menu:"ContextMenu",Apps:"ContextMenu",Scroll:"ScrollLock",MozPrintableKey:"Unidentified"},dn={8:"Backspace",9:"Tab",12:"Clear",13:"Enter",16:"Shift",17:"Control",18:"Alt",19:"Pause",20:"CapsLock",27:"Escape",32:" ",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",45:"Insert",46:"Delete",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"NumLock",145:"ScrollLock",224:"Meta"},fn={Alt:"altKey",Control:"ctrlKey",Meta:"metaKey",Shift:"shiftKey"};function pn(e){var t=this.nativeEvent;return t.getModifierState?t.getModifierState(e):!!(e=fn[e])&&!!t[e]}function mn(){return pn}var hn=Qt(f({},en,{key:function(e){if(e.key){var t=cn[e.key]||e.key;if("Unidentified"!==t)return t}return"keypress"===e.type?13===(e=Gt(e))?"Enter":String.fromCharCode(e):"keydown"===e.type||"keyup"===e.type?dn[e.keyCode]||"Unidentified":""},code:0,location:0,ctrlKey:0,shiftKey:0,altKey:0,metaKey:0,repeat:0,locale:0,getModifierState:mn,charCode:function(e){return"keypress"===e.type?Gt(e):0},keyCode:function(e){return"keydown"===e.type||"keyup"===e.type?e.keyCode:0},which:function(e){return"keypress"===e.type?Gt(e):"keydown"===e.type||"keyup"===e.type?e.keyCode:0}})),gn=Qt(f({},nn,{pointerId:0,width:0,height:0,pressure:0,tangentialPressure:0,tiltX:0,tiltY:0,twist:0,pointerType:0,isPrimary:0})),yn=Qt(f({},en,{touches:0,targetTouches:0,changedTouches:0,altKey:0,metaKey:0,ctrlKey:0,shiftKey:0,getModifierState:mn})),bn=Qt(f({},Zt,{propertyName:0,elapsedTime:0,pseudoElement:0})),vn=Qt(f({},nn,{deltaX:function(e){return"deltaX"in e?e.deltaX:"wheelDeltaX"in e?-e.wheelDeltaX:0},deltaY:function(e){return"deltaY"in e?e.deltaY:"wheelDeltaY"in e?-e.wheelDeltaY:"wheelDelta"in e?-e.wheelDelta:0},deltaZ:0,deltaMode:0})),wn=Qt(f({},Zt,{newState:0,oldState:0})),kn=[9,13,27,32],Sn=It&&"CompositionEvent"in window,xn=null;It&&"documentMode"in document&&(xn=document.documentMode);var _n=It&&"TextEvent"in window&&!xn,En=It&&(!Sn||xn&&8<xn&&11>=xn),Cn=String.fromCharCode(32),An=!1;function Tn(e,t){switch(e){case"keyup":return-1!==kn.indexOf(t.keyCode);case"keydown":return 229!==t.keyCode;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Ln(e){return"object"==typeof(e=e.detail)&&"data"in e?e.data:null}var jn=!1;var Pn={color:!0,date:!0,datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0};function Nn(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return"input"===t?!!Pn[e.type]:"textarea"===t}function On(e,t,n,r){Nt?Ot?Ot.push(r):Ot=[r]:Nt=r,0<(t=Hc(t,"onChange")).length&&(n=new Jt("onChange","change",null,n,r),e.push({event:n,listeners:t}))}var Rn=null,Mn=null;function Dn(e){Dc(e,0)}function Fn(e){if(ft(qe(e)))return e}function In(e,t){if("change"===e)return t}var Bn=!1;if(It){var zn;if(It){var $n="oninput"in document;if(!$n){var Un=document.createElement("div");Un.setAttribute("oninput","return;"),$n="function"==typeof Un.oninput}zn=$n}else zn=!1;Bn=zn&&(!document.documentMode||9<document.documentMode)}function qn(){Rn&&(Rn.detachEvent("onpropertychange",Hn),Mn=Rn=null)}function Hn(e){if("value"===e.propertyName&&Fn(Mn)){var t=[];On(t,Mn,e,Pt(e)),Dt(Dn,t)}}function Gn(e,t,n){"focusin"===e?(qn(),Mn=n,(Rn=t).attachEvent("onpropertychange",Hn)):"focusout"===e&&qn()}function Vn(e){if("selectionchange"===e||"keyup"===e||"keydown"===e)return Fn(Mn)}function Wn(e,t){if("click"===e)return Fn(t)}function Qn(e,t){if("input"===e||"change"===e)return Fn(t)}var Kn="function"==typeof Object.is?Object.is:function(e,t){return e===t&&(0!==e||1/e==1/t)||e!=e&&t!=t};function Yn(e,t){if(Kn(e,t))return!0;if("object"!=typeof e||null===e||"object"!=typeof t||null===t)return!1;var n=Object.keys(e),r=Object.keys(t);if(n.length!==r.length)return!1;for(r=0;r<n.length;r++){var a=n[r];if(!Y.call(t,a)||!Kn(e[a],t[a]))return!1}return!0}function Xn(e){for(;e&&e.firstChild;)e=e.firstChild;return e}function Zn(e,t){var n,r=Xn(e);for(e=0;r;){if(3===r.nodeType){if(n=e+r.textContent.length,e<=t&&n>=t)return{node:r,offset:t-e};e=n}e:{for(;r;){if(r.nextSibling){r=r.nextSibling;break e}r=r.parentNode}r=void 0}r=Xn(r)}}function Jn(e,t){return!(!e||!t)&&(e===t||(!e||3!==e.nodeType)&&(t&&3===t.nodeType?Jn(e,t.parentNode):"contains"in e?e.contains(t):!!e.compareDocumentPosition&&!!(16&e.compareDocumentPosition(t))))}function er(e){for(var t=pt((e=null!=e&&null!=e.ownerDocument&&null!=e.ownerDocument.defaultView?e.ownerDocument.defaultView:window).document);t instanceof e.HTMLIFrameElement;){try{var n="string"==typeof t.contentWindow.location.href}catch(r){n=!1}if(!n)break;t=pt((e=t.contentWindow).document)}return t}function tr(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&("input"===t&&("text"===e.type||"search"===e.type||"tel"===e.type||"url"===e.type||"password"===e.type)||"textarea"===t||"true"===e.contentEditable)}var nr=It&&"documentMode"in document&&11>=document.documentMode,rr=null,ar=null,or=null,ir=!1;function lr(e,t,n){var r=n.window===n?n.document:9===n.nodeType?n:n.ownerDocument;ir||null==rr||rr!==pt(r)||("selectionStart"in(r=rr)&&tr(r)?r={start:r.selectionStart,end:r.selectionEnd}:r={anchorNode:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection()).anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset},or&&Yn(or,r)||(or=r,0<(r=Hc(ar,"onSelect")).length&&(t=new Jt("onSelect","select",null,t,n),e.push({event:t,listeners:r}),t.target=rr)))}function sr(e,t){var n={};return n[e.toLowerCase()]=t.toLowerCase(),n["Webkit"+e]="webkit"+t,n["Moz"+e]="moz"+t,n}var ur={animationend:sr("Animation","AnimationEnd"),animationiteration:sr("Animation","AnimationIteration"),animationstart:sr("Animation","AnimationStart"),transitionrun:sr("Transition","TransitionRun"),transitionstart:sr("Transition","TransitionStart"),transitioncancel:sr("Transition","TransitionCancel"),transitionend:sr("Transition","TransitionEnd")},cr={},dr={};function fr(e){if(cr[e])return cr[e];if(!ur[e])return e;var t,n=ur[e];for(t in n)if(n.hasOwnProperty(t)&&t in dr)return cr[e]=n[t];return e}It&&(dr=document.createElement("div").style,"AnimationEvent"in window||(delete ur.animationend.animation,delete ur.animationiteration.animation,delete ur.animationstart.animation),"TransitionEvent"in window||delete ur.transitionend.transition);var pr=fr("animationend"),mr=fr("animationiteration"),hr=fr("animationstart"),gr=fr("transitionrun"),yr=fr("transitionstart"),br=fr("transitioncancel"),vr=fr("transitionend"),wr=new Map,kr="abort auxClick beforeToggle cancel canPlay canPlayThrough click close contextMenu copy cut drag dragEnd dragEnter dragExit dragLeave dragOver dragStart drop durationChange emptied encrypted ended error gotPointerCapture input invalid keyDown keyPress keyUp load loadedData loadedMetadata loadStart lostPointerCapture mouseDown mouseMove mouseOut mouseOver mouseUp paste pause play playing pointerCancel pointerDown pointerMove pointerOut pointerOver pointerUp progress rateChange reset resize seeked seeking stalled submit suspend timeUpdate touchCancel touchEnd touchStart volumeChange scroll toggle touchMove waiting wheel".split(" ");function Sr(e,t){wr.set(e,t),Qe(t,[e])}kr.push("scrollEnd");var xr=new WeakMap;function _r(e,t){if("object"==typeof e&&null!==e){var n=xr.get(e);return void 0!==n?n:(t={value:e,source:t,stack:st(t)},xr.set(e,t),t)}return{value:e,source:t,stack:st(t)}}var Er=[],Cr=0,Ar=0;function Tr(){for(var e=Cr,t=Ar=Cr=0;t<e;){var n=Er[t];Er[t++]=null;var r=Er[t];Er[t++]=null;var a=Er[t];Er[t++]=null;var o=Er[t];if(Er[t++]=null,null!==r&&null!==a){var i=r.pending;null===i?a.next=a:(a.next=i.next,i.next=a),r.pending=a}0!==o&&Nr(n,a,o)}}function Lr(e,t,n,r){Er[Cr++]=e,Er[Cr++]=t,Er[Cr++]=n,Er[Cr++]=r,Ar|=r,e.lanes|=r,null!==(e=e.alternate)&&(e.lanes|=r)}function jr(e,t,n,r){return Lr(e,t,n,r),Or(e)}function Pr(e,t){return Lr(e,null,null,t),Or(e)}function Nr(e,t,n){e.lanes|=n;var r=e.alternate;null!==r&&(r.lanes|=n);for(var a=!1,o=e.return;null!==o;)o.childLanes|=n,null!==(r=o.alternate)&&(r.childLanes|=n),22===o.tag&&(null===(e=o.stateNode)||1&e._visibility||(a=!0)),e=o,o=o.return;return 3===e.tag?(o=e.stateNode,a&&null!==t&&(a=31-pe(n),null===(r=(e=o.hiddenUpdates)[a])?e[a]=[t]:r.push(t),t.lane=536870912|n),o):null}function Or(e){if(50<Nu)throw Nu=0,Ou=null,Error(i(185));for(var t=e.return;null!==t;)t=(e=t).return;return 3===e.tag?e.stateNode:null}var Rr={};function Mr(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.refCleanup=this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function Dr(e,t,n,r){return new Mr(e,t,n,r)}function Fr(e){return!(!(e=e.prototype)||!e.isReactComponent)}function Ir(e,t){var n=e.alternate;return null===n?((n=Dr(e.tag,t,e.key,e.mode)).elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=65011712&e.flags,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=null===t?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n.refCleanup=e.refCleanup,n}function Br(e,t){e.flags&=65011714;var n=e.alternate;return null===n?(e.childLanes=0,e.lanes=t,e.child=null,e.subtreeFlags=0,e.memoizedProps=null,e.memoizedState=null,e.updateQueue=null,e.dependencies=null,e.stateNode=null):(e.childLanes=n.childLanes,e.lanes=n.lanes,e.child=n.child,e.subtreeFlags=0,e.deletions=null,e.memoizedProps=n.memoizedProps,e.memoizedState=n.memoizedState,e.updateQueue=n.updateQueue,e.type=n.type,t=n.dependencies,e.dependencies=null===t?null:{lanes:t.lanes,firstContext:t.firstContext}),e}function zr(e,t,n,r,a,o){var l=0;if(r=e,"function"==typeof e)Fr(e)&&(l=1);else if("string"==typeof e)l=function(e,t,n){if(1===n||null!=t.itemProp)return!1;switch(e){case"meta":case"title":return!0;case"style":if("string"!=typeof t.precedence||"string"!=typeof t.href||""===t.href)break;return!0;case"link":if("string"!=typeof t.rel||"string"!=typeof t.href||""===t.href||t.onLoad||t.onError)break;return"stylesheet"!==t.rel||(e=t.disabled,"string"==typeof t.precedence&&null==e);case"script":if(t.async&&"function"!=typeof t.async&&"symbol"!=typeof t.async&&!t.onLoad&&!t.onError&&t.src&&"string"==typeof t.src)return!0}return!1}(e,n,U.current)?26:"html"===e||"head"===e||"body"===e?27:5;else e:switch(e){case A:return(e=Dr(31,n,t,a)).elementType=A,e.lanes=o,e;case g:return $r(n.children,a,o,t);case y:l=8,a|=24;break;case b:return(e=Dr(12,n,t,2|a)).elementType=b,e.lanes=o,e;case x:return(e=Dr(13,n,t,a)).elementType=x,e.lanes=o,e;case _:return(e=Dr(19,n,t,a)).elementType=_,e.lanes=o,e;default:if("object"==typeof e&&null!==e)switch(e.$$typeof){case v:case k:l=10;break e;case w:l=9;break e;case S:l=11;break e;case E:l=14;break e;case C:l=16,r=null;break e}l=29,n=Error(i(130,null===e?"null":typeof e,"")),r=null}return(t=Dr(l,n,t,a)).elementType=e,t.type=r,t.lanes=o,t}function $r(e,t,n,r){return(e=Dr(7,e,r,t)).lanes=n,e}function Ur(e,t,n){return(e=Dr(6,e,null,t)).lanes=n,e}function qr(e,t,n){return(t=Dr(4,null!==e.children?e.children:[],e.key,t)).lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}var Hr=[],Gr=0,Vr=null,Wr=0,Qr=[],Kr=0,Yr=null,Xr=1,Zr="";function Jr(e,t){Hr[Gr++]=Wr,Hr[Gr++]=Vr,Vr=e,Wr=t}function ea(e,t,n){Qr[Kr++]=Xr,Qr[Kr++]=Zr,Qr[Kr++]=Yr,Yr=e;var r=Xr;e=Zr;var a=32-pe(r)-1;r&=~(1<<a),n+=1;var o=32-pe(t)+a;if(30<o){var i=a-a%5;o=(r&(1<<i)-1).toString(32),r>>=i,a-=i,Xr=1<<32-pe(t)+a|n<<a|r,Zr=o+e}else Xr=1<<o|n<<a|r,Zr=e}function ta(e){null!==e.return&&(Jr(e,1),ea(e,1,0))}function na(e){for(;e===Vr;)Vr=Hr[--Gr],Hr[Gr]=null,Wr=Hr[--Gr],Hr[Gr]=null;for(;e===Yr;)Yr=Qr[--Kr],Qr[Kr]=null,Zr=Qr[--Kr],Qr[Kr]=null,Xr=Qr[--Kr],Qr[Kr]=null}var ra=null,aa=null,oa=!1,ia=null,la=!1,sa=Error(i(519));function ua(e){throw ha(_r(Error(i(418,"")),e)),sa}function ca(e){var t=e.stateNode,n=e.type,r=e.memoizedProps;switch(t[Ne]=e,t[Oe]=r,n){case"dialog":Fc("cancel",t),Fc("close",t);break;case"iframe":case"object":case"embed":Fc("load",t);break;case"video":case"audio":for(n=0;n<Rc.length;n++)Fc(Rc[n],t);break;case"source":Fc("error",t);break;case"img":case"image":case"link":Fc("error",t),Fc("load",t);break;case"details":Fc("toggle",t);break;case"input":Fc("invalid",t),yt(t,r.value,r.defaultValue,r.checked,r.defaultChecked,r.type,r.name,!0),dt(t);break;case"select":Fc("invalid",t);break;case"textarea":Fc("invalid",t),kt(t,r.value,r.defaultValue,r.children),dt(t)}"string"!=typeof(n=r.children)&&"number"!=typeof n&&"bigint"!=typeof n||t.textContent===""+n||!0===r.suppressHydrationWarning||Yc(t.textContent,n)?(null!=r.popover&&(Fc("beforetoggle",t),Fc("toggle",t)),null!=r.onScroll&&Fc("scroll",t),null!=r.onScrollEnd&&Fc("scrollend",t),null!=r.onClick&&(t.onclick=Xc),t=!0):t=!1,t||ua(e)}function da(e){for(ra=e.return;ra;)switch(ra.tag){case 5:case 13:return void(la=!1);case 27:case 3:return void(la=!0);default:ra=ra.return}}function fa(e){if(e!==ra)return!1;if(!oa)return da(e),oa=!0,!1;var t,n=e.tag;if((t=3!==n&&27!==n)&&((t=5===n)&&(t=!("form"!==(t=e.type)&&"button"!==t)||id(e.type,e.memoizedProps)),t=!t),t&&aa&&ua(e),da(e),13===n){if(!(e=null!==(e=e.memoizedState)?e.dehydrated:null))throw Error(i(317));e:{for(e=e.nextSibling,n=0;e;){if(8===e.nodeType)if("/$"===(t=e.data)){if(0===n){aa=yd(e.nextSibling);break e}n--}else"$"!==t&&"$!"!==t&&"$?"!==t||n++;e=e.nextSibling}aa=null}}else 27===n?(n=aa,pd(e.type)?(e=bd,bd=null,aa=e):aa=n):aa=ra?yd(e.stateNode.nextSibling):null;return!0}function pa(){aa=ra=null,oa=!1}function ma(){var e=ia;return null!==e&&(null===vu?vu=e:vu.push.apply(vu,e),ia=null),e}function ha(e){null===ia?ia=[e]:ia.push(e)}var ga=B(null),ya=null,ba=null;function va(e,t,n){$(ga,t._currentValue),t._currentValue=n}function wa(e){e._currentValue=ga.current,z(ga)}function ka(e,t,n){for(;null!==e;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,null!==r&&(r.childLanes|=t)):null!==r&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function Sa(e,t,n,r){var a=e.child;for(null!==a&&(a.return=e);null!==a;){var o=a.dependencies;if(null!==o){var l=a.child;o=o.firstContext;e:for(;null!==o;){var s=o;o=a;for(var u=0;u<t.length;u++)if(s.context===t[u]){o.lanes|=n,null!==(s=o.alternate)&&(s.lanes|=n),ka(o.return,n,e),r||(l=null);break e}o=s.next}}else if(18===a.tag){if(null===(l=a.return))throw Error(i(341));l.lanes|=n,null!==(o=l.alternate)&&(o.lanes|=n),ka(l,n,e),l=null}else l=a.child;if(null!==l)l.return=a;else for(l=a;null!==l;){if(l===e){l=null;break}if(null!==(a=l.sibling)){a.return=l.return,l=a;break}l=l.return}a=l}}function xa(e,t,n,r){e=null;for(var a=t,o=!1;null!==a;){if(!o)if(524288&a.flags)o=!0;else if(262144&a.flags)break;if(10===a.tag){var l=a.alternate;if(null===l)throw Error(i(387));if(null!==(l=l.memoizedProps)){var s=a.type;Kn(a.pendingProps.value,l.value)||(null!==e?e.push(s):e=[s])}}else if(a===G.current){if(null===(l=a.alternate))throw Error(i(387));l.memoizedState.memoizedState!==a.memoizedState.memoizedState&&(null!==e?e.push(Qd):e=[Qd])}a=a.return}null!==e&&Sa(t,e,n,r),t.flags|=262144}function _a(e){for(e=e.firstContext;null!==e;){if(!Kn(e.context._currentValue,e.memoizedValue))return!0;e=e.next}return!1}function Ea(e){ya=e,ba=null,null!==(e=e.dependencies)&&(e.firstContext=null)}function Ca(e){return Ta(ya,e)}function Aa(e,t){return null===ya&&Ea(e),Ta(e,t)}function Ta(e,t){var n=t._currentValue;if(t={context:t,memoizedValue:n,next:null},null===ba){if(null===e)throw Error(i(308));ba=t,e.dependencies={lanes:0,firstContext:t},e.flags|=524288}else ba=ba.next=t;return n}var La="undefined"!=typeof AbortController?AbortController:function(){var e=[],t=this.signal={aborted:!1,addEventListener:function(t,n){e.push(n)}};this.abort=function(){t.aborted=!0,e.forEach((function(e){return e()}))}},ja=r.unstable_scheduleCallback,Pa=r.unstable_NormalPriority,Na={$$typeof:k,Consumer:null,Provider:null,_currentValue:null,_currentValue2:null,_threadCount:0};function Oa(){return{controller:new La,data:new Map,refCount:0}}function Ra(e){e.refCount--,0===e.refCount&&ja(Pa,(function(){e.controller.abort()}))}var Ma=null,Da=0,Fa=0,Ia=null;function Ba(){if(0===--Da&&null!==Ma){null!==Ia&&(Ia.status="fulfilled");var e=Ma;Ma=null,Fa=0,Ia=null;for(var t=0;t<e.length;t++)(0,e[t])()}}var za=R.S;R.S=function(e,t){"object"==typeof t&&null!==t&&"function"==typeof t.then&&function(e,t){if(null===Ma){var n=Ma=[];Da=0,Fa=Lc(),Ia={status:"pending",value:void 0,then:function(e){n.push(e)}}}Da++,t.then(Ba,Ba)}(0,t),null!==za&&za(e,t)};var $a=B(null);function Ua(){var e=$a.current;return null!==e?e:ru.pooledCache}function qa(e,t){$($a,null===t?$a.current:t.pool)}function Ha(){var e=Ua();return null===e?null:{parent:Na._currentValue,pool:e}}var Ga=Error(i(460)),Va=Error(i(474)),Wa=Error(i(542)),Qa={then:function(){}};function Ka(e){return"fulfilled"===(e=e.status)||"rejected"===e}function Ya(){}function Xa(e,t,n){switch(void 0===(n=e[n])?e.push(t):n!==t&&(t.then(Ya,Ya),t=n),t.status){case"fulfilled":return t.value;case"rejected":throw eo(e=t.reason),e;default:if("string"==typeof t.status)t.then(Ya,Ya);else{if(null!==(e=ru)&&100<e.shellSuspendCounter)throw Error(i(482));(e=t).status="pending",e.then((function(e){if("pending"===t.status){var n=t;n.status="fulfilled",n.value=e}}),(function(e){if("pending"===t.status){var n=t;n.status="rejected",n.reason=e}}))}switch(t.status){case"fulfilled":return t.value;case"rejected":throw eo(e=t.reason),e}throw Za=t,Ga}}var Za=null;function Ja(){if(null===Za)throw Error(i(459));var e=Za;return Za=null,e}function eo(e){if(e===Ga||e===Wa)throw Error(i(483))}var to=!1;function no(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function ro(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function ao(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function oo(e,t,n){var r=e.updateQueue;if(null===r)return null;if(r=r.shared,2&nu){var a=r.pending;return null===a?t.next=t:(t.next=a.next,a.next=t),r.pending=t,t=Or(e),Nr(e,null,n),t}return Lr(e,r,t,n),Or(e)}function io(e,t,n){if(null!==(t=t.updateQueue)&&(t=t.shared,4194048&n)){var r=t.lanes;n|=r&=e.pendingLanes,t.lanes=n,Ae(e,n)}}function lo(e,t){var n=e.updateQueue,r=e.alternate;if(null!==r&&n===(r=r.updateQueue)){var a=null,o=null;if(null!==(n=n.firstBaseUpdate)){do{var i={lane:n.lane,tag:n.tag,payload:n.payload,callback:null,next:null};null===o?a=o=i:o=o.next=i,n=n.next}while(null!==n);null===o?a=o=t:o=o.next=t}else a=o=t;return n={baseState:r.baseState,firstBaseUpdate:a,lastBaseUpdate:o,shared:r.shared,callbacks:r.callbacks},void(e.updateQueue=n)}null===(e=n.lastBaseUpdate)?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}var so=!1;function uo(){if(so){if(null!==Ia)throw Ia}}function co(e,t,n,r){so=!1;var a=e.updateQueue;to=!1;var o=a.firstBaseUpdate,i=a.lastBaseUpdate,l=a.shared.pending;if(null!==l){a.shared.pending=null;var s=l,u=s.next;s.next=null,null===i?o=u:i.next=u,i=s;var c=e.alternate;null!==c&&((l=(c=c.updateQueue).lastBaseUpdate)!==i&&(null===l?c.firstBaseUpdate=u:l.next=u,c.lastBaseUpdate=s))}if(null!==o){var d=a.baseState;for(i=0,c=u=s=null,l=o;;){var p=-536870913&l.lane,m=p!==l.lane;if(m?(ou&p)===p:(r&p)===p){0!==p&&p===Fa&&(so=!0),null!==c&&(c=c.next={lane:0,tag:l.tag,payload:l.payload,callback:null,next:null});e:{var h=e,g=l;p=t;var y=n;switch(g.tag){case 1:if("function"==typeof(h=g.payload)){d=h.call(y,d,p);break e}d=h;break e;case 3:h.flags=-65537&h.flags|128;case 0:if(null==(p="function"==typeof(h=g.payload)?h.call(y,d,p):h))break e;d=f({},d,p);break e;case 2:to=!0}}null!==(p=l.callback)&&(e.flags|=64,m&&(e.flags|=8192),null===(m=a.callbacks)?a.callbacks=[p]:m.push(p))}else m={lane:p,tag:l.tag,payload:l.payload,callback:l.callback,next:null},null===c?(u=c=m,s=d):c=c.next=m,i|=p;if(null===(l=l.next)){if(null===(l=a.shared.pending))break;l=(m=l).next,m.next=null,a.lastBaseUpdate=m,a.shared.pending=null}}null===c&&(s=d),a.baseState=s,a.firstBaseUpdate=u,a.lastBaseUpdate=c,null===o&&(a.shared.lanes=0),pu|=i,e.lanes=i,e.memoizedState=d}}function fo(e,t){if("function"!=typeof e)throw Error(i(191,e));e.call(t)}function po(e,t){var n=e.callbacks;if(null!==n)for(e.callbacks=null,e=0;e<n.length;e++)fo(n[e],t)}var mo=B(null),ho=B(0);function go(e,t){$(ho,e=du),$(mo,t),du=e|t.baseLanes}function yo(){$(ho,du),$(mo,mo.current)}function bo(){du=ho.current,z(mo),z(ho)}var vo=0,wo=null,ko=null,So=null,xo=!1,_o=!1,Eo=!1,Co=0,Ao=0,To=null,Lo=0;function jo(){throw Error(i(321))}function Po(e,t){if(null===t)return!1;for(var n=0;n<t.length&&n<e.length;n++)if(!Kn(e[n],t[n]))return!1;return!0}function No(e,t,n,r,a,o){return vo=o,wo=t,t.memoizedState=null,t.updateQueue=null,t.lanes=0,R.H=null===e||null===e.memoizedState?Vi:Wi,Eo=!1,o=n(r,a),Eo=!1,_o&&(o=Ro(t,n,r,a)),Oo(e),o}function Oo(e){R.H=Gi;var t=null!==ko&&null!==ko.next;if(vo=0,So=ko=wo=null,xo=!1,Ao=0,To=null,t)throw Error(i(300));null===e||Al||null!==(e=e.dependencies)&&_a(e)&&(Al=!0)}function Ro(e,t,n,r){wo=e;var a=0;do{if(_o&&(To=null),Ao=0,_o=!1,25<=a)throw Error(i(301));if(a+=1,So=ko=null,null!=e.updateQueue){var o=e.updateQueue;o.lastEffect=null,o.events=null,o.stores=null,null!=o.memoCache&&(o.memoCache.index=0)}R.H=Qi,o=t(n,r)}while(_o);return o}function Mo(){var e=R.H,t=e.useState()[0];return t="function"==typeof t.then?$o(t):t,e=e.useState()[0],(null!==ko?ko.memoizedState:null)!==e&&(wo.flags|=1024),t}function Do(){var e=0!==Co;return Co=0,e}function Fo(e,t,n){t.updateQueue=e.updateQueue,t.flags&=-2053,e.lanes&=~n}function Io(e){if(xo){for(e=e.memoizedState;null!==e;){var t=e.queue;null!==t&&(t.pending=null),e=e.next}xo=!1}vo=0,So=ko=wo=null,_o=!1,Ao=Co=0,To=null}function Bo(){var e={memoizedState:null,baseState:null,baseQueue:null,queue:null,next:null};return null===So?wo.memoizedState=So=e:So=So.next=e,So}function zo(){if(null===ko){var e=wo.alternate;e=null!==e?e.memoizedState:null}else e=ko.next;var t=null===So?wo.memoizedState:So.next;if(null!==t)So=t,ko=e;else{if(null===e){if(null===wo.alternate)throw Error(i(467));throw Error(i(310))}e={memoizedState:(ko=e).memoizedState,baseState:ko.baseState,baseQueue:ko.baseQueue,queue:ko.queue,next:null},null===So?wo.memoizedState=So=e:So=So.next=e}return So}function $o(e){var t=Ao;return Ao+=1,null===To&&(To=[]),e=Xa(To,e,t),t=wo,null===(null===So?t.memoizedState:So.next)&&(t=t.alternate,R.H=null===t||null===t.memoizedState?Vi:Wi),e}function Uo(e){if(null!==e&&"object"==typeof e){if("function"==typeof e.then)return $o(e);if(e.$$typeof===k)return Ca(e)}throw Error(i(438,String(e)))}function qo(e){var t=null,n=wo.updateQueue;if(null!==n&&(t=n.memoCache),null==t){var r=wo.alternate;null!==r&&(null!==(r=r.updateQueue)&&(null!=(r=r.memoCache)&&(t={data:r.data.map((function(e){return e.slice()})),index:0})))}if(null==t&&(t={data:[],index:0}),null===n&&(n={lastEffect:null,events:null,stores:null,memoCache:null},wo.updateQueue=n),n.memoCache=t,void 0===(n=t.data[t.index]))for(n=t.data[t.index]=Array(e),r=0;r<e;r++)n[r]=T;return t.index++,n}function Ho(e,t){return"function"==typeof t?t(e):t}function Go(e){return Vo(zo(),ko,e)}function Vo(e,t,n){var r=e.queue;if(null===r)throw Error(i(311));r.lastRenderedReducer=n;var a=e.baseQueue,o=r.pending;if(null!==o){if(null!==a){var l=a.next;a.next=o.next,o.next=l}t.baseQueue=a=o,r.pending=null}if(o=e.baseState,null===a)e.memoizedState=o;else{var s=l=null,u=null,c=t=a.next,d=!1;do{var f=-536870913&c.lane;if(f!==c.lane?(ou&f)===f:(vo&f)===f){var p=c.revertLane;if(0===p)null!==u&&(u=u.next={lane:0,revertLane:0,action:c.action,hasEagerState:c.hasEagerState,eagerState:c.eagerState,next:null}),f===Fa&&(d=!0);else{if((vo&p)===p){c=c.next,p===Fa&&(d=!0);continue}f={lane:0,revertLane:c.revertLane,action:c.action,hasEagerState:c.hasEagerState,eagerState:c.eagerState,next:null},null===u?(s=u=f,l=o):u=u.next=f,wo.lanes|=p,pu|=p}f=c.action,Eo&&n(o,f),o=c.hasEagerState?c.eagerState:n(o,f)}else p={lane:f,revertLane:c.revertLane,action:c.action,hasEagerState:c.hasEagerState,eagerState:c.eagerState,next:null},null===u?(s=u=p,l=o):u=u.next=p,wo.lanes|=f,pu|=f;c=c.next}while(null!==c&&c!==t);if(null===u?l=o:u.next=s,!Kn(o,e.memoizedState)&&(Al=!0,d&&null!==(n=Ia)))throw n;e.memoizedState=o,e.baseState=l,e.baseQueue=u,r.lastRenderedState=o}return null===a&&(r.lanes=0),[e.memoizedState,r.dispatch]}function Wo(e){var t=zo(),n=t.queue;if(null===n)throw Error(i(311));n.lastRenderedReducer=e;var r=n.dispatch,a=n.pending,o=t.memoizedState;if(null!==a){n.pending=null;var l=a=a.next;do{o=e(o,l.action),l=l.next}while(l!==a);Kn(o,t.memoizedState)||(Al=!0),t.memoizedState=o,null===t.baseQueue&&(t.baseState=o),n.lastRenderedState=o}return[o,r]}function Qo(e,t,n){var r=wo,a=zo(),o=oa;if(o){if(void 0===n)throw Error(i(407));n=n()}else n=t();var l=!Kn((ko||a).memoizedState,n);if(l&&(a.memoizedState=n,Al=!0),a=a.queue,yi(2048,8,Xo.bind(null,r,a,e),[e]),a.getSnapshot!==t||l||null!==So&&1&So.memoizedState.tag){if(r.flags|=2048,mi(9,{destroy:void 0,resource:void 0},Yo.bind(null,r,a,n,t),null),null===ru)throw Error(i(349));o||124&vo||Ko(r,t,n)}return n}function Ko(e,t,n){e.flags|=16384,e={getSnapshot:t,value:n},null===(t=wo.updateQueue)?(t={lastEffect:null,events:null,stores:null,memoCache:null},wo.updateQueue=t,t.stores=[e]):null===(n=t.stores)?t.stores=[e]:n.push(e)}function Yo(e,t,n,r){t.value=n,t.getSnapshot=r,Zo(t)&&Jo(e)}function Xo(e,t,n){return n((function(){Zo(t)&&Jo(e)}))}function Zo(e){var t=e.getSnapshot;e=e.value;try{var n=t();return!Kn(e,n)}catch(r){return!0}}function Jo(e){var t=Pr(e,2);null!==t&&Du(t,e,2)}function ei(e){var t=Bo();if("function"==typeof e){var n=e;if(e=n(),Eo){fe(!0);try{n()}finally{fe(!1)}}}return t.memoizedState=t.baseState=e,t.queue={pending:null,lanes:0,dispatch:null,lastRenderedReducer:Ho,lastRenderedState:e},t}function ti(e,t,n,r){return e.baseState=n,Vo(e,ko,"function"==typeof r?r:Ho)}function ni(e,t,n,r,a){if(Ui(e))throw Error(i(485));if(null!==(e=t.action)){var o={payload:a,action:e,next:null,isTransition:!0,status:"pending",value:null,reason:null,listeners:[],then:function(e){o.listeners.push(e)}};null!==R.T?n(!0):o.isTransition=!1,r(o),null===(n=t.pending)?(o.next=t.pending=o,ri(t,o)):(o.next=n.next,t.pending=n.next=o)}}function ri(e,t){var n=t.action,r=t.payload,a=e.state;if(t.isTransition){var o=R.T,i={};R.T=i;try{var l=n(a,r),s=R.S;null!==s&&s(i,l),ai(e,t,l)}catch(u){ii(e,t,u)}finally{R.T=o}}else try{ai(e,t,o=n(a,r))}catch(c){ii(e,t,c)}}function ai(e,t,n){null!==n&&"object"==typeof n&&"function"==typeof n.then?n.then((function(n){oi(e,t,n)}),(function(n){return ii(e,t,n)})):oi(e,t,n)}function oi(e,t,n){t.status="fulfilled",t.value=n,li(t),e.state=n,null!==(t=e.pending)&&((n=t.next)===t?e.pending=null:(n=n.next,t.next=n,ri(e,n)))}function ii(e,t,n){var r=e.pending;if(e.pending=null,null!==r){r=r.next;do{t.status="rejected",t.reason=n,li(t),t=t.next}while(t!==r)}e.action=null}function li(e){e=e.listeners;for(var t=0;t<e.length;t++)(0,e[t])()}function si(e,t){return t}function ui(e,t){if(oa){var n=ru.formState;if(null!==n){e:{var r=wo;if(oa){if(aa){t:{for(var a=aa,o=la;8!==a.nodeType;){if(!o){a=null;break t}if(null===(a=yd(a.nextSibling))){a=null;break t}}a="F!"===(o=a.data)||"F"===o?a:null}if(a){aa=yd(a.nextSibling),r="F!"===a.data;break e}}ua(r)}r=!1}r&&(t=n[0])}}return(n=Bo()).memoizedState=n.baseState=t,r={pending:null,lanes:0,dispatch:null,lastRenderedReducer:si,lastRenderedState:t},n.queue=r,n=Bi.bind(null,wo,r),r.dispatch=n,r=ei(!1),o=$i.bind(null,wo,!1,r.queue),a={state:t,dispatch:null,action:e,pending:null},(r=Bo()).queue=a,n=ni.bind(null,wo,a,o,n),a.dispatch=n,r.memoizedState=e,[t,n,!1]}function ci(e){return di(zo(),ko,e)}function di(e,t,n){if(t=Vo(e,t,si)[0],e=Go(Ho)[0],"object"==typeof t&&null!==t&&"function"==typeof t.then)try{var r=$o(t)}catch(i){if(i===Ga)throw Wa;throw i}else r=t;var a=(t=zo()).queue,o=a.dispatch;return n!==t.memoizedState&&(wo.flags|=2048,mi(9,{destroy:void 0,resource:void 0},fi.bind(null,a,n),null)),[r,o,e]}function fi(e,t){e.action=t}function pi(e){var t=zo(),n=ko;if(null!==n)return di(t,n,e);zo(),t=t.memoizedState;var r=(n=zo()).queue.dispatch;return n.memoizedState=e,[t,r,!1]}function mi(e,t,n,r){return e={tag:e,create:n,deps:r,inst:t,next:null},null===(t=wo.updateQueue)&&(t={lastEffect:null,events:null,stores:null,memoCache:null},wo.updateQueue=t),null===(n=t.lastEffect)?t.lastEffect=e.next=e:(r=n.next,n.next=e,e.next=r,t.lastEffect=e),e}function hi(){return zo().memoizedState}function gi(e,t,n,r){var a=Bo();r=void 0===r?null:r,wo.flags|=e,a.memoizedState=mi(1|t,{destroy:void 0,resource:void 0},n,r)}function yi(e,t,n,r){var a=zo();r=void 0===r?null:r;var o=a.memoizedState.inst;null!==ko&&null!==r&&Po(r,ko.memoizedState.deps)?a.memoizedState=mi(t,o,n,r):(wo.flags|=e,a.memoizedState=mi(1|t,o,n,r))}function bi(e,t){gi(8390656,8,e,t)}function vi(e,t){yi(2048,8,e,t)}function wi(e,t){return yi(4,2,e,t)}function ki(e,t){return yi(4,4,e,t)}function Si(e,t){if("function"==typeof t){e=e();var n=t(e);return function(){"function"==typeof n?n():t(null)}}if(null!=t)return e=e(),t.current=e,function(){t.current=null}}function xi(e,t,n){n=null!=n?n.concat([e]):null,yi(4,4,Si.bind(null,t,e),n)}function _i(){}function Ei(e,t){var n=zo();t=void 0===t?null:t;var r=n.memoizedState;return null!==t&&Po(t,r[1])?r[0]:(n.memoizedState=[e,t],e)}function Ci(e,t){var n=zo();t=void 0===t?null:t;var r=n.memoizedState;if(null!==t&&Po(t,r[1]))return r[0];if(r=e(),Eo){fe(!0);try{e()}finally{fe(!1)}}return n.memoizedState=[r,t],r}function Ai(e,t,n){return void 0===n||1073741824&vo?e.memoizedState=t:(e.memoizedState=n,e=Mu(),wo.lanes|=e,pu|=e,n)}function Ti(e,t,n,r){return Kn(n,t)?n:null!==mo.current?(e=Ai(e,n,r),Kn(e,t)||(Al=!0),e):42&vo?(e=Mu(),wo.lanes|=e,pu|=e,t):(Al=!0,e.memoizedState=n)}function Li(e,t,n,r,a){var o=M.p;M.p=0!==o&&8>o?o:8;var i,l,s,u=R.T,c={};R.T=c,$i(e,!1,t,n);try{var d=a(),f=R.S;if(null!==f&&f(c,d),null!==d&&"object"==typeof d&&"function"==typeof d.then)zi(e,t,(i=r,l=[],s={status:"pending",value:null,reason:null,then:function(e){l.push(e)}},d.then((function(){s.status="fulfilled",s.value=i;for(var e=0;e<l.length;e++)(0,l[e])(i)}),(function(e){for(s.status="rejected",s.reason=e,e=0;e<l.length;e++)(0,l[e])(void 0)})),s),Ru());else zi(e,t,r,Ru())}catch(p){zi(e,t,{then:function(){},status:"rejected",reason:p},Ru())}finally{M.p=o,R.T=u}}function ji(){}function Pi(e,t,n,r){if(5!==e.tag)throw Error(i(476));var a=Ni(e).queue;Li(e,a,t,D,null===n?ji:function(){return Oi(e),n(r)})}function Ni(e){var t=e.memoizedState;if(null!==t)return t;var n={};return(t={memoizedState:D,baseState:D,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Ho,lastRenderedState:D},next:null}).next={memoizedState:n,baseState:n,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Ho,lastRenderedState:n},next:null},e.memoizedState=t,null!==(e=e.alternate)&&(e.memoizedState=t),t}function Oi(e){zi(e,Ni(e).next.queue,{},Ru())}function Ri(){return Ca(Qd)}function Mi(){return zo().memoizedState}function Di(){return zo().memoizedState}function Fi(e){for(var t=e.return;null!==t;){switch(t.tag){case 24:case 3:var n=Ru(),r=oo(t,e=ao(n),n);return null!==r&&(Du(r,t,n),io(r,t,n)),t={cache:Oa()},void(e.payload=t)}t=t.return}}function Ii(e,t,n){var r=Ru();n={lane:r,revertLane:0,action:n,hasEagerState:!1,eagerState:null,next:null},Ui(e)?qi(t,n):null!==(n=jr(e,t,n,r))&&(Du(n,e,r),Hi(n,t,r))}function Bi(e,t,n){zi(e,t,n,Ru())}function zi(e,t,n,r){var a={lane:r,revertLane:0,action:n,hasEagerState:!1,eagerState:null,next:null};if(Ui(e))qi(t,a);else{var o=e.alternate;if(0===e.lanes&&(null===o||0===o.lanes)&&null!==(o=t.lastRenderedReducer))try{var i=t.lastRenderedState,l=o(i,n);if(a.hasEagerState=!0,a.eagerState=l,Kn(l,i))return Lr(e,t,a,0),null===ru&&Tr(),!1}catch(s){}if(null!==(n=jr(e,t,a,r)))return Du(n,e,r),Hi(n,t,r),!0}return!1}function $i(e,t,n,r){if(r={lane:2,revertLane:Lc(),action:r,hasEagerState:!1,eagerState:null,next:null},Ui(e)){if(t)throw Error(i(479))}else null!==(t=jr(e,n,r,2))&&Du(t,e,2)}function Ui(e){var t=e.alternate;return e===wo||null!==t&&t===wo}function qi(e,t){_o=xo=!0;var n=e.pending;null===n?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Hi(e,t,n){if(4194048&n){var r=t.lanes;n|=r&=e.pendingLanes,t.lanes=n,Ae(e,n)}}var Gi={readContext:Ca,use:Uo,useCallback:jo,useContext:jo,useEffect:jo,useImperativeHandle:jo,useLayoutEffect:jo,useInsertionEffect:jo,useMemo:jo,useReducer:jo,useRef:jo,useState:jo,useDebugValue:jo,useDeferredValue:jo,useTransition:jo,useSyncExternalStore:jo,useId:jo,useHostTransitionStatus:jo,useFormState:jo,useActionState:jo,useOptimistic:jo,useMemoCache:jo,useCacheRefresh:jo},Vi={readContext:Ca,use:Uo,useCallback:function(e,t){return Bo().memoizedState=[e,void 0===t?null:t],e},useContext:Ca,useEffect:bi,useImperativeHandle:function(e,t,n){n=null!=n?n.concat([e]):null,gi(4194308,4,Si.bind(null,t,e),n)},useLayoutEffect:function(e,t){return gi(4194308,4,e,t)},useInsertionEffect:function(e,t){gi(4,2,e,t)},useMemo:function(e,t){var n=Bo();t=void 0===t?null:t;var r=e();if(Eo){fe(!0);try{e()}finally{fe(!1)}}return n.memoizedState=[r,t],r},useReducer:function(e,t,n){var r=Bo();if(void 0!==n){var a=n(t);if(Eo){fe(!0);try{n(t)}finally{fe(!1)}}}else a=t;return r.memoizedState=r.baseState=a,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:a},r.queue=e,e=e.dispatch=Ii.bind(null,wo,e),[r.memoizedState,e]},useRef:function(e){return e={current:e},Bo().memoizedState=e},useState:function(e){var t=(e=ei(e)).queue,n=Bi.bind(null,wo,t);return t.dispatch=n,[e.memoizedState,n]},useDebugValue:_i,useDeferredValue:function(e,t){return Ai(Bo(),e,t)},useTransition:function(){var e=ei(!1);return e=Li.bind(null,wo,e.queue,!0,!1),Bo().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,n){var r=wo,a=Bo();if(oa){if(void 0===n)throw Error(i(407));n=n()}else{if(n=t(),null===ru)throw Error(i(349));124&ou||Ko(r,t,n)}a.memoizedState=n;var o={value:n,getSnapshot:t};return a.queue=o,bi(Xo.bind(null,r,o,e),[e]),r.flags|=2048,mi(9,{destroy:void 0,resource:void 0},Yo.bind(null,r,o,n,t),null),n},useId:function(){var e=Bo(),t=ru.identifierPrefix;if(oa){var n=Zr;t="\xab"+t+"R"+(n=(Xr&~(1<<32-pe(Xr)-1)).toString(32)+n),0<(n=Co++)&&(t+="H"+n.toString(32)),t+="\xbb"}else t="\xab"+t+"r"+(n=Lo++).toString(32)+"\xbb";return e.memoizedState=t},useHostTransitionStatus:Ri,useFormState:ui,useActionState:ui,useOptimistic:function(e){var t=Bo();t.memoizedState=t.baseState=e;var n={pending:null,lanes:0,dispatch:null,lastRenderedReducer:null,lastRenderedState:null};return t.queue=n,t=$i.bind(null,wo,!0,n),n.dispatch=t,[e,t]},useMemoCache:qo,useCacheRefresh:function(){return Bo().memoizedState=Fi.bind(null,wo)}},Wi={readContext:Ca,use:Uo,useCallback:Ei,useContext:Ca,useEffect:vi,useImperativeHandle:xi,useInsertionEffect:wi,useLayoutEffect:ki,useMemo:Ci,useReducer:Go,useRef:hi,useState:function(){return Go(Ho)},useDebugValue:_i,useDeferredValue:function(e,t){return Ti(zo(),ko.memoizedState,e,t)},useTransition:function(){var e=Go(Ho)[0],t=zo().memoizedState;return["boolean"==typeof e?e:$o(e),t]},useSyncExternalStore:Qo,useId:Mi,useHostTransitionStatus:Ri,useFormState:ci,useActionState:ci,useOptimistic:function(e,t){return ti(zo(),0,e,t)},useMemoCache:qo,useCacheRefresh:Di},Qi={readContext:Ca,use:Uo,useCallback:Ei,useContext:Ca,useEffect:vi,useImperativeHandle:xi,useInsertionEffect:wi,useLayoutEffect:ki,useMemo:Ci,useReducer:Wo,useRef:hi,useState:function(){return Wo(Ho)},useDebugValue:_i,useDeferredValue:function(e,t){var n=zo();return null===ko?Ai(n,e,t):Ti(n,ko.memoizedState,e,t)},useTransition:function(){var e=Wo(Ho)[0],t=zo().memoizedState;return["boolean"==typeof e?e:$o(e),t]},useSyncExternalStore:Qo,useId:Mi,useHostTransitionStatus:Ri,useFormState:pi,useActionState:pi,useOptimistic:function(e,t){var n=zo();return null!==ko?ti(n,0,e,t):(n.baseState=e,[e,n.queue.dispatch])},useMemoCache:qo,useCacheRefresh:Di},Ki=null,Yi=0;function Xi(e){var t=Yi;return Yi+=1,null===Ki&&(Ki=[]),Xa(Ki,e,t)}function Zi(e,t){t=t.props.ref,e.ref=void 0!==t?t:null}function Ji(e,t){if(t.$$typeof===p)throw Error(i(525));throw e=Object.prototype.toString.call(t),Error(i(31,"[object Object]"===e?"object with keys {"+Object.keys(t).join(", ")+"}":e))}function el(e){return(0,e._init)(e._payload)}function tl(e){function t(t,n){if(e){var r=t.deletions;null===r?(t.deletions=[n],t.flags|=16):r.push(n)}}function n(n,r){if(!e)return null;for(;null!==r;)t(n,r),r=r.sibling;return null}function r(e){for(var t=new Map;null!==e;)null!==e.key?t.set(e.key,e):t.set(e.index,e),e=e.sibling;return t}function a(e,t){return(e=Ir(e,t)).index=0,e.sibling=null,e}function o(t,n,r){return t.index=r,e?null!==(r=t.alternate)?(r=r.index)<n?(t.flags|=67108866,n):r:(t.flags|=67108866,n):(t.flags|=1048576,n)}function l(t){return e&&null===t.alternate&&(t.flags|=67108866),t}function s(e,t,n,r){return null===t||6!==t.tag?((t=Ur(n,e.mode,r)).return=e,t):((t=a(t,n)).return=e,t)}function u(e,t,n,r){var o=n.type;return o===g?d(e,t,n.props.children,r,n.key):null!==t&&(t.elementType===o||"object"==typeof o&&null!==o&&o.$$typeof===C&&el(o)===t.type)?(Zi(t=a(t,n.props),n),t.return=e,t):(Zi(t=zr(n.type,n.key,n.props,null,e.mode,r),n),t.return=e,t)}function c(e,t,n,r){return null===t||4!==t.tag||t.stateNode.containerInfo!==n.containerInfo||t.stateNode.implementation!==n.implementation?((t=qr(n,e.mode,r)).return=e,t):((t=a(t,n.children||[])).return=e,t)}function d(e,t,n,r,o){return null===t||7!==t.tag?((t=$r(n,e.mode,r,o)).return=e,t):((t=a(t,n)).return=e,t)}function f(e,t,n){if("string"==typeof t&&""!==t||"number"==typeof t||"bigint"==typeof t)return(t=Ur(""+t,e.mode,n)).return=e,t;if("object"==typeof t&&null!==t){switch(t.$$typeof){case m:return Zi(n=zr(t.type,t.key,t.props,null,e.mode,n),t),n.return=e,n;case h:return(t=qr(t,e.mode,n)).return=e,t;case C:return f(e,t=(0,t._init)(t._payload),n)}if(O(t)||j(t))return(t=$r(t,e.mode,n,null)).return=e,t;if("function"==typeof t.then)return f(e,Xi(t),n);if(t.$$typeof===k)return f(e,Aa(e,t),n);Ji(e,t)}return null}function p(e,t,n,r){var a=null!==t?t.key:null;if("string"==typeof n&&""!==n||"number"==typeof n||"bigint"==typeof n)return null!==a?null:s(e,t,""+n,r);if("object"==typeof n&&null!==n){switch(n.$$typeof){case m:return n.key===a?u(e,t,n,r):null;case h:return n.key===a?c(e,t,n,r):null;case C:return p(e,t,n=(a=n._init)(n._payload),r)}if(O(n)||j(n))return null!==a?null:d(e,t,n,r,null);if("function"==typeof n.then)return p(e,t,Xi(n),r);if(n.$$typeof===k)return p(e,t,Aa(e,n),r);Ji(e,n)}return null}function y(e,t,n,r,a){if("string"==typeof r&&""!==r||"number"==typeof r||"bigint"==typeof r)return s(t,e=e.get(n)||null,""+r,a);if("object"==typeof r&&null!==r){switch(r.$$typeof){case m:return u(t,e=e.get(null===r.key?n:r.key)||null,r,a);case h:return c(t,e=e.get(null===r.key?n:r.key)||null,r,a);case C:return y(e,t,n,r=(0,r._init)(r._payload),a)}if(O(r)||j(r))return d(t,e=e.get(n)||null,r,a,null);if("function"==typeof r.then)return y(e,t,n,Xi(r),a);if(r.$$typeof===k)return y(e,t,n,Aa(t,r),a);Ji(t,r)}return null}function b(s,u,c,d){if("object"==typeof c&&null!==c&&c.type===g&&null===c.key&&(c=c.props.children),"object"==typeof c&&null!==c){switch(c.$$typeof){case m:e:{for(var v=c.key;null!==u;){if(u.key===v){if((v=c.type)===g){if(7===u.tag){n(s,u.sibling),(d=a(u,c.props.children)).return=s,s=d;break e}}else if(u.elementType===v||"object"==typeof v&&null!==v&&v.$$typeof===C&&el(v)===u.type){n(s,u.sibling),Zi(d=a(u,c.props),c),d.return=s,s=d;break e}n(s,u);break}t(s,u),u=u.sibling}c.type===g?((d=$r(c.props.children,s.mode,d,c.key)).return=s,s=d):(Zi(d=zr(c.type,c.key,c.props,null,s.mode,d),c),d.return=s,s=d)}return l(s);case h:e:{for(v=c.key;null!==u;){if(u.key===v){if(4===u.tag&&u.stateNode.containerInfo===c.containerInfo&&u.stateNode.implementation===c.implementation){n(s,u.sibling),(d=a(u,c.children||[])).return=s,s=d;break e}n(s,u);break}t(s,u),u=u.sibling}(d=qr(c,s.mode,d)).return=s,s=d}return l(s);case C:return b(s,u,c=(v=c._init)(c._payload),d)}if(O(c))return function(a,i,l,s){for(var u=null,c=null,d=i,m=i=0,h=null;null!==d&&m<l.length;m++){d.index>m?(h=d,d=null):h=d.sibling;var g=p(a,d,l[m],s);if(null===g){null===d&&(d=h);break}e&&d&&null===g.alternate&&t(a,d),i=o(g,i,m),null===c?u=g:c.sibling=g,c=g,d=h}if(m===l.length)return n(a,d),oa&&Jr(a,m),u;if(null===d){for(;m<l.length;m++)null!==(d=f(a,l[m],s))&&(i=o(d,i,m),null===c?u=d:c.sibling=d,c=d);return oa&&Jr(a,m),u}for(d=r(d);m<l.length;m++)null!==(h=y(d,a,m,l[m],s))&&(e&&null!==h.alternate&&d.delete(null===h.key?m:h.key),i=o(h,i,m),null===c?u=h:c.sibling=h,c=h);return e&&d.forEach((function(e){return t(a,e)})),oa&&Jr(a,m),u}(s,u,c,d);if(j(c)){if("function"!=typeof(v=j(c)))throw Error(i(150));return function(a,l,s,u){if(null==s)throw Error(i(151));for(var c=null,d=null,m=l,h=l=0,g=null,b=s.next();null!==m&&!b.done;h++,b=s.next()){m.index>h?(g=m,m=null):g=m.sibling;var v=p(a,m,b.value,u);if(null===v){null===m&&(m=g);break}e&&m&&null===v.alternate&&t(a,m),l=o(v,l,h),null===d?c=v:d.sibling=v,d=v,m=g}if(b.done)return n(a,m),oa&&Jr(a,h),c;if(null===m){for(;!b.done;h++,b=s.next())null!==(b=f(a,b.value,u))&&(l=o(b,l,h),null===d?c=b:d.sibling=b,d=b);return oa&&Jr(a,h),c}for(m=r(m);!b.done;h++,b=s.next())null!==(b=y(m,a,h,b.value,u))&&(e&&null!==b.alternate&&m.delete(null===b.key?h:b.key),l=o(b,l,h),null===d?c=b:d.sibling=b,d=b);return e&&m.forEach((function(e){return t(a,e)})),oa&&Jr(a,h),c}(s,u,c=v.call(c),d)}if("function"==typeof c.then)return b(s,u,Xi(c),d);if(c.$$typeof===k)return b(s,u,Aa(s,c),d);Ji(s,c)}return"string"==typeof c&&""!==c||"number"==typeof c||"bigint"==typeof c?(c=""+c,null!==u&&6===u.tag?(n(s,u.sibling),(d=a(u,c)).return=s,s=d):(n(s,u),(d=Ur(c,s.mode,d)).return=s,s=d),l(s)):n(s,u)}return function(e,t,n,r){try{Yi=0;var a=b(e,t,n,r);return Ki=null,a}catch(i){if(i===Ga||i===Wa)throw i;var o=Dr(29,i,null,e.mode);return o.lanes=r,o.return=e,o}}}var nl=tl(!0),rl=tl(!1),al=B(null),ol=null;function il(e){var t=e.alternate;$(cl,1&cl.current),$(al,e),null===ol&&(null===t||null!==mo.current||null!==t.memoizedState)&&(ol=e)}function ll(e){if(22===e.tag){if($(cl,cl.current),$(al,e),null===ol){var t=e.alternate;null!==t&&null!==t.memoizedState&&(ol=e)}}else sl()}function sl(){$(cl,cl.current),$(al,al.current)}function ul(e){z(al),ol===e&&(ol=null),z(cl)}var cl=B(0);function dl(e){for(var t=e;null!==t;){if(13===t.tag){var n=t.memoizedState;if(null!==n&&(null===(n=n.dehydrated)||"$?"===n.data||gd(n)))return t}else if(19===t.tag&&void 0!==t.memoizedProps.revealOrder){if(128&t.flags)return t}else if(null!==t.child){t.child.return=t,t=t.child;continue}if(t===e)break;for(;null===t.sibling;){if(null===t.return||t.return===e)return null;t=t.return}t.sibling.return=t.return,t=t.sibling}return null}function fl(e,t,n,r){n=null==(n=n(r,t=e.memoizedState))?t:f({},t,n),e.memoizedState=n,0===e.lanes&&(e.updateQueue.baseState=n)}var pl={enqueueSetState:function(e,t,n){e=e._reactInternals;var r=Ru(),a=ao(r);a.payload=t,null!=n&&(a.callback=n),null!==(t=oo(e,a,r))&&(Du(t,e,r),io(t,e,r))},enqueueReplaceState:function(e,t,n){e=e._reactInternals;var r=Ru(),a=ao(r);a.tag=1,a.payload=t,null!=n&&(a.callback=n),null!==(t=oo(e,a,r))&&(Du(t,e,r),io(t,e,r))},enqueueForceUpdate:function(e,t){e=e._reactInternals;var n=Ru(),r=ao(n);r.tag=2,null!=t&&(r.callback=t),null!==(t=oo(e,r,n))&&(Du(t,e,n),io(t,e,n))}};function ml(e,t,n,r,a,o,i){return"function"==typeof(e=e.stateNode).shouldComponentUpdate?e.shouldComponentUpdate(r,o,i):!t.prototype||!t.prototype.isPureReactComponent||(!Yn(n,r)||!Yn(a,o))}function hl(e,t,n,r){e=t.state,"function"==typeof t.componentWillReceiveProps&&t.componentWillReceiveProps(n,r),"function"==typeof t.UNSAFE_componentWillReceiveProps&&t.UNSAFE_componentWillReceiveProps(n,r),t.state!==e&&pl.enqueueReplaceState(t,t.state,null)}function gl(e,t){var n=t;if("ref"in t)for(var r in n={},t)"ref"!==r&&(n[r]=t[r]);if(e=e.defaultProps)for(var a in n===t&&(n=f({},n)),e)void 0===n[a]&&(n[a]=e[a]);return n}var yl="function"==typeof reportError?reportError:function(e){if("object"==typeof window&&"function"==typeof window.ErrorEvent){var t=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:"object"==typeof e&&null!==e&&"string"==typeof e.message?String(e.message):String(e),error:e});if(!window.dispatchEvent(t))return}else if("object"==typeof process&&"function"==typeof process.emit)return void process.emit("uncaughtException",e);console.error(e)};function bl(e){yl(e)}function vl(e){console.error(e)}function wl(e){yl(e)}function kl(e,t){try{(0,e.onUncaughtError)(t.value,{componentStack:t.stack})}catch(n){setTimeout((function(){throw n}))}}function Sl(e,t,n){try{(0,e.onCaughtError)(n.value,{componentStack:n.stack,errorBoundary:1===t.tag?t.stateNode:null})}catch(r){setTimeout((function(){throw r}))}}function xl(e,t,n){return(n=ao(n)).tag=3,n.payload={element:null},n.callback=function(){kl(e,t)},n}function _l(e){return(e=ao(e)).tag=3,e}function El(e,t,n,r){var a=n.type.getDerivedStateFromError;if("function"==typeof a){var o=r.value;e.payload=function(){return a(o)},e.callback=function(){Sl(t,n,r)}}var i=n.stateNode;null!==i&&"function"==typeof i.componentDidCatch&&(e.callback=function(){Sl(t,n,r),"function"!=typeof a&&(null===_u?_u=new Set([this]):_u.add(this));var e=r.stack;this.componentDidCatch(r.value,{componentStack:null!==e?e:""})})}var Cl=Error(i(461)),Al=!1;function Tl(e,t,n,r){t.child=null===e?rl(t,null,n,r):nl(t,e.child,n,r)}function Ll(e,t,n,r,a){n=n.render;var o=t.ref;if("ref"in r){var i={};for(var l in r)"ref"!==l&&(i[l]=r[l])}else i=r;return Ea(t),r=No(e,t,n,i,o,a),l=Do(),null===e||Al?(oa&&l&&ta(t),t.flags|=1,Tl(e,t,r,a),t.child):(Fo(e,t,a),Kl(e,t,a))}function jl(e,t,n,r,a){if(null===e){var o=n.type;return"function"!=typeof o||Fr(o)||void 0!==o.defaultProps||null!==n.compare?((e=zr(n.type,null,r,t,t.mode,a)).ref=t.ref,e.return=t,t.child=e):(t.tag=15,t.type=o,Pl(e,t,o,r,a))}if(o=e.child,!Yl(e,a)){var i=o.memoizedProps;if((n=null!==(n=n.compare)?n:Yn)(i,r)&&e.ref===t.ref)return Kl(e,t,a)}return t.flags|=1,(e=Ir(o,r)).ref=t.ref,e.return=t,t.child=e}function Pl(e,t,n,r,a){if(null!==e){var o=e.memoizedProps;if(Yn(o,r)&&e.ref===t.ref){if(Al=!1,t.pendingProps=r=o,!Yl(e,a))return t.lanes=e.lanes,Kl(e,t,a);131072&e.flags&&(Al=!0)}}return Ml(e,t,n,r,a)}function Nl(e,t,n){var r=t.pendingProps,a=r.children,o=null!==e?e.memoizedState:null;if("hidden"===r.mode){if(128&t.flags){if(r=null!==o?o.baseLanes|n:n,null!==e){for(a=t.child=e.child,o=0;null!==a;)o=o|a.lanes|a.childLanes,a=a.sibling;t.childLanes=o&~r}else t.childLanes=0,t.child=null;return Ol(e,t,r,n)}if(!(536870912&n))return t.lanes=t.childLanes=536870912,Ol(e,t,null!==o?o.baseLanes|n:n,n);t.memoizedState={baseLanes:0,cachePool:null},null!==e&&qa(0,null!==o?o.cachePool:null),null!==o?go(t,o):yo(),ll(t)}else null!==o?(qa(0,o.cachePool),go(t,o),sl(),t.memoizedState=null):(null!==e&&qa(0,null),yo(),sl());return Tl(e,t,a,n),t.child}function Ol(e,t,n,r){var a=Ua();return a=null===a?null:{parent:Na._currentValue,pool:a},t.memoizedState={baseLanes:n,cachePool:a},null!==e&&qa(0,null),yo(),ll(t),null!==e&&xa(e,t,r,!0),null}function Rl(e,t){var n=t.ref;if(null===n)null!==e&&null!==e.ref&&(t.flags|=4194816);else{if("function"!=typeof n&&"object"!=typeof n)throw Error(i(284));null!==e&&e.ref===n||(t.flags|=4194816)}}function Ml(e,t,n,r,a){return Ea(t),n=No(e,t,n,r,void 0,a),r=Do(),null===e||Al?(oa&&r&&ta(t),t.flags|=1,Tl(e,t,n,a),t.child):(Fo(e,t,a),Kl(e,t,a))}function Dl(e,t,n,r,a,o){return Ea(t),t.updateQueue=null,n=Ro(t,r,n,a),Oo(e),r=Do(),null===e||Al?(oa&&r&&ta(t),t.flags|=1,Tl(e,t,n,o),t.child):(Fo(e,t,o),Kl(e,t,o))}function Fl(e,t,n,r,a){if(Ea(t),null===t.stateNode){var o=Rr,i=n.contextType;"object"==typeof i&&null!==i&&(o=Ca(i)),o=new n(r,o),t.memoizedState=null!==o.state&&void 0!==o.state?o.state:null,o.updater=pl,t.stateNode=o,o._reactInternals=t,(o=t.stateNode).props=r,o.state=t.memoizedState,o.refs={},no(t),i=n.contextType,o.context="object"==typeof i&&null!==i?Ca(i):Rr,o.state=t.memoizedState,"function"==typeof(i=n.getDerivedStateFromProps)&&(fl(t,n,i,r),o.state=t.memoizedState),"function"==typeof n.getDerivedStateFromProps||"function"==typeof o.getSnapshotBeforeUpdate||"function"!=typeof o.UNSAFE_componentWillMount&&"function"!=typeof o.componentWillMount||(i=o.state,"function"==typeof o.componentWillMount&&o.componentWillMount(),"function"==typeof o.UNSAFE_componentWillMount&&o.UNSAFE_componentWillMount(),i!==o.state&&pl.enqueueReplaceState(o,o.state,null),co(t,r,o,a),uo(),o.state=t.memoizedState),"function"==typeof o.componentDidMount&&(t.flags|=4194308),r=!0}else if(null===e){o=t.stateNode;var l=t.memoizedProps,s=gl(n,l);o.props=s;var u=o.context,c=n.contextType;i=Rr,"object"==typeof c&&null!==c&&(i=Ca(c));var d=n.getDerivedStateFromProps;c="function"==typeof d||"function"==typeof o.getSnapshotBeforeUpdate,l=t.pendingProps!==l,c||"function"!=typeof o.UNSAFE_componentWillReceiveProps&&"function"!=typeof o.componentWillReceiveProps||(l||u!==i)&&hl(t,o,r,i),to=!1;var f=t.memoizedState;o.state=f,co(t,r,o,a),uo(),u=t.memoizedState,l||f!==u||to?("function"==typeof d&&(fl(t,n,d,r),u=t.memoizedState),(s=to||ml(t,n,s,r,f,u,i))?(c||"function"!=typeof o.UNSAFE_componentWillMount&&"function"!=typeof o.componentWillMount||("function"==typeof o.componentWillMount&&o.componentWillMount(),"function"==typeof o.UNSAFE_componentWillMount&&o.UNSAFE_componentWillMount()),"function"==typeof o.componentDidMount&&(t.flags|=4194308)):("function"==typeof o.componentDidMount&&(t.flags|=4194308),t.memoizedProps=r,t.memoizedState=u),o.props=r,o.state=u,o.context=i,r=s):("function"==typeof o.componentDidMount&&(t.flags|=4194308),r=!1)}else{o=t.stateNode,ro(e,t),c=gl(n,i=t.memoizedProps),o.props=c,d=t.pendingProps,f=o.context,u=n.contextType,s=Rr,"object"==typeof u&&null!==u&&(s=Ca(u)),(u="function"==typeof(l=n.getDerivedStateFromProps)||"function"==typeof o.getSnapshotBeforeUpdate)||"function"!=typeof o.UNSAFE_componentWillReceiveProps&&"function"!=typeof o.componentWillReceiveProps||(i!==d||f!==s)&&hl(t,o,r,s),to=!1,f=t.memoizedState,o.state=f,co(t,r,o,a),uo();var p=t.memoizedState;i!==d||f!==p||to||null!==e&&null!==e.dependencies&&_a(e.dependencies)?("function"==typeof l&&(fl(t,n,l,r),p=t.memoizedState),(c=to||ml(t,n,c,r,f,p,s)||null!==e&&null!==e.dependencies&&_a(e.dependencies))?(u||"function"!=typeof o.UNSAFE_componentWillUpdate&&"function"!=typeof o.componentWillUpdate||("function"==typeof o.componentWillUpdate&&o.componentWillUpdate(r,p,s),"function"==typeof o.UNSAFE_componentWillUpdate&&o.UNSAFE_componentWillUpdate(r,p,s)),"function"==typeof o.componentDidUpdate&&(t.flags|=4),"function"==typeof o.getSnapshotBeforeUpdate&&(t.flags|=1024)):("function"!=typeof o.componentDidUpdate||i===e.memoizedProps&&f===e.memoizedState||(t.flags|=4),"function"!=typeof o.getSnapshotBeforeUpdate||i===e.memoizedProps&&f===e.memoizedState||(t.flags|=1024),t.memoizedProps=r,t.memoizedState=p),o.props=r,o.state=p,o.context=s,r=c):("function"!=typeof o.componentDidUpdate||i===e.memoizedProps&&f===e.memoizedState||(t.flags|=4),"function"!=typeof o.getSnapshotBeforeUpdate||i===e.memoizedProps&&f===e.memoizedState||(t.flags|=1024),r=!1)}return o=r,Rl(e,t),r=!!(128&t.flags),o||r?(o=t.stateNode,n=r&&"function"!=typeof n.getDerivedStateFromError?null:o.render(),t.flags|=1,null!==e&&r?(t.child=nl(t,e.child,null,a),t.child=nl(t,null,n,a)):Tl(e,t,n,a),t.memoizedState=o.state,e=t.child):e=Kl(e,t,a),e}function Il(e,t,n,r){return pa(),t.flags|=256,Tl(e,t,n,r),t.child}var Bl={dehydrated:null,treeContext:null,retryLane:0,hydrationErrors:null};function zl(e){return{baseLanes:e,cachePool:Ha()}}function $l(e,t,n){return e=null!==e?e.childLanes&~n:0,t&&(e|=gu),e}function Ul(e,t,n){var r,a=t.pendingProps,o=!1,l=!!(128&t.flags);if((r=l)||(r=(null===e||null!==e.memoizedState)&&!!(2&cl.current)),r&&(o=!0,t.flags&=-129),r=!!(32&t.flags),t.flags&=-33,null===e){if(oa){if(o?il(t):sl(),oa){var s,u=aa;if(s=u){e:{for(s=u,u=la;8!==s.nodeType;){if(!u){u=null;break e}if(null===(s=yd(s.nextSibling))){u=null;break e}}u=s}null!==u?(t.memoizedState={dehydrated:u,treeContext:null!==Yr?{id:Xr,overflow:Zr}:null,retryLane:536870912,hydrationErrors:null},(s=Dr(18,null,null,0)).stateNode=u,s.return=t,t.child=s,ra=t,aa=null,s=!0):s=!1}s||ua(t)}if(null!==(u=t.memoizedState)&&null!==(u=u.dehydrated))return gd(u)?t.lanes=32:t.lanes=536870912,null;ul(t)}return u=a.children,a=a.fallback,o?(sl(),u=Hl({mode:"hidden",children:u},o=t.mode),a=$r(a,o,n,null),u.return=t,a.return=t,u.sibling=a,t.child=u,(o=t.child).memoizedState=zl(n),o.childLanes=$l(e,r,n),t.memoizedState=Bl,a):(il(t),ql(t,u))}if(null!==(s=e.memoizedState)&&null!==(u=s.dehydrated)){if(l)256&t.flags?(il(t),t.flags&=-257,t=Gl(e,t,n)):null!==t.memoizedState?(sl(),t.child=e.child,t.flags|=128,t=null):(sl(),o=a.fallback,u=t.mode,a=Hl({mode:"visible",children:a.children},u),(o=$r(o,u,n,null)).flags|=2,a.return=t,o.return=t,a.sibling=o,t.child=a,nl(t,e.child,null,n),(a=t.child).memoizedState=zl(n),a.childLanes=$l(e,r,n),t.memoizedState=Bl,t=o);else if(il(t),gd(u)){if(r=u.nextSibling&&u.nextSibling.dataset)var c=r.dgst;r=c,(a=Error(i(419))).stack="",a.digest=r,ha({value:a,source:null,stack:null}),t=Gl(e,t,n)}else if(Al||xa(e,t,n,!1),r=0!==(n&e.childLanes),Al||r){if(null!==(r=ru)&&(0!==(a=0!==((a=42&(a=n&-n)?1:Te(a))&(r.suspendedLanes|n))?0:a)&&a!==s.retryLane))throw s.retryLane=a,Pr(e,a),Du(r,e,a),Cl;"$?"===u.data||Wu(),t=Gl(e,t,n)}else"$?"===u.data?(t.flags|=192,t.child=e.child,t=null):(e=s.treeContext,aa=yd(u.nextSibling),ra=t,oa=!0,ia=null,la=!1,null!==e&&(Qr[Kr++]=Xr,Qr[Kr++]=Zr,Qr[Kr++]=Yr,Xr=e.id,Zr=e.overflow,Yr=t),(t=ql(t,a.children)).flags|=4096);return t}return o?(sl(),o=a.fallback,u=t.mode,c=(s=e.child).sibling,(a=Ir(s,{mode:"hidden",children:a.children})).subtreeFlags=65011712&s.subtreeFlags,null!==c?o=Ir(c,o):(o=$r(o,u,n,null)).flags|=2,o.return=t,a.return=t,a.sibling=o,t.child=a,a=o,o=t.child,null===(u=e.child.memoizedState)?u=zl(n):(null!==(s=u.cachePool)?(c=Na._currentValue,s=s.parent!==c?{parent:c,pool:c}:s):s=Ha(),u={baseLanes:u.baseLanes|n,cachePool:s}),o.memoizedState=u,o.childLanes=$l(e,r,n),t.memoizedState=Bl,a):(il(t),e=(n=e.child).sibling,(n=Ir(n,{mode:"visible",children:a.children})).return=t,n.sibling=null,null!==e&&(null===(r=t.deletions)?(t.deletions=[e],t.flags|=16):r.push(e)),t.child=n,t.memoizedState=null,n)}function ql(e,t){return(t=Hl({mode:"visible",children:t},e.mode)).return=e,e.child=t}function Hl(e,t){return(e=Dr(22,e,null,t)).lanes=0,e.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null},e}function Gl(e,t,n){return nl(t,e.child,null,n),(e=ql(t,t.pendingProps.children)).flags|=2,t.memoizedState=null,e}function Vl(e,t,n){e.lanes|=t;var r=e.alternate;null!==r&&(r.lanes|=t),ka(e.return,t,n)}function Wl(e,t,n,r,a){var o=e.memoizedState;null===o?e.memoizedState={isBackwards:t,rendering:null,renderingStartTime:0,last:r,tail:n,tailMode:a}:(o.isBackwards=t,o.rendering=null,o.renderingStartTime=0,o.last=r,o.tail=n,o.tailMode=a)}function Ql(e,t,n){var r=t.pendingProps,a=r.revealOrder,o=r.tail;if(Tl(e,t,r.children,n),2&(r=cl.current))r=1&r|2,t.flags|=128;else{if(null!==e&&128&e.flags)e:for(e=t.child;null!==e;){if(13===e.tag)null!==e.memoizedState&&Vl(e,n,t);else if(19===e.tag)Vl(e,n,t);else if(null!==e.child){e.child.return=e,e=e.child;continue}if(e===t)break e;for(;null===e.sibling;){if(null===e.return||e.return===t)break e;e=e.return}e.sibling.return=e.return,e=e.sibling}r&=1}switch($(cl,r),a){case"forwards":for(n=t.child,a=null;null!==n;)null!==(e=n.alternate)&&null===dl(e)&&(a=n),n=n.sibling;null===(n=a)?(a=t.child,t.child=null):(a=n.sibling,n.sibling=null),Wl(t,!1,a,n,o);break;case"backwards":for(n=null,a=t.child,t.child=null;null!==a;){if(null!==(e=a.alternate)&&null===dl(e)){t.child=a;break}e=a.sibling,a.sibling=n,n=a,a=e}Wl(t,!0,n,null,o);break;case"together":Wl(t,!1,null,null,void 0);break;default:t.memoizedState=null}return t.child}function Kl(e,t,n){if(null!==e&&(t.dependencies=e.dependencies),pu|=t.lanes,0===(n&t.childLanes)){if(null===e)return null;if(xa(e,t,n,!1),0===(n&t.childLanes))return null}if(null!==e&&t.child!==e.child)throw Error(i(153));if(null!==t.child){for(n=Ir(e=t.child,e.pendingProps),t.child=n,n.return=t;null!==e.sibling;)e=e.sibling,(n=n.sibling=Ir(e,e.pendingProps)).return=t;n.sibling=null}return t.child}function Yl(e,t){return 0!==(e.lanes&t)||!(null===(e=e.dependencies)||!_a(e))}function Xl(e,t,n){if(null!==e)if(e.memoizedProps!==t.pendingProps)Al=!0;else{if(!(Yl(e,n)||128&t.flags))return Al=!1,function(e,t,n){switch(t.tag){case 3:V(t,t.stateNode.containerInfo),va(0,Na,e.memoizedState.cache),pa();break;case 27:case 5:Q(t);break;case 4:V(t,t.stateNode.containerInfo);break;case 10:va(0,t.type,t.memoizedProps.value);break;case 13:var r=t.memoizedState;if(null!==r)return null!==r.dehydrated?(il(t),t.flags|=128,null):0!==(n&t.child.childLanes)?Ul(e,t,n):(il(t),null!==(e=Kl(e,t,n))?e.sibling:null);il(t);break;case 19:var a=!!(128&e.flags);if((r=0!==(n&t.childLanes))||(xa(e,t,n,!1),r=0!==(n&t.childLanes)),a){if(r)return Ql(e,t,n);t.flags|=128}if(null!==(a=t.memoizedState)&&(a.rendering=null,a.tail=null,a.lastEffect=null),$(cl,cl.current),r)break;return null;case 22:case 23:return t.lanes=0,Nl(e,t,n);case 24:va(0,Na,e.memoizedState.cache)}return Kl(e,t,n)}(e,t,n);Al=!!(131072&e.flags)}else Al=!1,oa&&1048576&t.flags&&ea(t,Wr,t.index);switch(t.lanes=0,t.tag){case 16:e:{e=t.pendingProps;var r=t.elementType,a=r._init;if(r=a(r._payload),t.type=r,"function"!=typeof r){if(null!=r){if((a=r.$$typeof)===S){t.tag=11,t=Ll(null,t,r,e,n);break e}if(a===E){t.tag=14,t=jl(null,t,r,e,n);break e}}throw t=N(r)||r,Error(i(306,t,""))}Fr(r)?(e=gl(r,e),t.tag=1,t=Fl(null,t,r,e,n)):(t.tag=0,t=Ml(null,t,r,e,n))}return t;case 0:return Ml(e,t,t.type,t.pendingProps,n);case 1:return Fl(e,t,r=t.type,a=gl(r,t.pendingProps),n);case 3:e:{if(V(t,t.stateNode.containerInfo),null===e)throw Error(i(387));r=t.pendingProps;var o=t.memoizedState;a=o.element,ro(e,t),co(t,r,null,n);var l=t.memoizedState;if(r=l.cache,va(0,Na,r),r!==o.cache&&Sa(t,[Na],n,!0),uo(),r=l.element,o.isDehydrated){if(o={element:r,isDehydrated:!1,cache:l.cache},t.updateQueue.baseState=o,t.memoizedState=o,256&t.flags){t=Il(e,t,r,n);break e}if(r!==a){ha(a=_r(Error(i(424)),t)),t=Il(e,t,r,n);break e}if(9===(e=t.stateNode.containerInfo).nodeType)e=e.body;else e="HTML"===e.nodeName?e.ownerDocument.body:e;for(aa=yd(e.firstChild),ra=t,oa=!0,ia=null,la=!0,n=rl(t,null,r,n),t.child=n;n;)n.flags=-3&n.flags|4096,n=n.sibling}else{if(pa(),r===a){t=Kl(e,t,n);break e}Tl(e,t,r,n)}t=t.child}return t;case 26:return Rl(e,t),null===e?(n=Td(t.type,null,t.pendingProps,null))?t.memoizedState=n:oa||(n=t.type,e=t.pendingProps,(r=rd(H.current).createElement(n))[Ne]=t,r[Oe]=e,ed(r,n,e),Ge(r),t.stateNode=r):t.memoizedState=Td(t.type,e.memoizedProps,t.pendingProps,e.memoizedState),null;case 27:return Q(t),null===e&&oa&&(r=t.stateNode=wd(t.type,t.pendingProps,H.current),ra=t,la=!0,a=aa,pd(t.type)?(bd=a,aa=yd(r.firstChild)):aa=a),Tl(e,t,t.pendingProps.children,n),Rl(e,t),null===e&&(t.flags|=4194304),t.child;case 5:return null===e&&oa&&((a=r=aa)&&(null!==(r=function(e,t,n,r){for(;1===e.nodeType;){var a=n;if(e.nodeName.toLowerCase()!==t.toLowerCase()){if(!r&&("INPUT"!==e.nodeName||"hidden"!==e.type))break}else if(r){if(!e[Be])switch(t){case"meta":if(!e.hasAttribute("itemprop"))break;return e;case"link":if("stylesheet"===(o=e.getAttribute("rel"))&&e.hasAttribute("data-precedence"))break;if(o!==a.rel||e.getAttribute("href")!==(null==a.href||""===a.href?null:a.href)||e.getAttribute("crossorigin")!==(null==a.crossOrigin?null:a.crossOrigin)||e.getAttribute("title")!==(null==a.title?null:a.title))break;return e;case"style":if(e.hasAttribute("data-precedence"))break;return e;case"script":if(((o=e.getAttribute("src"))!==(null==a.src?null:a.src)||e.getAttribute("type")!==(null==a.type?null:a.type)||e.getAttribute("crossorigin")!==(null==a.crossOrigin?null:a.crossOrigin))&&o&&e.hasAttribute("async")&&!e.hasAttribute("itemprop"))break;return e;default:return e}}else{if("input"!==t||"hidden"!==e.type)return e;var o=null==a.name?null:""+a.name;if("hidden"===a.type&&e.getAttribute("name")===o)return e}if(null===(e=yd(e.nextSibling)))break}return null}(r,t.type,t.pendingProps,la))?(t.stateNode=r,ra=t,aa=yd(r.firstChild),la=!1,a=!0):a=!1),a||ua(t)),Q(t),a=t.type,o=t.pendingProps,l=null!==e?e.memoizedProps:null,r=o.children,id(a,o)?r=null:null!==l&&id(a,l)&&(t.flags|=32),null!==t.memoizedState&&(a=No(e,t,Mo,null,null,n),Qd._currentValue=a),Rl(e,t),Tl(e,t,r,n),t.child;case 6:return null===e&&oa&&((e=n=aa)&&(null!==(n=function(e,t,n){if(""===t)return null;for(;3!==e.nodeType;){if((1!==e.nodeType||"INPUT"!==e.nodeName||"hidden"!==e.type)&&!n)return null;if(null===(e=yd(e.nextSibling)))return null}return e}(n,t.pendingProps,la))?(t.stateNode=n,ra=t,aa=null,e=!0):e=!1),e||ua(t)),null;case 13:return Ul(e,t,n);case 4:return V(t,t.stateNode.containerInfo),r=t.pendingProps,null===e?t.child=nl(t,null,r,n):Tl(e,t,r,n),t.child;case 11:return Ll(e,t,t.type,t.pendingProps,n);case 7:return Tl(e,t,t.pendingProps,n),t.child;case 8:case 12:return Tl(e,t,t.pendingProps.children,n),t.child;case 10:return r=t.pendingProps,va(0,t.type,r.value),Tl(e,t,r.children,n),t.child;case 9:return a=t.type._context,r=t.pendingProps.children,Ea(t),r=r(a=Ca(a)),t.flags|=1,Tl(e,t,r,n),t.child;case 14:return jl(e,t,t.type,t.pendingProps,n);case 15:return Pl(e,t,t.type,t.pendingProps,n);case 19:return Ql(e,t,n);case 31:return r=t.pendingProps,n=t.mode,r={mode:r.mode,children:r.children},null===e?((n=Hl(r,n)).ref=t.ref,t.child=n,n.return=t,t=n):((n=Ir(e.child,r)).ref=t.ref,t.child=n,n.return=t,t=n),t;case 22:return Nl(e,t,n);case 24:return Ea(t),r=Ca(Na),null===e?(null===(a=Ua())&&(a=ru,o=Oa(),a.pooledCache=o,o.refCount++,null!==o&&(a.pooledCacheLanes|=n),a=o),t.memoizedState={parent:r,cache:a},no(t),va(0,Na,a)):(0!==(e.lanes&n)&&(ro(e,t),co(t,null,null,n),uo()),a=e.memoizedState,o=t.memoizedState,a.parent!==r?(a={parent:r,cache:r},t.memoizedState=a,0===t.lanes&&(t.memoizedState=t.updateQueue.baseState=a),va(0,Na,r)):(r=o.cache,va(0,Na,r),r!==a.cache&&Sa(t,[Na],n,!0))),Tl(e,t,t.pendingProps.children,n),t.child;case 29:throw t.pendingProps}throw Error(i(156,t.tag))}function Zl(e){e.flags|=4}function Jl(e,t){if("stylesheet"!==t.type||4&t.state.loading)e.flags&=-16777217;else if(e.flags|=16777216,!$d(t)){if(null!==(t=al.current)&&((4194048&ou)===ou?null!==ol:(62914560&ou)!==ou&&!(536870912&ou)||t!==ol))throw Za=Qa,Va;e.flags|=8192}}function es(e,t){null!==t&&(e.flags|=4),16384&e.flags&&(t=22!==e.tag?xe():536870912,e.lanes|=t,yu|=t)}function ts(e,t){if(!oa)switch(e.tailMode){case"hidden":t=e.tail;for(var n=null;null!==t;)null!==t.alternate&&(n=t),t=t.sibling;null===n?e.tail=null:n.sibling=null;break;case"collapsed":n=e.tail;for(var r=null;null!==n;)null!==n.alternate&&(r=n),n=n.sibling;null===r?t||null===e.tail?e.tail=null:e.tail.sibling=null:r.sibling=null}}function ns(e){var t=null!==e.alternate&&e.alternate.child===e.child,n=0,r=0;if(t)for(var a=e.child;null!==a;)n|=a.lanes|a.childLanes,r|=65011712&a.subtreeFlags,r|=65011712&a.flags,a.return=e,a=a.sibling;else for(a=e.child;null!==a;)n|=a.lanes|a.childLanes,r|=a.subtreeFlags,r|=a.flags,a.return=e,a=a.sibling;return e.subtreeFlags|=r,e.childLanes=n,t}function rs(e,t,n){var r=t.pendingProps;switch(na(t),t.tag){case 31:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:case 1:return ns(t),null;case 3:return n=t.stateNode,r=null,null!==e&&(r=e.memoizedState.cache),t.memoizedState.cache!==r&&(t.flags|=2048),wa(Na),W(),n.pendingContext&&(n.context=n.pendingContext,n.pendingContext=null),null!==e&&null!==e.child||(fa(t)?Zl(t):null===e||e.memoizedState.isDehydrated&&!(256&t.flags)||(t.flags|=1024,ma())),ns(t),null;case 26:return n=t.memoizedState,null===e?(Zl(t),null!==n?(ns(t),Jl(t,n)):(ns(t),t.flags&=-16777217)):n?n!==e.memoizedState?(Zl(t),ns(t),Jl(t,n)):(ns(t),t.flags&=-16777217):(e.memoizedProps!==r&&Zl(t),ns(t),t.flags&=-16777217),null;case 27:K(t),n=H.current;var a=t.type;if(null!==e&&null!=t.stateNode)e.memoizedProps!==r&&Zl(t);else{if(!r){if(null===t.stateNode)throw Error(i(166));return ns(t),null}e=U.current,fa(t)?ca(t):(e=wd(a,r,n),t.stateNode=e,Zl(t))}return ns(t),null;case 5:if(K(t),n=t.type,null!==e&&null!=t.stateNode)e.memoizedProps!==r&&Zl(t);else{if(!r){if(null===t.stateNode)throw Error(i(166));return ns(t),null}if(e=U.current,fa(t))ca(t);else{switch(a=rd(H.current),e){case 1:e=a.createElementNS("http://www.w3.org/2000/svg",n);break;case 2:e=a.createElementNS("http://www.w3.org/1998/Math/MathML",n);break;default:switch(n){case"svg":e=a.createElementNS("http://www.w3.org/2000/svg",n);break;case"math":e=a.createElementNS("http://www.w3.org/1998/Math/MathML",n);break;case"script":(e=a.createElement("div")).innerHTML="<script><\/script>",e=e.removeChild(e.firstChild);break;case"select":e="string"==typeof r.is?a.createElement("select",{is:r.is}):a.createElement("select"),r.multiple?e.multiple=!0:r.size&&(e.size=r.size);break;default:e="string"==typeof r.is?a.createElement(n,{is:r.is}):a.createElement(n)}}e[Ne]=t,e[Oe]=r;e:for(a=t.child;null!==a;){if(5===a.tag||6===a.tag)e.appendChild(a.stateNode);else if(4!==a.tag&&27!==a.tag&&null!==a.child){a.child.return=a,a=a.child;continue}if(a===t)break e;for(;null===a.sibling;){if(null===a.return||a.return===t)break e;a=a.return}a.sibling.return=a.return,a=a.sibling}t.stateNode=e;e:switch(ed(e,n,r),n){case"button":case"input":case"select":case"textarea":e=!!r.autoFocus;break e;case"img":e=!0;break e;default:e=!1}e&&Zl(t)}}return ns(t),t.flags&=-16777217,null;case 6:if(e&&null!=t.stateNode)e.memoizedProps!==r&&Zl(t);else{if("string"!=typeof r&&null===t.stateNode)throw Error(i(166));if(e=H.current,fa(t)){if(e=t.stateNode,n=t.memoizedProps,r=null,null!==(a=ra))switch(a.tag){case 27:case 5:r=a.memoizedProps}e[Ne]=t,(e=!!(e.nodeValue===n||null!==r&&!0===r.suppressHydrationWarning||Yc(e.nodeValue,n)))||ua(t)}else(e=rd(e).createTextNode(r))[Ne]=t,t.stateNode=e}return ns(t),null;case 13:if(r=t.memoizedState,null===e||null!==e.memoizedState&&null!==e.memoizedState.dehydrated){if(a=fa(t),null!==r&&null!==r.dehydrated){if(null===e){if(!a)throw Error(i(318));if(!(a=null!==(a=t.memoizedState)?a.dehydrated:null))throw Error(i(317));a[Ne]=t}else pa(),!(128&t.flags)&&(t.memoizedState=null),t.flags|=4;ns(t),a=!1}else a=ma(),null!==e&&null!==e.memoizedState&&(e.memoizedState.hydrationErrors=a),a=!0;if(!a)return 256&t.flags?(ul(t),t):(ul(t),null)}if(ul(t),128&t.flags)return t.lanes=n,t;if(n=null!==r,e=null!==e&&null!==e.memoizedState,n){a=null,null!==(r=t.child).alternate&&null!==r.alternate.memoizedState&&null!==r.alternate.memoizedState.cachePool&&(a=r.alternate.memoizedState.cachePool.pool);var o=null;null!==r.memoizedState&&null!==r.memoizedState.cachePool&&(o=r.memoizedState.cachePool.pool),o!==a&&(r.flags|=2048)}return n!==e&&n&&(t.child.flags|=8192),es(t,t.updateQueue),ns(t),null;case 4:return W(),null===e&&zc(t.stateNode.containerInfo),ns(t),null;case 10:return wa(t.type),ns(t),null;case 19:if(z(cl),null===(a=t.memoizedState))return ns(t),null;if(r=!!(128&t.flags),null===(o=a.rendering))if(r)ts(a,!1);else{if(0!==fu||null!==e&&128&e.flags)for(e=t.child;null!==e;){if(null!==(o=dl(e))){for(t.flags|=128,ts(a,!1),e=o.updateQueue,t.updateQueue=e,es(t,e),t.subtreeFlags=0,e=n,n=t.child;null!==n;)Br(n,e),n=n.sibling;return $(cl,1&cl.current|2),t.child}e=e.sibling}null!==a.tail&&te()>Su&&(t.flags|=128,r=!0,ts(a,!1),t.lanes=4194304)}else{if(!r)if(null!==(e=dl(o))){if(t.flags|=128,r=!0,e=e.updateQueue,t.updateQueue=e,es(t,e),ts(a,!0),null===a.tail&&"hidden"===a.tailMode&&!o.alternate&&!oa)return ns(t),null}else 2*te()-a.renderingStartTime>Su&&536870912!==n&&(t.flags|=128,r=!0,ts(a,!1),t.lanes=4194304);a.isBackwards?(o.sibling=t.child,t.child=o):(null!==(e=a.last)?e.sibling=o:t.child=o,a.last=o)}return null!==a.tail?(t=a.tail,a.rendering=t,a.tail=t.sibling,a.renderingStartTime=te(),t.sibling=null,e=cl.current,$(cl,r?1&e|2:1&e),t):(ns(t),null);case 22:case 23:return ul(t),bo(),r=null!==t.memoizedState,null!==e?null!==e.memoizedState!==r&&(t.flags|=8192):r&&(t.flags|=8192),r?!!(536870912&n)&&!(128&t.flags)&&(ns(t),6&t.subtreeFlags&&(t.flags|=8192)):ns(t),null!==(n=t.updateQueue)&&es(t,n.retryQueue),n=null,null!==e&&null!==e.memoizedState&&null!==e.memoizedState.cachePool&&(n=e.memoizedState.cachePool.pool),r=null,null!==t.memoizedState&&null!==t.memoizedState.cachePool&&(r=t.memoizedState.cachePool.pool),r!==n&&(t.flags|=2048),null!==e&&z($a),null;case 24:return n=null,null!==e&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),wa(Na),ns(t),null;case 25:case 30:return null}throw Error(i(156,t.tag))}function as(e,t){switch(na(t),t.tag){case 1:return 65536&(e=t.flags)?(t.flags=-65537&e|128,t):null;case 3:return wa(Na),W(),65536&(e=t.flags)&&!(128&e)?(t.flags=-65537&e|128,t):null;case 26:case 27:case 5:return K(t),null;case 13:if(ul(t),null!==(e=t.memoizedState)&&null!==e.dehydrated){if(null===t.alternate)throw Error(i(340));pa()}return 65536&(e=t.flags)?(t.flags=-65537&e|128,t):null;case 19:return z(cl),null;case 4:return W(),null;case 10:return wa(t.type),null;case 22:case 23:return ul(t),bo(),null!==e&&z($a),65536&(e=t.flags)?(t.flags=-65537&e|128,t):null;case 24:return wa(Na),null;default:return null}}function os(e,t){switch(na(t),t.tag){case 3:wa(Na),W();break;case 26:case 27:case 5:K(t);break;case 4:W();break;case 13:ul(t);break;case 19:z(cl);break;case 10:wa(t.type);break;case 22:case 23:ul(t),bo(),null!==e&&z($a);break;case 24:wa(Na)}}function is(e,t){try{var n=t.updateQueue,r=null!==n?n.lastEffect:null;if(null!==r){var a=r.next;n=a;do{if((n.tag&e)===e){r=void 0;var o=n.create,i=n.inst;r=o(),i.destroy=r}n=n.next}while(n!==a)}}catch(l){cc(t,t.return,l)}}function ls(e,t,n){try{var r=t.updateQueue,a=null!==r?r.lastEffect:null;if(null!==a){var o=a.next;r=o;do{if((r.tag&e)===e){var i=r.inst,l=i.destroy;if(void 0!==l){i.destroy=void 0,a=t;var s=n,u=l;try{u()}catch(c){cc(a,s,c)}}}r=r.next}while(r!==o)}}catch(c){cc(t,t.return,c)}}function ss(e){var t=e.updateQueue;if(null!==t){var n=e.stateNode;try{po(t,n)}catch(r){cc(e,e.return,r)}}}function us(e,t,n){n.props=gl(e.type,e.memoizedProps),n.state=e.memoizedState;try{n.componentWillUnmount()}catch(r){cc(e,t,r)}}function cs(e,t){try{var n=e.ref;if(null!==n){switch(e.tag){case 26:case 27:case 5:var r=e.stateNode;break;default:r=e.stateNode}"function"==typeof n?e.refCleanup=n(r):n.current=r}}catch(a){cc(e,t,a)}}function ds(e,t){var n=e.ref,r=e.refCleanup;if(null!==n)if("function"==typeof r)try{r()}catch(a){cc(e,t,a)}finally{e.refCleanup=null,null!=(e=e.alternate)&&(e.refCleanup=null)}else if("function"==typeof n)try{n(null)}catch(o){cc(e,t,o)}else n.current=null}function fs(e){var t=e.type,n=e.memoizedProps,r=e.stateNode;try{e:switch(t){case"button":case"input":case"select":case"textarea":n.autoFocus&&r.focus();break e;case"img":n.src?r.src=n.src:n.srcSet&&(r.srcset=n.srcSet)}}catch(a){cc(e,e.return,a)}}function ps(e,t,n){try{var r=e.stateNode;!function(e,t,n,r){switch(t){case"div":case"span":case"svg":case"path":case"a":case"g":case"p":case"li":break;case"input":var a=null,o=null,l=null,s=null,u=null,c=null,d=null;for(m in n){var f=n[m];if(n.hasOwnProperty(m)&&null!=f)switch(m){case"checked":case"value":break;case"defaultValue":u=f;default:r.hasOwnProperty(m)||Zc(e,t,m,null,r,f)}}for(var p in r){var m=r[p];if(f=n[p],r.hasOwnProperty(p)&&(null!=m||null!=f))switch(p){case"type":o=m;break;case"name":a=m;break;case"checked":c=m;break;case"defaultChecked":d=m;break;case"value":l=m;break;case"defaultValue":s=m;break;case"children":case"dangerouslySetInnerHTML":if(null!=m)throw Error(i(137,t));break;default:m!==f&&Zc(e,t,p,m,r,f)}}return void gt(e,l,s,u,c,d,o,a);case"select":for(o in m=l=s=p=null,n)if(u=n[o],n.hasOwnProperty(o)&&null!=u)switch(o){case"value":break;case"multiple":m=u;default:r.hasOwnProperty(o)||Zc(e,t,o,null,r,u)}for(a in r)if(o=r[a],u=n[a],r.hasOwnProperty(a)&&(null!=o||null!=u))switch(a){case"value":p=o;break;case"defaultValue":s=o;break;case"multiple":l=o;default:o!==u&&Zc(e,t,a,o,r,u)}return t=s,n=l,r=m,void(null!=p?vt(e,!!n,p,!1):!!r!=!!n&&(null!=t?vt(e,!!n,t,!0):vt(e,!!n,n?[]:"",!1)));case"textarea":for(s in m=p=null,n)if(a=n[s],n.hasOwnProperty(s)&&null!=a&&!r.hasOwnProperty(s))switch(s){case"value":case"children":break;default:Zc(e,t,s,null,r,a)}for(l in r)if(a=r[l],o=n[l],r.hasOwnProperty(l)&&(null!=a||null!=o))switch(l){case"value":p=a;break;case"defaultValue":m=a;break;case"children":break;case"dangerouslySetInnerHTML":if(null!=a)throw Error(i(91));break;default:a!==o&&Zc(e,t,l,a,r,o)}return void wt(e,p,m);case"option":for(var h in n)if(p=n[h],n.hasOwnProperty(h)&&null!=p&&!r.hasOwnProperty(h))if("selected"===h)e.selected=!1;else Zc(e,t,h,null,r,p);for(u in r)if(p=r[u],m=n[u],r.hasOwnProperty(u)&&p!==m&&(null!=p||null!=m))if("selected"===u)e.selected=p&&"function"!=typeof p&&"symbol"!=typeof p;else Zc(e,t,u,p,r,m);return;case"img":case"link":case"area":case"base":case"br":case"col":case"embed":case"hr":case"keygen":case"meta":case"param":case"source":case"track":case"wbr":case"menuitem":for(var g in n)p=n[g],n.hasOwnProperty(g)&&null!=p&&!r.hasOwnProperty(g)&&Zc(e,t,g,null,r,p);for(c in r)if(p=r[c],m=n[c],r.hasOwnProperty(c)&&p!==m&&(null!=p||null!=m))switch(c){case"children":case"dangerouslySetInnerHTML":if(null!=p)throw Error(i(137,t));break;default:Zc(e,t,c,p,r,m)}return;default:if(Ct(t)){for(var y in n)p=n[y],n.hasOwnProperty(y)&&void 0!==p&&!r.hasOwnProperty(y)&&Jc(e,t,y,void 0,r,p);for(d in r)p=r[d],m=n[d],!r.hasOwnProperty(d)||p===m||void 0===p&&void 0===m||Jc(e,t,d,p,r,m);return}}for(var b in n)p=n[b],n.hasOwnProperty(b)&&null!=p&&!r.hasOwnProperty(b)&&Zc(e,t,b,null,r,p);for(f in r)p=r[f],m=n[f],!r.hasOwnProperty(f)||p===m||null==p&&null==m||Zc(e,t,f,p,r,m)}(r,e.type,n,t),r[Oe]=t}catch(a){cc(e,e.return,a)}}function ms(e){return 5===e.tag||3===e.tag||26===e.tag||27===e.tag&&pd(e.type)||4===e.tag}function hs(e){e:for(;;){for(;null===e.sibling;){if(null===e.return||ms(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;5!==e.tag&&6!==e.tag&&18!==e.tag;){if(27===e.tag&&pd(e.type))continue e;if(2&e.flags)continue e;if(null===e.child||4===e.tag)continue e;e.child.return=e,e=e.child}if(!(2&e.flags))return e.stateNode}}function gs(e,t,n){var r=e.tag;if(5===r||6===r)e=e.stateNode,t?(9===n.nodeType?n.body:"HTML"===n.nodeName?n.ownerDocument.body:n).insertBefore(e,t):((t=9===n.nodeType?n.body:"HTML"===n.nodeName?n.ownerDocument.body:n).appendChild(e),null!=(n=n._reactRootContainer)||null!==t.onclick||(t.onclick=Xc));else if(4!==r&&(27===r&&pd(e.type)&&(n=e.stateNode,t=null),null!==(e=e.child)))for(gs(e,t,n),e=e.sibling;null!==e;)gs(e,t,n),e=e.sibling}function ys(e,t,n){var r=e.tag;if(5===r||6===r)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(4!==r&&(27===r&&pd(e.type)&&(n=e.stateNode),null!==(e=e.child)))for(ys(e,t,n),e=e.sibling;null!==e;)ys(e,t,n),e=e.sibling}function bs(e){var t=e.stateNode,n=e.memoizedProps;try{for(var r=e.type,a=t.attributes;a.length;)t.removeAttributeNode(a[0]);ed(t,r,n),t[Ne]=e,t[Oe]=n}catch(o){cc(e,e.return,o)}}var vs=!1,ws=!1,ks=!1,Ss="function"==typeof WeakSet?WeakSet:Set,xs=null;function _s(e,t,n){var r=n.flags;switch(n.tag){case 0:case 11:case 15:Fs(e,n),4&r&&is(5,n);break;case 1:if(Fs(e,n),4&r)if(e=n.stateNode,null===t)try{e.componentDidMount()}catch(i){cc(n,n.return,i)}else{var a=gl(n.type,t.memoizedProps);t=t.memoizedState;try{e.componentDidUpdate(a,t,e.__reactInternalSnapshotBeforeUpdate)}catch(l){cc(n,n.return,l)}}64&r&&ss(n),512&r&&cs(n,n.return);break;case 3:if(Fs(e,n),64&r&&null!==(e=n.updateQueue)){if(t=null,null!==n.child)switch(n.child.tag){case 27:case 5:case 1:t=n.child.stateNode}try{po(e,t)}catch(i){cc(n,n.return,i)}}break;case 27:null===t&&4&r&&bs(n);case 26:case 5:Fs(e,n),null===t&&4&r&&fs(n),512&r&&cs(n,n.return);break;case 12:Fs(e,n);break;case 13:Fs(e,n),4&r&&js(e,n),64&r&&(null!==(e=n.memoizedState)&&(null!==(e=e.dehydrated)&&function(e,t){var n=e.ownerDocument;if("$?"!==e.data||"complete"===n.readyState)t();else{var r=function(){t(),n.removeEventListener("DOMContentLoaded",r)};n.addEventListener("DOMContentLoaded",r),e._reactRetry=r}}(e,n=mc.bind(null,n))));break;case 22:if(!(r=null!==n.memoizedState||vs)){t=null!==t&&null!==t.memoizedState||ws,a=vs;var o=ws;vs=r,(ws=t)&&!o?Bs(e,n,!!(8772&n.subtreeFlags)):Fs(e,n),vs=a,ws=o}break;case 30:break;default:Fs(e,n)}}function Es(e){var t=e.alternate;null!==t&&(e.alternate=null,Es(t)),e.child=null,e.deletions=null,e.sibling=null,5===e.tag&&(null!==(t=e.stateNode)&&ze(t)),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}var Cs=null,As=!1;function Ts(e,t,n){for(n=n.child;null!==n;)Ls(e,t,n),n=n.sibling}function Ls(e,t,n){if(de&&"function"==typeof de.onCommitFiberUnmount)try{de.onCommitFiberUnmount(ce,n)}catch(o){}switch(n.tag){case 26:ws||ds(n,t),Ts(e,t,n),n.memoizedState?n.memoizedState.count--:n.stateNode&&(n=n.stateNode).parentNode.removeChild(n);break;case 27:ws||ds(n,t);var r=Cs,a=As;pd(n.type)&&(Cs=n.stateNode,As=!1),Ts(e,t,n),kd(n.stateNode),Cs=r,As=a;break;case 5:ws||ds(n,t);case 6:if(r=Cs,a=As,Cs=null,Ts(e,t,n),As=a,null!==(Cs=r))if(As)try{(9===Cs.nodeType?Cs.body:"HTML"===Cs.nodeName?Cs.ownerDocument.body:Cs).removeChild(n.stateNode)}catch(i){cc(n,t,i)}else try{Cs.removeChild(n.stateNode)}catch(i){cc(n,t,i)}break;case 18:null!==Cs&&(As?(md(9===(e=Cs).nodeType?e.body:"HTML"===e.nodeName?e.ownerDocument.body:e,n.stateNode),Tf(e)):md(Cs,n.stateNode));break;case 4:r=Cs,a=As,Cs=n.stateNode.containerInfo,As=!0,Ts(e,t,n),Cs=r,As=a;break;case 0:case 11:case 14:case 15:ws||ls(2,n,t),ws||ls(4,n,t),Ts(e,t,n);break;case 1:ws||(ds(n,t),"function"==typeof(r=n.stateNode).componentWillUnmount&&us(n,t,r)),Ts(e,t,n);break;case 21:Ts(e,t,n);break;case 22:ws=(r=ws)||null!==n.memoizedState,Ts(e,t,n),ws=r;break;default:Ts(e,t,n)}}function js(e,t){if(null===t.memoizedState&&(null!==(e=t.alternate)&&(null!==(e=e.memoizedState)&&null!==(e=e.dehydrated))))try{Tf(e)}catch(n){cc(t,t.return,n)}}function Ps(e,t){var n=function(e){switch(e.tag){case 13:case 19:var t=e.stateNode;return null===t&&(t=e.stateNode=new Ss),t;case 22:return null===(t=(e=e.stateNode)._retryCache)&&(t=e._retryCache=new Ss),t;default:throw Error(i(435,e.tag))}}(e);t.forEach((function(t){var r=hc.bind(null,e,t);n.has(t)||(n.add(t),t.then(r,r))}))}function Ns(e,t){var n=t.deletions;if(null!==n)for(var r=0;r<n.length;r++){var a=n[r],o=e,l=t,s=l;e:for(;null!==s;){switch(s.tag){case 27:if(pd(s.type)){Cs=s.stateNode,As=!1;break e}break;case 5:Cs=s.stateNode,As=!1;break e;case 3:case 4:Cs=s.stateNode.containerInfo,As=!0;break e}s=s.return}if(null===Cs)throw Error(i(160));Ls(o,l,a),Cs=null,As=!1,null!==(o=a.alternate)&&(o.return=null),a.return=null}if(13878&t.subtreeFlags)for(t=t.child;null!==t;)Rs(t,e),t=t.sibling}var Os=null;function Rs(e,t){var n=e.alternate,r=e.flags;switch(e.tag){case 0:case 11:case 14:case 15:Ns(t,e),Ms(e),4&r&&(ls(3,e,e.return),is(3,e),ls(5,e,e.return));break;case 1:Ns(t,e),Ms(e),512&r&&(ws||null===n||ds(n,n.return)),64&r&&vs&&(null!==(e=e.updateQueue)&&(null!==(r=e.callbacks)&&(n=e.shared.hiddenCallbacks,e.shared.hiddenCallbacks=null===n?r:n.concat(r))));break;case 26:var a=Os;if(Ns(t,e),Ms(e),512&r&&(ws||null===n||ds(n,n.return)),4&r){var o=null!==n?n.memoizedState:null;if(r=e.memoizedState,null===n)if(null===r)if(null===e.stateNode){e:{r=e.type,n=e.memoizedProps,a=a.ownerDocument||a;t:switch(r){case"title":(!(o=a.getElementsByTagName("title")[0])||o[Be]||o[Ne]||"http://www.w3.org/2000/svg"===o.namespaceURI||o.hasAttribute("itemprop"))&&(o=a.createElement(r),a.head.insertBefore(o,a.querySelector("head > title"))),ed(o,r,n),o[Ne]=e,Ge(o),r=o;break e;case"link":var l=Bd("link","href",a).get(r+(n.href||""));if(l)for(var s=0;s<l.length;s++)if((o=l[s]).getAttribute("href")===(null==n.href||""===n.href?null:n.href)&&o.getAttribute("rel")===(null==n.rel?null:n.rel)&&o.getAttribute("title")===(null==n.title?null:n.title)&&o.getAttribute("crossorigin")===(null==n.crossOrigin?null:n.crossOrigin)){l.splice(s,1);break t}ed(o=a.createElement(r),r,n),a.head.appendChild(o);break;case"meta":if(l=Bd("meta","content",a).get(r+(n.content||"")))for(s=0;s<l.length;s++)if((o=l[s]).getAttribute("content")===(null==n.content?null:""+n.content)&&o.getAttribute("name")===(null==n.name?null:n.name)&&o.getAttribute("property")===(null==n.property?null:n.property)&&o.getAttribute("http-equiv")===(null==n.httpEquiv?null:n.httpEquiv)&&o.getAttribute("charset")===(null==n.charSet?null:n.charSet)){l.splice(s,1);break t}ed(o=a.createElement(r),r,n),a.head.appendChild(o);break;default:throw Error(i(468,r))}o[Ne]=e,Ge(o),r=o}e.stateNode=r}else zd(a,e.type,e.stateNode);else e.stateNode=Rd(a,r,e.memoizedProps);else o!==r?(null===o?null!==n.stateNode&&(n=n.stateNode).parentNode.removeChild(n):o.count--,null===r?zd(a,e.type,e.stateNode):Rd(a,r,e.memoizedProps)):null===r&&null!==e.stateNode&&ps(e,e.memoizedProps,n.memoizedProps)}break;case 27:Ns(t,e),Ms(e),512&r&&(ws||null===n||ds(n,n.return)),null!==n&&4&r&&ps(e,e.memoizedProps,n.memoizedProps);break;case 5:if(Ns(t,e),Ms(e),512&r&&(ws||null===n||ds(n,n.return)),32&e.flags){a=e.stateNode;try{St(a,"")}catch(m){cc(e,e.return,m)}}4&r&&null!=e.stateNode&&ps(e,a=e.memoizedProps,null!==n?n.memoizedProps:a),1024&r&&(ks=!0);break;case 6:if(Ns(t,e),Ms(e),4&r){if(null===e.stateNode)throw Error(i(162));r=e.memoizedProps,n=e.stateNode;try{n.nodeValue=r}catch(m){cc(e,e.return,m)}}break;case 3:if(Id=null,a=Os,Os=_d(t.containerInfo),Ns(t,e),Os=a,Ms(e),4&r&&null!==n&&n.memoizedState.isDehydrated)try{Tf(t.containerInfo)}catch(m){cc(e,e.return,m)}ks&&(ks=!1,Ds(e));break;case 4:r=Os,Os=_d(e.stateNode.containerInfo),Ns(t,e),Ms(e),Os=r;break;case 12:default:Ns(t,e),Ms(e);break;case 13:Ns(t,e),Ms(e),8192&e.child.flags&&null!==e.memoizedState!=(null!==n&&null!==n.memoizedState)&&(ku=te()),4&r&&(null!==(r=e.updateQueue)&&(e.updateQueue=null,Ps(e,r)));break;case 22:a=null!==e.memoizedState;var u=null!==n&&null!==n.memoizedState,c=vs,d=ws;if(vs=c||a,ws=d||u,Ns(t,e),ws=d,vs=c,Ms(e),8192&r)e:for(t=e.stateNode,t._visibility=a?-2&t._visibility:1|t._visibility,a&&(null===n||u||vs||ws||Is(e)),n=null,t=e;;){if(5===t.tag||26===t.tag){if(null===n){u=n=t;try{if(o=u.stateNode,a)"function"==typeof(l=o.style).setProperty?l.setProperty("display","none","important"):l.display="none";else{s=u.stateNode;var f=u.memoizedProps.style,p=null!=f&&f.hasOwnProperty("display")?f.display:null;s.style.display=null==p||"boolean"==typeof p?"":(""+p).trim()}}catch(m){cc(u,u.return,m)}}}else if(6===t.tag){if(null===n){u=t;try{u.stateNode.nodeValue=a?"":u.memoizedProps}catch(m){cc(u,u.return,m)}}}else if((22!==t.tag&&23!==t.tag||null===t.memoizedState||t===e)&&null!==t.child){t.child.return=t,t=t.child;continue}if(t===e)break e;for(;null===t.sibling;){if(null===t.return||t.return===e)break e;n===t&&(n=null),t=t.return}n===t&&(n=null),t.sibling.return=t.return,t=t.sibling}4&r&&(null!==(r=e.updateQueue)&&(null!==(n=r.retryQueue)&&(r.retryQueue=null,Ps(e,n))));break;case 19:Ns(t,e),Ms(e),4&r&&(null!==(r=e.updateQueue)&&(e.updateQueue=null,Ps(e,r)));case 30:case 21:}}function Ms(e){var t=e.flags;if(2&t){try{for(var n,r=e.return;null!==r;){if(ms(r)){n=r;break}r=r.return}if(null==n)throw Error(i(160));switch(n.tag){case 27:var a=n.stateNode;ys(e,hs(e),a);break;case 5:var o=n.stateNode;32&n.flags&&(St(o,""),n.flags&=-33),ys(e,hs(e),o);break;case 3:case 4:var l=n.stateNode.containerInfo;gs(e,hs(e),l);break;default:throw Error(i(161))}}catch(s){cc(e,e.return,s)}e.flags&=-3}4096&t&&(e.flags&=-4097)}function Ds(e){if(1024&e.subtreeFlags)for(e=e.child;null!==e;){var t=e;Ds(t),5===t.tag&&1024&t.flags&&t.stateNode.reset(),e=e.sibling}}function Fs(e,t){if(8772&t.subtreeFlags)for(t=t.child;null!==t;)_s(e,t.alternate,t),t=t.sibling}function Is(e){for(e=e.child;null!==e;){var t=e;switch(t.tag){case 0:case 11:case 14:case 15:ls(4,t,t.return),Is(t);break;case 1:ds(t,t.return);var n=t.stateNode;"function"==typeof n.componentWillUnmount&&us(t,t.return,n),Is(t);break;case 27:kd(t.stateNode);case 26:case 5:ds(t,t.return),Is(t);break;case 22:null===t.memoizedState&&Is(t);break;default:Is(t)}e=e.sibling}}function Bs(e,t,n){for(n=n&&!!(8772&t.subtreeFlags),t=t.child;null!==t;){var r=t.alternate,a=e,o=t,i=o.flags;switch(o.tag){case 0:case 11:case 15:Bs(a,o,n),is(4,o);break;case 1:if(Bs(a,o,n),"function"==typeof(a=(r=o).stateNode).componentDidMount)try{a.componentDidMount()}catch(u){cc(r,r.return,u)}if(null!==(a=(r=o).updateQueue)){var l=r.stateNode;try{var s=a.shared.hiddenCallbacks;if(null!==s)for(a.shared.hiddenCallbacks=null,a=0;a<s.length;a++)fo(s[a],l)}catch(u){cc(r,r.return,u)}}n&&64&i&&ss(o),cs(o,o.return);break;case 27:bs(o);case 26:case 5:Bs(a,o,n),n&&null===r&&4&i&&fs(o),cs(o,o.return);break;case 12:Bs(a,o,n);break;case 13:Bs(a,o,n),n&&4&i&&js(a,o);break;case 22:null===o.memoizedState&&Bs(a,o,n),cs(o,o.return);break;case 30:break;default:Bs(a,o,n)}t=t.sibling}}function zs(e,t){var n=null;null!==e&&null!==e.memoizedState&&null!==e.memoizedState.cachePool&&(n=e.memoizedState.cachePool.pool),e=null,null!==t.memoizedState&&null!==t.memoizedState.cachePool&&(e=t.memoizedState.cachePool.pool),e!==n&&(null!=e&&e.refCount++,null!=n&&Ra(n))}function $s(e,t){e=null,null!==t.alternate&&(e=t.alternate.memoizedState.cache),(t=t.memoizedState.cache)!==e&&(t.refCount++,null!=e&&Ra(e))}function Us(e,t,n,r){if(10256&t.subtreeFlags)for(t=t.child;null!==t;)qs(e,t,n,r),t=t.sibling}function qs(e,t,n,r){var a=t.flags;switch(t.tag){case 0:case 11:case 15:Us(e,t,n,r),2048&a&&is(9,t);break;case 1:case 13:default:Us(e,t,n,r);break;case 3:Us(e,t,n,r),2048&a&&(e=null,null!==t.alternate&&(e=t.alternate.memoizedState.cache),(t=t.memoizedState.cache)!==e&&(t.refCount++,null!=e&&Ra(e)));break;case 12:if(2048&a){Us(e,t,n,r),e=t.stateNode;try{var o=t.memoizedProps,i=o.id,l=o.onPostCommit;"function"==typeof l&&l(i,null===t.alternate?"mount":"update",e.passiveEffectDuration,-0)}catch(s){cc(t,t.return,s)}}else Us(e,t,n,r);break;case 23:break;case 22:o=t.stateNode,i=t.alternate,null!==t.memoizedState?2&o._visibility?Us(e,t,n,r):Gs(e,t):2&o._visibility?Us(e,t,n,r):(o._visibility|=2,Hs(e,t,n,r,!!(10256&t.subtreeFlags))),2048&a&&zs(i,t);break;case 24:Us(e,t,n,r),2048&a&&$s(t.alternate,t)}}function Hs(e,t,n,r,a){for(a=a&&!!(10256&t.subtreeFlags),t=t.child;null!==t;){var o=e,i=t,l=n,s=r,u=i.flags;switch(i.tag){case 0:case 11:case 15:Hs(o,i,l,s,a),is(8,i);break;case 23:break;case 22:var c=i.stateNode;null!==i.memoizedState?2&c._visibility?Hs(o,i,l,s,a):Gs(o,i):(c._visibility|=2,Hs(o,i,l,s,a)),a&&2048&u&&zs(i.alternate,i);break;case 24:Hs(o,i,l,s,a),a&&2048&u&&$s(i.alternate,i);break;default:Hs(o,i,l,s,a)}t=t.sibling}}function Gs(e,t){if(10256&t.subtreeFlags)for(t=t.child;null!==t;){var n=e,r=t,a=r.flags;switch(r.tag){case 22:Gs(n,r),2048&a&&zs(r.alternate,r);break;case 24:Gs(n,r),2048&a&&$s(r.alternate,r);break;default:Gs(n,r)}t=t.sibling}}var Vs=8192;function Ws(e){if(e.subtreeFlags&Vs)for(e=e.child;null!==e;)Qs(e),e=e.sibling}function Qs(e){switch(e.tag){case 26:Ws(e),e.flags&Vs&&null!==e.memoizedState&&function(e,t,n){if(null===Ud)throw Error(i(475));var r=Ud;if(!("stylesheet"!==t.type||"string"==typeof n.media&&!1===matchMedia(n.media).matches||4&t.state.loading)){if(null===t.instance){var a=Ld(n.href),o=e.querySelector(jd(a));if(o)return null!==(e=o._p)&&"object"==typeof e&&"function"==typeof e.then&&(r.count++,r=Hd.bind(r),e.then(r,r)),t.state.loading|=4,t.instance=o,void Ge(o);o=e.ownerDocument||e,n=Pd(n),(a=Sd.get(a))&&Dd(n,a),Ge(o=o.createElement("link"));var l=o;l._p=new Promise((function(e,t){l.onload=e,l.onerror=t})),ed(o,"link",n),t.instance=o}null===r.stylesheets&&(r.stylesheets=new Map),r.stylesheets.set(t,e),(e=t.state.preload)&&!(3&t.state.loading)&&(r.count++,t=Hd.bind(r),e.addEventListener("load",t),e.addEventListener("error",t))}}(Os,e.memoizedState,e.memoizedProps);break;case 5:default:Ws(e);break;case 3:case 4:var t=Os;Os=_d(e.stateNode.containerInfo),Ws(e),Os=t;break;case 22:null===e.memoizedState&&(null!==(t=e.alternate)&&null!==t.memoizedState?(t=Vs,Vs=16777216,Ws(e),Vs=t):Ws(e))}}function Ks(e){var t=e.alternate;if(null!==t&&null!==(e=t.child)){t.child=null;do{t=e.sibling,e.sibling=null,e=t}while(null!==e)}}function Ys(e){var t=e.deletions;if(16&e.flags){if(null!==t)for(var n=0;n<t.length;n++){var r=t[n];xs=r,Js(r,e)}Ks(e)}if(10256&e.subtreeFlags)for(e=e.child;null!==e;)Xs(e),e=e.sibling}function Xs(e){switch(e.tag){case 0:case 11:case 15:Ys(e),2048&e.flags&&ls(9,e,e.return);break;case 3:case 12:default:Ys(e);break;case 22:var t=e.stateNode;null!==e.memoizedState&&2&t._visibility&&(null===e.return||13!==e.return.tag)?(t._visibility&=-3,Zs(e)):Ys(e)}}function Zs(e){var t=e.deletions;if(16&e.flags){if(null!==t)for(var n=0;n<t.length;n++){var r=t[n];xs=r,Js(r,e)}Ks(e)}for(e=e.child;null!==e;){switch((t=e).tag){case 0:case 11:case 15:ls(8,t,t.return),Zs(t);break;case 22:2&(n=t.stateNode)._visibility&&(n._visibility&=-3,Zs(t));break;default:Zs(t)}e=e.sibling}}function Js(e,t){for(;null!==xs;){var n=xs;switch(n.tag){case 0:case 11:case 15:ls(8,n,t);break;case 23:case 22:if(null!==n.memoizedState&&null!==n.memoizedState.cachePool){var r=n.memoizedState.cachePool.pool;null!=r&&r.refCount++}break;case 24:Ra(n.memoizedState.cache)}if(null!==(r=n.child))r.return=n,xs=r;else e:for(n=e;null!==xs;){var a=(r=xs).sibling,o=r.return;if(Es(r),r===n){xs=null;break e}if(null!==a){a.return=o,xs=a;break e}xs=o}}}var eu={getCacheForType:function(e){var t=Ca(Na),n=t.data.get(e);return void 0===n&&(n=e(),t.data.set(e,n)),n}},tu="function"==typeof WeakMap?WeakMap:Map,nu=0,ru=null,au=null,ou=0,iu=0,lu=null,su=!1,uu=!1,cu=!1,du=0,fu=0,pu=0,mu=0,hu=0,gu=0,yu=0,bu=null,vu=null,wu=!1,ku=0,Su=1/0,xu=null,_u=null,Eu=0,Cu=null,Au=null,Tu=0,Lu=0,ju=null,Pu=null,Nu=0,Ou=null;function Ru(){if(2&nu&&0!==ou)return ou&-ou;if(null!==R.T){return 0!==Fa?Fa:Lc()}return je()}function Mu(){0===gu&&(gu=536870912&ou&&!oa?536870912:Se());var e=al.current;return null!==e&&(e.flags|=32),gu}function Du(e,t,n){(e!==ru||2!==iu&&9!==iu)&&null===e.cancelPendingCommit||(qu(e,0),zu(e,ou,gu,!1)),Ee(e,n),2&nu&&e===ru||(e===ru&&(!(2&nu)&&(mu|=n),4===fu&&zu(e,ou,gu,!1)),Sc(e))}function Fu(e,t,n){if(6&nu)throw Error(i(327));for(var r=!n&&!(124&t)&&0===(t&e.expiredLanes)||we(e,t),a=r?function(e,t){var n=nu;nu|=2;var r=Gu(),a=Vu();ru!==e||ou!==t?(xu=null,Su=te()+500,qu(e,t)):uu=we(e,t);e:for(;;)try{if(0!==iu&&null!==au){t=au;var o=lu;t:switch(iu){case 1:iu=0,lu=null,Ju(e,t,o,1);break;case 2:case 9:if(Ka(o)){iu=0,lu=null,Zu(t);break}t=function(){2!==iu&&9!==iu||ru!==e||(iu=7),Sc(e)},o.then(t,t);break e;case 3:iu=7;break e;case 4:iu=5;break e;case 7:Ka(o)?(iu=0,lu=null,Zu(t)):(iu=0,lu=null,Ju(e,t,o,7));break;case 5:var l=null;switch(au.tag){case 26:l=au.memoizedState;case 5:case 27:var s=au;if(!l||$d(l)){iu=0,lu=null;var u=s.sibling;if(null!==u)au=u;else{var c=s.return;null!==c?(au=c,ec(c)):au=null}break t}}iu=0,lu=null,Ju(e,t,o,5);break;case 6:iu=0,lu=null,Ju(e,t,o,6);break;case 8:Uu(),fu=6;break e;default:throw Error(i(462))}}Yu();break}catch(d){Hu(e,d)}return ba=ya=null,R.H=r,R.A=a,nu=n,null!==au?0:(ru=null,ou=0,Tr(),fu)}(e,t):Qu(e,t,!0),o=r;;){if(0===a){uu&&!r&&zu(e,t,0,!1);break}if(n=e.current.alternate,!o||Bu(n)){if(2===a){if(o=t,e.errorRecoveryDisabledLanes&o)var l=0;else l=0!==(l=-536870913&e.pendingLanes)?l:536870912&l?536870912:0;if(0!==l){t=l;e:{var s=e;a=bu;var u=s.current.memoizedState.isDehydrated;if(u&&(qu(s,l).flags|=256),2!==(l=Qu(s,l,!1))){if(cu&&!u){s.errorRecoveryDisabledLanes|=o,mu|=o,a=4;break e}o=vu,vu=a,null!==o&&(null===vu?vu=o:vu.push.apply(vu,o))}a=l}if(o=!1,2!==a)continue}}if(1===a){qu(e,0),zu(e,t,0,!0);break}e:{switch(r=e,o=a){case 0:case 1:throw Error(i(345));case 4:if((4194048&t)!==t)break;case 6:zu(r,t,gu,!su);break e;case 2:vu=null;break;case 3:case 5:break;default:throw Error(i(329))}if((62914560&t)===t&&10<(a=ku+300-te())){if(zu(r,t,gu,!su),0!==ve(r,0,!0))break e;r.timeoutHandle=sd(Iu.bind(null,r,n,vu,xu,wu,t,gu,mu,yu,su,o,2,-0,0),a)}else Iu(r,n,vu,xu,wu,t,gu,mu,yu,su,o,0,-0,0)}break}a=Qu(e,t,!1),o=!1}Sc(e)}function Iu(e,t,n,r,a,o,l,s,u,c,d,f,p,m){if(e.timeoutHandle=-1,(8192&(f=t.subtreeFlags)||!(16785408&~f))&&(Ud={stylesheets:null,count:0,unsuspend:qd},Qs(t),null!==(f=function(){if(null===Ud)throw Error(i(475));var e=Ud;return e.stylesheets&&0===e.count&&Vd(e,e.stylesheets),0<e.count?function(t){var n=setTimeout((function(){if(e.stylesheets&&Vd(e,e.stylesheets),e.unsuspend){var t=e.unsuspend;e.unsuspend=null,t()}}),6e4);return e.unsuspend=t,function(){e.unsuspend=null,clearTimeout(n)}}:null}())))return e.cancelPendingCommit=f(nc.bind(null,e,t,o,n,r,a,l,s,u,d,1,p,m)),void zu(e,o,l,!c);nc(e,t,o,n,r,a,l,s,u)}function Bu(e){for(var t=e;;){var n=t.tag;if((0===n||11===n||15===n)&&16384&t.flags&&(null!==(n=t.updateQueue)&&null!==(n=n.stores)))for(var r=0;r<n.length;r++){var a=n[r],o=a.getSnapshot;a=a.value;try{if(!Kn(o(),a))return!1}catch(i){return!1}}if(n=t.child,16384&t.subtreeFlags&&null!==n)n.return=t,t=n;else{if(t===e)break;for(;null===t.sibling;){if(null===t.return||t.return===e)return!0;t=t.return}t.sibling.return=t.return,t=t.sibling}}return!0}function zu(e,t,n,r){t&=~hu,t&=~mu,e.suspendedLanes|=t,e.pingedLanes&=~t,r&&(e.warmLanes|=t),r=e.expirationTimes;for(var a=t;0<a;){var o=31-pe(a),i=1<<o;r[o]=-1,a&=~i}0!==n&&Ce(e,n,t)}function $u(){return!!(6&nu)||(xc(0,!1),!1)}function Uu(){if(null!==au){if(0===iu)var e=au.return;else ba=ya=null,Io(e=au),Ki=null,Yi=0,e=au;for(;null!==e;)os(e.alternate,e),e=e.return;au=null}}function qu(e,t){var n=e.timeoutHandle;-1!==n&&(e.timeoutHandle=-1,ud(n)),null!==(n=e.cancelPendingCommit)&&(e.cancelPendingCommit=null,n()),Uu(),ru=e,au=n=Ir(e.current,null),ou=t,iu=0,lu=null,su=!1,uu=we(e,t),cu=!1,yu=gu=hu=mu=pu=fu=0,vu=bu=null,wu=!1,8&t&&(t|=32&t);var r=e.entangledLanes;if(0!==r)for(e=e.entanglements,r&=t;0<r;){var a=31-pe(r),o=1<<a;t|=e[a],r&=~o}return du=t,Tr(),n}function Hu(e,t){wo=null,R.H=Gi,t===Ga||t===Wa?(t=Ja(),iu=3):t===Va?(t=Ja(),iu=4):iu=t===Cl?8:null!==t&&"object"==typeof t&&"function"==typeof t.then?6:1,lu=t,null===au&&(fu=1,kl(e,_r(t,e.current)))}function Gu(){var e=R.H;return R.H=Gi,null===e?Gi:e}function Vu(){var e=R.A;return R.A=eu,e}function Wu(){fu=4,su||(4194048&ou)!==ou&&null!==al.current||(uu=!0),!(134217727&pu)&&!(134217727&mu)||null===ru||zu(ru,ou,gu,!1)}function Qu(e,t,n){var r=nu;nu|=2;var a=Gu(),o=Vu();ru===e&&ou===t||(xu=null,qu(e,t)),t=!1;var i=fu;e:for(;;)try{if(0!==iu&&null!==au){var l=au,s=lu;switch(iu){case 8:Uu(),i=6;break e;case 3:case 2:case 9:case 6:null===al.current&&(t=!0);var u=iu;if(iu=0,lu=null,Ju(e,l,s,u),n&&uu){i=0;break e}break;default:u=iu,iu=0,lu=null,Ju(e,l,s,u)}}Ku(),i=fu;break}catch(c){Hu(e,c)}return t&&e.shellSuspendCounter++,ba=ya=null,nu=r,R.H=a,R.A=o,null===au&&(ru=null,ou=0,Tr()),i}function Ku(){for(;null!==au;)Xu(au)}function Yu(){for(;null!==au&&!J();)Xu(au)}function Xu(e){var t=Xl(e.alternate,e,du);e.memoizedProps=e.pendingProps,null===t?ec(e):au=t}function Zu(e){var t=e,n=t.alternate;switch(t.tag){case 15:case 0:t=Dl(n,t,t.pendingProps,t.type,void 0,ou);break;case 11:t=Dl(n,t,t.pendingProps,t.type.render,t.ref,ou);break;case 5:Io(t);default:os(n,t),t=Xl(n,t=au=Br(t,du),du)}e.memoizedProps=e.pendingProps,null===t?ec(e):au=t}function Ju(e,t,n,r){ba=ya=null,Io(t),Ki=null,Yi=0;var a=t.return;try{if(function(e,t,n,r,a){if(n.flags|=32768,null!==r&&"object"==typeof r&&"function"==typeof r.then){if(null!==(t=n.alternate)&&xa(t,n,a,!0),null!==(n=al.current)){switch(n.tag){case 13:return null===ol?Wu():null===n.alternate&&0===fu&&(fu=3),n.flags&=-257,n.flags|=65536,n.lanes=a,r===Qa?n.flags|=16384:(null===(t=n.updateQueue)?n.updateQueue=new Set([r]):t.add(r),dc(e,r,a)),!1;case 22:return n.flags|=65536,r===Qa?n.flags|=16384:(null===(t=n.updateQueue)?(t={transitions:null,markerInstances:null,retryQueue:new Set([r])},n.updateQueue=t):null===(n=t.retryQueue)?t.retryQueue=new Set([r]):n.add(r),dc(e,r,a)),!1}throw Error(i(435,n.tag))}return dc(e,r,a),Wu(),!1}if(oa)return null!==(t=al.current)?(!(65536&t.flags)&&(t.flags|=256),t.flags|=65536,t.lanes=a,r!==sa&&ha(_r(e=Error(i(422),{cause:r}),n))):(r!==sa&&ha(_r(t=Error(i(423),{cause:r}),n)),(e=e.current.alternate).flags|=65536,a&=-a,e.lanes|=a,r=_r(r,n),lo(e,a=xl(e.stateNode,r,a)),4!==fu&&(fu=2)),!1;var o=Error(i(520),{cause:r});if(o=_r(o,n),null===bu?bu=[o]:bu.push(o),4!==fu&&(fu=2),null===t)return!0;r=_r(r,n),n=t;do{switch(n.tag){case 3:return n.flags|=65536,e=a&-a,n.lanes|=e,lo(n,e=xl(n.stateNode,r,e)),!1;case 1:if(t=n.type,o=n.stateNode,!(128&n.flags||"function"!=typeof t.getDerivedStateFromError&&(null===o||"function"!=typeof o.componentDidCatch||null!==_u&&_u.has(o))))return n.flags|=65536,a&=-a,n.lanes|=a,El(a=_l(a),e,n,r),lo(n,a),!1}n=n.return}while(null!==n);return!1}(e,a,t,n,ou))return fu=1,kl(e,_r(n,e.current)),void(au=null)}catch(o){if(null!==a)throw au=a,o;return fu=1,kl(e,_r(n,e.current)),void(au=null)}32768&t.flags?(oa||1===r?e=!0:uu||536870912&ou?e=!1:(su=e=!0,(2===r||9===r||3===r||6===r)&&(null!==(r=al.current)&&13===r.tag&&(r.flags|=16384))),tc(t,e)):ec(t)}function ec(e){var t=e;do{if(32768&t.flags)return void tc(t,su);e=t.return;var n=rs(t.alternate,t,du);if(null!==n)return void(au=n);if(null!==(t=t.sibling))return void(au=t);au=t=e}while(null!==t);0===fu&&(fu=5)}function tc(e,t){do{var n=as(e.alternate,e);if(null!==n)return n.flags&=32767,void(au=n);if(null!==(n=e.return)&&(n.flags|=32768,n.subtreeFlags=0,n.deletions=null),!t&&null!==(e=e.sibling))return void(au=e);au=e=n}while(null!==e);fu=6,au=null}function nc(e,t,n,r,a,o,l,s,u){e.cancelPendingCommit=null;do{lc()}while(0!==Eu);if(6&nu)throw Error(i(327));if(null!==t){if(t===e.current)throw Error(i(177));if(o=t.lanes|t.childLanes,function(e,t,n,r,a,o){var i=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var l=e.entanglements,s=e.expirationTimes,u=e.hiddenUpdates;for(n=i&~n;0<n;){var c=31-pe(n),d=1<<c;l[c]=0,s[c]=-1;var f=u[c];if(null!==f)for(u[c]=null,c=0;c<f.length;c++){var p=f[c];null!==p&&(p.lane&=-536870913)}n&=~d}0!==r&&Ce(e,r,0),0!==o&&0===a&&0!==e.tag&&(e.suspendedLanes|=o&~(i&~t))}(e,n,o|=Ar,l,s,u),e===ru&&(au=ru=null,ou=0),Au=t,Cu=e,Tu=n,Lu=o,ju=a,Pu=r,10256&t.subtreeFlags||10256&t.flags?(e.callbackNode=null,e.callbackPriority=0,X(oe,(function(){return sc(),null}))):(e.callbackNode=null,e.callbackPriority=0),r=!!(13878&t.flags),13878&t.subtreeFlags||r){r=R.T,R.T=null,a=M.p,M.p=2,l=nu,nu|=4;try{!function(e,t){if(e=e.containerInfo,td=nf,tr(e=er(e))){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{var r=(n=(n=e.ownerDocument)&&n.defaultView||window).getSelection&&n.getSelection();if(r&&0!==r.rangeCount){n=r.anchorNode;var a=r.anchorOffset,o=r.focusNode;r=r.focusOffset;try{n.nodeType,o.nodeType}catch(g){n=null;break e}var l=0,s=-1,u=-1,c=0,d=0,f=e,p=null;t:for(;;){for(var m;f!==n||0!==a&&3!==f.nodeType||(s=l+a),f!==o||0!==r&&3!==f.nodeType||(u=l+r),3===f.nodeType&&(l+=f.nodeValue.length),null!==(m=f.firstChild);)p=f,f=m;for(;;){if(f===e)break t;if(p===n&&++c===a&&(s=l),p===o&&++d===r&&(u=l),null!==(m=f.nextSibling))break;p=(f=p).parentNode}f=m}n=-1===s||-1===u?null:{start:s,end:u}}else n=null}n=n||{start:0,end:0}}else n=null;for(nd={focusedElem:e,selectionRange:n},nf=!1,xs=t;null!==xs;)if(e=(t=xs).child,1024&t.subtreeFlags&&null!==e)e.return=t,xs=e;else for(;null!==xs;){switch(o=(t=xs).alternate,e=t.flags,t.tag){case 0:case 11:case 15:case 5:case 26:case 27:case 6:case 4:case 17:break;case 1:if(1024&e&&null!==o){e=void 0,n=t,a=o.memoizedProps,o=o.memoizedState,r=n.stateNode;try{var h=gl(n.type,a,(n.elementType,n.type));e=r.getSnapshotBeforeUpdate(h,o),r.__reactInternalSnapshotBeforeUpdate=e}catch(y){cc(n,n.return,y)}}break;case 3:if(1024&e)if(9===(n=(e=t.stateNode.containerInfo).nodeType))hd(e);else if(1===n)switch(e.nodeName){case"HEAD":case"HTML":case"BODY":hd(e);break;default:e.textContent=""}break;default:if(1024&e)throw Error(i(163))}if(null!==(e=t.sibling)){e.return=t.return,xs=e;break}xs=t.return}}(e,t)}finally{nu=l,M.p=a,R.T=r}}Eu=1,rc(),ac(),oc()}}function rc(){if(1===Eu){Eu=0;var e=Cu,t=Au,n=!!(13878&t.flags);if(13878&t.subtreeFlags||n){n=R.T,R.T=null;var r=M.p;M.p=2;var a=nu;nu|=4;try{Rs(t,e);var o=nd,i=er(e.containerInfo),l=o.focusedElem,s=o.selectionRange;if(i!==l&&l&&l.ownerDocument&&Jn(l.ownerDocument.documentElement,l)){if(null!==s&&tr(l)){var u=s.start,c=s.end;if(void 0===c&&(c=u),"selectionStart"in l)l.selectionStart=u,l.selectionEnd=Math.min(c,l.value.length);else{var d=l.ownerDocument||document,f=d&&d.defaultView||window;if(f.getSelection){var p=f.getSelection(),m=l.textContent.length,h=Math.min(s.start,m),g=void 0===s.end?h:Math.min(s.end,m);!p.extend&&h>g&&(i=g,g=h,h=i);var y=Zn(l,h),b=Zn(l,g);if(y&&b&&(1!==p.rangeCount||p.anchorNode!==y.node||p.anchorOffset!==y.offset||p.focusNode!==b.node||p.focusOffset!==b.offset)){var v=d.createRange();v.setStart(y.node,y.offset),p.removeAllRanges(),h>g?(p.addRange(v),p.extend(b.node,b.offset)):(v.setEnd(b.node,b.offset),p.addRange(v))}}}}for(d=[],p=l;p=p.parentNode;)1===p.nodeType&&d.push({element:p,left:p.scrollLeft,top:p.scrollTop});for("function"==typeof l.focus&&l.focus(),l=0;l<d.length;l++){var w=d[l];w.element.scrollLeft=w.left,w.element.scrollTop=w.top}}nf=!!td,nd=td=null}finally{nu=a,M.p=r,R.T=n}}e.current=t,Eu=2}}function ac(){if(2===Eu){Eu=0;var e=Cu,t=Au,n=!!(8772&t.flags);if(8772&t.subtreeFlags||n){n=R.T,R.T=null;var r=M.p;M.p=2;var a=nu;nu|=4;try{_s(e,t.alternate,t)}finally{nu=a,M.p=r,R.T=n}}Eu=3}}function oc(){if(4===Eu||3===Eu){Eu=0,ee();var e=Cu,t=Au,n=Tu,r=Pu;10256&t.subtreeFlags||10256&t.flags?Eu=5:(Eu=0,Au=Cu=null,ic(e,e.pendingLanes));var a=e.pendingLanes;if(0===a&&(_u=null),Le(n),t=t.stateNode,de&&"function"==typeof de.onCommitFiberRoot)try{de.onCommitFiberRoot(ce,t,void 0,!(128&~t.current.flags))}catch(s){}if(null!==r){t=R.T,a=M.p,M.p=2,R.T=null;try{for(var o=e.onRecoverableError,i=0;i<r.length;i++){var l=r[i];o(l.value,{componentStack:l.stack})}}finally{R.T=t,M.p=a}}3&Tu&&lc(),Sc(e),a=e.pendingLanes,4194090&n&&42&a?e===Ou?Nu++:(Nu=0,Ou=e):Nu=0,xc(0,!1)}}function ic(e,t){0===(e.pooledCacheLanes&=t)&&(null!=(t=e.pooledCache)&&(e.pooledCache=null,Ra(t)))}function lc(e){return rc(),ac(),oc(),sc()}function sc(){if(5!==Eu)return!1;var e=Cu,t=Lu;Lu=0;var n=Le(Tu),r=R.T,a=M.p;try{M.p=32>n?32:n,R.T=null,n=ju,ju=null;var o=Cu,l=Tu;if(Eu=0,Au=Cu=null,Tu=0,6&nu)throw Error(i(331));var s=nu;if(nu|=4,Xs(o.current),qs(o,o.current,l,n),nu=s,xc(0,!1),de&&"function"==typeof de.onPostCommitFiberRoot)try{de.onPostCommitFiberRoot(ce,o)}catch(u){}return!0}finally{M.p=a,R.T=r,ic(e,t)}}function uc(e,t,n){t=_r(n,t),null!==(e=oo(e,t=xl(e.stateNode,t,2),2))&&(Ee(e,2),Sc(e))}function cc(e,t,n){if(3===e.tag)uc(e,e,n);else for(;null!==t;){if(3===t.tag){uc(t,e,n);break}if(1===t.tag){var r=t.stateNode;if("function"==typeof t.type.getDerivedStateFromError||"function"==typeof r.componentDidCatch&&(null===_u||!_u.has(r))){e=_r(n,e),null!==(r=oo(t,n=_l(2),2))&&(El(n,r,t,e),Ee(r,2),Sc(r));break}}t=t.return}}function dc(e,t,n){var r=e.pingCache;if(null===r){r=e.pingCache=new tu;var a=new Set;r.set(t,a)}else void 0===(a=r.get(t))&&(a=new Set,r.set(t,a));a.has(n)||(cu=!0,a.add(n),e=fc.bind(null,e,t,n),t.then(e,e))}function fc(e,t,n){var r=e.pingCache;null!==r&&r.delete(t),e.pingedLanes|=e.suspendedLanes&n,e.warmLanes&=~n,ru===e&&(ou&n)===n&&(4===fu||3===fu&&(62914560&ou)===ou&&300>te()-ku?!(2&nu)&&qu(e,0):hu|=n,yu===ou&&(yu=0)),Sc(e)}function pc(e,t){0===t&&(t=xe()),null!==(e=Pr(e,t))&&(Ee(e,t),Sc(e))}function mc(e){var t=e.memoizedState,n=0;null!==t&&(n=t.retryLane),pc(e,n)}function hc(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,a=e.memoizedState;null!==a&&(n=a.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(i(314))}null!==r&&r.delete(t),pc(e,n)}var gc=null,yc=null,bc=!1,vc=!1,wc=!1,kc=0;function Sc(e){e!==yc&&null===e.next&&(null===yc?gc=yc=e:yc=yc.next=e),vc=!0,bc||(bc=!0,dd((function(){6&nu?X(re,_c):Ec()})))}function xc(e,t){if(!wc&&vc){wc=!0;do{for(var n=!1,r=gc;null!==r;){if(!t)if(0!==e){var a=r.pendingLanes;if(0===a)var o=0;else{var i=r.suspendedLanes,l=r.pingedLanes;o=(1<<31-pe(42|e)+1)-1,o=201326741&(o&=a&~(i&~l))?201326741&o|1:o?2|o:0}0!==o&&(n=!0,Tc(r,o))}else o=ou,!(3&(o=ve(r,r===ru?o:0,null!==r.cancelPendingCommit||-1!==r.timeoutHandle)))||we(r,o)||(n=!0,Tc(r,o));r=r.next}}while(n);wc=!1}}function _c(){Ec()}function Ec(){vc=bc=!1;var e=0;0!==kc&&(function(){var e=window.event;if(e&&"popstate"===e.type)return e!==ld&&(ld=e,!0);return ld=null,!1}()&&(e=kc),kc=0);for(var t=te(),n=null,r=gc;null!==r;){var a=r.next,o=Cc(r,t);0===o?(r.next=null,null===n?gc=a:n.next=a,null===a&&(yc=n)):(n=r,(0!==e||3&o)&&(vc=!0)),r=a}xc(e,!1)}function Cc(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,a=e.expirationTimes,o=-62914561&e.pendingLanes;0<o;){var i=31-pe(o),l=1<<i,s=a[i];-1===s?0!==(l&n)&&0===(l&r)||(a[i]=ke(l,t)):s<=t&&(e.expiredLanes|=l),o&=~l}if(n=ou,n=ve(e,e===(t=ru)?n:0,null!==e.cancelPendingCommit||-1!==e.timeoutHandle),r=e.callbackNode,0===n||e===t&&(2===iu||9===iu)||null!==e.cancelPendingCommit)return null!==r&&null!==r&&Z(r),e.callbackNode=null,e.callbackPriority=0;if(!(3&n)||we(e,n)){if((t=n&-n)===e.callbackPriority)return t;switch(null!==r&&Z(r),Le(n)){case 2:case 8:n=ae;break;case 32:default:n=oe;break;case 268435456:n=le}return r=Ac.bind(null,e),n=X(n,r),e.callbackPriority=t,e.callbackNode=n,t}return null!==r&&null!==r&&Z(r),e.callbackPriority=2,e.callbackNode=null,2}function Ac(e,t){if(0!==Eu&&5!==Eu)return e.callbackNode=null,e.callbackPriority=0,null;var n=e.callbackNode;if(lc()&&e.callbackNode!==n)return null;var r=ou;return 0===(r=ve(e,e===ru?r:0,null!==e.cancelPendingCommit||-1!==e.timeoutHandle))?null:(Fu(e,r,t),Cc(e,te()),null!=e.callbackNode&&e.callbackNode===n?Ac.bind(null,e):null)}function Tc(e,t){if(lc())return null;Fu(e,t,!0)}function Lc(){return 0===kc&&(kc=Se()),kc}function jc(e){return null==e||"symbol"==typeof e||"boolean"==typeof e?null:"function"==typeof e?e:Lt(""+e)}function Pc(e,t){var n=t.ownerDocument.createElement("input");return n.name=t.name,n.value=t.value,e.id&&n.setAttribute("form",e.id),t.parentNode.insertBefore(n,t),e=new FormData(e),n.parentNode.removeChild(n),e}for(var Nc=0;Nc<kr.length;Nc++){var Oc=kr[Nc];Sr(Oc.toLowerCase(),"on"+(Oc[0].toUpperCase()+Oc.slice(1)))}Sr(pr,"onAnimationEnd"),Sr(mr,"onAnimationIteration"),Sr(hr,"onAnimationStart"),Sr("dblclick","onDoubleClick"),Sr("focusin","onFocus"),Sr("focusout","onBlur"),Sr(gr,"onTransitionRun"),Sr(yr,"onTransitionStart"),Sr(br,"onTransitionCancel"),Sr(vr,"onTransitionEnd"),Ke("onMouseEnter",["mouseout","mouseover"]),Ke("onMouseLeave",["mouseout","mouseover"]),Ke("onPointerEnter",["pointerout","pointerover"]),Ke("onPointerLeave",["pointerout","pointerover"]),Qe("onChange","change click focusin focusout input keydown keyup selectionchange".split(" ")),Qe("onSelect","focusout contextmenu dragend focusin keydown keyup mousedown mouseup selectionchange".split(" ")),Qe("onBeforeInput",["compositionend","keypress","textInput","paste"]),Qe("onCompositionEnd","compositionend focusout keydown keypress keyup mousedown".split(" ")),Qe("onCompositionStart","compositionstart focusout keydown keypress keyup mousedown".split(" ")),Qe("onCompositionUpdate","compositionupdate focusout keydown keypress keyup mousedown".split(" "));var Rc="abort canplay canplaythrough durationchange emptied encrypted ended error loadeddata loadedmetadata loadstart pause play playing progress ratechange resize seeked seeking stalled suspend timeupdate volumechange waiting".split(" "),Mc=new Set("beforetoggle cancel close invalid load scroll scrollend toggle".split(" ").concat(Rc));function Dc(e,t){t=!!(4&t);for(var n=0;n<e.length;n++){var r=e[n],a=r.event;r=r.listeners;e:{var o=void 0;if(t)for(var i=r.length-1;0<=i;i--){var l=r[i],s=l.instance,u=l.currentTarget;if(l=l.listener,s!==o&&a.isPropagationStopped())break e;o=l,a.currentTarget=u;try{o(a)}catch(c){yl(c)}a.currentTarget=null,o=s}else for(i=0;i<r.length;i++){if(s=(l=r[i]).instance,u=l.currentTarget,l=l.listener,s!==o&&a.isPropagationStopped())break e;o=l,a.currentTarget=u;try{o(a)}catch(c){yl(c)}a.currentTarget=null,o=s}}}}function Fc(e,t){var n=t[Me];void 0===n&&(n=t[Me]=new Set);var r=e+"__bubble";n.has(r)||($c(t,e,2,!1),n.add(r))}function Ic(e,t,n){var r=0;t&&(r|=4),$c(n,e,r,t)}var Bc="_reactListening"+Math.random().toString(36).slice(2);function zc(e){if(!e[Bc]){e[Bc]=!0,Ve.forEach((function(t){"selectionchange"!==t&&(Mc.has(t)||Ic(t,!1,e),Ic(t,!0,e))}));var t=9===e.nodeType?e:e.ownerDocument;null===t||t[Bc]||(t[Bc]=!0,Ic("selectionchange",!1,t))}}function $c(e,t,n,r){switch(cf(t)){case 2:var a=rf;break;case 8:a=af;break;default:a=of}n=a.bind(null,t,n,e),a=void 0,!Bt||"touchstart"!==t&&"touchmove"!==t&&"wheel"!==t||(a=!0),r?void 0!==a?e.addEventListener(t,n,{capture:!0,passive:a}):e.addEventListener(t,n,!0):void 0!==a?e.addEventListener(t,n,{passive:a}):e.addEventListener(t,n,!1)}function Uc(e,t,n,r,a){var o=r;if(!(1&t||2&t||null===r))e:for(;;){if(null===r)return;var i=r.tag;if(3===i||4===i){var l=r.stateNode.containerInfo;if(l===a)break;if(4===i)for(i=r.return;null!==i;){var u=i.tag;if((3===u||4===u)&&i.stateNode.containerInfo===a)return;i=i.return}for(;null!==l;){if(null===(i=$e(l)))return;if(5===(u=i.tag)||6===u||26===u||27===u){r=o=i;continue e}l=l.parentNode}}r=r.return}Dt((function(){var r=o,a=Pt(n),i=[];e:{var l=wr.get(e);if(void 0!==l){var u=Jt,c=e;switch(e){case"keypress":if(0===Gt(n))break e;case"keydown":case"keyup":u=hn;break;case"focusin":c="focus",u=on;break;case"focusout":c="blur",u=on;break;case"beforeblur":case"afterblur":u=on;break;case"click":if(2===n.button)break e;case"auxclick":case"dblclick":case"mousedown":case"mousemove":case"mouseup":case"mouseout":case"mouseover":case"contextmenu":u=rn;break;case"drag":case"dragend":case"dragenter":case"dragexit":case"dragleave":case"dragover":case"dragstart":case"drop":u=an;break;case"touchcancel":case"touchend":case"touchmove":case"touchstart":u=yn;break;case pr:case mr:case hr:u=ln;break;case vr:u=bn;break;case"scroll":case"scrollend":u=tn;break;case"wheel":u=vn;break;case"copy":case"cut":case"paste":u=sn;break;case"gotpointercapture":case"lostpointercapture":case"pointercancel":case"pointerdown":case"pointermove":case"pointerout":case"pointerover":case"pointerup":u=gn;break;case"toggle":case"beforetoggle":u=wn}var d=!!(4&t),f=!d&&("scroll"===e||"scrollend"===e),p=d?null!==l?l+"Capture":null:l;d=[];for(var m,h=r;null!==h;){var g=h;if(m=g.stateNode,5!==(g=g.tag)&&26!==g&&27!==g||null===m||null===p||null!=(g=Ft(h,p))&&d.push(qc(h,g,m)),f)break;h=h.return}0<d.length&&(l=new u(l,c,null,n,a),i.push({event:l,listeners:d}))}}if(!(7&t)){if(u="mouseout"===e||"pointerout"===e,(!(l="mouseover"===e||"pointerover"===e)||n===jt||!(c=n.relatedTarget||n.fromElement)||!$e(c)&&!c[Re])&&(u||l)&&(l=a.window===a?a:(l=a.ownerDocument)?l.defaultView||l.parentWindow:window,u?(u=r,null!==(c=(c=n.relatedTarget||n.toElement)?$e(c):null)&&(f=s(c),d=c.tag,c!==f||5!==d&&27!==d&&6!==d)&&(c=null)):(u=null,c=r),u!==c)){if(d=rn,g="onMouseLeave",p="onMouseEnter",h="mouse","pointerout"!==e&&"pointerover"!==e||(d=gn,g="onPointerLeave",p="onPointerEnter",h="pointer"),f=null==u?l:qe(u),m=null==c?l:qe(c),(l=new d(g,h+"leave",u,n,a)).target=f,l.relatedTarget=m,g=null,$e(a)===r&&((d=new d(p,h+"enter",c,n,a)).target=m,d.relatedTarget=f,g=d),f=g,u&&c)e:{for(p=c,h=0,m=d=u;m;m=Gc(m))h++;for(m=0,g=p;g;g=Gc(g))m++;for(;0<h-m;)d=Gc(d),h--;for(;0<m-h;)p=Gc(p),m--;for(;h--;){if(d===p||null!==p&&d===p.alternate)break e;d=Gc(d),p=Gc(p)}d=null}else d=null;null!==u&&Vc(i,l,u,d,!1),null!==c&&null!==f&&Vc(i,f,c,d,!0)}if("select"===(u=(l=r?qe(r):window).nodeName&&l.nodeName.toLowerCase())||"input"===u&&"file"===l.type)var y=In;else if(Nn(l))if(Bn)y=Qn;else{y=Vn;var b=Gn}else!(u=l.nodeName)||"input"!==u.toLowerCase()||"checkbox"!==l.type&&"radio"!==l.type?r&&Ct(r.elementType)&&(y=In):y=Wn;switch(y&&(y=y(e,r))?On(i,y,n,a):(b&&b(e,l,r),"focusout"===e&&r&&"number"===l.type&&null!=r.memoizedProps.value&&bt(l,"number",l.value)),b=r?qe(r):window,e){case"focusin":(Nn(b)||"true"===b.contentEditable)&&(rr=b,ar=r,or=null);break;case"focusout":or=ar=rr=null;break;case"mousedown":ir=!0;break;case"contextmenu":case"mouseup":case"dragend":ir=!1,lr(i,n,a);break;case"selectionchange":if(nr)break;case"keydown":case"keyup":lr(i,n,a)}var v;if(Sn)e:{switch(e){case"compositionstart":var w="onCompositionStart";break e;case"compositionend":w="onCompositionEnd";break e;case"compositionupdate":w="onCompositionUpdate";break e}w=void 0}else jn?Tn(e,n)&&(w="onCompositionEnd"):"keydown"===e&&229===n.keyCode&&(w="onCompositionStart");w&&(En&&"ko"!==n.locale&&(jn||"onCompositionStart"!==w?"onCompositionEnd"===w&&jn&&(v=Ht()):(Ut="value"in($t=a)?$t.value:$t.textContent,jn=!0)),0<(b=Hc(r,w)).length&&(w=new un(w,e,null,n,a),i.push({event:w,listeners:b}),v?w.data=v:null!==(v=Ln(n))&&(w.data=v))),(v=_n?function(e,t){switch(e){case"compositionend":return Ln(t);case"keypress":return 32!==t.which?null:(An=!0,Cn);case"textInput":return(e=t.data)===Cn&&An?null:e;default:return null}}(e,n):function(e,t){if(jn)return"compositionend"===e||!Sn&&Tn(e,t)?(e=Ht(),qt=Ut=$t=null,jn=!1,e):null;switch(e){case"paste":default:return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1<t.char.length)return t.char;if(t.which)return String.fromCharCode(t.which)}return null;case"compositionend":return En&&"ko"!==t.locale?null:t.data}}(e,n))&&(0<(w=Hc(r,"onBeforeInput")).length&&(b=new un("onBeforeInput","beforeinput",null,n,a),i.push({event:b,listeners:w}),b.data=v)),function(e,t,n,r,a){if("submit"===t&&n&&n.stateNode===a){var o=jc((a[Oe]||null).action),i=r.submitter;i&&null!==(t=(t=i[Oe]||null)?jc(t.formAction):i.getAttribute("formAction"))&&(o=t,i=null);var l=new Jt("action","action",null,r,a);e.push({event:l,listeners:[{instance:null,listener:function(){if(r.defaultPrevented){if(0!==kc){var e=i?Pc(a,i):new FormData(a);Pi(n,{pending:!0,data:e,method:a.method,action:o},null,e)}}else"function"==typeof o&&(l.preventDefault(),e=i?Pc(a,i):new FormData(a),Pi(n,{pending:!0,data:e,method:a.method,action:o},o,e))},currentTarget:a}]})}}(i,e,r,n,a)}Dc(i,t)}))}function qc(e,t,n){return{instance:e,listener:t,currentTarget:n}}function Hc(e,t){for(var n=t+"Capture",r=[];null!==e;){var a=e,o=a.stateNode;if(5!==(a=a.tag)&&26!==a&&27!==a||null===o||(null!=(a=Ft(e,n))&&r.unshift(qc(e,a,o)),null!=(a=Ft(e,t))&&r.push(qc(e,a,o))),3===e.tag)return r;e=e.return}return[]}function Gc(e){if(null===e)return null;do{e=e.return}while(e&&5!==e.tag&&27!==e.tag);return e||null}function Vc(e,t,n,r,a){for(var o=t._reactName,i=[];null!==n&&n!==r;){var l=n,s=l.alternate,u=l.stateNode;if(l=l.tag,null!==s&&s===r)break;5!==l&&26!==l&&27!==l||null===u||(s=u,a?null!=(u=Ft(n,o))&&i.unshift(qc(n,u,s)):a||null!=(u=Ft(n,o))&&i.push(qc(n,u,s))),n=n.return}0!==i.length&&e.push({event:t,listeners:i})}var Wc=/\r\n?/g,Qc=/\u0000|\uFFFD/g;function Kc(e){return("string"==typeof e?e:""+e).replace(Wc,"\n").replace(Qc,"")}function Yc(e,t){return t=Kc(t),Kc(e)===t}function Xc(){}function Zc(e,t,n,r,a,o){switch(n){case"children":"string"==typeof r?"body"===t||"textarea"===t&&""===r||St(e,r):("number"==typeof r||"bigint"==typeof r)&&"body"!==t&&St(e,""+r);break;case"className":nt(e,"class",r);break;case"tabIndex":nt(e,"tabindex",r);break;case"dir":case"role":case"viewBox":case"width":case"height":nt(e,n,r);break;case"style":Et(e,r,o);break;case"data":if("object"!==t){nt(e,"data",r);break}case"src":case"href":if(""===r&&("a"!==t||"href"!==n)){e.removeAttribute(n);break}if(null==r||"function"==typeof r||"symbol"==typeof r||"boolean"==typeof r){e.removeAttribute(n);break}r=Lt(""+r),e.setAttribute(n,r);break;case"action":case"formAction":if("function"==typeof r){e.setAttribute(n,"javascript:throw new Error('A React form was unexpectedly submitted. If you called form.submit() manually, consider using form.requestSubmit() instead. If you\\'re trying to use event.stopPropagation() in a submit event handler, consider also calling event.preventDefault().')");break}if("function"==typeof o&&("formAction"===n?("input"!==t&&Zc(e,t,"name",a.name,a,null),Zc(e,t,"formEncType",a.formEncType,a,null),Zc(e,t,"formMethod",a.formMethod,a,null),Zc(e,t,"formTarget",a.formTarget,a,null)):(Zc(e,t,"encType",a.encType,a,null),Zc(e,t,"method",a.method,a,null),Zc(e,t,"target",a.target,a,null))),null==r||"symbol"==typeof r||"boolean"==typeof r){e.removeAttribute(n);break}r=Lt(""+r),e.setAttribute(n,r);break;case"onClick":null!=r&&(e.onclick=Xc);break;case"onScroll":null!=r&&Fc("scroll",e);break;case"onScrollEnd":null!=r&&Fc("scrollend",e);break;case"dangerouslySetInnerHTML":if(null!=r){if("object"!=typeof r||!("__html"in r))throw Error(i(61));if(null!=(n=r.__html)){if(null!=a.children)throw Error(i(60));e.innerHTML=n}}break;case"multiple":e.multiple=r&&"function"!=typeof r&&"symbol"!=typeof r;break;case"muted":e.muted=r&&"function"!=typeof r&&"symbol"!=typeof r;break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"defaultValue":case"defaultChecked":case"innerHTML":case"ref":case"autoFocus":break;case"xlinkHref":if(null==r||"function"==typeof r||"boolean"==typeof r||"symbol"==typeof r){e.removeAttribute("xlink:href");break}n=Lt(""+r),e.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",n);break;case"contentEditable":case"spellCheck":case"draggable":case"value":case"autoReverse":case"externalResourcesRequired":case"focusable":case"preserveAlpha":null!=r&&"function"!=typeof r&&"symbol"!=typeof r?e.setAttribute(n,""+r):e.removeAttribute(n);break;case"inert":case"allowFullScreen":case"async":case"autoPlay":case"controls":case"default":case"defer":case"disabled":case"disablePictureInPicture":case"disableRemotePlayback":case"formNoValidate":case"hidden":case"loop":case"noModule":case"noValidate":case"open":case"playsInline":case"readOnly":case"required":case"reversed":case"scoped":case"seamless":case"itemScope":r&&"function"!=typeof r&&"symbol"!=typeof r?e.setAttribute(n,""):e.removeAttribute(n);break;case"capture":case"download":!0===r?e.setAttribute(n,""):!1!==r&&null!=r&&"function"!=typeof r&&"symbol"!=typeof r?e.setAttribute(n,r):e.removeAttribute(n);break;case"cols":case"rows":case"size":case"span":null!=r&&"function"!=typeof r&&"symbol"!=typeof r&&!isNaN(r)&&1<=r?e.setAttribute(n,r):e.removeAttribute(n);break;case"rowSpan":case"start":null==r||"function"==typeof r||"symbol"==typeof r||isNaN(r)?e.removeAttribute(n):e.setAttribute(n,r);break;case"popover":Fc("beforetoggle",e),Fc("toggle",e),tt(e,"popover",r);break;case"xlinkActuate":rt(e,"http://www.w3.org/1999/xlink","xlink:actuate",r);break;case"xlinkArcrole":rt(e,"http://www.w3.org/1999/xlink","xlink:arcrole",r);break;case"xlinkRole":rt(e,"http://www.w3.org/1999/xlink","xlink:role",r);break;case"xlinkShow":rt(e,"http://www.w3.org/1999/xlink","xlink:show",r);break;case"xlinkTitle":rt(e,"http://www.w3.org/1999/xlink","xlink:title",r);break;case"xlinkType":rt(e,"http://www.w3.org/1999/xlink","xlink:type",r);break;case"xmlBase":rt(e,"http://www.w3.org/XML/1998/namespace","xml:base",r);break;case"xmlLang":rt(e,"http://www.w3.org/XML/1998/namespace","xml:lang",r);break;case"xmlSpace":rt(e,"http://www.w3.org/XML/1998/namespace","xml:space",r);break;case"is":tt(e,"is",r);break;case"innerText":case"textContent":break;default:(!(2<n.length)||"o"!==n[0]&&"O"!==n[0]||"n"!==n[1]&&"N"!==n[1])&&tt(e,n=At.get(n)||n,r)}}function Jc(e,t,n,r,a,o){switch(n){case"style":Et(e,r,o);break;case"dangerouslySetInnerHTML":if(null!=r){if("object"!=typeof r||!("__html"in r))throw Error(i(61));if(null!=(n=r.__html)){if(null!=a.children)throw Error(i(60));e.innerHTML=n}}break;case"children":"string"==typeof r?St(e,r):("number"==typeof r||"bigint"==typeof r)&&St(e,""+r);break;case"onScroll":null!=r&&Fc("scroll",e);break;case"onScrollEnd":null!=r&&Fc("scrollend",e);break;case"onClick":null!=r&&(e.onclick=Xc);break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"innerHTML":case"ref":case"innerText":case"textContent":break;default:We.hasOwnProperty(n)||("o"!==n[0]||"n"!==n[1]||(a=n.endsWith("Capture"),t=n.slice(2,a?n.length-7:void 0),"function"==typeof(o=null!=(o=e[Oe]||null)?o[n]:null)&&e.removeEventListener(t,o,a),"function"!=typeof r)?n in e?e[n]=r:!0===r?e.setAttribute(n,""):tt(e,n,r):("function"!=typeof o&&null!==o&&(n in e?e[n]=null:e.hasAttribute(n)&&e.removeAttribute(n)),e.addEventListener(t,r,a)))}}function ed(e,t,n){switch(t){case"div":case"span":case"svg":case"path":case"a":case"g":case"p":case"li":break;case"img":Fc("error",e),Fc("load",e);var r,a=!1,o=!1;for(r in n)if(n.hasOwnProperty(r)){var l=n[r];if(null!=l)switch(r){case"src":a=!0;break;case"srcSet":o=!0;break;case"children":case"dangerouslySetInnerHTML":throw Error(i(137,t));default:Zc(e,t,r,l,n,null)}}return o&&Zc(e,t,"srcSet",n.srcSet,n,null),void(a&&Zc(e,t,"src",n.src,n,null));case"input":Fc("invalid",e);var s=r=l=o=null,u=null,c=null;for(a in n)if(n.hasOwnProperty(a)){var d=n[a];if(null!=d)switch(a){case"name":o=d;break;case"type":l=d;break;case"checked":u=d;break;case"defaultChecked":c=d;break;case"value":r=d;break;case"defaultValue":s=d;break;case"children":case"dangerouslySetInnerHTML":if(null!=d)throw Error(i(137,t));break;default:Zc(e,t,a,d,n,null)}}return yt(e,r,s,u,c,l,o,!1),void dt(e);case"select":for(o in Fc("invalid",e),a=l=r=null,n)if(n.hasOwnProperty(o)&&null!=(s=n[o]))switch(o){case"value":r=s;break;case"defaultValue":l=s;break;case"multiple":a=s;default:Zc(e,t,o,s,n,null)}return t=r,n=l,e.multiple=!!a,void(null!=t?vt(e,!!a,t,!1):null!=n&&vt(e,!!a,n,!0));case"textarea":for(l in Fc("invalid",e),r=o=a=null,n)if(n.hasOwnProperty(l)&&null!=(s=n[l]))switch(l){case"value":a=s;break;case"defaultValue":o=s;break;case"children":r=s;break;case"dangerouslySetInnerHTML":if(null!=s)throw Error(i(91));break;default:Zc(e,t,l,s,n,null)}return kt(e,a,o,r),void dt(e);case"option":for(u in n)if(n.hasOwnProperty(u)&&null!=(a=n[u]))if("selected"===u)e.selected=a&&"function"!=typeof a&&"symbol"!=typeof a;else Zc(e,t,u,a,n,null);return;case"dialog":Fc("beforetoggle",e),Fc("toggle",e),Fc("cancel",e),Fc("close",e);break;case"iframe":case"object":Fc("load",e);break;case"video":case"audio":for(a=0;a<Rc.length;a++)Fc(Rc[a],e);break;case"image":Fc("error",e),Fc("load",e);break;case"details":Fc("toggle",e);break;case"embed":case"source":case"link":Fc("error",e),Fc("load",e);case"area":case"base":case"br":case"col":case"hr":case"keygen":case"meta":case"param":case"track":case"wbr":case"menuitem":for(c in n)if(n.hasOwnProperty(c)&&null!=(a=n[c]))switch(c){case"children":case"dangerouslySetInnerHTML":throw Error(i(137,t));default:Zc(e,t,c,a,n,null)}return;default:if(Ct(t)){for(d in n)n.hasOwnProperty(d)&&(void 0!==(a=n[d])&&Jc(e,t,d,a,n,void 0));return}}for(s in n)n.hasOwnProperty(s)&&(null!=(a=n[s])&&Zc(e,t,s,a,n,null))}var td=null,nd=null;function rd(e){return 9===e.nodeType?e:e.ownerDocument}function ad(e){switch(e){case"http://www.w3.org/2000/svg":return 1;case"http://www.w3.org/1998/Math/MathML":return 2;default:return 0}}function od(e,t){if(0===e)switch(t){case"svg":return 1;case"math":return 2;default:return 0}return 1===e&&"foreignObject"===t?0:e}function id(e,t){return"textarea"===e||"noscript"===e||"string"==typeof t.children||"number"==typeof t.children||"bigint"==typeof t.children||"object"==typeof t.dangerouslySetInnerHTML&&null!==t.dangerouslySetInnerHTML&&null!=t.dangerouslySetInnerHTML.__html}var ld=null;var sd="function"==typeof setTimeout?setTimeout:void 0,ud="function"==typeof clearTimeout?clearTimeout:void 0,cd="function"==typeof Promise?Promise:void 0,dd="function"==typeof queueMicrotask?queueMicrotask:void 0!==cd?function(e){return cd.resolve(null).then(e).catch(fd)}:sd;function fd(e){setTimeout((function(){throw e}))}function pd(e){return"head"===e}function md(e,t){var n=t,r=0,a=0;do{var o=n.nextSibling;if(e.removeChild(n),o&&8===o.nodeType)if("/$"===(n=o.data)){if(0<r&&8>r){n=r;var i=e.ownerDocument;if(1&n&&kd(i.documentElement),2&n&&kd(i.body),4&n)for(kd(n=i.head),i=n.firstChild;i;){var l=i.nextSibling,s=i.nodeName;i[Be]||"SCRIPT"===s||"STYLE"===s||"LINK"===s&&"stylesheet"===i.rel.toLowerCase()||n.removeChild(i),i=l}}if(0===a)return e.removeChild(o),void Tf(t);a--}else"$"===n||"$?"===n||"$!"===n?a++:r=n.charCodeAt(0)-48;else r=0;n=o}while(n);Tf(t)}function hd(e){var t=e.firstChild;for(t&&10===t.nodeType&&(t=t.nextSibling);t;){var n=t;switch(t=t.nextSibling,n.nodeName){case"HTML":case"HEAD":case"BODY":hd(n),ze(n);continue;case"SCRIPT":case"STYLE":continue;case"LINK":if("stylesheet"===n.rel.toLowerCase())continue}e.removeChild(n)}}function gd(e){return"$!"===e.data||"$?"===e.data&&"complete"===e.ownerDocument.readyState}function yd(e){for(;null!=e;e=e.nextSibling){var t=e.nodeType;if(1===t||3===t)break;if(8===t){if("$"===(t=e.data)||"$!"===t||"$?"===t||"F!"===t||"F"===t)break;if("/$"===t)return null}}return e}var bd=null;function vd(e){e=e.previousSibling;for(var t=0;e;){if(8===e.nodeType){var n=e.data;if("$"===n||"$!"===n||"$?"===n){if(0===t)return e;t--}else"/$"===n&&t++}e=e.previousSibling}return null}function wd(e,t,n){switch(t=rd(n),e){case"html":if(!(e=t.documentElement))throw Error(i(452));return e;case"head":if(!(e=t.head))throw Error(i(453));return e;case"body":if(!(e=t.body))throw Error(i(454));return e;default:throw Error(i(451))}}function kd(e){for(var t=e.attributes;t.length;)e.removeAttributeNode(t[0]);ze(e)}var Sd=new Map,xd=new Set;function _d(e){return"function"==typeof e.getRootNode?e.getRootNode():9===e.nodeType?e:e.ownerDocument}var Ed=M.d;M.d={f:function(){var e=Ed.f(),t=$u();return e||t},r:function(e){var t=Ue(e);null!==t&&5===t.tag&&"form"===t.type?Oi(t):Ed.r(e)},D:function(e){Ed.D(e),Ad("dns-prefetch",e,null)},C:function(e,t){Ed.C(e,t),Ad("preconnect",e,t)},L:function(e,t,n){Ed.L(e,t,n);var r=Cd;if(r&&e&&t){var a='link[rel="preload"][as="'+ht(t)+'"]';"image"===t&&n&&n.imageSrcSet?(a+='[imagesrcset="'+ht(n.imageSrcSet)+'"]',"string"==typeof n.imageSizes&&(a+='[imagesizes="'+ht(n.imageSizes)+'"]')):a+='[href="'+ht(e)+'"]';var o=a;switch(t){case"style":o=Ld(e);break;case"script":o=Nd(e)}Sd.has(o)||(e=f({rel:"preload",href:"image"===t&&n&&n.imageSrcSet?void 0:e,as:t},n),Sd.set(o,e),null!==r.querySelector(a)||"style"===t&&r.querySelector(jd(o))||"script"===t&&r.querySelector(Od(o))||(ed(t=r.createElement("link"),"link",e),Ge(t),r.head.appendChild(t)))}},m:function(e,t){Ed.m(e,t);var n=Cd;if(n&&e){var r=t&&"string"==typeof t.as?t.as:"script",a='link[rel="modulepreload"][as="'+ht(r)+'"][href="'+ht(e)+'"]',o=a;switch(r){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":o=Nd(e)}if(!Sd.has(o)&&(e=f({rel:"modulepreload",href:e},t),Sd.set(o,e),null===n.querySelector(a))){switch(r){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(n.querySelector(Od(o)))return}ed(r=n.createElement("link"),"link",e),Ge(r),n.head.appendChild(r)}}},X:function(e,t){Ed.X(e,t);var n=Cd;if(n&&e){var r=He(n).hoistableScripts,a=Nd(e),o=r.get(a);o||((o=n.querySelector(Od(a)))||(e=f({src:e,async:!0},t),(t=Sd.get(a))&&Fd(e,t),Ge(o=n.createElement("script")),ed(o,"link",e),n.head.appendChild(o)),o={type:"script",instance:o,count:1,state:null},r.set(a,o))}},S:function(e,t,n){Ed.S(e,t,n);var r=Cd;if(r&&e){var a=He(r).hoistableStyles,o=Ld(e);t=t||"default";var i=a.get(o);if(!i){var l={loading:0,preload:null};if(i=r.querySelector(jd(o)))l.loading=5;else{e=f({rel:"stylesheet",href:e,"data-precedence":t},n),(n=Sd.get(o))&&Dd(e,n);var s=i=r.createElement("link");Ge(s),ed(s,"link",e),s._p=new Promise((function(e,t){s.onload=e,s.onerror=t})),s.addEventListener("load",(function(){l.loading|=1})),s.addEventListener("error",(function(){l.loading|=2})),l.loading|=4,Md(i,t,r)}i={type:"stylesheet",instance:i,count:1,state:l},a.set(o,i)}}},M:function(e,t){Ed.M(e,t);var n=Cd;if(n&&e){var r=He(n).hoistableScripts,a=Nd(e),o=r.get(a);o||((o=n.querySelector(Od(a)))||(e=f({src:e,async:!0,type:"module"},t),(t=Sd.get(a))&&Fd(e,t),Ge(o=n.createElement("script")),ed(o,"link",e),n.head.appendChild(o)),o={type:"script",instance:o,count:1,state:null},r.set(a,o))}}};var Cd="undefined"==typeof document?null:document;function Ad(e,t,n){var r=Cd;if(r&&"string"==typeof t&&t){var a=ht(t);a='link[rel="'+e+'"][href="'+a+'"]',"string"==typeof n&&(a+='[crossorigin="'+n+'"]'),xd.has(a)||(xd.add(a),e={rel:e,crossOrigin:n,href:t},null===r.querySelector(a)&&(ed(t=r.createElement("link"),"link",e),Ge(t),r.head.appendChild(t)))}}function Td(e,t,n,r){var a,o,l,s,u=(u=H.current)?_d(u):null;if(!u)throw Error(i(446));switch(e){case"meta":case"title":return null;case"style":return"string"==typeof n.precedence&&"string"==typeof n.href?(t=Ld(n.href),(r=(n=He(u).hoistableStyles).get(t))||(r={type:"style",instance:null,count:0,state:null},n.set(t,r)),r):{type:"void",instance:null,count:0,state:null};case"link":if("stylesheet"===n.rel&&"string"==typeof n.href&&"string"==typeof n.precedence){e=Ld(n.href);var c=He(u).hoistableStyles,d=c.get(e);if(d||(u=u.ownerDocument||u,d={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},c.set(e,d),(c=u.querySelector(jd(e)))&&!c._p&&(d.instance=c,d.state.loading=5),Sd.has(e)||(n={rel:"preload",as:"style",href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},Sd.set(e,n),c||(a=u,o=e,l=n,s=d.state,a.querySelector('link[rel="preload"][as="style"]['+o+"]")?s.loading=1:(o=a.createElement("link"),s.preload=o,o.addEventListener("load",(function(){return s.loading|=1})),o.addEventListener("error",(function(){return s.loading|=2})),ed(o,"link",l),Ge(o),a.head.appendChild(o))))),t&&null===r)throw Error(i(528,""));return d}if(t&&null!==r)throw Error(i(529,""));return null;case"script":return t=n.async,"string"==typeof(n=n.src)&&t&&"function"!=typeof t&&"symbol"!=typeof t?(t=Nd(n),(r=(n=He(u).hoistableScripts).get(t))||(r={type:"script",instance:null,count:0,state:null},n.set(t,r)),r):{type:"void",instance:null,count:0,state:null};default:throw Error(i(444,e))}}function Ld(e){return'href="'+ht(e)+'"'}function jd(e){return'link[rel="stylesheet"]['+e+"]"}function Pd(e){return f({},e,{"data-precedence":e.precedence,precedence:null})}function Nd(e){return'[src="'+ht(e)+'"]'}function Od(e){return"script[async]"+e}function Rd(e,t,n){if(t.count++,null===t.instance)switch(t.type){case"style":var r=e.querySelector('style[data-href~="'+ht(n.href)+'"]');if(r)return t.instance=r,Ge(r),r;var a=f({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return Ge(r=(e.ownerDocument||e).createElement("style")),ed(r,"style",a),Md(r,n.precedence,e),t.instance=r;case"stylesheet":a=Ld(n.href);var o=e.querySelector(jd(a));if(o)return t.state.loading|=4,t.instance=o,Ge(o),o;r=Pd(n),(a=Sd.get(a))&&Dd(r,a),Ge(o=(e.ownerDocument||e).createElement("link"));var l=o;return l._p=new Promise((function(e,t){l.onload=e,l.onerror=t})),ed(o,"link",r),t.state.loading|=4,Md(o,n.precedence,e),t.instance=o;case"script":return o=Nd(n.src),(a=e.querySelector(Od(o)))?(t.instance=a,Ge(a),a):(r=n,(a=Sd.get(o))&&Fd(r=f({},n),a),Ge(a=(e=e.ownerDocument||e).createElement("script")),ed(a,"link",r),e.head.appendChild(a),t.instance=a);case"void":return null;default:throw Error(i(443,t.type))}else"stylesheet"===t.type&&!(4&t.state.loading)&&(r=t.instance,t.state.loading|=4,Md(r,n.precedence,e));return t.instance}function Md(e,t,n){for(var r=n.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),a=r.length?r[r.length-1]:null,o=a,i=0;i<r.length;i++){var l=r[i];if(l.dataset.precedence===t)o=l;else if(o!==a)break}o?o.parentNode.insertBefore(e,o.nextSibling):(t=9===n.nodeType?n.head:n).insertBefore(e,t.firstChild)}function Dd(e,t){null==e.crossOrigin&&(e.crossOrigin=t.crossOrigin),null==e.referrerPolicy&&(e.referrerPolicy=t.referrerPolicy),null==e.title&&(e.title=t.title)}function Fd(e,t){null==e.crossOrigin&&(e.crossOrigin=t.crossOrigin),null==e.referrerPolicy&&(e.referrerPolicy=t.referrerPolicy),null==e.integrity&&(e.integrity=t.integrity)}var Id=null;function Bd(e,t,n){if(null===Id){var r=new Map,a=Id=new Map;a.set(n,r)}else(r=(a=Id).get(n))||(r=new Map,a.set(n,r));if(r.has(e))return r;for(r.set(e,null),n=n.getElementsByTagName(e),a=0;a<n.length;a++){var o=n[a];if(!(o[Be]||o[Ne]||"link"===e&&"stylesheet"===o.getAttribute("rel"))&&"http://www.w3.org/2000/svg"!==o.namespaceURI){var i=o.getAttribute(t)||"";i=e+i;var l=r.get(i);l?l.push(o):r.set(i,[o])}}return r}function zd(e,t,n){(e=e.ownerDocument||e).head.insertBefore(n,"title"===t?e.querySelector("head > title"):null)}function $d(e){return!!("stylesheet"!==e.type||3&e.state.loading)}var Ud=null;function qd(){}function Hd(){if(this.count--,0===this.count)if(this.stylesheets)Vd(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}var Gd=null;function Vd(e,t){e.stylesheets=null,null!==e.unsuspend&&(e.count++,Gd=new Map,t.forEach(Wd,e),Gd=null,Hd.call(e))}function Wd(e,t){if(!(4&t.state.loading)){var n=Gd.get(e);if(n)var r=n.get(null);else{n=new Map,Gd.set(e,n);for(var a=e.querySelectorAll("link[data-precedence],style[data-precedence]"),o=0;o<a.length;o++){var i=a[o];"LINK"!==i.nodeName&&"not all"===i.getAttribute("media")||(n.set(i.dataset.precedence,i),r=i)}r&&n.set(null,r)}i=(a=t.instance).getAttribute("data-precedence"),(o=n.get(i)||r)===r&&n.set(null,a),n.set(i,a),this.count++,r=Hd.bind(this),a.addEventListener("load",r),a.addEventListener("error",r),o?o.parentNode.insertBefore(a,o.nextSibling):(e=9===e.nodeType?e.head:e).insertBefore(a,e.firstChild),t.state.loading|=4}}var Qd={$$typeof:k,Provider:null,Consumer:null,_currentValue:D,_currentValue2:D,_threadCount:0};function Kd(e,t,n,r,a,o,i,l){this.tag=1,this.containerInfo=e,this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.next=this.pendingContext=this.context=this.cancelPendingCommit=null,this.callbackPriority=0,this.expirationTimes=_e(-1),this.entangledLanes=this.shellSuspendCounter=this.errorRecoveryDisabledLanes=this.expiredLanes=this.warmLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=_e(0),this.hiddenUpdates=_e(null),this.identifierPrefix=r,this.onUncaughtError=a,this.onCaughtError=o,this.onRecoverableError=i,this.pooledCache=null,this.pooledCacheLanes=0,this.formState=l,this.incompleteTransitions=new Map}function Yd(e,t,n,r,a,o,i,l,s,u,c,d){return e=new Kd(e,t,n,i,l,s,u,d),t=1,!0===o&&(t|=24),o=Dr(3,null,null,t),e.current=o,o.stateNode=e,(t=Oa()).refCount++,e.pooledCache=t,t.refCount++,o.memoizedState={element:r,isDehydrated:n,cache:t},no(o),e}function Xd(e){return e?e=Rr:Rr}function Zd(e,t,n,r,a,o){a=Xd(a),null===r.context?r.context=a:r.pendingContext=a,(r=ao(t)).payload={element:n},null!==(o=void 0===o?null:o)&&(r.callback=o),null!==(n=oo(e,r,t))&&(Du(n,0,t),io(n,e,t))}function Jd(e,t){if(null!==(e=e.memoizedState)&&null!==e.dehydrated){var n=e.retryLane;e.retryLane=0!==n&&n<t?n:t}}function ef(e,t){Jd(e,t),(e=e.alternate)&&Jd(e,t)}function tf(e){if(13===e.tag){var t=Pr(e,67108864);null!==t&&Du(t,0,67108864),ef(e,67108864)}}var nf=!0;function rf(e,t,n,r){var a=R.T;R.T=null;var o=M.p;try{M.p=2,of(e,t,n,r)}finally{M.p=o,R.T=a}}function af(e,t,n,r){var a=R.T;R.T=null;var o=M.p;try{M.p=8,of(e,t,n,r)}finally{M.p=o,R.T=a}}function of(e,t,n,r){if(nf){var a=lf(r);if(null===a)Uc(e,t,r,sf,n),vf(e,r);else if(function(e,t,n,r,a){switch(t){case"focusin":return ff=wf(ff,e,t,n,r,a),!0;case"dragenter":return pf=wf(pf,e,t,n,r,a),!0;case"mouseover":return mf=wf(mf,e,t,n,r,a),!0;case"pointerover":var o=a.pointerId;return hf.set(o,wf(hf.get(o)||null,e,t,n,r,a)),!0;case"gotpointercapture":return o=a.pointerId,gf.set(o,wf(gf.get(o)||null,e,t,n,r,a)),!0}return!1}(a,e,t,n,r))r.stopPropagation();else if(vf(e,r),4&t&&-1<bf.indexOf(e)){for(;null!==a;){var o=Ue(a);if(null!==o)switch(o.tag){case 3:if((o=o.stateNode).current.memoizedState.isDehydrated){var i=be(o.pendingLanes);if(0!==i){var l=o;for(l.pendingLanes|=2,l.entangledLanes|=2;i;){var s=1<<31-pe(i);l.entanglements[1]|=s,i&=~s}Sc(o),!(6&nu)&&(Su=te()+500,xc(0,!1))}}break;case 13:null!==(l=Pr(o,2))&&Du(l,0,2),$u(),ef(o,2)}if(null===(o=lf(r))&&Uc(e,t,r,sf,n),o===a)break;a=o}null!==a&&r.stopPropagation()}else Uc(e,t,r,null,n)}}function lf(e){return uf(e=Pt(e))}var sf=null;function uf(e){if(sf=null,null!==(e=$e(e))){var t=s(e);if(null===t)e=null;else{var n=t.tag;if(13===n){if(null!==(e=u(t)))return e;e=null}else if(3===n){if(t.stateNode.current.memoizedState.isDehydrated)return 3===t.tag?t.stateNode.containerInfo:null;e=null}else t!==e&&(e=null)}}return sf=e,null}function cf(e){switch(e){case"beforetoggle":case"cancel":case"click":case"close":case"contextmenu":case"copy":case"cut":case"auxclick":case"dblclick":case"dragend":case"dragstart":case"drop":case"focusin":case"focusout":case"input":case"invalid":case"keydown":case"keypress":case"keyup":case"mousedown":case"mouseup":case"paste":case"pause":case"play":case"pointercancel":case"pointerdown":case"pointerup":case"ratechange":case"reset":case"resize":case"seeked":case"submit":case"toggle":case"touchcancel":case"touchend":case"touchstart":case"volumechange":case"change":case"selectionchange":case"textInput":case"compositionstart":case"compositionend":case"compositionupdate":case"beforeblur":case"afterblur":case"beforeinput":case"blur":case"fullscreenchange":case"focus":case"hashchange":case"popstate":case"select":case"selectstart":return 2;case"drag":case"dragenter":case"dragexit":case"dragleave":case"dragover":case"mousemove":case"mouseout":case"mouseover":case"pointermove":case"pointerout":case"pointerover":case"scroll":case"touchmove":case"wheel":case"mouseenter":case"mouseleave":case"pointerenter":case"pointerleave":return 8;case"message":switch(ne()){case re:return 2;case ae:return 8;case oe:case ie:return 32;case le:return 268435456;default:return 32}default:return 32}}var df=!1,ff=null,pf=null,mf=null,hf=new Map,gf=new Map,yf=[],bf="mousedown mouseup touchcancel touchend touchstart auxclick dblclick pointercancel pointerdown pointerup dragend dragstart drop compositionend compositionstart keydown keypress keyup input textInput copy cut paste click change contextmenu reset".split(" ");function vf(e,t){switch(e){case"focusin":case"focusout":ff=null;break;case"dragenter":case"dragleave":pf=null;break;case"mouseover":case"mouseout":mf=null;break;case"pointerover":case"pointerout":hf.delete(t.pointerId);break;case"gotpointercapture":case"lostpointercapture":gf.delete(t.pointerId)}}function wf(e,t,n,r,a,o){return null===e||e.nativeEvent!==o?(e={blockedOn:t,domEventName:n,eventSystemFlags:r,nativeEvent:o,targetContainers:[a]},null!==t&&(null!==(t=Ue(t))&&tf(t)),e):(e.eventSystemFlags|=r,t=e.targetContainers,null!==a&&-1===t.indexOf(a)&&t.push(a),e)}function kf(e){var t=$e(e.target);if(null!==t){var n=s(t);if(null!==n)if(13===(t=n.tag)){if(null!==(t=u(n)))return e.blockedOn=t,void function(e,t){var n=M.p;try{return M.p=e,t()}finally{M.p=n}}(e.priority,(function(){if(13===n.tag){var e=Ru();e=Te(e);var t=Pr(n,e);null!==t&&Du(t,0,e),ef(n,e)}}))}else if(3===t&&n.stateNode.current.memoizedState.isDehydrated)return void(e.blockedOn=3===n.tag?n.stateNode.containerInfo:null)}e.blockedOn=null}function Sf(e){if(null!==e.blockedOn)return!1;for(var t=e.targetContainers;0<t.length;){var n=lf(e.nativeEvent);if(null!==n)return null!==(t=Ue(n))&&tf(t),e.blockedOn=n,!1;var r=new(n=e.nativeEvent).constructor(n.type,n);jt=r,n.target.dispatchEvent(r),jt=null,t.shift()}return!0}function xf(e,t,n){Sf(e)&&n.delete(t)}function _f(){df=!1,null!==ff&&Sf(ff)&&(ff=null),null!==pf&&Sf(pf)&&(pf=null),null!==mf&&Sf(mf)&&(mf=null),hf.forEach(xf),gf.forEach(xf)}function Ef(e,t){e.blockedOn===t&&(e.blockedOn=null,df||(df=!0,r.unstable_scheduleCallback(r.unstable_NormalPriority,_f)))}var Cf=null;function Af(e){Cf!==e&&(Cf=e,r.unstable_scheduleCallback(r.unstable_NormalPriority,(function(){Cf===e&&(Cf=null);for(var t=0;t<e.length;t+=3){var n=e[t],r=e[t+1],a=e[t+2];if("function"!=typeof r){if(null===uf(r||n))continue;break}var o=Ue(n);null!==o&&(e.splice(t,3),t-=3,Pi(o,{pending:!0,data:a,method:n.method,action:r},r,a))}})))}function Tf(e){function t(t){return Ef(t,e)}null!==ff&&Ef(ff,e),null!==pf&&Ef(pf,e),null!==mf&&Ef(mf,e),hf.forEach(t),gf.forEach(t);for(var n=0;n<yf.length;n++){var r=yf[n];r.blockedOn===e&&(r.blockedOn=null)}for(;0<yf.length&&null===(n=yf[0]).blockedOn;)kf(n),null===n.blockedOn&&yf.shift();if(null!=(n=(e.ownerDocument||e).$$reactFormReplay))for(r=0;r<n.length;r+=3){var a=n[r],o=n[r+1],i=a[Oe]||null;if("function"==typeof o)i||Af(n);else if(i){var l=null;if(o&&o.hasAttribute("formAction")){if(a=o,i=o[Oe]||null)l=i.formAction;else if(null!==uf(a))continue}else l=i.action;"function"==typeof l?n[r+1]=l:(n.splice(r,3),r-=3),Af(n)}}}function Lf(e){this._internalRoot=e}function jf(e){this._internalRoot=e}jf.prototype.render=Lf.prototype.render=function(e){var t=this._internalRoot;if(null===t)throw Error(i(409));Zd(t.current,Ru(),e,t,null,null)},jf.prototype.unmount=Lf.prototype.unmount=function(){var e=this._internalRoot;if(null!==e){this._internalRoot=null;var t=e.containerInfo;Zd(e.current,2,null,e,null,null),$u(),t[Re]=null}},jf.prototype.unstable_scheduleHydration=function(e){if(e){var t=je();e={blockedOn:null,target:e,priority:t};for(var n=0;n<yf.length&&0!==t&&t<yf[n].priority;n++);yf.splice(n,0,e),0===n&&kf(e)}};var Pf=a.version;if("19.1.0"!==Pf)throw Error(i(527,Pf,"19.1.0"));M.findDOMNode=function(e){var t=e._reactInternals;if(void 0===t){if("function"==typeof e.render)throw Error(i(188));throw e=Object.keys(e).join(","),Error(i(268,e))}return e=function(e){var t=e.alternate;if(!t){if(null===(t=s(e)))throw Error(i(188));return t!==e?null:e}for(var n=e,r=t;;){var a=n.return;if(null===a)break;var o=a.alternate;if(null===o){if(null!==(r=a.return)){n=r;continue}break}if(a.child===o.child){for(o=a.child;o;){if(o===n)return c(a),e;if(o===r)return c(a),t;o=o.sibling}throw Error(i(188))}if(n.return!==r.return)n=a,r=o;else{for(var l=!1,u=a.child;u;){if(u===n){l=!0,n=a,r=o;break}if(u===r){l=!0,r=a,n=o;break}u=u.sibling}if(!l){for(u=o.child;u;){if(u===n){l=!0,n=o,r=a;break}if(u===r){l=!0,r=o,n=a;break}u=u.sibling}if(!l)throw Error(i(189))}}if(n.alternate!==r)throw Error(i(190))}if(3!==n.tag)throw Error(i(188));return n.stateNode.current===n?e:t}(t),e=null===(e=null!==e?d(e):null)?null:e.stateNode};var Nf={bundleType:0,version:"19.1.0",rendererPackageName:"react-dom",currentDispatcherRef:R,reconcilerVersion:"19.1.0"};if("undefined"!=typeof __REACT_DEVTOOLS_GLOBAL_HOOK__){var Of=__REACT_DEVTOOLS_GLOBAL_HOOK__;if(!Of.isDisabled&&Of.supportsFiber)try{ce=Of.inject(Nf),de=Of}catch(Mf){}}t.createRoot=function(e,t){if(!l(e))throw Error(i(299));var n=!1,r="",a=bl,o=vl,s=wl;return null!=t&&(!0===t.unstable_strictMode&&(n=!0),void 0!==t.identifierPrefix&&(r=t.identifierPrefix),void 0!==t.onUncaughtError&&(a=t.onUncaughtError),void 0!==t.onCaughtError&&(o=t.onCaughtError),void 0!==t.onRecoverableError&&(s=t.onRecoverableError),void 0!==t.unstable_transitionCallbacks&&t.unstable_transitionCallbacks),t=Yd(e,1,!1,null,0,n,r,a,o,s,0,null),e[Re]=t.current,zc(e),new Lf(t)},t.hydrateRoot=function(e,t,n){if(!l(e))throw Error(i(299));var r=!1,a="",o=bl,s=vl,u=wl,c=null;return null!=n&&(!0===n.unstable_strictMode&&(r=!0),void 0!==n.identifierPrefix&&(a=n.identifierPrefix),void 0!==n.onUncaughtError&&(o=n.onUncaughtError),void 0!==n.onCaughtError&&(s=n.onCaughtError),void 0!==n.onRecoverableError&&(u=n.onRecoverableError),void 0!==n.unstable_transitionCallbacks&&n.unstable_transitionCallbacks,void 0!==n.formState&&(c=n.formState)),(t=Yd(e,1,!0,t,0,r,a,o,s,u,0,c)).context=Xd(null),n=t.current,(a=ao(r=Te(r=Ru()))).callback=null,oo(n,a,r),n=r,t.current.lanes=n,Ee(t,n),Sc(t),e[Re]=t.current,zc(e),new jf(t)},t.version="19.1.0"},1312:(e,t,n)=>{"use strict";n.d(t,{A:()=>u,T:()=>s});var r=n(6540),a=n(4848);function o(e,t){const n=e.split(/(\{\w+\})/).map(((e,n)=>{if(n%2==1){const n=t?.[e.slice(1,-1)];if(void 0!==n)return n}return e}));return n.some((e=>(0,r.isValidElement)(e)))?n.map(((e,t)=>(0,r.isValidElement)(e)?r.cloneElement(e,{key:t}):e)).filter((e=>""!==e)):n.join("")}var i=n(2654);function l({id:e,message:t}){if(void 0===e&&void 0===t)throw new Error("Docusaurus translation declarations must have at least a translation id or a default translation message");return i[e??t]??t??e}function s({message:e,id:t},n){return o(l({message:e,id:t}),n)}function u({children:e,id:t,values:n}){if(e&&"string"!=typeof e)throw console.warn("Illegal <Translate> children",e),new Error("The Docusaurus <Translate> component only accept simple string values");const r=l({message:e,id:t});return(0,a.jsx)(a.Fragment,{children:o(r,n)})}},1422:(e,t,n)=>{"use strict";n.d(t,{N:()=>h,u:()=>s});var r=n(6540),a=n(205),o=n(3109),i=n(4848);const l="ease-in-out";function s({initialState:e}){const[t,n]=(0,r.useState)(e??!1),a=(0,r.useCallback)((()=>{n((e=>!e))}),[]);return{collapsed:t,setCollapsed:n,toggleCollapsed:a}}const u={display:"none",overflow:"hidden",height:"0px"},c={display:"block",overflow:"visible",height:"auto"};function d(e,t){const n=t?u:c;e.style.display=n.display,e.style.overflow=n.overflow,e.style.height=n.height}function f({collapsibleRef:e,collapsed:t,animation:n}){const a=(0,r.useRef)(!1);(0,r.useEffect)((()=>{const r=e.current;function i(){const e=r.scrollHeight,t=n?.duration??function(e){if((0,o.O)())return 1;const t=e/36;return Math.round(10*(4+15*t**.25+t/5))}(e);return{transition:`height ${t}ms ${n?.easing??l}`,height:`${e}px`}}function s(){const e=i();r.style.transition=e.transition,r.style.height=e.height}if(!a.current)return d(r,t),void(a.current=!0);return r.style.willChange="height",function(){const e=requestAnimationFrame((()=>{t?(s(),requestAnimationFrame((()=>{r.style.height=u.height,r.style.overflow=u.overflow}))):(r.style.display="block",requestAnimationFrame((()=>{s()})))}));return()=>cancelAnimationFrame(e)}()}),[e,t,n])}function p({as:e="div",collapsed:t,children:n,animation:a,onCollapseTransitionEnd:o,className:l}){const s=(0,r.useRef)(null);return f({collapsibleRef:s,collapsed:t,animation:a}),(0,i.jsx)(e,{ref:s,onTransitionEnd:e=>{"height"===e.propertyName&&(d(s.current,t),o?.(t))},className:l,children:n})}function m({collapsed:e,...t}){const[n,o]=(0,r.useState)(!e),[l,s]=(0,r.useState)(e);return(0,a.A)((()=>{e||o(!0)}),[e]),(0,a.A)((()=>{n&&s(e)}),[n,e]),n?(0,i.jsx)(p,{...t,collapsed:l}):null}function h({lazy:e,...t}){const n=e?m:p;return(0,i.jsx)(n,{...t})}},1463:(e,t,n)=>{"use strict";n.d(t,{A:()=>o});n(6540);var r=n(5260),a=n(4848);function o({locale:e,version:t,tag:n}){const o=e;return(0,a.jsxs)(r.A,{children:[e&&(0,a.jsx)("meta",{name:"docusaurus_locale",content:e}),t&&(0,a.jsx)("meta",{name:"docusaurus_version",content:t}),n&&(0,a.jsx)("meta",{name:"docusaurus_tag",content:n}),o&&(0,a.jsx)("meta",{name:"docsearch:language",content:o}),t&&(0,a.jsx)("meta",{name:"docsearch:version",content:t}),n&&(0,a.jsx)("meta",{name:"docsearch:docusaurus_tag",content:n})]})}},1513:(e,t,n)=>{"use strict";n.d(t,{zR:()=>w,TM:()=>C,yJ:()=>p,sC:()=>T,AO:()=>f});var r=n(8168);function a(e){return"/"===e.charAt(0)}function o(e,t){for(var n=t,r=n+1,a=e.length;r<a;n+=1,r+=1)e[n]=e[r];e.pop()}const i=function(e,t){void 0===t&&(t="");var n,r=e&&e.split("/")||[],i=t&&t.split("/")||[],l=e&&a(e),s=t&&a(t),u=l||s;if(e&&a(e)?i=r:r.length&&(i.pop(),i=i.concat(r)),!i.length)return"/";if(i.length){var c=i[i.length-1];n="."===c||".."===c||""===c}else n=!1;for(var d=0,f=i.length;f>=0;f--){var p=i[f];"."===p?o(i,f):".."===p?(o(i,f),d++):d&&(o(i,f),d--)}if(!u)for(;d--;d)i.unshift("..");!u||""===i[0]||i[0]&&a(i[0])||i.unshift("");var m=i.join("/");return n&&"/"!==m.substr(-1)&&(m+="/"),m};var l=n(1561);function s(e){return"/"===e.charAt(0)?e:"/"+e}function u(e){return"/"===e.charAt(0)?e.substr(1):e}function c(e,t){return function(e,t){return 0===e.toLowerCase().indexOf(t.toLowerCase())&&-1!=="/?#".indexOf(e.charAt(t.length))}(e,t)?e.substr(t.length):e}function d(e){return"/"===e.charAt(e.length-1)?e.slice(0,-1):e}function f(e){var t=e.pathname,n=e.search,r=e.hash,a=t||"/";return n&&"?"!==n&&(a+="?"===n.charAt(0)?n:"?"+n),r&&"#"!==r&&(a+="#"===r.charAt(0)?r:"#"+r),a}function p(e,t,n,a){var o;"string"==typeof e?(o=function(e){var t=e||"/",n="",r="",a=t.indexOf("#");-1!==a&&(r=t.substr(a),t=t.substr(0,a));var o=t.indexOf("?");return-1!==o&&(n=t.substr(o),t=t.substr(0,o)),{pathname:t,search:"?"===n?"":n,hash:"#"===r?"":r}}(e),o.state=t):(void 0===(o=(0,r.A)({},e)).pathname&&(o.pathname=""),o.search?"?"!==o.search.charAt(0)&&(o.search="?"+o.search):o.search="",o.hash?"#"!==o.hash.charAt(0)&&(o.hash="#"+o.hash):o.hash="",void 0!==t&&void 0===o.state&&(o.state=t));try{o.pathname=decodeURI(o.pathname)}catch(l){throw l instanceof URIError?new URIError('Pathname "'+o.pathname+'" could not be decoded. This is likely caused by an invalid percent-encoding.'):l}return n&&(o.key=n),a?o.pathname?"/"!==o.pathname.charAt(0)&&(o.pathname=i(o.pathname,a.pathname)):o.pathname=a.pathname:o.pathname||(o.pathname="/"),o}function m(){var e=null;var t=[];return{setPrompt:function(t){return e=t,function(){e===t&&(e=null)}},confirmTransitionTo:function(t,n,r,a){if(null!=e){var o="function"==typeof e?e(t,n):e;"string"==typeof o?"function"==typeof r?r(o,a):a(!0):a(!1!==o)}else a(!0)},appendListener:function(e){var n=!0;function r(){n&&e.apply(void 0,arguments)}return t.push(r),function(){n=!1,t=t.filter((function(e){return e!==r}))}},notifyListeners:function(){for(var e=arguments.length,n=new Array(e),r=0;r<e;r++)n[r]=arguments[r];t.forEach((function(e){return e.apply(void 0,n)}))}}}var h=!("undefined"==typeof window||!window.document||!window.document.createElement);function g(e,t){t(window.confirm(e))}var y="popstate",b="hashchange";function v(){try{return window.history.state||{}}catch(e){return{}}}function w(e){void 0===e&&(e={}),h||(0,l.A)(!1);var t,n=window.history,a=(-1===(t=window.navigator.userAgent).indexOf("Android 2.")&&-1===t.indexOf("Android 4.0")||-1===t.indexOf("Mobile Safari")||-1!==t.indexOf("Chrome")||-1!==t.indexOf("Windows Phone"))&&window.history&&"pushState"in window.history,o=!(-1===window.navigator.userAgent.indexOf("Trident")),i=e,u=i.forceRefresh,w=void 0!==u&&u,k=i.getUserConfirmation,S=void 0===k?g:k,x=i.keyLength,_=void 0===x?6:x,E=e.basename?d(s(e.basename)):"";function C(e){var t=e||{},n=t.key,r=t.state,a=window.location,o=a.pathname+a.search+a.hash;return E&&(o=c(o,E)),p(o,r,n)}function A(){return Math.random().toString(36).substr(2,_)}var T=m();function L(e){(0,r.A)($,e),$.length=n.length,T.notifyListeners($.location,$.action)}function j(e){(function(e){return void 0===e.state&&-1===navigator.userAgent.indexOf("CriOS")})(e)||O(C(e.state))}function P(){O(C(v()))}var N=!1;function O(e){if(N)N=!1,L();else{T.confirmTransitionTo(e,"POP",S,(function(t){t?L({action:"POP",location:e}):function(e){var t=$.location,n=M.indexOf(t.key);-1===n&&(n=0);var r=M.indexOf(e.key);-1===r&&(r=0);var a=n-r;a&&(N=!0,F(a))}(e)}))}}var R=C(v()),M=[R.key];function D(e){return E+f(e)}function F(e){n.go(e)}var I=0;function B(e){1===(I+=e)&&1===e?(window.addEventListener(y,j),o&&window.addEventListener(b,P)):0===I&&(window.removeEventListener(y,j),o&&window.removeEventListener(b,P))}var z=!1;var $={length:n.length,action:"POP",location:R,createHref:D,push:function(e,t){var r="PUSH",o=p(e,t,A(),$.location);T.confirmTransitionTo(o,r,S,(function(e){if(e){var t=D(o),i=o.key,l=o.state;if(a)if(n.pushState({key:i,state:l},null,t),w)window.location.href=t;else{var s=M.indexOf($.location.key),u=M.slice(0,s+1);u.push(o.key),M=u,L({action:r,location:o})}else window.location.href=t}}))},replace:function(e,t){var r="REPLACE",o=p(e,t,A(),$.location);T.confirmTransitionTo(o,r,S,(function(e){if(e){var t=D(o),i=o.key,l=o.state;if(a)if(n.replaceState({key:i,state:l},null,t),w)window.location.replace(t);else{var s=M.indexOf($.location.key);-1!==s&&(M[s]=o.key),L({action:r,location:o})}else window.location.replace(t)}}))},go:F,goBack:function(){F(-1)},goForward:function(){F(1)},block:function(e){void 0===e&&(e=!1);var t=T.setPrompt(e);return z||(B(1),z=!0),function(){return z&&(z=!1,B(-1)),t()}},listen:function(e){var t=T.appendListener(e);return B(1),function(){B(-1),t()}}};return $}var k="hashchange",S={hashbang:{encodePath:function(e){return"!"===e.charAt(0)?e:"!/"+u(e)},decodePath:function(e){return"!"===e.charAt(0)?e.substr(1):e}},noslash:{encodePath:u,decodePath:s},slash:{encodePath:s,decodePath:s}};function x(e){var t=e.indexOf("#");return-1===t?e:e.slice(0,t)}function _(){var e=window.location.href,t=e.indexOf("#");return-1===t?"":e.substring(t+1)}function E(e){window.location.replace(x(window.location.href)+"#"+e)}function C(e){void 0===e&&(e={}),h||(0,l.A)(!1);var t=window.history,n=(window.navigator.userAgent.indexOf("Firefox"),e),a=n.getUserConfirmation,o=void 0===a?g:a,i=n.hashType,u=void 0===i?"slash":i,y=e.basename?d(s(e.basename)):"",b=S[u],v=b.encodePath,w=b.decodePath;function C(){var e=w(_());return y&&(e=c(e,y)),p(e)}var A=m();function T(e){(0,r.A)(z,e),z.length=t.length,A.notifyListeners(z.location,z.action)}var L=!1,j=null;function P(){var e,t,n=_(),r=v(n);if(n!==r)E(r);else{var a=C(),i=z.location;if(!L&&(t=a,(e=i).pathname===t.pathname&&e.search===t.search&&e.hash===t.hash))return;if(j===f(a))return;j=null,function(e){if(L)L=!1,T();else{var t="POP";A.confirmTransitionTo(e,t,o,(function(n){n?T({action:t,location:e}):function(e){var t=z.location,n=M.lastIndexOf(f(t));-1===n&&(n=0);var r=M.lastIndexOf(f(e));-1===r&&(r=0);var a=n-r;a&&(L=!0,D(a))}(e)}))}}(a)}}var N=_(),O=v(N);N!==O&&E(O);var R=C(),M=[f(R)];function D(e){t.go(e)}var F=0;function I(e){1===(F+=e)&&1===e?window.addEventListener(k,P):0===F&&window.removeEventListener(k,P)}var B=!1;var z={length:t.length,action:"POP",location:R,createHref:function(e){var t=document.querySelector("base"),n="";return t&&t.getAttribute("href")&&(n=x(window.location.href)),n+"#"+v(y+f(e))},push:function(e,t){var n="PUSH",r=p(e,void 0,void 0,z.location);A.confirmTransitionTo(r,n,o,(function(e){if(e){var t=f(r),a=v(y+t);if(_()!==a){j=t,function(e){window.location.hash=e}(a);var o=M.lastIndexOf(f(z.location)),i=M.slice(0,o+1);i.push(t),M=i,T({action:n,location:r})}else T()}}))},replace:function(e,t){var n="REPLACE",r=p(e,void 0,void 0,z.location);A.confirmTransitionTo(r,n,o,(function(e){if(e){var t=f(r),a=v(y+t);_()!==a&&(j=t,E(a));var o=M.indexOf(f(z.location));-1!==o&&(M[o]=t),T({action:n,location:r})}}))},go:D,goBack:function(){D(-1)},goForward:function(){D(1)},block:function(e){void 0===e&&(e=!1);var t=A.setPrompt(e);return B||(I(1),B=!0),function(){return B&&(B=!1,I(-1)),t()}},listen:function(e){var t=A.appendListener(e);return I(1),function(){I(-1),t()}}};return z}function A(e,t,n){return Math.min(Math.max(e,t),n)}function T(e){void 0===e&&(e={});var t=e,n=t.getUserConfirmation,a=t.initialEntries,o=void 0===a?["/"]:a,i=t.initialIndex,l=void 0===i?0:i,s=t.keyLength,u=void 0===s?6:s,c=m();function d(e){(0,r.A)(w,e),w.length=w.entries.length,c.notifyListeners(w.location,w.action)}function h(){return Math.random().toString(36).substr(2,u)}var g=A(l,0,o.length-1),y=o.map((function(e){return p(e,void 0,"string"==typeof e?h():e.key||h())})),b=f;function v(e){var t=A(w.index+e,0,w.entries.length-1),r=w.entries[t];c.confirmTransitionTo(r,"POP",n,(function(e){e?d({action:"POP",location:r,index:t}):d()}))}var w={length:y.length,action:"POP",location:y[g],index:g,entries:y,createHref:b,push:function(e,t){var r="PUSH",a=p(e,t,h(),w.location);c.confirmTransitionTo(a,r,n,(function(e){if(e){var t=w.index+1,n=w.entries.slice(0);n.length>t?n.splice(t,n.length-t,a):n.push(a),d({action:r,location:a,index:t,entries:n})}}))},replace:function(e,t){var r="REPLACE",a=p(e,t,h(),w.location);c.confirmTransitionTo(a,r,n,(function(e){e&&(w.entries[w.index]=a,d({action:r,location:a}))}))},go:v,goBack:function(){v(-1)},goForward:function(){v(1)},canGo:function(e){var t=w.index+e;return t>=0&&t<w.entries.length},block:function(e){return void 0===e&&(e=!1),c.setPrompt(e)},listen:function(e){return c.appendListener(e)}};return w}},1561:(e,t,n)=>{"use strict";n.d(t,{A:()=>o});var r=!0,a="Invariant failed";function o(e,t){if(!e){if(r)throw new Error(a);var n="function"==typeof t?t():t,o=n?"".concat(a,": ").concat(n):a;throw new Error(o)}}},1635:(e,t,n)=>{"use strict";n.r(t),n.d(t,{__addDisposableResource:()=>M,__assign:()=>o,__asyncDelegator:()=>E,__asyncGenerator:()=>_,__asyncValues:()=>C,__await:()=>x,__awaiter:()=>m,__classPrivateFieldGet:()=>N,__classPrivateFieldIn:()=>R,__classPrivateFieldSet:()=>O,__createBinding:()=>g,__decorate:()=>l,__disposeResources:()=>F,__esDecorate:()=>u,__exportStar:()=>y,__extends:()=>a,__generator:()=>h,__importDefault:()=>P,__importStar:()=>j,__makeTemplateObject:()=>A,__metadata:()=>p,__param:()=>s,__propKey:()=>d,__read:()=>v,__rest:()=>i,__rewriteRelativeImportExtension:()=>I,__runInitializers:()=>c,__setFunctionName:()=>f,__spread:()=>w,__spreadArray:()=>S,__spreadArrays:()=>k,__values:()=>b,default:()=>B});var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},r(e,t)};function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}var o=function(){return o=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var a in t=arguments[n])Object.prototype.hasOwnProperty.call(t,a)&&(e[a]=t[a]);return e},o.apply(this,arguments)};function i(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var a=0;for(r=Object.getOwnPropertySymbols(e);a<r.length;a++)t.indexOf(r[a])<0&&Object.prototype.propertyIsEnumerable.call(e,r[a])&&(n[r[a]]=e[r[a]])}return n}function l(e,t,n,r){var a,o=arguments.length,i=o<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,n):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,n,r);else for(var l=e.length-1;l>=0;l--)(a=e[l])&&(i=(o<3?a(i):o>3?a(t,n,i):a(t,n))||i);return o>3&&i&&Object.defineProperty(t,n,i),i}function s(e,t){return function(n,r){t(n,r,e)}}function u(e,t,n,r,a,o){function i(e){if(void 0!==e&&"function"!=typeof e)throw new TypeError("Function expected");return e}for(var l,s=r.kind,u="getter"===s?"get":"setter"===s?"set":"value",c=!t&&e?r.static?e:e.prototype:null,d=t||(c?Object.getOwnPropertyDescriptor(c,r.name):{}),f=!1,p=n.length-1;p>=0;p--){var m={};for(var h in r)m[h]="access"===h?{}:r[h];for(var h in r.access)m.access[h]=r.access[h];m.addInitializer=function(e){if(f)throw new TypeError("Cannot add initializers after decoration has completed");o.push(i(e||null))};var g=(0,n[p])("accessor"===s?{get:d.get,set:d.set}:d[u],m);if("accessor"===s){if(void 0===g)continue;if(null===g||"object"!=typeof g)throw new TypeError("Object expected");(l=i(g.get))&&(d.get=l),(l=i(g.set))&&(d.set=l),(l=i(g.init))&&a.unshift(l)}else(l=i(g))&&("field"===s?a.unshift(l):d[u]=l)}c&&Object.defineProperty(c,r.name,d),f=!0}function c(e,t,n){for(var r=arguments.length>2,a=0;a<t.length;a++)n=r?t[a].call(e,n):t[a].call(e);return r?n:void 0}function d(e){return"symbol"==typeof e?e:"".concat(e)}function f(e,t,n){return"symbol"==typeof t&&(t=t.description?"[".concat(t.description,"]"):""),Object.defineProperty(e,"name",{configurable:!0,value:n?"".concat(n," ",t):t})}function p(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)}function m(e,t,n,r){return new(n||(n=Promise))((function(a,o){function i(e){try{s(r.next(e))}catch(t){o(t)}}function l(e){try{s(r.throw(e))}catch(t){o(t)}}function s(e){var t;e.done?a(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(i,l)}s((r=r.apply(e,t||[])).next())}))}function h(e,t){var n,r,a,o={label:0,sent:function(){if(1&a[0])throw a[1];return a[1]},trys:[],ops:[]},i=Object.create(("function"==typeof Iterator?Iterator:Object).prototype);return i.next=l(0),i.throw=l(1),i.return=l(2),"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function l(l){return function(s){return function(l){if(n)throw new TypeError("Generator is already executing.");for(;i&&(i=0,l[0]&&(o=0)),o;)try{if(n=1,r&&(a=2&l[0]?r.return:l[0]?r.throw||((a=r.return)&&a.call(r),0):r.next)&&!(a=a.call(r,l[1])).done)return a;switch(r=0,a&&(l=[2&l[0],a.value]),l[0]){case 0:case 1:a=l;break;case 4:return o.label++,{value:l[1],done:!1};case 5:o.label++,r=l[1],l=[0];continue;case 7:l=o.ops.pop(),o.trys.pop();continue;default:if(!(a=o.trys,(a=a.length>0&&a[a.length-1])||6!==l[0]&&2!==l[0])){o=0;continue}if(3===l[0]&&(!a||l[1]>a[0]&&l[1]<a[3])){o.label=l[1];break}if(6===l[0]&&o.label<a[1]){o.label=a[1],a=l;break}if(a&&o.label<a[2]){o.label=a[2],o.ops.push(l);break}a[2]&&o.ops.pop(),o.trys.pop();continue}l=t.call(e,o)}catch(s){l=[6,s],r=0}finally{n=a=0}if(5&l[0])throw l[1];return{value:l[0]?l[1]:void 0,done:!0}}([l,s])}}}var g=Object.create?function(e,t,n,r){void 0===r&&(r=n);var a=Object.getOwnPropertyDescriptor(t,n);a&&!("get"in a?!t.__esModule:a.writable||a.configurable)||(a={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,r,a)}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]};function y(e,t){for(var n in e)"default"===n||Object.prototype.hasOwnProperty.call(t,n)||g(t,e,n)}function b(e){var t="function"==typeof Symbol&&Symbol.iterator,n=t&&e[t],r=0;if(n)return n.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&r>=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function v(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,a,o=n.call(e),i=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)i.push(r.value)}catch(l){a={error:l}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(a)throw a.error}}return i}function w(){for(var e=[],t=0;t<arguments.length;t++)e=e.concat(v(arguments[t]));return e}function k(){for(var e=0,t=0,n=arguments.length;t<n;t++)e+=arguments[t].length;var r=Array(e),a=0;for(t=0;t<n;t++)for(var o=arguments[t],i=0,l=o.length;i<l;i++,a++)r[a]=o[i];return r}function S(e,t,n){if(n||2===arguments.length)for(var r,a=0,o=t.length;a<o;a++)!r&&a in t||(r||(r=Array.prototype.slice.call(t,0,a)),r[a]=t[a]);return e.concat(r||Array.prototype.slice.call(t))}function x(e){return this instanceof x?(this.v=e,this):new x(e)}function _(e,t,n){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var r,a=n.apply(e,t||[]),o=[];return r=Object.create(("function"==typeof AsyncIterator?AsyncIterator:Object).prototype),i("next"),i("throw"),i("return",(function(e){return function(t){return Promise.resolve(t).then(e,u)}})),r[Symbol.asyncIterator]=function(){return this},r;function i(e,t){a[e]&&(r[e]=function(t){return new Promise((function(n,r){o.push([e,t,n,r])>1||l(e,t)}))},t&&(r[e]=t(r[e])))}function l(e,t){try{(n=a[e](t)).value instanceof x?Promise.resolve(n.value.v).then(s,u):c(o[0][2],n)}catch(r){c(o[0][3],r)}var n}function s(e){l("next",e)}function u(e){l("throw",e)}function c(e,t){e(t),o.shift(),o.length&&l(o[0][0],o[0][1])}}function E(e){var t,n;return t={},r("next"),r("throw",(function(e){throw e})),r("return"),t[Symbol.iterator]=function(){return this},t;function r(r,a){t[r]=e[r]?function(t){return(n=!n)?{value:x(e[r](t)),done:!1}:a?a(t):t}:a}}function C(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t,n=e[Symbol.asyncIterator];return n?n.call(e):(e=b(e),t={},r("next"),r("throw"),r("return"),t[Symbol.asyncIterator]=function(){return this},t);function r(n){t[n]=e[n]&&function(t){return new Promise((function(r,a){(function(e,t,n,r){Promise.resolve(r).then((function(t){e({value:t,done:n})}),t)})(r,a,(t=e[n](t)).done,t.value)}))}}}function A(e,t){return Object.defineProperty?Object.defineProperty(e,"raw",{value:t}):e.raw=t,e}var T=Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t},L=function(e){return L=Object.getOwnPropertyNames||function(e){var t=[];for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[t.length]=n);return t},L(e)};function j(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n=L(e),r=0;r<n.length;r++)"default"!==n[r]&&g(t,e,n[r]);return T(t,e),t}function P(e){return e&&e.__esModule?e:{default:e}}function N(e,t,n,r){if("a"===n&&!r)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!r:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===n?r:"a"===n?r.call(e):r?r.value:t.get(e)}function O(e,t,n,r,a){if("m"===r)throw new TypeError("Private method is not writable");if("a"===r&&!a)throw new TypeError("Private accessor was defined without a setter");if("function"==typeof t?e!==t||!a:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return"a"===r?a.call(e,n):a?a.value=n:t.set(e,n),n}function R(e,t){if(null===t||"object"!=typeof t&&"function"!=typeof t)throw new TypeError("Cannot use 'in' operator on non-object");return"function"==typeof e?t===e:e.has(t)}function M(e,t,n){if(null!=t){if("object"!=typeof t&&"function"!=typeof t)throw new TypeError("Object expected.");var r,a;if(n){if(!Symbol.asyncDispose)throw new TypeError("Symbol.asyncDispose is not defined.");r=t[Symbol.asyncDispose]}if(void 0===r){if(!Symbol.dispose)throw new TypeError("Symbol.dispose is not defined.");r=t[Symbol.dispose],n&&(a=r)}if("function"!=typeof r)throw new TypeError("Object not disposable.");a&&(r=function(){try{a.call(this)}catch(e){return Promise.reject(e)}}),e.stack.push({value:t,dispose:r,async:n})}else n&&e.stack.push({async:!0});return t}var D="function"==typeof SuppressedError?SuppressedError:function(e,t,n){var r=new Error(n);return r.name="SuppressedError",r.error=e,r.suppressed=t,r};function F(e){function t(t){e.error=e.hasError?new D(t,e.error,"An error was suppressed during disposal."):t,e.hasError=!0}var n,r=0;return function a(){for(;n=e.stack.pop();)try{if(!n.async&&1===r)return r=0,e.stack.push(n),Promise.resolve().then(a);if(n.dispose){var o=n.dispose.call(n.value);if(n.async)return r|=2,Promise.resolve(o).then(a,(function(e){return t(e),a()}))}else r|=1}catch(i){t(i)}if(1===r)return e.hasError?Promise.reject(e.error):Promise.resolve();if(e.hasError)throw e.error}()}function I(e,t){return"string"==typeof e&&/^\.\.?\//.test(e)?e.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i,(function(e,n,r,a,o){return n?t?".jsx":".js":!r||a&&o?r+a+"."+o.toLowerCase()+"js":e})):e}const B={__extends:a,__assign:o,__rest:i,__decorate:l,__param:s,__esDecorate:u,__runInitializers:c,__propKey:d,__setFunctionName:f,__metadata:p,__awaiter:m,__generator:h,__createBinding:g,__exportStar:y,__values:b,__read:v,__spread:w,__spreadArrays:k,__spreadArray:S,__await:x,__asyncGenerator:_,__asyncDelegator:E,__asyncValues:C,__makeTemplateObject:A,__importStar:j,__importDefault:P,__classPrivateFieldGet:N,__classPrivateFieldSet:O,__classPrivateFieldIn:R,__addDisposableResource:M,__disposeResources:F,__rewriteRelativeImportExtension:I}},1656:(e,t,n)=>{"use strict";n.d(t,{A:()=>At});var r=n(6540),a=n(4164),o=n(7489),i=n(5500),l=n(6347),s=n(1312),u=n(5062),c=n(4848);const d="__docusaurus_skipToContent_fallback";function f(e){e.setAttribute("tabindex","-1"),e.focus(),e.removeAttribute("tabindex")}function p(){const e=(0,r.useRef)(null),{action:t}=(0,l.W6)(),n=(0,r.useCallback)((e=>{e.preventDefault();const t=document.querySelector("main:first-of-type")??document.getElementById(d);t&&f(t)}),[]);return(0,u.$)((({location:n})=>{e.current&&!n.hash&&"PUSH"===t&&f(e.current)})),{containerRef:e,onClick:n}}const m=(0,s.T)({id:"theme.common.skipToMainContent",description:"The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation",message:"Skip to main content"});function h(e){const t=e.children??m,{containerRef:n,onClick:r}=p();return(0,c.jsx)("div",{ref:n,role:"region","aria-label":m,children:(0,c.jsx)("a",{...e,href:`#${d}`,onClick:r,children:t})})}var g=n(7559),y=n(4090);const b={skipToContent:"skipToContent_fXgn"};function v(){return(0,c.jsx)(h,{className:b.skipToContent})}var w=n(6342),k=n(5041);function S({width:e=21,height:t=21,color:n="currentColor",strokeWidth:r=1.2,className:a,...o}){return(0,c.jsx)("svg",{viewBox:"0 0 15 15",width:e,height:t,...o,children:(0,c.jsx)("g",{stroke:n,strokeWidth:r,children:(0,c.jsx)("path",{d:"M.75.75l13.5 13.5M14.25.75L.75 14.25"})})})}const x={closeButton:"closeButton_CVFx"};function _(e){return(0,c.jsx)("button",{type:"button","aria-label":(0,s.T)({id:"theme.AnnouncementBar.closeButtonAriaLabel",message:"Close",description:"The ARIA label for close button of announcement bar"}),...e,className:(0,a.A)("clean-btn close",x.closeButton,e.className),children:(0,c.jsx)(S,{width:14,height:14,strokeWidth:3.1})})}const E={content:"content_knG7"};function C(e){const{announcementBar:t}=(0,w.p)(),{content:n}=t;return(0,c.jsx)("div",{...e,className:(0,a.A)(E.content,e.className),dangerouslySetInnerHTML:{__html:n}})}const A={announcementBar:"announcementBar_mb4j",announcementBarPlaceholder:"announcementBarPlaceholder_vyr4",announcementBarClose:"announcementBarClose_gvF7",announcementBarContent:"announcementBarContent_xLdY"};function T(){const{announcementBar:e}=(0,w.p)(),{isActive:t,close:n}=(0,k.M)();if(!t)return null;const{backgroundColor:r,textColor:o,isCloseable:i}=e;return(0,c.jsxs)("div",{className:(0,a.A)(g.G.announcementBar.container,A.announcementBar),style:{backgroundColor:r,color:o},role:"banner",children:[i&&(0,c.jsx)("div",{className:A.announcementBarPlaceholder}),(0,c.jsx)(C,{className:A.announcementBarContent}),i&&(0,c.jsx)(_,{onClick:n,className:A.announcementBarClose})]})}var L=n(9876),j=n(3104);var P=n(9532),N=n(5600);const O=r.createContext(null);function R({children:e}){const t=function(){const e=(0,L.M)(),t=(0,N.YL)(),[n,a]=(0,r.useState)(!1),o=null!==t.component,i=(0,P.ZC)(o);return(0,r.useEffect)((()=>{o&&!i&&a(!0)}),[o,i]),(0,r.useEffect)((()=>{o?e.shown||a(!0):a(!1)}),[e.shown,o]),(0,r.useMemo)((()=>[n,a]),[n])}();return(0,c.jsx)(O.Provider,{value:t,children:e})}function M(e){if(e.component){const t=e.component;return(0,c.jsx)(t,{...e.props})}}function D(){const e=(0,r.useContext)(O);if(!e)throw new P.dV("NavbarSecondaryMenuDisplayProvider");const[t,n]=e,a=(0,r.useCallback)((()=>n(!1)),[n]),o=(0,N.YL)();return(0,r.useMemo)((()=>({shown:t,hide:a,content:M(o)})),[a,o,t])}function F(e){return parseInt(r.version.split(".")[0],10)<19?{inert:e?"":void 0}:{inert:e}}function I({children:e,inert:t}){return(0,c.jsx)("div",{className:(0,a.A)(g.G.layout.navbar.mobileSidebar.panel,"navbar-sidebar__item menu"),...F(t),children:e})}function B({header:e,primaryMenu:t,secondaryMenu:n}){const{shown:r}=D();return(0,c.jsxs)("div",{className:(0,a.A)(g.G.layout.navbar.mobileSidebar.container,"navbar-sidebar"),children:[e,(0,c.jsxs)("div",{className:(0,a.A)("navbar-sidebar__items",{"navbar-sidebar__items--show-secondary":r}),children:[(0,c.jsx)(I,{inert:r,children:t}),(0,c.jsx)(I,{inert:!r,children:n})]})]})}var z=n(5293),$=n(2303);function U(e){return(0,c.jsx)("svg",{viewBox:"0 0 24 24",width:24,height:24,...e,children:(0,c.jsx)("path",{fill:"currentColor",d:"M12,9c1.65,0,3,1.35,3,3s-1.35,3-3,3s-3-1.35-3-3S10.35,9,12,9 M12,7c-2.76,0-5,2.24-5,5s2.24,5,5,5s5-2.24,5-5 S14.76,7,12,7L12,7z M2,13l2,0c0.55,0,1-0.45,1-1s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S1.45,13,2,13z M20,13l2,0c0.55,0,1-0.45,1-1 s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S19.45,13,20,13z M11,2v2c0,0.55,0.45,1,1,1s1-0.45,1-1V2c0-0.55-0.45-1-1-1S11,1.45,11,2z M11,20v2c0,0.55,0.45,1,1,1s1-0.45,1-1v-2c0-0.55-0.45-1-1-1C11.45,19,11,19.45,11,20z M5.99,4.58c-0.39-0.39-1.03-0.39-1.41,0 c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0s0.39-1.03,0-1.41L5.99,4.58z M18.36,16.95 c-0.39-0.39-1.03-0.39-1.41,0c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0c0.39-0.39,0.39-1.03,0-1.41 L18.36,16.95z M19.42,5.99c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06c-0.39,0.39-0.39,1.03,0,1.41 s1.03,0.39,1.41,0L19.42,5.99z M7.05,18.36c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06 c-0.39,0.39-0.39,1.03,0,1.41s1.03,0.39,1.41,0L7.05,18.36z"})})}function q(e){return(0,c.jsx)("svg",{viewBox:"0 0 24 24",width:24,height:24,...e,children:(0,c.jsx)("path",{fill:"currentColor",d:"M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27C17.45,17.19,14.93,19,12,19 c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36 c-0.98,1.37-2.58,2.26-4.4,2.26c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"})})}function H(e){return(0,c.jsx)("svg",{viewBox:"0 0 24 24",width:24,height:24,...e,children:(0,c.jsx)("path",{fill:"currentColor",d:"m12 21c4.971 0 9-4.029 9-9s-4.029-9-9-9-9 4.029-9 9 4.029 9 9 9zm4.95-13.95c1.313 1.313 2.05 3.093 2.05 4.95s-0.738 3.637-2.05 4.95c-1.313 1.313-3.093 2.05-4.95 2.05v-14c1.857 0 3.637 0.737 4.95 2.05z"})})}const G="toggle_vylO",V="toggleButton_gllP",W="toggleIcon_g3eP",Q="systemToggleIcon_QzmC",K="lightToggleIcon_pyhR",Y="darkToggleIcon_wfgR",X="toggleButtonDisabled_aARS";function Z(e){switch(e){case null:return(0,s.T)({message:"system mode",id:"theme.colorToggle.ariaLabel.mode.system",description:"The name for the system color mode"});case"light":return(0,s.T)({message:"light mode",id:"theme.colorToggle.ariaLabel.mode.light",description:"The name for the light color mode"});case"dark":return(0,s.T)({message:"dark mode",id:"theme.colorToggle.ariaLabel.mode.dark",description:"The name for the dark color mode"});default:throw new Error(`unexpected color mode ${e}`)}}function J(e){return(0,s.T)({message:"Switch between dark and light mode (currently {mode})",id:"theme.colorToggle.ariaLabel",description:"The ARIA label for the color mode toggle"},{mode:Z(e)})}function ee(){return(0,c.jsxs)(c.Fragment,{children:[(0,c.jsx)(U,{"aria-hidden":!0,className:(0,a.A)(W,K)}),(0,c.jsx)(q,{"aria-hidden":!0,className:(0,a.A)(W,Y)}),(0,c.jsx)(H,{"aria-hidden":!0,className:(0,a.A)(W,Q)})]})}function te({className:e,buttonClassName:t,respectPrefersColorScheme:n,value:r,onChange:o}){const i=(0,$.A)();return(0,c.jsx)("div",{className:(0,a.A)(G,e),children:(0,c.jsx)("button",{className:(0,a.A)("clean-btn",V,!i&&X,t),type:"button",onClick:()=>o(function(e,t){if(!t)return"dark"===e?"light":"dark";switch(e){case null:return"light";case"light":return"dark";case"dark":return null;default:throw new Error(`unexpected color mode ${e}`)}}(r,n)),disabled:!i,title:Z(r),"aria-label":J(r),children:(0,c.jsx)(ee,{})})})}const ne=r.memo(te),re={darkNavbarColorModeToggle:"darkNavbarColorModeToggle_X3D1"};function ae({className:e}){const t=(0,w.p)().navbar.style,{disableSwitch:n,respectPrefersColorScheme:r}=(0,w.p)().colorMode,{colorModeChoice:a,setColorMode:o}=(0,z.G)();return n?null:(0,c.jsx)(ne,{className:e,buttonClassName:"dark"===t?re.darkNavbarColorModeToggle:void 0,respectPrefersColorScheme:r,value:a,onChange:o})}var oe=n(3465);function ie(){return(0,c.jsx)(oe.A,{className:"navbar__brand",imageClassName:"navbar__logo",titleClassName:"navbar__title text--truncate"})}function le(){const e=(0,L.M)();return(0,c.jsx)("button",{type:"button","aria-label":(0,s.T)({id:"theme.docs.sidebar.closeSidebarButtonAriaLabel",message:"Close navigation bar",description:"The ARIA label for close button of mobile sidebar"}),className:"clean-btn navbar-sidebar__close",onClick:()=>e.toggle(),children:(0,c.jsx)(S,{color:"var(--ifm-color-emphasis-600)"})})}function se(){return(0,c.jsxs)("div",{className:"navbar-sidebar__brand",children:[(0,c.jsx)(ie,{}),(0,c.jsx)(ae,{className:"margin-right--md"}),(0,c.jsx)(le,{})]})}var ue=n(8774),ce=n(6025),de=n(6654);function fe(e,t){return void 0!==e&&void 0!==t&&new RegExp(e,"gi").test(t)}var pe=n(3186);function me({activeBasePath:e,activeBaseRegex:t,to:n,href:r,label:a,html:o,isDropdownLink:i,prependBaseUrlToHref:l,...s}){const u=(0,ce.Ay)(n),d=(0,ce.Ay)(e),f=(0,ce.Ay)(r,{forcePrependBaseUrl:!0}),p=a&&r&&!(0,de.A)(r),m=o?{dangerouslySetInnerHTML:{__html:o}}:{children:(0,c.jsxs)(c.Fragment,{children:[a,p&&(0,c.jsx)(pe.A,{...i&&{width:12,height:12}})]})};return r?(0,c.jsx)(ue.A,{href:l?f:r,...s,...m}):(0,c.jsx)(ue.A,{to:u,isNavLink:!0,...(e||t)&&{isActive:(e,n)=>t?fe(t,n.pathname):n.pathname.startsWith(d)},...s,...m})}function he({className:e,isDropdownItem:t,...n}){return(0,c.jsx)("li",{className:"menu__list-item",children:(0,c.jsx)(me,{className:(0,a.A)("menu__link",e),...n})})}function ge({className:e,isDropdownItem:t=!1,...n}){const r=(0,c.jsx)(me,{className:(0,a.A)(t?"dropdown__link":"navbar__item navbar__link",e),isDropdownLink:t,...n});return t?(0,c.jsx)("li",{children:r}):r}function ye({mobile:e=!1,position:t,...n}){const r=e?he:ge;return(0,c.jsx)(r,{...n,activeClassName:n.activeClassName??(e?"menu__link--active":"navbar__link--active")})}var be=n(1422),ve=n(9169),we=n(4586);const ke="dropdownNavbarItemMobile_J0Sd";function Se(e,t){return e.some((e=>function(e,t){return!!(0,ve.ys)(e.to,t)||!!fe(e.activeBaseRegex,t)||!(!e.activeBasePath||!t.startsWith(e.activeBasePath))}(e,t)))}function xe({collapsed:e,onClick:t}){return(0,c.jsx)("button",{"aria-label":e?(0,s.T)({id:"theme.navbar.mobileDropdown.collapseButton.expandAriaLabel",message:"Expand the dropdown",description:"The ARIA label of the button to expand the mobile dropdown navbar item"}):(0,s.T)({id:"theme.navbar.mobileDropdown.collapseButton.collapseAriaLabel",message:"Collapse the dropdown",description:"The ARIA label of the button to collapse the mobile dropdown navbar item"}),"aria-expanded":!e,type:"button",className:"clean-btn menu__caret",onClick:t})}function _e({items:e,className:t,position:n,onClick:o,...i}){const s=function(){const{siteConfig:{baseUrl:e}}=(0,we.A)(),{pathname:t}=(0,l.zy)();return t.replace(e,"/")}(),u=(0,ve.ys)(i.to,s),d=Se(e,s),{collapsed:f,toggleCollapsed:p}=function({active:e}){const{collapsed:t,toggleCollapsed:n,setCollapsed:a}=(0,be.u)({initialState:()=>!e});return(0,r.useEffect)((()=>{e&&a(!1)}),[e,a]),{collapsed:t,toggleCollapsed:n}}({active:u||d}),m=i.to?void 0:"#";return(0,c.jsxs)("li",{className:(0,a.A)("menu__list-item",{"menu__list-item--collapsed":f}),children:[(0,c.jsxs)("div",{className:(0,a.A)("menu__list-item-collapsible",{"menu__list-item-collapsible--active":u}),children:[(0,c.jsx)(me,{role:"button",className:(0,a.A)(ke,"menu__link menu__link--sublist",t),href:m,...i,onClick:e=>{"#"===m&&e.preventDefault(),p()},children:i.children??i.label}),(0,c.jsx)(xe,{collapsed:f,onClick:e=>{e.preventDefault(),p()}})]}),(0,c.jsx)(be.N,{lazy:!0,as:"ul",className:"menu__list",collapsed:f,children:e.map(((e,t)=>(0,r.createElement)(Be,{mobile:!0,isDropdownItem:!0,onClick:o,activeClassName:"menu__link--active",...e,key:t})))})]})}function Ee({items:e,position:t,className:n,onClick:o,...i}){const l=(0,r.useRef)(null),[s,u]=(0,r.useState)(!1);return(0,r.useEffect)((()=>{const e=e=>{l.current&&!l.current.contains(e.target)&&u(!1)};return document.addEventListener("mousedown",e),document.addEventListener("touchstart",e),document.addEventListener("focusin",e),()=>{document.removeEventListener("mousedown",e),document.removeEventListener("touchstart",e),document.removeEventListener("focusin",e)}}),[l]),(0,c.jsxs)("div",{ref:l,className:(0,a.A)("navbar__item","dropdown","dropdown--hoverable",{"dropdown--right":"right"===t,"dropdown--show":s}),children:[(0,c.jsx)(me,{"aria-haspopup":"true","aria-expanded":s,role:"button",href:i.to?void 0:"#",className:(0,a.A)("navbar__link",n),...i,onClick:i.to?void 0:e=>e.preventDefault(),onKeyDown:e=>{"Enter"===e.key&&(e.preventDefault(),u(!s))},children:i.children??i.label}),(0,c.jsx)("ul",{className:"dropdown__menu",children:e.map(((e,t)=>(0,r.createElement)(Be,{isDropdownItem:!0,activeClassName:"dropdown__link--active",...e,key:t})))})]})}function Ce({mobile:e=!1,...t}){const n=e?_e:Ee;return(0,c.jsx)(n,{...t})}var Ae=n(2131);function Te({width:e=20,height:t=20,...n}){return(0,c.jsx)("svg",{viewBox:"0 0 24 24",width:e,height:t,"aria-hidden":!0,...n,children:(0,c.jsx)("path",{fill:"currentColor",d:"M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z"})})}const Le="iconLanguage_nlXk";var je=n(418);const Pe={navbarSearchContainer:"navbarSearchContainer_Bca1"};function Ne({children:e,className:t}){return(0,c.jsx)("div",{className:(0,a.A)(t,Pe.navbarSearchContainer),children:e})}var Oe=n(4070),Re=n(6972);var Me=n(3886);function De({docsPluginId:e,configs:t}){return function(e,t){if(t){const n=new Map(e.map((e=>[e.name,e]))),r=(t,r)=>{const a=n.get(t);if(!a)throw new Error(`No docs version exist for name '${t}', please verify your 'docsVersionDropdown' navbar item versions config.\nAvailable version names:\n- ${e.map((e=>`${e.name}`)).join("\n- ")}`);return{version:a,label:r?.label??a.label}};return Array.isArray(t)?t.map((e=>r(e,void 0))):Object.entries(t).map((([e,t])=>r(e,t)))}return e.map((e=>({version:e,label:e.label})))}((0,Oe.jh)(e),t)}function Fe(e,t){return t.alternateDocVersions[e.name]??function(e){return e.docs.find((t=>t.id===e.mainDocId))}(e)}const Ie={default:ye,localeDropdown:function({mobile:e,dropdownItemsBefore:t,dropdownItemsAfter:n,queryString:r="",...a}){const{i18n:{currentLocale:o,locales:i,localeConfigs:u}}=(0,we.A)(),d=(0,Ae.o)(),{search:f,hash:p}=(0,l.zy)(),m=[...t,...i.map((t=>{const n=`${`pathname://${d.createUrl({locale:t,fullyQualified:!1})}`}${f}${p}${r}`;return{label:u[t].label,lang:u[t].htmlLang,to:n,target:"_self",autoAddBaseUrl:!1,className:t===o?e?"menu__link--active":"dropdown__link--active":""}})),...n],h=e?(0,s.T)({message:"Languages",id:"theme.navbar.mobileLanguageDropdown.label",description:"The label for the mobile language switcher dropdown"}):u[o].label;return(0,c.jsx)(Ce,{...a,mobile:e,label:(0,c.jsxs)(c.Fragment,{children:[(0,c.jsx)(Te,{className:Le}),h]}),items:m})},search:function({mobile:e,className:t}){return e?null:(0,c.jsx)(Ne,{className:t,children:(0,c.jsx)(je.A,{})})},dropdown:Ce,html:function({value:e,className:t,mobile:n=!1,isDropdownItem:r=!1}){const o=r?"li":"div";return(0,c.jsx)(o,{className:(0,a.A)({navbar__item:!n&&!r,"menu__list-item":n},t),dangerouslySetInnerHTML:{__html:e}})},doc:function({docId:e,label:t,docsPluginId:n,...r}){const{activeDoc:a}=(0,Oe.zK)(n),o=(0,Re.QB)(e,n),i=a?.path===o?.path;return null===o||o.unlisted&&!i?null:(0,c.jsx)(ye,{exact:!0,...r,isActive:()=>i||!!a?.sidebar&&a.sidebar===o.sidebar,label:t??o.id,to:o.path})},docSidebar:function({sidebarId:e,label:t,docsPluginId:n,...r}){const{activeDoc:a}=(0,Oe.zK)(n),o=(0,Re.fW)(e,n).link;if(!o)throw new Error(`DocSidebarNavbarItem: Sidebar with ID "${e}" doesn't have anything to be linked to.`);return(0,c.jsx)(ye,{exact:!0,...r,isActive:()=>a?.sidebar===e,label:t??o.label,to:o.path})},docsVersion:function({label:e,to:t,docsPluginId:n,...r}){const a=(0,Re.Vd)(n)[0],o=e??a.label,i=t??(e=>e.docs.find((t=>t.id===e.mainDocId)))(a).path;return(0,c.jsx)(ye,{...r,label:o,to:i})},docsVersionDropdown:function({mobile:e,docsPluginId:t,dropdownActiveClassDisabled:n,dropdownItemsBefore:r,dropdownItemsAfter:a,versions:o,...i}){const{search:u,hash:d}=(0,l.zy)(),f=(0,Oe.zK)(t),{savePreferredVersionName:p}=(0,Me.g1)(t),m=De({docsPluginId:t,configs:o}),h=function({docsPluginId:e,versionItems:t}){return(0,Re.Vd)(e).map((e=>t.find((t=>t.version===e)))).filter((e=>void 0!==e))[0]??t[0]}({docsPluginId:t,versionItems:m}),g=[...r,...m.map((function({version:e,label:t}){return{label:t,to:`${Fe(e,f).path}${u}${d}`,isActive:()=>e===f.activeVersion,onClick:()=>p(e.name)}})),...a],y=e&&g.length>1?(0,s.T)({id:"theme.navbar.mobileVersionsDropdown.label",message:"Versions",description:"The label for the navbar versions dropdown on mobile view"}):h.label,b=e&&g.length>1?void 0:Fe(h.version,f).path;return g.length<=1?(0,c.jsx)(ye,{...i,mobile:e,label:y,to:b,isActive:n?()=>!1:void 0}):(0,c.jsx)(Ce,{...i,mobile:e,label:y,to:b,items:g,isActive:n?()=>!1:void 0})}};function Be({type:e,...t}){const n=function(e,t){return e&&"default"!==e?e:"items"in t?"dropdown":"default"}(e,t),r=Ie[n];if(!r)throw new Error(`No NavbarItem component found for type "${e}".`);return(0,c.jsx)(r,{...t})}function ze(){const e=(0,L.M)(),t=(0,w.p)().navbar.items;return(0,c.jsx)("ul",{className:"menu__list",children:t.map(((t,n)=>(0,r.createElement)(Be,{mobile:!0,...t,onClick:()=>e.toggle(),key:n})))})}function $e(e){return(0,c.jsx)("button",{...e,type:"button",className:"clean-btn navbar-sidebar__back",children:(0,c.jsx)(s.A,{id:"theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel",description:"The label of the back button to return to main menu, inside the mobile navbar sidebar secondary menu (notably used to display the docs sidebar)",children:"\u2190 Back to main menu"})})}function Ue(){const e=0===(0,w.p)().navbar.items.length,t=D();return(0,c.jsxs)(c.Fragment,{children:[!e&&(0,c.jsx)($e,{onClick:()=>t.hide()}),t.content]})}function qe(){const e=(0,L.M)();return function(e=!0){(0,r.useEffect)((()=>(document.body.style.overflow=e?"hidden":"visible",()=>{document.body.style.overflow="visible"})),[e])}(e.shown),e.shouldRender?(0,c.jsx)(B,{header:(0,c.jsx)(se,{}),primaryMenu:(0,c.jsx)(ze,{}),secondaryMenu:(0,c.jsx)(Ue,{})}):null}const He={navbarHideable:"navbarHideable_m1mJ",navbarHidden:"navbarHidden_jGov"};function Ge(e){return(0,c.jsx)("div",{role:"presentation",...e,className:(0,a.A)("navbar-sidebar__backdrop",e.className)})}function Ve({children:e}){const{navbar:{hideOnScroll:t,style:n}}=(0,w.p)(),o=(0,L.M)(),{navbarRef:i,isNavbarVisible:l}=function(e){const[t,n]=(0,r.useState)(e),a=(0,r.useRef)(!1),o=(0,r.useRef)(0),i=(0,r.useCallback)((e=>{null!==e&&(o.current=e.getBoundingClientRect().height)}),[]);return(0,j.Mq)((({scrollY:t},r)=>{if(!e)return;if(t<o.current)return void n(!0);if(a.current)return void(a.current=!1);const i=r?.scrollY,l=document.documentElement.scrollHeight-o.current,s=window.innerHeight;i&&t>=i?n(!1):t+s<l&&n(!0)})),(0,u.$)((t=>{if(!e)return;const r=t.location.hash;if(r?document.getElementById(r.substring(1)):void 0)return a.current=!0,void n(!1);n(!0)})),{navbarRef:i,isNavbarVisible:t}}(t);return(0,c.jsxs)("nav",{ref:i,"aria-label":(0,s.T)({id:"theme.NavBar.navAriaLabel",message:"Main",description:"The ARIA label for the main navigation"}),className:(0,a.A)(g.G.layout.navbar.container,"navbar","navbar--fixed-top",t&&[He.navbarHideable,!l&&He.navbarHidden],{"navbar--dark":"dark"===n,"navbar--primary":"primary"===n,"navbar-sidebar--show":o.shown}),children:[e,(0,c.jsx)(Ge,{onClick:o.toggle}),(0,c.jsx)(qe,{})]})}var We=n(440);const Qe={errorBoundaryError:"errorBoundaryError_a6uf",errorBoundaryFallback:"errorBoundaryFallback_VBag"};function Ke(e){return(0,c.jsx)("button",{type:"button",...e,children:(0,c.jsx)(s.A,{id:"theme.ErrorPageContent.tryAgain",description:"The label of the button to try again rendering when the React error boundary captures an error",children:"Try again"})})}function Ye({error:e}){const t=(0,We.rA)(e).map((e=>e.message)).join("\n\nCause:\n");return(0,c.jsx)("p",{className:Qe.errorBoundaryError,children:t})}class Xe extends r.Component{componentDidCatch(e,t){throw this.props.onError(e,t)}render(){return this.props.children}}const Ze="right";function Je({width:e=30,height:t=30,className:n,...r}){return(0,c.jsx)("svg",{className:n,width:e,height:t,viewBox:"0 0 30 30","aria-hidden":"true",...r,children:(0,c.jsx)("path",{stroke:"currentColor",strokeLinecap:"round",strokeMiterlimit:"10",strokeWidth:"2",d:"M4 7h22M4 15h22M4 23h22"})})}function et(){const{toggle:e,shown:t}=(0,L.M)();return(0,c.jsx)("button",{onClick:e,"aria-label":(0,s.T)({id:"theme.docs.sidebar.toggleSidebarButtonAriaLabel",message:"Toggle navigation bar",description:"The ARIA label for hamburger menu button of mobile navigation"}),"aria-expanded":t,className:"navbar__toggle clean-btn",type:"button",children:(0,c.jsx)(Je,{})})}const tt={colorModeToggle:"colorModeToggle_DEke"};function nt({items:e}){return(0,c.jsx)(c.Fragment,{children:e.map(((e,t)=>(0,c.jsx)(Xe,{onError:t=>new Error(`A theme navbar item failed to render.\nPlease double-check the following navbar item (themeConfig.navbar.items) of your Docusaurus config:\n${JSON.stringify(e,null,2)}`,{cause:t}),children:(0,c.jsx)(Be,{...e})},t)))})}function rt({left:e,right:t}){return(0,c.jsxs)("div",{className:"navbar__inner",children:[(0,c.jsx)("div",{className:(0,a.A)(g.G.layout.navbar.containerLeft,"navbar__items"),children:e}),(0,c.jsx)("div",{className:(0,a.A)(g.G.layout.navbar.containerRight,"navbar__items navbar__items--right"),children:t})]})}function at(){const e=(0,L.M)(),t=(0,w.p)().navbar.items,[n,r]=function(e){function t(e){return"left"===(e.position??Ze)}return[e.filter(t),e.filter((e=>!t(e)))]}(t),a=t.find((e=>"search"===e.type));return(0,c.jsx)(rt,{left:(0,c.jsxs)(c.Fragment,{children:[!e.disabled&&(0,c.jsx)(et,{}),(0,c.jsx)(ie,{}),(0,c.jsx)(nt,{items:n})]}),right:(0,c.jsxs)(c.Fragment,{children:[(0,c.jsx)(nt,{items:r}),(0,c.jsx)(ae,{className:tt.colorModeToggle}),!a&&(0,c.jsx)(Ne,{children:(0,c.jsx)(je.A,{})})]})})}function ot(){return(0,c.jsx)(Ve,{children:(0,c.jsx)(at,{})})}function it({item:e}){const{to:t,href:n,label:r,prependBaseUrlToHref:o,className:i,...l}=e,s=(0,ce.Ay)(t),u=(0,ce.Ay)(n,{forcePrependBaseUrl:!0});return(0,c.jsxs)(ue.A,{className:(0,a.A)("footer__link-item",i),...n?{href:o?u:n}:{to:s},...l,children:[r,n&&!(0,de.A)(n)&&(0,c.jsx)(pe.A,{})]})}function lt({item:e}){return e.html?(0,c.jsx)("li",{className:(0,a.A)("footer__item",e.className),dangerouslySetInnerHTML:{__html:e.html}}):(0,c.jsx)("li",{className:"footer__item",children:(0,c.jsx)(it,{item:e})},e.href??e.to)}function st({column:e}){return(0,c.jsxs)("div",{className:(0,a.A)(g.G.layout.footer.column,"col footer__col",e.className),children:[(0,c.jsx)("div",{className:"footer__title",children:e.title}),(0,c.jsx)("ul",{className:"footer__items clean-list",children:e.items.map(((e,t)=>(0,c.jsx)(lt,{item:e},t)))})]})}function ut({columns:e}){return(0,c.jsx)("div",{className:"row footer__links",children:e.map(((e,t)=>(0,c.jsx)(st,{column:e},t)))})}function ct(){return(0,c.jsx)("span",{className:"footer__link-separator",children:"\xb7"})}function dt({item:e}){return e.html?(0,c.jsx)("span",{className:(0,a.A)("footer__link-item",e.className),dangerouslySetInnerHTML:{__html:e.html}}):(0,c.jsx)(it,{item:e})}function ft({links:e}){return(0,c.jsx)("div",{className:"footer__links text--center",children:(0,c.jsx)("div",{className:"footer__links",children:e.map(((t,n)=>(0,c.jsxs)(r.Fragment,{children:[(0,c.jsx)(dt,{item:t}),e.length!==n+1&&(0,c.jsx)(ct,{})]},n)))})})}function pt({links:e}){return function(e){return"title"in e[0]}(e)?(0,c.jsx)(ut,{columns:e}):(0,c.jsx)(ft,{links:e})}var mt=n(1122);const ht="footerLogoLink_BH7S";function gt({logo:e}){const{withBaseUrl:t}=(0,ce.hH)(),n={light:t(e.src),dark:t(e.srcDark??e.src)};return(0,c.jsx)(mt.A,{className:(0,a.A)("footer__logo",e.className),alt:e.alt,sources:n,width:e.width,height:e.height,style:e.style})}function yt({logo:e}){return e.href?(0,c.jsx)(ue.A,{href:e.href,className:ht,target:e.target,children:(0,c.jsx)(gt,{logo:e})}):(0,c.jsx)(gt,{logo:e})}function bt({copyright:e}){return(0,c.jsx)("div",{className:"footer__copyright",dangerouslySetInnerHTML:{__html:e}})}function vt({style:e,links:t,logo:n,copyright:r}){return(0,c.jsx)("footer",{className:(0,a.A)(g.G.layout.footer.container,"footer",{"footer--dark":"dark"===e}),children:(0,c.jsxs)("div",{className:"container container-fluid",children:[t,(n||r)&&(0,c.jsxs)("div",{className:"footer__bottom text--center",children:[n&&(0,c.jsx)("div",{className:"margin-bottom--sm",children:n}),r]})]})})}function wt(){const{footer:e}=(0,w.p)();if(!e)return null;const{copyright:t,links:n,logo:r,style:a}=e;return(0,c.jsx)(vt,{style:a,links:n&&n.length>0&&(0,c.jsx)(pt,{links:n}),logo:r&&(0,c.jsx)(yt,{logo:r}),copyright:t&&(0,c.jsx)(bt,{copyright:t})})}const kt=r.memo(wt),St=(0,P.fM)([z.a,k.o,j.Tv,Me.VQ,i.Jx,function({children:e}){return(0,c.jsx)(N.y_,{children:(0,c.jsx)(L.e,{children:(0,c.jsx)(R,{children:e})})})}]);function xt({children:e}){return(0,c.jsx)(St,{children:e})}var _t=n(1107);function Et({error:e,tryAgain:t}){return(0,c.jsx)("main",{className:"container margin-vert--xl",children:(0,c.jsx)("div",{className:"row",children:(0,c.jsxs)("div",{className:"col col--6 col--offset-3",children:[(0,c.jsx)(_t.A,{as:"h1",className:"hero__title",children:(0,c.jsx)(s.A,{id:"theme.ErrorPageContent.title",description:"The title of the fallback page when the page crashed",children:"This page crashed."})}),(0,c.jsx)("div",{className:"margin-vert--lg",children:(0,c.jsx)(Ke,{onClick:t,className:"button button--primary shadow--lw"})}),(0,c.jsx)("hr",{}),(0,c.jsx)("div",{className:"margin-vert--md",children:(0,c.jsx)(Ye,{error:e})})]})})})}const Ct={mainWrapper:"mainWrapper_z2l0"};function At(e){const{children:t,noFooter:n,wrapperClassName:r,title:l,description:s}=e;return(0,y.J)(),(0,c.jsxs)(xt,{children:[(0,c.jsx)(i.be,{title:l,description:s}),(0,c.jsx)(v,{}),(0,c.jsx)(T,{}),(0,c.jsx)(ot,{}),(0,c.jsx)("div",{id:d,className:(0,a.A)(g.G.layout.main.container,g.G.wrapper.main,Ct.mainWrapper,r),children:(0,c.jsx)(o.A,{fallback:e=>(0,c.jsx)(Et,{...e}),children:t})}),!n&&(0,c.jsx)(kt,{})]})}},1682:(e,t,n)=>{"use strict";function r(e){return Array.from(new Set(e))}function a(e,t){const n={};let r=0;for(const a of e){const e=t(a,r);n[e]??=[],n[e].push(a),r+=1}return n}n.d(t,{$z:()=>a,sb:()=>r})},1765:(e,t,n)=>{"use strict";n.d(t,{My:()=>A,f4:()=>ne});var r,a,o,i,l,s,u,c=n(6540),d=n(4164),f=Object.create,p=Object.defineProperty,m=Object.defineProperties,h=Object.getOwnPropertyDescriptor,g=Object.getOwnPropertyDescriptors,y=Object.getOwnPropertyNames,b=Object.getOwnPropertySymbols,v=Object.getPrototypeOf,w=Object.prototype.hasOwnProperty,k=Object.prototype.propertyIsEnumerable,S=(e,t,n)=>t in e?p(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,x=(e,t)=>{for(var n in t||(t={}))w.call(t,n)&&S(e,n,t[n]);if(b)for(var n of b(t))k.call(t,n)&&S(e,n,t[n]);return e},_=(e,t)=>m(e,g(t)),E=(e,t)=>{var n={};for(var r in e)w.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&b)for(var r of b(e))t.indexOf(r)<0&&k.call(e,r)&&(n[r]=e[r]);return n},C=(r={"../../node_modules/.pnpm/prismjs@1.29.0_patch_hash=vrxx3pzkik6jpmgpayxfjunetu/node_modules/prismjs/prism.js"(e,t){var n=function(){var e=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,t=0,n={},r={util:{encode:function e(t){return t instanceof a?new a(t.type,e(t.content),t.alias):Array.isArray(t)?t.map(e):t.replace(/&/g,"&").replace(/</g,"<").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).slice(8,-1)},objId:function(e){return e.__id||Object.defineProperty(e,"__id",{value:++t}),e.__id},clone:function e(t,n){var a,o;switch(n=n||{},r.util.type(t)){case"Object":if(o=r.util.objId(t),n[o])return n[o];for(var i in a={},n[o]=a,t)t.hasOwnProperty(i)&&(a[i]=e(t[i],n));return a;case"Array":return o=r.util.objId(t),n[o]?n[o]:(a=[],n[o]=a,t.forEach((function(t,r){a[r]=e(t,n)})),a);default:return t}},getLanguage:function(t){for(;t;){var n=e.exec(t.className);if(n)return n[1].toLowerCase();t=t.parentElement}return"none"},setLanguage:function(t,n){t.className=t.className.replace(RegExp(e,"gi"),""),t.classList.add("language-"+n)},isActive:function(e,t,n){for(var r="no-"+t;e;){var a=e.classList;if(a.contains(t))return!0;if(a.contains(r))return!1;e=e.parentElement}return!!n}},languages:{plain:n,plaintext:n,text:n,txt:n,extend:function(e,t){var n=r.util.clone(r.languages[e]);for(var a in t)n[a]=t[a];return n},insertBefore:function(e,t,n,a){var o=(a=a||r.languages)[e],i={};for(var l in o)if(o.hasOwnProperty(l)){if(l==t)for(var s in n)n.hasOwnProperty(s)&&(i[s]=n[s]);n.hasOwnProperty(l)||(i[l]=o[l])}var u=a[e];return a[e]=i,r.languages.DFS(r.languages,(function(t,n){n===u&&t!=e&&(this[t]=i)})),i},DFS:function e(t,n,a,o){o=o||{};var i=r.util.objId;for(var l in t)if(t.hasOwnProperty(l)){n.call(t,l,t[l],a||l);var s=t[l],u=r.util.type(s);"Object"!==u||o[i(s)]?"Array"!==u||o[i(s)]||(o[i(s)]=!0,e(s,n,l,o)):(o[i(s)]=!0,e(s,n,null,o))}}},plugins:{},highlight:function(e,t,n){var o={code:e,grammar:t,language:n};if(r.hooks.run("before-tokenize",o),!o.grammar)throw new Error('The language "'+o.language+'" has no grammar.');return o.tokens=r.tokenize(o.code,o.grammar),r.hooks.run("after-tokenize",o),a.stringify(r.util.encode(o.tokens),o.language)},tokenize:function(e,t){var n=t.rest;if(n){for(var r in n)t[r]=n[r];delete t.rest}var a=new l;return s(a,a.head,e),i(e,a,t,a.head,0),function(e){for(var t=[],n=e.head.next;n!==e.tail;)t.push(n.value),n=n.next;return t}(a)},hooks:{all:{},add:function(e,t){var n=r.hooks.all;n[e]=n[e]||[],n[e].push(t)},run:function(e,t){var n=r.hooks.all[e];if(n&&n.length)for(var a,o=0;a=n[o++];)a(t)}},Token:a};function a(e,t,n,r){this.type=e,this.content=t,this.alias=n,this.length=0|(r||"").length}function o(e,t,n,r){e.lastIndex=t;var a=e.exec(n);if(a&&r&&a[1]){var o=a[1].length;a.index+=o,a[0]=a[0].slice(o)}return a}function i(e,t,n,l,c,d){for(var f in n)if(n.hasOwnProperty(f)&&n[f]){var p=n[f];p=Array.isArray(p)?p:[p];for(var m=0;m<p.length;++m){if(d&&d.cause==f+","+m)return;var h=p[m],g=h.inside,y=!!h.lookbehind,b=!!h.greedy,v=h.alias;if(b&&!h.pattern.global){var w=h.pattern.toString().match(/[imsuy]*$/)[0];h.pattern=RegExp(h.pattern.source,w+"g")}for(var k=h.pattern||h,S=l.next,x=c;S!==t.tail&&!(d&&x>=d.reach);x+=S.value.length,S=S.next){var _=S.value;if(t.length>e.length)return;if(!(_ instanceof a)){var E,C=1;if(b){if(!(E=o(k,x,e,y))||E.index>=e.length)break;var A=E.index,T=E.index+E[0].length,L=x;for(L+=S.value.length;A>=L;)L+=(S=S.next).value.length;if(x=L-=S.value.length,S.value instanceof a)continue;for(var j=S;j!==t.tail&&(L<T||"string"==typeof j.value);j=j.next)C++,L+=j.value.length;C--,_=e.slice(x,L),E.index-=x}else if(!(E=o(k,0,_,y)))continue;A=E.index;var P=E[0],N=_.slice(0,A),O=_.slice(A+P.length),R=x+_.length;d&&R>d.reach&&(d.reach=R);var M=S.prev;if(N&&(M=s(t,M,N),x+=N.length),u(t,M,C),S=s(t,M,new a(f,g?r.tokenize(P,g):P,v,P)),O&&s(t,S,O),C>1){var D={cause:f+","+m,reach:R};i(e,t,n,S.prev,x,D),d&&D.reach>d.reach&&(d.reach=D.reach)}}}}}}function l(){var e={value:null,prev:null,next:null},t={value:null,prev:e,next:null};e.next=t,this.head=e,this.tail=t,this.length=0}function s(e,t,n){var r=t.next,a={value:n,prev:t,next:r};return t.next=a,r.prev=a,e.length++,a}function u(e,t,n){for(var r=t.next,a=0;a<n&&r!==e.tail;a++)r=r.next;t.next=r,r.prev=t,e.length-=a}return a.stringify=function e(t,n){if("string"==typeof t)return t;if(Array.isArray(t)){var a="";return t.forEach((function(t){a+=e(t,n)})),a}var o={type:t.type,content:e(t.content,n),tag:"span",classes:["token",t.type],attributes:{},language:n},i=t.alias;i&&(Array.isArray(i)?Array.prototype.push.apply(o.classes,i):o.classes.push(i)),r.hooks.run("wrap",o);var l="";for(var s in o.attributes)l+=" "+s+'="'+(o.attributes[s]||"").replace(/"/g,""")+'"';return"<"+o.tag+' class="'+o.classes.join(" ")+'"'+l+">"+o.content+"</"+o.tag+">"},r}();t.exports=n,n.default=n}},function(){return a||(0,r[y(r)[0]])((a={exports:{}}).exports,a),a.exports}),A=((e,t,n)=>(n=null!=e?f(v(e)):{},((e,t,n,r)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let a of y(t))w.call(e,a)||a===n||p(e,a,{get:()=>t[a],enumerable:!(r=h(t,a))||r.enumerable});return e})(!t&&e&&e.__esModule?n:p(n,"default",{value:e,enumerable:!0}),e)))(C());A.languages.markup={comment:{pattern:/<!--(?:(?!<!--)[\s\S])*?-->/,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/<!DOCTYPE(?:[^>"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|<!--(?:[^-]|-(?!->))*-->)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^<!|>$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern:/<!\[CDATA\[[\s\S]*?\]\]>/i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},A.languages.markup.tag.inside["attr-value"].inside.entity=A.languages.markup.entity,A.languages.markup.doctype.inside["internal-subset"].inside=A.languages.markup,A.hooks.add("wrap",(function(e){"entity"===e.type&&(e.attributes.title=e.content.replace(/&/,"&"))})),Object.defineProperty(A.languages.markup.tag,"addInlined",{value:function(e,t){var n;(t=((n=((n={})["language-"+t]={pattern:/(^<!\[CDATA\[)[\s\S]+?(?=\]\]>$)/i,lookbehind:!0,inside:A.languages[t]},n.cdata=/^<!\[CDATA\[|\]\]>$/i,{"included-cdata":{pattern:/<!\[CDATA\[[\s\S]*?\]\]>/i,inside:n}}))["language-"+t]={pattern:/[\s\S]+/,inside:A.languages[t]},{}))[e]={pattern:RegExp(/(<__[^>]*>)(?:<!\[CDATA\[(?:[^\]]|\](?!\]>))*\]\]>|(?!<!\[CDATA\[)[\s\S])*?(?=<\/__>)/.source.replace(/__/g,(function(){return e})),"i"),lookbehind:!0,greedy:!0,inside:n},A.languages.insertBefore("markup","cdata",t)}}),Object.defineProperty(A.languages.markup.tag,"addAttribute",{value:function(e,t){A.languages.markup.tag.inside["special-attr"].push({pattern:RegExp(/(^|["'\s])/.source+"(?:"+e+")"+/\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))/.source,"i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[t,"language-"+t],inside:A.languages[t]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),A.languages.html=A.languages.markup,A.languages.mathml=A.languages.markup,A.languages.svg=A.languages.markup,A.languages.xml=A.languages.extend("markup",{}),A.languages.ssml=A.languages.xml,A.languages.atom=A.languages.xml,A.languages.rss=A.languages.xml,o=A,i={pattern:/\\[\\(){}[\]^$+*?|.]/,alias:"escape"},s="(?:[^\\\\-]|"+(l=/\\(?:x[\da-fA-F]{2}|u[\da-fA-F]{4}|u\{[\da-fA-F]+\}|0[0-7]{0,2}|[123][0-7]{2}|c[a-zA-Z]|.)/).source+")",s=RegExp(s+"-"+s),u={pattern:/(<|')[^<>']+(?=[>']$)/,lookbehind:!0,alias:"variable"},o.languages.regex={"char-class":{pattern:/((?:^|[^\\])(?:\\\\)*)\[(?:[^\\\]]|\\[\s\S])*\]/,lookbehind:!0,inside:{"char-class-negation":{pattern:/(^\[)\^/,lookbehind:!0,alias:"operator"},"char-class-punctuation":{pattern:/^\[|\]$/,alias:"punctuation"},range:{pattern:s,inside:{escape:l,"range-punctuation":{pattern:/-/,alias:"operator"}}},"special-escape":i,"char-set":{pattern:/\\[wsd]|\\p\{[^{}]+\}/i,alias:"class-name"},escape:l}},"special-escape":i,"char-set":{pattern:/\.|\\[wsd]|\\p\{[^{}]+\}/i,alias:"class-name"},backreference:[{pattern:/\\(?![123][0-7]{2})[1-9]/,alias:"keyword"},{pattern:/\\k<[^<>']+>/,alias:"keyword",inside:{"group-name":u}}],anchor:{pattern:/[$^]|\\[ABbGZz]/,alias:"function"},escape:l,group:[{pattern:/\((?:\?(?:<[^<>']+>|'[^<>']+'|[>:]|<?[=!]|[idmnsuxU]+(?:-[idmnsuxU]+)?:?))?/,alias:"punctuation",inside:{"group-name":u}},{pattern:/\)/,alias:"punctuation"}],quantifier:{pattern:/(?:[+*?]|\{\d+(?:,\d*)?\})[?+]?/,alias:"number"},alternation:{pattern:/\|/,alias:"keyword"}},A.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/},A.languages.javascript=A.languages.extend("clike",{"class-name":[A.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:{pattern:RegExp(/(^|[^\w$])/.source+"(?:"+/NaN|Infinity/.source+"|"+/0[bB][01]+(?:_[01]+)*n?/.source+"|"+/0[oO][0-7]+(?:_[0-7]+)*n?/.source+"|"+/0[xX][\dA-Fa-f]+(?:_[\dA-Fa-f]+)*n?/.source+"|"+/\d+(?:_\d+)*n/.source+"|"+/(?:\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\.\d+(?:_\d+)*)(?:[Ee][+-]?\d+(?:_\d+)*)?/.source+")"+/(?![\w$])/.source),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),A.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,A.languages.insertBefore("javascript","keyword",{regex:{pattern:RegExp(/((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)/.source+/\//.source+"(?:"+/(?:\[(?:[^\]\\\r\n]|\\.)*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}/.source+"|"+/(?:\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.)*\])*\])*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}v[dgimyus]{0,7}/.source+")"+/(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/.source),lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:A.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:A.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:A.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:A.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:A.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),A.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:A.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),A.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),A.languages.markup&&(A.languages.markup.tag.addInlined("script","javascript"),A.languages.markup.tag.addAttribute(/on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)/.source,"javascript")),A.languages.js=A.languages.javascript,A.languages.actionscript=A.languages.extend("javascript",{keyword:/\b(?:as|break|case|catch|class|const|default|delete|do|dynamic|each|else|extends|final|finally|for|function|get|if|implements|import|in|include|instanceof|interface|internal|is|namespace|native|new|null|override|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|use|var|void|while|with)\b/,operator:/\+\+|--|(?:[+\-*\/%^]|&&?|\|\|?|<<?|>>?>?|[!=]=?)=?|[~?@]/}),A.languages.actionscript["class-name"].alias="function",delete A.languages.actionscript.parameter,delete A.languages.actionscript["literal-property"],A.languages.markup&&A.languages.insertBefore("actionscript","string",{xml:{pattern:/(^|[^.])<\/?\w+(?:\s+[^\s>\/=]+=("|')(?:\\[\s\S]|(?!\2)[^\\])*\2)*\s*\/?>/,lookbehind:!0,inside:A.languages.markup}}),function(e){var t=/#(?!\{).+/,n={pattern:/#\{[^}]+\}/,alias:"variable"};e.languages.coffeescript=e.languages.extend("javascript",{comment:t,string:[{pattern:/'(?:\\[\s\S]|[^\\'])*'/,greedy:!0},{pattern:/"(?:\\[\s\S]|[^\\"])*"/,greedy:!0,inside:{interpolation:n}}],keyword:/\b(?:and|break|by|catch|class|continue|debugger|delete|do|each|else|extend|extends|false|finally|for|if|in|instanceof|is|isnt|let|loop|namespace|new|no|not|null|of|off|on|or|own|return|super|switch|then|this|throw|true|try|typeof|undefined|unless|until|when|while|window|with|yes|yield)\b/,"class-member":{pattern:/@(?!\d)\w+/,alias:"variable"}}),e.languages.insertBefore("coffeescript","comment",{"multiline-comment":{pattern:/###[\s\S]+?###/,alias:"comment"},"block-regex":{pattern:/\/{3}[\s\S]*?\/{3}/,alias:"regex",inside:{comment:t,interpolation:n}}}),e.languages.insertBefore("coffeescript","string",{"inline-javascript":{pattern:/`(?:\\[\s\S]|[^\\`])*`/,inside:{delimiter:{pattern:/^`|`$/,alias:"punctuation"},script:{pattern:/[\s\S]+/,alias:"language-javascript",inside:e.languages.javascript}}},"multiline-string":[{pattern:/'''[\s\S]*?'''/,greedy:!0,alias:"string"},{pattern:/"""[\s\S]*?"""/,greedy:!0,alias:"string",inside:{interpolation:n}}]}),e.languages.insertBefore("coffeescript","keyword",{property:/(?!\d)\w+(?=\s*:(?!:))/}),delete e.languages.coffeescript["template-string"],e.languages.coffee=e.languages.coffeescript}(A),function(e){var t=e.languages.javadoclike={parameter:{pattern:/(^[\t ]*(?:\/{3}|\*|\/\*\*)\s*@(?:arg|arguments|param)\s+)\w+/m,lookbehind:!0},keyword:{pattern:/(^[\t ]*(?:\/{3}|\*|\/\*\*)\s*|\{)@[a-z][a-zA-Z-]+\b/m,lookbehind:!0},punctuation:/[{}]/};Object.defineProperty(t,"addSupport",{value:function(t,n){(t="string"==typeof t?[t]:t).forEach((function(t){var r=function(e){e.inside||(e.inside={}),e.inside.rest=n},a="doc-comment";if(o=e.languages[t]){var o,i=o[a];if((i=i||(o=e.languages.insertBefore(t,"comment",{"doc-comment":{pattern:/(^|[^\\])\/\*\*[^/][\s\S]*?(?:\*\/|$)/,lookbehind:!0,alias:"comment"}}))[a])instanceof RegExp&&(i=o[a]={pattern:i}),Array.isArray(i))for(var l=0,s=i.length;l<s;l++)i[l]instanceof RegExp&&(i[l]={pattern:i[l]}),r(i[l]);else r(i)}}))}}),t.addSupport(["java","javascript","php"],t)}(A),function(e){var t=/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;(t=(e.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:RegExp("@[\\w-](?:"+/[^;{\s"']|\s+(?!\s)/.source+"|"+t.source+")*?"+/(?:;|(?=\s*\{))/.source),inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+t.source+"|"+/(?:[^\\\r\n()"']|\\[\s\S])*/.source+")\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+t.source+"$"),alias:"url"}}},selector:{pattern:RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+t.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:t,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},e.languages.css.atrule.inside.rest=e.languages.css,e.languages.markup))&&(t.tag.addInlined("style","css"),t.tag.addAttribute("style","css"))}(A),function(e){var t=/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,n=(t=(e.languages.css.selector={pattern:e.languages.css.selector.pattern,lookbehind:!0,inside:t={"pseudo-element":/:(?:after|before|first-letter|first-line|selection)|::[-\w]+/,"pseudo-class":/:[-\w]+/,class:/\.[-\w]+/,id:/#[-\w]+/,attribute:{pattern:RegExp("\\[(?:[^[\\]\"']|"+t.source+")*\\]"),greedy:!0,inside:{punctuation:/^\[|\]$/,"case-sensitivity":{pattern:/(\s)[si]$/i,lookbehind:!0,alias:"keyword"},namespace:{pattern:/^(\s*)(?:(?!\s)[-*\w\xA0-\uFFFF])*\|(?!=)/,lookbehind:!0,inside:{punctuation:/\|$/}},"attr-name":{pattern:/^(\s*)(?:(?!\s)[-\w\xA0-\uFFFF])+/,lookbehind:!0},"attr-value":[t,{pattern:/(=\s*)(?:(?!\s)[-\w\xA0-\uFFFF])+(?=\s*$)/,lookbehind:!0}],operator:/[|~*^$]?=/}},"n-th":[{pattern:/(\(\s*)[+-]?\d*[\dn](?:\s*[+-]\s*\d+)?(?=\s*\))/,lookbehind:!0,inside:{number:/[\dn]+/,operator:/[+-]/}},{pattern:/(\(\s*)(?:even|odd)(?=\s*\))/i,lookbehind:!0}],combinator:/>|\+|~|\|\|/,punctuation:/[(),]/}},e.languages.css.atrule.inside["selector-function-argument"].inside=t,e.languages.insertBefore("css","property",{variable:{pattern:/(^|[^-\w\xA0-\uFFFF])--(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*/i,lookbehind:!0}}),{pattern:/(\b\d+)(?:%|[a-z]+(?![\w-]))/,lookbehind:!0}),{pattern:/(^|[^\w.-])-?(?:\d+(?:\.\d+)?|\.\d+)/,lookbehind:!0});e.languages.insertBefore("css","function",{operator:{pattern:/(\s)[+\-*\/](?=\s)/,lookbehind:!0},hexcode:{pattern:/\B#[\da-f]{3,8}\b/i,alias:"color"},color:[{pattern:/(^|[^\w-])(?:AliceBlue|AntiqueWhite|Aqua|Aquamarine|Azure|Beige|Bisque|Black|BlanchedAlmond|Blue|BlueViolet|Brown|BurlyWood|CadetBlue|Chartreuse|Chocolate|Coral|CornflowerBlue|Cornsilk|Crimson|Cyan|DarkBlue|DarkCyan|DarkGoldenRod|DarkGr[ae]y|DarkGreen|DarkKhaki|DarkMagenta|DarkOliveGreen|DarkOrange|DarkOrchid|DarkRed|DarkSalmon|DarkSeaGreen|DarkSlateBlue|DarkSlateGr[ae]y|DarkTurquoise|DarkViolet|DeepPink|DeepSkyBlue|DimGr[ae]y|DodgerBlue|FireBrick|FloralWhite|ForestGreen|Fuchsia|Gainsboro|GhostWhite|Gold|GoldenRod|Gr[ae]y|Green|GreenYellow|HoneyDew|HotPink|IndianRed|Indigo|Ivory|Khaki|Lavender|LavenderBlush|LawnGreen|LemonChiffon|LightBlue|LightCoral|LightCyan|LightGoldenRodYellow|LightGr[ae]y|LightGreen|LightPink|LightSalmon|LightSeaGreen|LightSkyBlue|LightSlateGr[ae]y|LightSteelBlue|LightYellow|Lime|LimeGreen|Linen|Magenta|Maroon|MediumAquaMarine|MediumBlue|MediumOrchid|MediumPurple|MediumSeaGreen|MediumSlateBlue|MediumSpringGreen|MediumTurquoise|MediumVioletRed|MidnightBlue|MintCream|MistyRose|Moccasin|NavajoWhite|Navy|OldLace|Olive|OliveDrab|Orange|OrangeRed|Orchid|PaleGoldenRod|PaleGreen|PaleTurquoise|PaleVioletRed|PapayaWhip|PeachPuff|Peru|Pink|Plum|PowderBlue|Purple|RebeccaPurple|Red|RosyBrown|RoyalBlue|SaddleBrown|Salmon|SandyBrown|SeaGreen|SeaShell|Sienna|Silver|SkyBlue|SlateBlue|SlateGr[ae]y|Snow|SpringGreen|SteelBlue|Tan|Teal|Thistle|Tomato|Transparent|Turquoise|Violet|Wheat|White|WhiteSmoke|Yellow|YellowGreen)(?![\w-])/i,lookbehind:!0},{pattern:/\b(?:hsl|rgb)\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*\)\B|\b(?:hsl|rgb)a\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*,\s*(?:0|0?\.\d+|1)\s*\)\B/i,inside:{unit:t,number:n,function:/[\w-]+(?=\()/,punctuation:/[(),]/}}],entity:/\\[\da-f]{1,8}/i,unit:t,number:n})}(A),function(e){var t=/[*&][^\s[\]{},]+/,n=/!(?:<[\w\-%#;/?:@&=+$,.!~*'()[\]]+>|(?:[a-zA-Z\d-]*!)?[\w\-%#;/?:@&=+$.~*'()]+)?/,r="(?:"+n.source+"(?:[ \t]+"+t.source+")?|"+t.source+"(?:[ \t]+"+n.source+")?)",a=/(?:[^\s\x00-\x08\x0e-\x1f!"#%&'*,\-:>?@[\]`{|}\x7f-\x84\x86-\x9f\ud800-\udfff\ufffe\uffff]|[?:-]<PLAIN>)(?:[ \t]*(?:(?![#:])<PLAIN>|:<PLAIN>))*/.source.replace(/<PLAIN>/g,(function(){return/[^\s\x00-\x08\x0e-\x1f,[\]{}\x7f-\x84\x86-\x9f\ud800-\udfff\ufffe\uffff]/.source})),o=/"(?:[^"\\\r\n]|\\.)*"|'(?:[^'\\\r\n]|\\.)*'/.source;function i(e,t){t=(t||"").replace(/m/g,"")+"m";var n=/([:\-,[{]\s*(?:\s<<prop>>[ \t]+)?)(?:<<value>>)(?=[ \t]*(?:$|,|\]|\}|(?:[\r\n]\s*)?#))/.source.replace(/<<prop>>/g,(function(){return r})).replace(/<<value>>/g,(function(){return e}));return RegExp(n,t)}e.languages.yaml={scalar:{pattern:RegExp(/([\-:]\s*(?:\s<<prop>>[ \t]+)?[|>])[ \t]*(?:((?:\r?\n|\r)[ \t]+)\S[^\r\n]*(?:\2[^\r\n]+)*)/.source.replace(/<<prop>>/g,(function(){return r}))),lookbehind:!0,alias:"string"},comment:/#.*/,key:{pattern:RegExp(/((?:^|[:\-,[{\r\n?])[ \t]*(?:<<prop>>[ \t]+)?)<<key>>(?=\s*:\s)/.source.replace(/<<prop>>/g,(function(){return r})).replace(/<<key>>/g,(function(){return"(?:"+a+"|"+o+")"}))),lookbehind:!0,greedy:!0,alias:"atrule"},directive:{pattern:/(^[ \t]*)%.+/m,lookbehind:!0,alias:"important"},datetime:{pattern:i(/\d{4}-\d\d?-\d\d?(?:[tT]|[ \t]+)\d\d?:\d{2}:\d{2}(?:\.\d*)?(?:[ \t]*(?:Z|[-+]\d\d?(?::\d{2})?))?|\d{4}-\d{2}-\d{2}|\d\d?:\d{2}(?::\d{2}(?:\.\d*)?)?/.source),lookbehind:!0,alias:"number"},boolean:{pattern:i(/false|true/.source,"i"),lookbehind:!0,alias:"important"},null:{pattern:i(/null|~/.source,"i"),lookbehind:!0,alias:"important"},string:{pattern:i(o),lookbehind:!0,greedy:!0},number:{pattern:i(/[+-]?(?:0x[\da-f]+|0o[0-7]+|(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?|\.inf|\.nan)/.source,"i"),lookbehind:!0},tag:n,important:t,punctuation:/---|[:[\]{}\-,|>?]|\.\.\./},e.languages.yml=e.languages.yaml}(A),function(e){var t=/(?:\\.|[^\\\n\r]|(?:\n|\r\n?)(?![\r\n]))/.source;function n(e){return e=e.replace(/<inner>/g,(function(){return t})),RegExp(/((?:^|[^\\])(?:\\{2})*)/.source+"(?:"+e+")")}var r=/(?:\\.|``(?:[^`\r\n]|`(?!`))+``|`[^`\r\n]+`|[^\\|\r\n`])+/.source,a=/\|?__(?:\|__)+\|?(?:(?:\n|\r\n?)|(?![\s\S]))/.source.replace(/__/g,(function(){return r})),o=/\|?[ \t]*:?-{3,}:?[ \t]*(?:\|[ \t]*:?-{3,}:?[ \t]*)+\|?(?:\n|\r\n?)/.source,i=(e.languages.markdown=e.languages.extend("markup",{}),e.languages.insertBefore("markdown","prolog",{"front-matter-block":{pattern:/(^(?:\s*[\r\n])?)---(?!.)[\s\S]*?[\r\n]---(?!.)/,lookbehind:!0,greedy:!0,inside:{punctuation:/^---|---$/,"front-matter":{pattern:/\S+(?:\s+\S+)*/,alias:["yaml","language-yaml"],inside:e.languages.yaml}}},blockquote:{pattern:/^>(?:[\t ]*>)*/m,alias:"punctuation"},table:{pattern:RegExp("^"+a+o+"(?:"+a+")*","m"),inside:{"table-data-rows":{pattern:RegExp("^("+a+o+")(?:"+a+")*$"),lookbehind:!0,inside:{"table-data":{pattern:RegExp(r),inside:e.languages.markdown},punctuation:/\|/}},"table-line":{pattern:RegExp("^("+a+")"+o+"$"),lookbehind:!0,inside:{punctuation:/\||:?-{3,}:?/}},"table-header-row":{pattern:RegExp("^"+a+"$"),inside:{"table-header":{pattern:RegExp(r),alias:"important",inside:e.languages.markdown},punctuation:/\|/}}}},code:[{pattern:/((?:^|\n)[ \t]*\n|(?:^|\r\n?)[ \t]*\r\n?)(?: {4}|\t).+(?:(?:\n|\r\n?)(?: {4}|\t).+)*/,lookbehind:!0,alias:"keyword"},{pattern:/^```[\s\S]*?^```$/m,greedy:!0,inside:{"code-block":{pattern:/^(```.*(?:\n|\r\n?))[\s\S]+?(?=(?:\n|\r\n?)^```$)/m,lookbehind:!0},"code-language":{pattern:/^(```).+/,lookbehind:!0},punctuation:/```/}}],title:[{pattern:/\S.*(?:\n|\r\n?)(?:==+|--+)(?=[ \t]*$)/m,alias:"important",inside:{punctuation:/==+$|--+$/}},{pattern:/(^\s*)#.+/m,lookbehind:!0,alias:"important",inside:{punctuation:/^#+|#+$/}}],hr:{pattern:/(^\s*)([*-])(?:[\t ]*\2){2,}(?=\s*$)/m,lookbehind:!0,alias:"punctuation"},list:{pattern:/(^\s*)(?:[*+-]|\d+\.)(?=[\t ].)/m,lookbehind:!0,alias:"punctuation"},"url-reference":{pattern:/!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:\\.|[^>\\])+>)(?:[\t ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\)))?/,inside:{variable:{pattern:/^(!?\[)[^\]]+/,lookbehind:!0},string:/(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\))$/,punctuation:/^[\[\]!:]|[<>]/},alias:"url"},bold:{pattern:n(/\b__(?:(?!_)<inner>|_(?:(?!_)<inner>)+_)+__\b|\*\*(?:(?!\*)<inner>|\*(?:(?!\*)<inner>)+\*)+\*\*/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^..)[\s\S]+(?=..$)/,lookbehind:!0,inside:{}},punctuation:/\*\*|__/}},italic:{pattern:n(/\b_(?:(?!_)<inner>|__(?:(?!_)<inner>)+__)+_\b|\*(?:(?!\*)<inner>|\*\*(?:(?!\*)<inner>)+\*\*)+\*/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^.)[\s\S]+(?=.$)/,lookbehind:!0,inside:{}},punctuation:/[*_]/}},strike:{pattern:n(/(~~?)(?:(?!~)<inner>)+\2/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^~~?)[\s\S]+(?=\1$)/,lookbehind:!0,inside:{}},punctuation:/~~?/}},"code-snippet":{pattern:/(^|[^\\`])(?:``[^`\r\n]+(?:`[^`\r\n]+)*``(?!`)|`[^`\r\n]+`(?!`))/,lookbehind:!0,greedy:!0,alias:["code","keyword"]},url:{pattern:n(/!?\[(?:(?!\])<inner>)+\](?:\([^\s)]+(?:[\t ]+"(?:\\.|[^"\\])*")?\)|[ \t]?\[(?:(?!\])<inner>)+\])/.source),lookbehind:!0,greedy:!0,inside:{operator:/^!/,content:{pattern:/(^\[)[^\]]+(?=\])/,lookbehind:!0,inside:{}},variable:{pattern:/(^\][ \t]?\[)[^\]]+(?=\]$)/,lookbehind:!0},url:{pattern:/(^\]\()[^\s)]+/,lookbehind:!0},string:{pattern:/(^[ \t]+)"(?:\\.|[^"\\])*"(?=\)$)/,lookbehind:!0}}}}),["url","bold","italic","strike"].forEach((function(t){["url","bold","italic","strike","code-snippet"].forEach((function(n){t!==n&&(e.languages.markdown[t].inside.content.inside[n]=e.languages.markdown[n])}))})),e.hooks.add("after-tokenize",(function(e){"markdown"!==e.language&&"md"!==e.language||function e(t){if(t&&"string"!=typeof t)for(var n=0,r=t.length;n<r;n++){var a,o=t[n];"code"!==o.type?e(o.content):(a=o.content[1],o=o.content[3],a&&o&&"code-language"===a.type&&"code-block"===o.type&&"string"==typeof a.content&&(a=a.content.replace(/\b#/g,"sharp").replace(/\b\+\+/g,"pp"),a="language-"+(a=(/[a-z][\w-]*/i.exec(a)||[""])[0].toLowerCase()),o.alias?"string"==typeof o.alias?o.alias=[o.alias,a]:o.alias.push(a):o.alias=[a]))}}(e.tokens)})),e.hooks.add("wrap",(function(t){if("code-block"===t.type){for(var n="",r=0,a=t.classes.length;r<a;r++){var o=t.classes[r];if(o=/language-(.+)/.exec(o)){n=o[1];break}}var u,c=e.languages[n];c?t.content=e.highlight(t.content.replace(i,"").replace(/&(\w{1,8}|#x?[\da-f]{1,8});/gi,(function(e,t){var n;return"#"===(t=t.toLowerCase())[0]?(n="x"===t[1]?parseInt(t.slice(2),16):Number(t.slice(1)),s(n)):l[t]||e})),c,n):n&&"none"!==n&&e.plugins.autoloader&&(u="md-"+(new Date).valueOf()+"-"+Math.floor(1e16*Math.random()),t.attributes.id=u,e.plugins.autoloader.loadLanguages(n,(function(){var t=document.getElementById(u);t&&(t.innerHTML=e.highlight(t.textContent,e.languages[n],n))})))}})),RegExp(e.languages.markup.tag.pattern.source,"gi")),l={amp:"&",lt:"<",gt:">",quot:'"'},s=String.fromCodePoint||String.fromCharCode;e.languages.md=e.languages.markdown}(A),A.languages.graphql={comment:/#.*/,description:{pattern:/(?:"""(?:[^"]|(?!""")")*"""|"(?:\\.|[^\\"\r\n])*")(?=\s*[a-z_])/i,greedy:!0,alias:"string",inside:{"language-markdown":{pattern:/(^"(?:"")?)(?!\1)[\s\S]+(?=\1$)/,lookbehind:!0,inside:A.languages.markdown}}},string:{pattern:/"""(?:[^"]|(?!""")")*"""|"(?:\\.|[^\\"\r\n])*"/,greedy:!0},number:/(?:\B-|\b)\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,boolean:/\b(?:false|true)\b/,variable:/\$[a-z_]\w*/i,directive:{pattern:/@[a-z_]\w*/i,alias:"function"},"attr-name":{pattern:/\b[a-z_]\w*(?=\s*(?:\((?:[^()"]|"(?:\\.|[^\\"\r\n])*")*\))?:)/i,greedy:!0},"atom-input":{pattern:/\b[A-Z]\w*Input\b/,alias:"class-name"},scalar:/\b(?:Boolean|Float|ID|Int|String)\b/,constant:/\b[A-Z][A-Z_\d]*\b/,"class-name":{pattern:/(\b(?:enum|implements|interface|on|scalar|type|union)\s+|&\s*|:\s*|\[)[A-Z_]\w*/,lookbehind:!0},fragment:{pattern:/(\bfragment\s+|\.{3}\s*(?!on\b))[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},"definition-mutation":{pattern:/(\bmutation\s+)[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},"definition-query":{pattern:/(\bquery\s+)[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},keyword:/\b(?:directive|enum|extend|fragment|implements|input|interface|mutation|on|query|repeatable|scalar|schema|subscription|type|union)\b/,operator:/[!=|&]|\.{3}/,"property-query":/\w+(?=\s*\()/,object:/\w+(?=\s*\{)/,punctuation:/[!(){}\[\]:=,]/,property:/\w+/},A.hooks.add("after-tokenize",(function(e){if("graphql"===e.language)for(var t=e.tokens.filter((function(e){return"string"!=typeof e&&"comment"!==e.type&&"scalar"!==e.type})),n=0;n<t.length;){var r=t[n++];if("keyword"===r.type&&"mutation"===r.content){var a=[];if(d(["definition-mutation","punctuation"])&&"("===c(1).content){n+=2;var o=f(/^\($/,/^\)$/);if(-1===o)continue;for(;n<o;n++){var i=c(0);"variable"===i.type&&(p(i,"variable-input"),a.push(i.content))}n=o+1}if(d(["punctuation","property-query"])&&"{"===c(0).content&&(n++,p(c(0),"property-mutation"),0<a.length)){var l=f(/^\{$/,/^\}$/);if(-1!==l)for(var s=n;s<l;s++){var u=t[s];"variable"===u.type&&0<=a.indexOf(u.content)&&p(u,"variable-input")}}}}function c(e){return t[n+e]}function d(e,t){t=t||0;for(var n=0;n<e.length;n++){var r=c(n+t);if(!r||r.type!==e[n])return}return 1}function f(e,r){for(var a=1,o=n;o<t.length;o++){var i=t[o],l=i.content;if("punctuation"===i.type&&"string"==typeof l)if(e.test(l))a++;else if(r.test(l)&&0===--a)return o}return-1}function p(e,t){var n=e.alias;n?Array.isArray(n)||(e.alias=n=[n]):e.alias=n=[],n.push(t)}})),A.languages.sql={comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|(?:--|\/\/|#).*)/,lookbehind:!0},variable:[{pattern:/@(["'`])(?:\\[\s\S]|(?!\1)[^\\])+\1/,greedy:!0},/@[\w.$]+/],string:{pattern:/(^|[^@\\])("|')(?:\\[\s\S]|(?!\2)[^\\]|\2\2)*\2/,greedy:!0,lookbehind:!0},identifier:{pattern:/(^|[^@\\])`(?:\\[\s\S]|[^`\\]|``)*`/,greedy:!0,lookbehind:!0,inside:{punctuation:/^`|`$/}},function:/\b(?:AVG|COUNT|FIRST|FORMAT|LAST|LCASE|LEN|MAX|MID|MIN|MOD|NOW|ROUND|SUM|UCASE)(?=\s*\()/i,keyword:/\b(?:ACTION|ADD|AFTER|ALGORITHM|ALL|ALTER|ANALYZE|ANY|APPLY|AS|ASC|AUTHORIZATION|AUTO_INCREMENT|BACKUP|BDB|BEGIN|BERKELEYDB|BIGINT|BINARY|BIT|BLOB|BOOL|BOOLEAN|BREAK|BROWSE|BTREE|BULK|BY|CALL|CASCADED?|CASE|CHAIN|CHAR(?:ACTER|SET)?|CHECK(?:POINT)?|CLOSE|CLUSTERED|COALESCE|COLLATE|COLUMNS?|COMMENT|COMMIT(?:TED)?|COMPUTE|CONNECT|CONSISTENT|CONSTRAINT|CONTAINS(?:TABLE)?|CONTINUE|CONVERT|CREATE|CROSS|CURRENT(?:_DATE|_TIME|_TIMESTAMP|_USER)?|CURSOR|CYCLE|DATA(?:BASES?)?|DATE(?:TIME)?|DAY|DBCC|DEALLOCATE|DEC|DECIMAL|DECLARE|DEFAULT|DEFINER|DELAYED|DELETE|DELIMITERS?|DENY|DESC|DESCRIBE|DETERMINISTIC|DISABLE|DISCARD|DISK|DISTINCT|DISTINCTROW|DISTRIBUTED|DO|DOUBLE|DROP|DUMMY|DUMP(?:FILE)?|DUPLICATE|ELSE(?:IF)?|ENABLE|ENCLOSED|END|ENGINE|ENUM|ERRLVL|ERRORS|ESCAPED?|EXCEPT|EXEC(?:UTE)?|EXISTS|EXIT|EXPLAIN|EXTENDED|FETCH|FIELDS|FILE|FILLFACTOR|FIRST|FIXED|FLOAT|FOLLOWING|FOR(?: EACH ROW)?|FORCE|FOREIGN|FREETEXT(?:TABLE)?|FROM|FULL|FUNCTION|GEOMETRY(?:COLLECTION)?|GLOBAL|GOTO|GRANT|GROUP|HANDLER|HASH|HAVING|HOLDLOCK|HOUR|IDENTITY(?:COL|_INSERT)?|IF|IGNORE|IMPORT|INDEX|INFILE|INNER|INNODB|INOUT|INSERT|INT|INTEGER|INTERSECT|INTERVAL|INTO|INVOKER|ISOLATION|ITERATE|JOIN|KEYS?|KILL|LANGUAGE|LAST|LEAVE|LEFT|LEVEL|LIMIT|LINENO|LINES|LINESTRING|LOAD|LOCAL|LOCK|LONG(?:BLOB|TEXT)|LOOP|MATCH(?:ED)?|MEDIUM(?:BLOB|INT|TEXT)|MERGE|MIDDLEINT|MINUTE|MODE|MODIFIES|MODIFY|MONTH|MULTI(?:LINESTRING|POINT|POLYGON)|NATIONAL|NATURAL|NCHAR|NEXT|NO|NONCLUSTERED|NULLIF|NUMERIC|OFF?|OFFSETS?|ON|OPEN(?:DATASOURCE|QUERY|ROWSET)?|OPTIMIZE|OPTION(?:ALLY)?|ORDER|OUT(?:ER|FILE)?|OVER|PARTIAL|PARTITION|PERCENT|PIVOT|PLAN|POINT|POLYGON|PRECEDING|PRECISION|PREPARE|PREV|PRIMARY|PRINT|PRIVILEGES|PROC(?:EDURE)?|PUBLIC|PURGE|QUICK|RAISERROR|READS?|REAL|RECONFIGURE|REFERENCES|RELEASE|RENAME|REPEAT(?:ABLE)?|REPLACE|REPLICATION|REQUIRE|RESIGNAL|RESTORE|RESTRICT|RETURN(?:ING|S)?|REVOKE|RIGHT|ROLLBACK|ROUTINE|ROW(?:COUNT|GUIDCOL|S)?|RTREE|RULE|SAVE(?:POINT)?|SCHEMA|SECOND|SELECT|SERIAL(?:IZABLE)?|SESSION(?:_USER)?|SET(?:USER)?|SHARE|SHOW|SHUTDOWN|SIMPLE|SMALLINT|SNAPSHOT|SOME|SONAME|SQL|START(?:ING)?|STATISTICS|STATUS|STRIPED|SYSTEM_USER|TABLES?|TABLESPACE|TEMP(?:ORARY|TABLE)?|TERMINATED|TEXT(?:SIZE)?|THEN|TIME(?:STAMP)?|TINY(?:BLOB|INT|TEXT)|TOP?|TRAN(?:SACTIONS?)?|TRIGGER|TRUNCATE|TSEQUAL|TYPES?|UNBOUNDED|UNCOMMITTED|UNDEFINED|UNION|UNIQUE|UNLOCK|UNPIVOT|UNSIGNED|UPDATE(?:TEXT)?|USAGE|USE|USER|USING|VALUES?|VAR(?:BINARY|CHAR|CHARACTER|YING)|VIEW|WAITFOR|WARNINGS|WHEN|WHERE|WHILE|WITH(?: ROLLUP|IN)?|WORK|WRITE(?:TEXT)?|YEAR)\b/i,boolean:/\b(?:FALSE|NULL|TRUE)\b/i,number:/\b0x[\da-f]+\b|\b\d+(?:\.\d*)?|\B\.\d+\b/i,operator:/[-+*\/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?|\b(?:AND|BETWEEN|DIV|ILIKE|IN|IS|LIKE|NOT|OR|REGEXP|RLIKE|SOUNDS LIKE|XOR)\b/i,punctuation:/[;[\]()`,.]/},function(e){var t=e.languages.javascript["template-string"],n=t.pattern.source,r=t.inside.interpolation,a=r.inside["interpolation-punctuation"],o=r.pattern.source;function i(t,r){if(e.languages[t])return{pattern:RegExp("((?:"+r+")\\s*)"+n),lookbehind:!0,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},"embedded-code":{pattern:/[\s\S]+/,alias:t}}}}function l(t,n,r){return t={code:t,grammar:n,language:r},e.hooks.run("before-tokenize",t),t.tokens=e.tokenize(t.code,t.grammar),e.hooks.run("after-tokenize",t),t.tokens}function s(t,n,i){var s=e.tokenize(t,{interpolation:{pattern:RegExp(o),lookbehind:!0}}),u=0,c={},d=(s=l(s.map((function(e){if("string"==typeof e)return e;var n,r;for(e=e.content;-1!==t.indexOf((r=u++,n="___"+i.toUpperCase()+"_"+r+"___")););return c[n]=e,n})).join(""),n,i),Object.keys(c));return u=0,function t(n){for(var o=0;o<n.length;o++){if(u>=d.length)return;var i,s,f,p,m,h,g,y=n[o];"string"==typeof y||"string"==typeof y.content?(i=d[u],-1!==(g=(h="string"==typeof y?y:y.content).indexOf(i))&&(++u,s=h.substring(0,g),m=c[i],f=void 0,(p={})["interpolation-punctuation"]=a,3===(p=e.tokenize(m,p)).length&&((f=[1,1]).push.apply(f,l(p[1],e.languages.javascript,"javascript")),p.splice.apply(p,f)),f=new e.Token("interpolation",p,r.alias,m),p=h.substring(g+i.length),m=[],s&&m.push(s),m.push(f),p&&(t(h=[p]),m.push.apply(m,h)),"string"==typeof y?(n.splice.apply(n,[o,1].concat(m)),o+=m.length-1):y.content=m)):(g=y.content,Array.isArray(g)?t(g):t([g]))}}(s),new e.Token(i,s,"language-"+i,t)}e.languages.javascript["template-string"]=[i("css",/\b(?:styled(?:\([^)]*\))?(?:\s*\.\s*\w+(?:\([^)]*\))*)*|css(?:\s*\.\s*(?:global|resolve))?|createGlobalStyle|keyframes)/.source),i("html",/\bhtml|\.\s*(?:inner|outer)HTML\s*\+?=/.source),i("svg",/\bsvg/.source),i("markdown",/\b(?:markdown|md)/.source),i("graphql",/\b(?:gql|graphql(?:\s*\.\s*experimental)?)/.source),i("sql",/\bsql/.source),t].filter(Boolean);var u={javascript:!0,js:!0,typescript:!0,ts:!0,jsx:!0,tsx:!0};function c(e){return"string"==typeof e?e:Array.isArray(e)?e.map(c).join(""):c(e.content)}e.hooks.add("after-tokenize",(function(t){t.language in u&&function t(n){for(var r=0,a=n.length;r<a;r++){var o,i,l,u=n[r];"string"!=typeof u&&(o=u.content,Array.isArray(o)?"template-string"===u.type?(u=o[1],3===o.length&&"string"!=typeof u&&"embedded-code"===u.type&&(i=c(u),u=u.alias,u=Array.isArray(u)?u[0]:u,l=e.languages[u])&&(o[1]=s(i,l,u))):t(o):"string"!=typeof o&&t([o]))}}(t.tokens)}))}(A),function(e){e.languages.typescript=e.languages.extend("javascript",{"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|type)\s+)(?!keyof\b)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?:\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>)?/,lookbehind:!0,greedy:!0,inside:null},builtin:/\b(?:Array|Function|Promise|any|boolean|console|never|number|string|symbol|unknown)\b/}),e.languages.typescript.keyword.push(/\b(?:abstract|declare|is|keyof|readonly|require)\b/,/\b(?:asserts|infer|interface|module|namespace|type)\b(?=\s*(?:[{_$a-zA-Z\xA0-\uFFFF]|$))/,/\btype\b(?=\s*(?:[\{*]|$))/),delete e.languages.typescript.parameter,delete e.languages.typescript["literal-property"];var t=e.languages.extend("typescript",{});delete t["class-name"],e.languages.typescript["class-name"].inside=t,e.languages.insertBefore("typescript","function",{decorator:{pattern:/@[$\w\xA0-\uFFFF]+/,inside:{at:{pattern:/^@/,alias:"operator"},function:/^[\s\S]+/}},"generic-function":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>(?=\s*\()/,greedy:!0,inside:{function:/^#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:t}}}}),e.languages.ts=e.languages.typescript}(A),function(e){var t=e.languages.javascript,n=/\{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})+\}/.source,r="(@(?:arg|argument|param|property)\\s+(?:"+n+"\\s+)?)";e.languages.jsdoc=e.languages.extend("javadoclike",{parameter:{pattern:RegExp(r+/(?:(?!\s)[$\w\xA0-\uFFFF.])+(?=\s|$)/.source),lookbehind:!0,inside:{punctuation:/\./}}}),e.languages.insertBefore("jsdoc","keyword",{"optional-parameter":{pattern:RegExp(r+/\[(?:(?!\s)[$\w\xA0-\uFFFF.])+(?:=[^[\]]+)?\](?=\s|$)/.source),lookbehind:!0,inside:{parameter:{pattern:/(^\[)[$\w\xA0-\uFFFF\.]+/,lookbehind:!0,inside:{punctuation:/\./}},code:{pattern:/(=)[\s\S]*(?=\]$)/,lookbehind:!0,inside:t,alias:"language-javascript"},punctuation:/[=[\]]/}},"class-name":[{pattern:RegExp(/(@(?:augments|class|extends|interface|memberof!?|template|this|typedef)\s+(?:<TYPE>\s+)?)[A-Z]\w*(?:\.[A-Z]\w*)*/.source.replace(/<TYPE>/g,(function(){return n}))),lookbehind:!0,inside:{punctuation:/\./}},{pattern:RegExp("(@[a-z]+\\s+)"+n),lookbehind:!0,inside:{string:t.string,number:t.number,boolean:t.boolean,keyword:e.languages.typescript.keyword,operator:/=>|\.\.\.|[&|?:*]/,punctuation:/[.,;=<>{}()[\]]/}}],example:{pattern:/(@example\s+(?!\s))(?:[^@\s]|\s+(?!\s))+?(?=\s*(?:\*\s*)?(?:@\w|\*\/))/,lookbehind:!0,inside:{code:{pattern:/^([\t ]*(?:\*\s*)?)\S.*$/m,lookbehind:!0,inside:t,alias:"language-javascript"}}}}),e.languages.javadoclike.addSupport("javascript",e.languages.jsdoc)}(A),function(e){e.languages.flow=e.languages.extend("javascript",{}),e.languages.insertBefore("flow","keyword",{type:[{pattern:/\b(?:[Bb]oolean|Function|[Nn]umber|[Ss]tring|[Ss]ymbol|any|mixed|null|void)\b/,alias:"class-name"}]}),e.languages.flow["function-variable"].pattern=/(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=\s*(?:function\b|(?:\([^()]*\)(?:\s*:\s*\w+)?|(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/i,delete e.languages.flow.parameter,e.languages.insertBefore("flow","operator",{"flow-punctuation":{pattern:/\{\||\|\}/,alias:"punctuation"}}),Array.isArray(e.languages.flow.keyword)||(e.languages.flow.keyword=[e.languages.flow.keyword]),e.languages.flow.keyword.unshift({pattern:/(^|[^$]\b)(?:Class|declare|opaque|type)\b(?!\$)/,lookbehind:!0},{pattern:/(^|[^$]\B)\$(?:Diff|Enum|Exact|Keys|ObjMap|PropertyType|Record|Shape|Subtype|Supertype|await)\b(?!\$)/,lookbehind:!0})}(A),A.languages.n4js=A.languages.extend("javascript",{keyword:/\b(?:Array|any|boolean|break|case|catch|class|const|constructor|continue|debugger|declare|default|delete|do|else|enum|export|extends|false|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|module|new|null|number|package|private|protected|public|return|set|static|string|super|switch|this|throw|true|try|typeof|var|void|while|with|yield)\b/}),A.languages.insertBefore("n4js","constant",{annotation:{pattern:/@+\w+/,alias:"operator"}}),A.languages.n4jsd=A.languages.n4js,function(e){function t(e,t){return RegExp(e.replace(/<ID>/g,(function(){return/(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*/.source})),t)}e.languages.insertBefore("javascript","function-variable",{"method-variable":{pattern:RegExp("(\\.\\s*)"+e.languages.javascript["function-variable"].pattern.source),lookbehind:!0,alias:["function-variable","method","function","property-access"]}}),e.languages.insertBefore("javascript","function",{method:{pattern:RegExp("(\\.\\s*)"+e.languages.javascript.function.source),lookbehind:!0,alias:["function","property-access"]}}),e.languages.insertBefore("javascript","constant",{"known-class-name":[{pattern:/\b(?:(?:Float(?:32|64)|(?:Int|Uint)(?:8|16|32)|Uint8Clamped)?Array|ArrayBuffer|BigInt|Boolean|DataView|Date|Error|Function|Intl|JSON|(?:Weak)?(?:Map|Set)|Math|Number|Object|Promise|Proxy|Reflect|RegExp|String|Symbol|WebAssembly)\b/,alias:"class-name"},{pattern:/\b(?:[A-Z]\w*)Error\b/,alias:"class-name"}]}),e.languages.insertBefore("javascript","keyword",{imports:{pattern:t(/(\bimport\b\s*)(?:<ID>(?:\s*,\s*(?:\*\s*as\s+<ID>|\{[^{}]*\}))?|\*\s*as\s+<ID>|\{[^{}]*\})(?=\s*\bfrom\b)/.source),lookbehind:!0,inside:e.languages.javascript},exports:{pattern:t(/(\bexport\b\s*)(?:\*(?:\s*as\s+<ID>)?(?=\s*\bfrom\b)|\{[^{}]*\})/.source),lookbehind:!0,inside:e.languages.javascript}}),e.languages.javascript.keyword.unshift({pattern:/\b(?:as|default|export|from|import)\b/,alias:"module"},{pattern:/\b(?:await|break|catch|continue|do|else|finally|for|if|return|switch|throw|try|while|yield)\b/,alias:"control-flow"},{pattern:/\bnull\b/,alias:["null","nil"]},{pattern:/\bundefined\b/,alias:"nil"}),e.languages.insertBefore("javascript","operator",{spread:{pattern:/\.{3}/,alias:"operator"},arrow:{pattern:/=>/,alias:"operator"}}),e.languages.insertBefore("javascript","punctuation",{"property-access":{pattern:t(/(\.\s*)#?<ID>/.source),lookbehind:!0},"maybe-class-name":{pattern:/(^|[^$\w\xA0-\uFFFF])[A-Z][$\w\xA0-\uFFFF]+/,lookbehind:!0},dom:{pattern:/\b(?:document|(?:local|session)Storage|location|navigator|performance|window)\b/,alias:"variable"},console:{pattern:/\bconsole(?=\s*\.)/,alias:"class-name"}});for(var n=["function","function-variable","method","method-variable","property-access"],r=0;r<n.length;r++){var a=n[r],o=e.languages.javascript[a];a=(o="RegExp"===e.util.type(o)?e.languages.javascript[a]={pattern:o}:o).inside||{};(o.inside=a)["maybe-class-name"]=/^[A-Z][\s\S]*/}}(A),function(e){var t=e.util.clone(e.languages.javascript),n=/(?:\s|\/\/.*(?!.)|\/\*(?:[^*]|\*(?!\/))\*\/)/.source,r=/(?:\{(?:\{(?:\{[^{}]*\}|[^{}])*\}|[^{}])*\})/.source,a=/(?:\{<S>*\.{3}(?:[^{}]|<BRACES>)*\})/.source;function o(e,t){return e=e.replace(/<S>/g,(function(){return n})).replace(/<BRACES>/g,(function(){return r})).replace(/<SPREAD>/g,(function(){return a})),RegExp(e,t)}function i(t){for(var n=[],r=0;r<t.length;r++){var a=t[r],o=!1;"string"!=typeof a&&("tag"===a.type&&a.content[0]&&"tag"===a.content[0].type?"</"===a.content[0].content[0].content?0<n.length&&n[n.length-1].tagName===l(a.content[0].content[1])&&n.pop():"/>"!==a.content[a.content.length-1].content&&n.push({tagName:l(a.content[0].content[1]),openedBraces:0}):0<n.length&&"punctuation"===a.type&&"{"===a.content?n[n.length-1].openedBraces++:0<n.length&&0<n[n.length-1].openedBraces&&"punctuation"===a.type&&"}"===a.content?n[n.length-1].openedBraces--:o=!0),(o||"string"==typeof a)&&0<n.length&&0===n[n.length-1].openedBraces&&(o=l(a),r<t.length-1&&("string"==typeof t[r+1]||"plain-text"===t[r+1].type)&&(o+=l(t[r+1]),t.splice(r+1,1)),0<r&&("string"==typeof t[r-1]||"plain-text"===t[r-1].type)&&(o=l(t[r-1])+o,t.splice(r-1,1),r--),t[r]=new e.Token("plain-text",o,null,o)),a.content&&"string"!=typeof a.content&&i(a.content)}}a=o(a).source,e.languages.jsx=e.languages.extend("markup",t),e.languages.jsx.tag.pattern=o(/<\/?(?:[\w.:-]+(?:<S>+(?:[\w.:$-]+(?:=(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s{'"/>=]+|<BRACES>))?|<SPREAD>))*<S>*\/?)?>/.source),e.languages.jsx.tag.inside.tag.pattern=/^<\/?[^\s>\/]*/,e.languages.jsx.tag.inside["attr-value"].pattern=/=(?!\{)(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s'">]+)/,e.languages.jsx.tag.inside.tag.inside["class-name"]=/^[A-Z]\w*(?:\.[A-Z]\w*)*$/,e.languages.jsx.tag.inside.comment=t.comment,e.languages.insertBefore("inside","attr-name",{spread:{pattern:o(/<SPREAD>/.source),inside:e.languages.jsx}},e.languages.jsx.tag),e.languages.insertBefore("inside","special-attr",{script:{pattern:o(/=<BRACES>/.source),alias:"language-javascript",inside:{"script-punctuation":{pattern:/^=(?=\{)/,alias:"punctuation"},rest:e.languages.jsx}}},e.languages.jsx.tag);var l=function(e){return e?"string"==typeof e?e:"string"==typeof e.content?e.content:e.content.map(l).join(""):""};e.hooks.add("after-tokenize",(function(e){"jsx"!==e.language&&"tsx"!==e.language||i(e.tokens)}))}(A),function(e){var t=e.util.clone(e.languages.typescript);(t=(e.languages.tsx=e.languages.extend("jsx",t),delete e.languages.tsx.parameter,delete e.languages.tsx["literal-property"],e.languages.tsx.tag)).pattern=RegExp(/(^|[^\w$]|(?=<\/))/.source+"(?:"+t.pattern.source+")",t.pattern.flags),t.lookbehind=!0}(A),A.languages.swift={comment:{pattern:/(^|[^\\:])(?:\/\/.*|\/\*(?:[^/*]|\/(?!\*)|\*(?!\/)|\/\*(?:[^*]|\*(?!\/))*\*\/)*\*\/)/,lookbehind:!0,greedy:!0},"string-literal":[{pattern:RegExp(/(^|[^"#])/.source+"(?:"+/"(?:\\(?:\((?:[^()]|\([^()]*\))*\)|\r\n|[^(])|[^\\\r\n"])*"/.source+"|"+/"""(?:\\(?:\((?:[^()]|\([^()]*\))*\)|[^(])|[^\\"]|"(?!""))*"""/.source+")"+/(?!["#])/.source),lookbehind:!0,greedy:!0,inside:{interpolation:{pattern:/(\\\()(?:[^()]|\([^()]*\))*(?=\))/,lookbehind:!0,inside:null},"interpolation-punctuation":{pattern:/^\)|\\\($/,alias:"punctuation"},punctuation:/\\(?=[\r\n])/,string:/[\s\S]+/}},{pattern:RegExp(/(^|[^"#])(#+)/.source+"(?:"+/"(?:\\(?:#+\((?:[^()]|\([^()]*\))*\)|\r\n|[^#])|[^\\\r\n])*?"/.source+"|"+/"""(?:\\(?:#+\((?:[^()]|\([^()]*\))*\)|[^#])|[^\\])*?"""/.source+")\\2"),lookbehind:!0,greedy:!0,inside:{interpolation:{pattern:/(\\#+\()(?:[^()]|\([^()]*\))*(?=\))/,lookbehind:!0,inside:null},"interpolation-punctuation":{pattern:/^\)|\\#+\($/,alias:"punctuation"},string:/[\s\S]+/}}],directive:{pattern:RegExp(/#/.source+"(?:"+/(?:elseif|if)\b/.source+"(?:[ \t]*"+/(?:![ \t]*)?(?:\b\w+\b(?:[ \t]*\((?:[^()]|\([^()]*\))*\))?|\((?:[^()]|\([^()]*\))*\))(?:[ \t]*(?:&&|\|\|))?/.source+")+|"+/(?:else|endif)\b/.source+")"),alias:"property",inside:{"directive-name":/^#\w+/,boolean:/\b(?:false|true)\b/,number:/\b\d+(?:\.\d+)*\b/,operator:/!|&&|\|\||[<>]=?/,punctuation:/[(),]/}},literal:{pattern:/#(?:colorLiteral|column|dsohandle|file(?:ID|Literal|Path)?|function|imageLiteral|line)\b/,alias:"constant"},"other-directive":{pattern:/#\w+\b/,alias:"property"},attribute:{pattern:/@\w+/,alias:"atrule"},"function-definition":{pattern:/(\bfunc\s+)\w+/,lookbehind:!0,alias:"function"},label:{pattern:/\b(break|continue)\s+\w+|\b[a-zA-Z_]\w*(?=\s*:\s*(?:for|repeat|while)\b)/,lookbehind:!0,alias:"important"},keyword:/\b(?:Any|Protocol|Self|Type|actor|as|assignment|associatedtype|associativity|async|await|break|case|catch|class|continue|convenience|default|defer|deinit|didSet|do|dynamic|else|enum|extension|fallthrough|fileprivate|final|for|func|get|guard|higherThan|if|import|in|indirect|infix|init|inout|internal|is|isolated|lazy|left|let|lowerThan|mutating|none|nonisolated|nonmutating|open|operator|optional|override|postfix|precedencegroup|prefix|private|protocol|public|repeat|required|rethrows|return|right|safe|self|set|some|static|struct|subscript|super|switch|throw|throws|try|typealias|unowned|unsafe|var|weak|where|while|willSet)\b/,boolean:/\b(?:false|true)\b/,nil:{pattern:/\bnil\b/,alias:"constant"},"short-argument":/\$\d+\b/,omit:{pattern:/\b_\b/,alias:"keyword"},number:/\b(?:[\d_]+(?:\.[\de_]+)?|0x[a-f0-9_]+(?:\.[a-f0-9p_]+)?|0b[01_]+|0o[0-7_]+)\b/i,"class-name":/\b[A-Z](?:[A-Z_\d]*[a-z]\w*)?\b/,function:/\b[a-z_]\w*(?=\s*\()/i,constant:/\b(?:[A-Z_]{2,}|k[A-Z][A-Za-z_]+)\b/,operator:/[-+*/%=!<>&|^~?]+|\.[.\-+*/%=!<>&|^~?]+/,punctuation:/[{}[\]();,.:\\]/},A.languages.swift["string-literal"].forEach((function(e){e.inside.interpolation.inside=A.languages.swift})),function(e){e.languages.kotlin=e.languages.extend("clike",{keyword:{pattern:/(^|[^.])\b(?:abstract|actual|annotation|as|break|by|catch|class|companion|const|constructor|continue|crossinline|data|do|dynamic|else|enum|expect|external|final|finally|for|fun|get|if|import|in|infix|init|inline|inner|interface|internal|is|lateinit|noinline|null|object|open|operator|out|override|package|private|protected|public|reified|return|sealed|set|super|suspend|tailrec|this|throw|to|try|typealias|val|var|vararg|when|where|while)\b/,lookbehind:!0},function:[{pattern:/(?:`[^\r\n`]+`|\b\w+)(?=\s*\()/,greedy:!0},{pattern:/(\.)(?:`[^\r\n`]+`|\w+)(?=\s*\{)/,lookbehind:!0,greedy:!0}],number:/\b(?:0[xX][\da-fA-F]+(?:_[\da-fA-F]+)*|0[bB][01]+(?:_[01]+)*|\d+(?:_\d+)*(?:\.\d+(?:_\d+)*)?(?:[eE][+-]?\d+(?:_\d+)*)?[fFL]?)\b/,operator:/\+[+=]?|-[-=>]?|==?=?|!(?:!|==?)?|[\/*%<>]=?|[?:]:?|\.\.|&&|\|\||\b(?:and|inv|or|shl|shr|ushr|xor)\b/}),delete e.languages.kotlin["class-name"];var t={"interpolation-punctuation":{pattern:/^\$\{?|\}$/,alias:"punctuation"},expression:{pattern:/[\s\S]+/,inside:e.languages.kotlin}};e.languages.insertBefore("kotlin","string",{"string-literal":[{pattern:/"""(?:[^$]|\$(?:(?!\{)|\{[^{}]*\}))*?"""/,alias:"multiline",inside:{interpolation:{pattern:/\$(?:[a-z_]\w*|\{[^{}]*\})/i,inside:t},string:/[\s\S]+/}},{pattern:/"(?:[^"\\\r\n$]|\\.|\$(?:(?!\{)|\{[^{}]*\}))*"/,alias:"singleline",inside:{interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$(?:[a-z_]\w*|\{[^{}]*\})/i,lookbehind:!0,inside:t},string:/[\s\S]+/}}],char:{pattern:/'(?:[^'\\\r\n]|\\(?:.|u[a-fA-F0-9]{0,4}))'/,greedy:!0}}),delete e.languages.kotlin.string,e.languages.insertBefore("kotlin","keyword",{annotation:{pattern:/\B@(?:\w+:)?(?:[A-Z]\w*|\[[^\]]+\])/,alias:"builtin"}}),e.languages.insertBefore("kotlin","function",{label:{pattern:/\b\w+@|@\w+\b/,alias:"symbol"}}),e.languages.kt=e.languages.kotlin,e.languages.kts=e.languages.kotlin}(A),A.languages.c=A.languages.extend("clike",{comment:{pattern:/\/\/(?:[^\r\n\\]|\\(?:\r\n?|\n|(?![\r\n])))*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},string:{pattern:/"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/,greedy:!0},"class-name":{pattern:/(\b(?:enum|struct)\s+(?:__attribute__\s*\(\([\s\S]*?\)\)\s*)?)\w+|\b[a-z]\w*_t\b/,lookbehind:!0},keyword:/\b(?:_Alignas|_Alignof|_Atomic|_Bool|_Complex|_Generic|_Imaginary|_Noreturn|_Static_assert|_Thread_local|__attribute__|asm|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|inline|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|typeof|union|unsigned|void|volatile|while)\b/,function:/\b[a-z_]\w*(?=\s*\()/i,number:/(?:\b0x(?:[\da-f]+(?:\.[\da-f]*)?|\.[\da-f]+)(?:p[+-]?\d+)?|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?)[ful]{0,4}/i,operator:/>>=?|<<=?|->|([-+&|:])\1|[?:~]|[-+*/%&|^!=<>]=?/}),A.languages.insertBefore("c","string",{char:{pattern:/'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n]){0,32}'/,greedy:!0}}),A.languages.insertBefore("c","string",{macro:{pattern:/(^[\t ]*)#\s*[a-z](?:[^\r\n\\/]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|\\(?:\r\n|[\s\S]))*/im,lookbehind:!0,greedy:!0,alias:"property",inside:{string:[{pattern:/^(#\s*include\s*)<[^>]+>/,lookbehind:!0},A.languages.c.string],char:A.languages.c.char,comment:A.languages.c.comment,"macro-name":[{pattern:/(^#\s*define\s+)\w+\b(?!\()/i,lookbehind:!0},{pattern:/(^#\s*define\s+)\w+\b(?=\()/i,lookbehind:!0,alias:"function"}],directive:{pattern:/^(#\s*)[a-z]+/,lookbehind:!0,alias:"keyword"},"directive-hash":/^#/,punctuation:/##|\\(?=[\r\n])/,expression:{pattern:/\S[\s\S]*/,inside:A.languages.c}}}}),A.languages.insertBefore("c","function",{constant:/\b(?:EOF|NULL|SEEK_CUR|SEEK_END|SEEK_SET|__DATE__|__FILE__|__LINE__|__TIMESTAMP__|__TIME__|__func__|stderr|stdin|stdout)\b/}),delete A.languages.c.boolean,A.languages.objectivec=A.languages.extend("c",{string:{pattern:/@?"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/,greedy:!0},keyword:/\b(?:asm|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|in|inline|int|long|register|return|self|short|signed|sizeof|static|struct|super|switch|typedef|typeof|union|unsigned|void|volatile|while)\b|(?:@interface|@end|@implementation|@protocol|@class|@public|@protected|@private|@property|@try|@catch|@finally|@throw|@synthesize|@dynamic|@selector)\b/,operator:/-[->]?|\+\+?|!=?|<<?=?|>>?=?|==?|&&?|\|\|?|[~^%?*\/@]/}),delete A.languages.objectivec["class-name"],A.languages.objc=A.languages.objectivec,A.languages.reason=A.languages.extend("clike",{string:{pattern:/"(?:\\(?:\r\n|[\s\S])|[^\\\r\n"])*"/,greedy:!0},"class-name":/\b[A-Z]\w*/,keyword:/\b(?:and|as|assert|begin|class|constraint|do|done|downto|else|end|exception|external|for|fun|function|functor|if|in|include|inherit|initializer|lazy|let|method|module|mutable|new|nonrec|object|of|open|or|private|rec|sig|struct|switch|then|to|try|type|val|virtual|when|while|with)\b/,operator:/\.{3}|:[:=]|\|>|->|=(?:==?|>)?|<=?|>=?|[|^?'#!~`]|[+\-*\/]\.?|\b(?:asr|land|lor|lsl|lsr|lxor|mod)\b/}),A.languages.insertBefore("reason","class-name",{char:{pattern:/'(?:\\x[\da-f]{2}|\\o[0-3][0-7][0-7]|\\\d{3}|\\.|[^'\\\r\n])'/,greedy:!0},constructor:/\b[A-Z]\w*\b(?!\s*\.)/,label:{pattern:/\b[a-z]\w*(?=::)/,alias:"symbol"}}),delete A.languages.reason.function,function(e){for(var t=/\/\*(?:[^*/]|\*(?!\/)|\/(?!\*)|<self>)*\*\//.source,n=0;n<2;n++)t=t.replace(/<self>/g,(function(){return t}));t=t.replace(/<self>/g,(function(){return/[^\s\S]/.source})),e.languages.rust={comment:[{pattern:RegExp(/(^|[^\\])/.source+t),lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/b?"(?:\\[\s\S]|[^\\"])*"|b?r(#*)"(?:[^"]|"(?!\1))*"\1/,greedy:!0},char:{pattern:/b?'(?:\\(?:x[0-7][\da-fA-F]|u\{(?:[\da-fA-F]_*){1,6}\}|.)|[^\\\r\n\t'])'/,greedy:!0},attribute:{pattern:/#!?\[(?:[^\[\]"]|"(?:\\[\s\S]|[^\\"])*")*\]/,greedy:!0,alias:"attr-name",inside:{string:null}},"closure-params":{pattern:/([=(,:]\s*|\bmove\s*)\|[^|]*\||\|[^|]*\|(?=\s*(?:\{|->))/,lookbehind:!0,greedy:!0,inside:{"closure-punctuation":{pattern:/^\||\|$/,alias:"punctuation"},rest:null}},"lifetime-annotation":{pattern:/'\w+/,alias:"symbol"},"fragment-specifier":{pattern:/(\$\w+:)[a-z]+/,lookbehind:!0,alias:"punctuation"},variable:/\$\w+/,"function-definition":{pattern:/(\bfn\s+)\w+/,lookbehind:!0,alias:"function"},"type-definition":{pattern:/(\b(?:enum|struct|trait|type|union)\s+)\w+/,lookbehind:!0,alias:"class-name"},"module-declaration":[{pattern:/(\b(?:crate|mod)\s+)[a-z][a-z_\d]*/,lookbehind:!0,alias:"namespace"},{pattern:/(\b(?:crate|self|super)\s*)::\s*[a-z][a-z_\d]*\b(?:\s*::(?:\s*[a-z][a-z_\d]*\s*::)*)?/,lookbehind:!0,alias:"namespace",inside:{punctuation:/::/}}],keyword:[/\b(?:Self|abstract|as|async|await|become|box|break|const|continue|crate|do|dyn|else|enum|extern|final|fn|for|if|impl|in|let|loop|macro|match|mod|move|mut|override|priv|pub|ref|return|self|static|struct|super|trait|try|type|typeof|union|unsafe|unsized|use|virtual|where|while|yield)\b/,/\b(?:bool|char|f(?:32|64)|[ui](?:8|16|32|64|128|size)|str)\b/],function:/\b[a-z_]\w*(?=\s*(?:::\s*<|\())/,macro:{pattern:/\b\w+!/,alias:"property"},constant:/\b[A-Z_][A-Z_\d]+\b/,"class-name":/\b[A-Z]\w*\b/,namespace:{pattern:/(?:\b[a-z][a-z_\d]*\s*::\s*)*\b[a-z][a-z_\d]*\s*::(?!\s*<)/,inside:{punctuation:/::/}},number:/\b(?:0x[\dA-Fa-f](?:_?[\dA-Fa-f])*|0o[0-7](?:_?[0-7])*|0b[01](?:_?[01])*|(?:(?:\d(?:_?\d)*)?\.)?\d(?:_?\d)*(?:[Ee][+-]?\d+)?)(?:_?(?:f32|f64|[iu](?:8|16|32|64|size)?))?\b/,boolean:/\b(?:false|true)\b/,punctuation:/->|\.\.=|\.{1,3}|::|[{}[\];(),:]/,operator:/[-+*\/%!^]=?|=[=>]?|&[&=]?|\|[|=]?|<<?=?|>>?=?|[@?]/},e.languages.rust["closure-params"].inside.rest=e.languages.rust,e.languages.rust.attribute.inside.string=e.languages.rust.string}(A),A.languages.go=A.languages.extend("clike",{string:{pattern:/(^|[^\\])"(?:\\.|[^"\\\r\n])*"|`[^`]*`/,lookbehind:!0,greedy:!0},keyword:/\b(?:break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go(?:to)?|if|import|interface|map|package|range|return|select|struct|switch|type|var)\b/,boolean:/\b(?:_|false|iota|nil|true)\b/,number:[/\b0(?:b[01_]+|o[0-7_]+)i?\b/i,/\b0x(?:[a-f\d_]+(?:\.[a-f\d_]*)?|\.[a-f\d_]+)(?:p[+-]?\d+(?:_\d+)*)?i?(?!\w)/i,/(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.\d[\d_]*)(?:e[+-]?[\d_]+)?i?(?!\w)/i],operator:/[*\/%^!=]=?|\+[=+]?|-[=-]?|\|[=|]?|&(?:=|&|\^=?)?|>(?:>=?|=)?|<(?:<=?|=|-)?|:=|\.\.\./,builtin:/\b(?:append|bool|byte|cap|close|complex|complex(?:64|128)|copy|delete|error|float(?:32|64)|u?int(?:8|16|32|64)?|imag|len|make|new|panic|print(?:ln)?|real|recover|rune|string|uintptr)\b/}),A.languages.insertBefore("go","string",{char:{pattern:/'(?:\\.|[^'\\\r\n]){0,10}'/,greedy:!0}}),delete A.languages.go["class-name"],function(e){var t=/\b(?:alignas|alignof|asm|auto|bool|break|case|catch|char|char16_t|char32_t|char8_t|class|co_await|co_return|co_yield|compl|concept|const|const_cast|consteval|constexpr|constinit|continue|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|final|float|for|friend|goto|if|import|inline|int|int16_t|int32_t|int64_t|int8_t|long|module|mutable|namespace|new|noexcept|nullptr|operator|override|private|protected|public|register|reinterpret_cast|requires|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|uint16_t|uint32_t|uint64_t|uint8_t|union|unsigned|using|virtual|void|volatile|wchar_t|while)\b/,n=/\b(?!<keyword>)\w+(?:\s*\.\s*\w+)*\b/.source.replace(/<keyword>/g,(function(){return t.source}));e.languages.cpp=e.languages.extend("c",{"class-name":[{pattern:RegExp(/(\b(?:class|concept|enum|struct|typename)\s+)(?!<keyword>)\w+/.source.replace(/<keyword>/g,(function(){return t.source}))),lookbehind:!0},/\b[A-Z]\w*(?=\s*::\s*\w+\s*\()/,/\b[A-Z_]\w*(?=\s*::\s*~\w+\s*\()/i,/\b\w+(?=\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>\s*::\s*\w+\s*\()/],keyword:t,number:{pattern:/(?:\b0b[01']+|\b0x(?:[\da-f']+(?:\.[\da-f']*)?|\.[\da-f']+)(?:p[+-]?[\d']+)?|(?:\b[\d']+(?:\.[\d']*)?|\B\.[\d']+)(?:e[+-]?[\d']+)?)[ful]{0,4}/i,greedy:!0},operator:/>>=?|<<=?|->|--|\+\+|&&|\|\||[?:~]|<=>|[-+*/%&|^!=<>]=?|\b(?:and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\b/,boolean:/\b(?:false|true)\b/}),e.languages.insertBefore("cpp","string",{module:{pattern:RegExp(/(\b(?:import|module)\s+)/.source+"(?:"+/"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|<[^<>\r\n]*>/.source+"|"+/<mod-name>(?:\s*:\s*<mod-name>)?|:\s*<mod-name>/.source.replace(/<mod-name>/g,(function(){return n}))+")"),lookbehind:!0,greedy:!0,inside:{string:/^[<"][\s\S]+/,operator:/:/,punctuation:/\./}},"raw-string":{pattern:/R"([^()\\ ]{0,16})\([\s\S]*?\)\1"/,alias:"string",greedy:!0}}),e.languages.insertBefore("cpp","keyword",{"generic-function":{pattern:/\b(?!operator\b)[a-z_]\w*\s*<(?:[^<>]|<[^<>]*>)*>(?=\s*\()/i,inside:{function:/^\w+/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:e.languages.cpp}}}}),e.languages.insertBefore("cpp","operator",{"double-colon":{pattern:/::/,alias:"punctuation"}}),e.languages.insertBefore("cpp","class-name",{"base-clause":{pattern:/(\b(?:class|struct)\s+\w+\s*:\s*)[^;{}"'\s]+(?:\s+[^;{}"'\s]+)*(?=\s*[;{])/,lookbehind:!0,greedy:!0,inside:e.languages.extend("cpp",{})}}),e.languages.insertBefore("inside","double-colon",{"class-name":/\b[a-z_]\w*\b(?!\s*::)/i},e.languages.cpp["base-clause"])}(A),A.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0,greedy:!0},"string-interpolation":{pattern:/(?:f|fr|rf)(?:("""|''')[\s\S]*?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:\{\{)*)\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}])+\})+\})+\}/,lookbehind:!0,inside:{"format-spec":{pattern:/(:)[^:(){}]+(?=\}$)/,lookbehind:!0},"conversion-option":{pattern:/![sra](?=[:}]$)/,alias:"punctuation"},rest:null}},string:/[\s\S]+/}},"triple-quoted-string":{pattern:/(?:[rub]|br|rb)?("""|''')[\s\S]*?\1/i,greedy:!0,alias:"string"},string:{pattern:/(?:[rub]|br|rb)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,greedy:!0},function:{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^[\t ]*)@\w+(?:\.\w+)*/m,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:_(?=\s*:)|and|as|assert|async|await|break|case|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|match|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,boolean:/\b(?:False|None|True)\b/,number:/\b0(?:b(?:_?[01])+|o(?:_?[0-7])+|x(?:_?[a-f0-9])+)\b|(?:\b\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\B\.\d+(?:_\d+)*)(?:e[+-]?\d+(?:_\d+)*)?j?(?!\w)/i,operator:/[-+%=]=?|!=|:=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},A.languages.python["string-interpolation"].inside.interpolation.inside.rest=A.languages.python,A.languages.py=A.languages.python,A.languages.json={property:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?=\s*:)/,lookbehind:!0,greedy:!0},string:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?!\s*:)/,lookbehind:!0,greedy:!0},comment:{pattern:/\/\/.*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},number:/-?\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,punctuation:/[{}[\],]/,operator:/:/,boolean:/\b(?:false|true)\b/,null:{pattern:/\bnull\b/,alias:"keyword"}},A.languages.webmanifest=A.languages.json;((e,t)=>{for(var n in t)p(e,n,{get:t[n],enumerable:!0})})({},{dracula:()=>T,duotoneDark:()=>L,duotoneLight:()=>j,github:()=>P,gruvboxMaterialDark:()=>Q,gruvboxMaterialLight:()=>K,jettwaveDark:()=>H,jettwaveLight:()=>G,nightOwl:()=>N,nightOwlLight:()=>O,oceanicNext:()=>D,okaidia:()=>F,oneDark:()=>V,oneLight:()=>W,palenight:()=>I,shadesOfPurple:()=>B,synthwave84:()=>z,ultramin:()=>$,vsDark:()=>U,vsLight:()=>q});var T={plain:{color:"#F8F8F2",backgroundColor:"#282A36"},styles:[{types:["prolog","constant","builtin"],style:{color:"rgb(189, 147, 249)"}},{types:["inserted","function"],style:{color:"rgb(80, 250, 123)"}},{types:["deleted"],style:{color:"rgb(255, 85, 85)"}},{types:["changed"],style:{color:"rgb(255, 184, 108)"}},{types:["punctuation","symbol"],style:{color:"rgb(248, 248, 242)"}},{types:["string","char","tag","selector"],style:{color:"rgb(255, 121, 198)"}},{types:["keyword","variable"],style:{color:"rgb(189, 147, 249)",fontStyle:"italic"}},{types:["comment"],style:{color:"rgb(98, 114, 164)"}},{types:["attr-name"],style:{color:"rgb(241, 250, 140)"}}]},L={plain:{backgroundColor:"#2a2734",color:"#9a86fd"},styles:[{types:["comment","prolog","doctype","cdata","punctuation"],style:{color:"#6c6783"}},{types:["namespace"],style:{opacity:.7}},{types:["tag","operator","number"],style:{color:"#e09142"}},{types:["property","function"],style:{color:"#9a86fd"}},{types:["tag-id","selector","atrule-id"],style:{color:"#eeebff"}},{types:["attr-name"],style:{color:"#c4b9fe"}},{types:["boolean","string","entity","url","attr-value","keyword","control","directive","unit","statement","regex","atrule","placeholder","variable"],style:{color:"#ffcc99"}},{types:["deleted"],style:{textDecorationLine:"line-through"}},{types:["inserted"],style:{textDecorationLine:"underline"}},{types:["italic"],style:{fontStyle:"italic"}},{types:["important","bold"],style:{fontWeight:"bold"}},{types:["important"],style:{color:"#c4b9fe"}}]},j={plain:{backgroundColor:"#faf8f5",color:"#728fcb"},styles:[{types:["comment","prolog","doctype","cdata","punctuation"],style:{color:"#b6ad9a"}},{types:["namespace"],style:{opacity:.7}},{types:["tag","operator","number"],style:{color:"#063289"}},{types:["property","function"],style:{color:"#b29762"}},{types:["tag-id","selector","atrule-id"],style:{color:"#2d2006"}},{types:["attr-name"],style:{color:"#896724"}},{types:["boolean","string","entity","url","attr-value","keyword","control","directive","unit","statement","regex","atrule"],style:{color:"#728fcb"}},{types:["placeholder","variable"],style:{color:"#93abdc"}},{types:["deleted"],style:{textDecorationLine:"line-through"}},{types:["inserted"],style:{textDecorationLine:"underline"}},{types:["italic"],style:{fontStyle:"italic"}},{types:["important","bold"],style:{fontWeight:"bold"}},{types:["important"],style:{color:"#896724"}}]},P={plain:{color:"#393A34",backgroundColor:"#f6f8fa"},styles:[{types:["comment","prolog","doctype","cdata"],style:{color:"#999988",fontStyle:"italic"}},{types:["namespace"],style:{opacity:.7}},{types:["string","attr-value"],style:{color:"#e3116c"}},{types:["punctuation","operator"],style:{color:"#393A34"}},{types:["entity","url","symbol","number","boolean","variable","constant","property","regex","inserted"],style:{color:"#36acaa"}},{types:["atrule","keyword","attr-name","selector"],style:{color:"#00a4db"}},{types:["function","deleted","tag"],style:{color:"#d73a49"}},{types:["function-variable"],style:{color:"#6f42c1"}},{types:["tag","selector","keyword"],style:{color:"#00009f"}}]},N={plain:{color:"#d6deeb",backgroundColor:"#011627"},styles:[{types:["changed"],style:{color:"rgb(162, 191, 252)",fontStyle:"italic"}},{types:["deleted"],style:{color:"rgba(239, 83, 80, 0.56)",fontStyle:"italic"}},{types:["inserted","attr-name"],style:{color:"rgb(173, 219, 103)",fontStyle:"italic"}},{types:["comment"],style:{color:"rgb(99, 119, 119)",fontStyle:"italic"}},{types:["string","url"],style:{color:"rgb(173, 219, 103)"}},{types:["variable"],style:{color:"rgb(214, 222, 235)"}},{types:["number"],style:{color:"rgb(247, 140, 108)"}},{types:["builtin","char","constant","function"],style:{color:"rgb(130, 170, 255)"}},{types:["punctuation"],style:{color:"rgb(199, 146, 234)"}},{types:["selector","doctype"],style:{color:"rgb(199, 146, 234)",fontStyle:"italic"}},{types:["class-name"],style:{color:"rgb(255, 203, 139)"}},{types:["tag","operator","keyword"],style:{color:"rgb(127, 219, 202)"}},{types:["boolean"],style:{color:"rgb(255, 88, 116)"}},{types:["property"],style:{color:"rgb(128, 203, 196)"}},{types:["namespace"],style:{color:"rgb(178, 204, 214)"}}]},O={plain:{color:"#403f53",backgroundColor:"#FBFBFB"},styles:[{types:["changed"],style:{color:"rgb(162, 191, 252)",fontStyle:"italic"}},{types:["deleted"],style:{color:"rgba(239, 83, 80, 0.56)",fontStyle:"italic"}},{types:["inserted","attr-name"],style:{color:"rgb(72, 118, 214)",fontStyle:"italic"}},{types:["comment"],style:{color:"rgb(152, 159, 177)",fontStyle:"italic"}},{types:["string","builtin","char","constant","url"],style:{color:"rgb(72, 118, 214)"}},{types:["variable"],style:{color:"rgb(201, 103, 101)"}},{types:["number"],style:{color:"rgb(170, 9, 130)"}},{types:["punctuation"],style:{color:"rgb(153, 76, 195)"}},{types:["function","selector","doctype"],style:{color:"rgb(153, 76, 195)",fontStyle:"italic"}},{types:["class-name"],style:{color:"rgb(17, 17, 17)"}},{types:["tag"],style:{color:"rgb(153, 76, 195)"}},{types:["operator","property","keyword","namespace"],style:{color:"rgb(12, 150, 155)"}},{types:["boolean"],style:{color:"rgb(188, 84, 84)"}}]},R="#c5a5c5",M="#8dc891",D={plain:{backgroundColor:"#282c34",color:"#ffffff"},styles:[{types:["attr-name"],style:{color:R}},{types:["attr-value"],style:{color:M}},{types:["comment","block-comment","prolog","doctype","cdata","shebang"],style:{color:"#999999"}},{types:["property","number","function-name","constant","symbol","deleted"],style:{color:"#5a9bcf"}},{types:["boolean"],style:{color:"#ff8b50"}},{types:["tag"],style:{color:"#fc929e"}},{types:["string"],style:{color:M}},{types:["punctuation"],style:{color:M}},{types:["selector","char","builtin","inserted"],style:{color:"#D8DEE9"}},{types:["function"],style:{color:"#79b6f2"}},{types:["operator","entity","url","variable"],style:{color:"#d7deea"}},{types:["keyword"],style:{color:R}},{types:["atrule","class-name"],style:{color:"#FAC863"}},{types:["important"],style:{fontWeight:"400"}},{types:["bold"],style:{fontWeight:"bold"}},{types:["italic"],style:{fontStyle:"italic"}},{types:["namespace"],style:{opacity:.7}}]},F={plain:{color:"#f8f8f2",backgroundColor:"#272822"},styles:[{types:["changed"],style:{color:"rgb(162, 191, 252)",fontStyle:"italic"}},{types:["deleted"],style:{color:"#f92672",fontStyle:"italic"}},{types:["inserted"],style:{color:"rgb(173, 219, 103)",fontStyle:"italic"}},{types:["comment"],style:{color:"#8292a2",fontStyle:"italic"}},{types:["string","url"],style:{color:"#a6e22e"}},{types:["variable"],style:{color:"#f8f8f2"}},{types:["number"],style:{color:"#ae81ff"}},{types:["builtin","char","constant","function","class-name"],style:{color:"#e6db74"}},{types:["punctuation"],style:{color:"#f8f8f2"}},{types:["selector","doctype"],style:{color:"#a6e22e",fontStyle:"italic"}},{types:["tag","operator","keyword"],style:{color:"#66d9ef"}},{types:["boolean"],style:{color:"#ae81ff"}},{types:["namespace"],style:{color:"rgb(178, 204, 214)",opacity:.7}},{types:["tag","property"],style:{color:"#f92672"}},{types:["attr-name"],style:{color:"#a6e22e !important"}},{types:["doctype"],style:{color:"#8292a2"}},{types:["rule"],style:{color:"#e6db74"}}]},I={plain:{color:"#bfc7d5",backgroundColor:"#292d3e"},styles:[{types:["comment"],style:{color:"rgb(105, 112, 152)",fontStyle:"italic"}},{types:["string","inserted"],style:{color:"rgb(195, 232, 141)"}},{types:["number"],style:{color:"rgb(247, 140, 108)"}},{types:["builtin","char","constant","function"],style:{color:"rgb(130, 170, 255)"}},{types:["punctuation","selector"],style:{color:"rgb(199, 146, 234)"}},{types:["variable"],style:{color:"rgb(191, 199, 213)"}},{types:["class-name","attr-name"],style:{color:"rgb(255, 203, 107)"}},{types:["tag","deleted"],style:{color:"rgb(255, 85, 114)"}},{types:["operator"],style:{color:"rgb(137, 221, 255)"}},{types:["boolean"],style:{color:"rgb(255, 88, 116)"}},{types:["keyword"],style:{fontStyle:"italic"}},{types:["doctype"],style:{color:"rgb(199, 146, 234)",fontStyle:"italic"}},{types:["namespace"],style:{color:"rgb(178, 204, 214)"}},{types:["url"],style:{color:"rgb(221, 221, 221)"}}]},B={plain:{color:"#9EFEFF",backgroundColor:"#2D2A55"},styles:[{types:["changed"],style:{color:"rgb(255, 238, 128)"}},{types:["deleted"],style:{color:"rgba(239, 83, 80, 0.56)"}},{types:["inserted"],style:{color:"rgb(173, 219, 103)"}},{types:["comment"],style:{color:"rgb(179, 98, 255)",fontStyle:"italic"}},{types:["punctuation"],style:{color:"rgb(255, 255, 255)"}},{types:["constant"],style:{color:"rgb(255, 98, 140)"}},{types:["string","url"],style:{color:"rgb(165, 255, 144)"}},{types:["variable"],style:{color:"rgb(255, 238, 128)"}},{types:["number","boolean"],style:{color:"rgb(255, 98, 140)"}},{types:["attr-name"],style:{color:"rgb(255, 180, 84)"}},{types:["keyword","operator","property","namespace","tag","selector","doctype"],style:{color:"rgb(255, 157, 0)"}},{types:["builtin","char","constant","function","class-name"],style:{color:"rgb(250, 208, 0)"}}]},z={plain:{backgroundColor:"linear-gradient(to bottom, #2a2139 75%, #34294f)",backgroundImage:"#34294f",color:"#f92aad",textShadow:"0 0 2px #100c0f, 0 0 5px #dc078e33, 0 0 10px #fff3"},styles:[{types:["comment","block-comment","prolog","doctype","cdata"],style:{color:"#495495",fontStyle:"italic"}},{types:["punctuation"],style:{color:"#ccc"}},{types:["tag","attr-name","namespace","number","unit","hexcode","deleted"],style:{color:"#e2777a"}},{types:["property","selector"],style:{color:"#72f1b8",textShadow:"0 0 2px #100c0f, 0 0 10px #257c5575, 0 0 35px #21272475"}},{types:["function-name"],style:{color:"#6196cc"}},{types:["boolean","selector-id","function"],style:{color:"#fdfdfd",textShadow:"0 0 2px #001716, 0 0 3px #03edf975, 0 0 5px #03edf975, 0 0 8px #03edf975"}},{types:["class-name","maybe-class-name","builtin"],style:{color:"#fff5f6",textShadow:"0 0 2px #000, 0 0 10px #fc1f2c75, 0 0 5px #fc1f2c75, 0 0 25px #fc1f2c75"}},{types:["constant","symbol"],style:{color:"#f92aad",textShadow:"0 0 2px #100c0f, 0 0 5px #dc078e33, 0 0 10px #fff3"}},{types:["important","atrule","keyword","selector-class"],style:{color:"#f4eee4",textShadow:"0 0 2px #393a33, 0 0 8px #f39f0575, 0 0 2px #f39f0575"}},{types:["string","char","attr-value","regex","variable"],style:{color:"#f87c32"}},{types:["parameter"],style:{fontStyle:"italic"}},{types:["entity","url"],style:{color:"#67cdcc"}},{types:["operator"],style:{color:"ffffffee"}},{types:["important","bold"],style:{fontWeight:"bold"}},{types:["italic"],style:{fontStyle:"italic"}},{types:["entity"],style:{cursor:"help"}},{types:["inserted"],style:{color:"green"}}]},$={plain:{color:"#282a2e",backgroundColor:"#ffffff"},styles:[{types:["comment"],style:{color:"rgb(197, 200, 198)"}},{types:["string","number","builtin","variable"],style:{color:"rgb(150, 152, 150)"}},{types:["class-name","function","tag","attr-name"],style:{color:"rgb(40, 42, 46)"}}]},U={plain:{color:"#9CDCFE",backgroundColor:"#1E1E1E"},styles:[{types:["prolog"],style:{color:"rgb(0, 0, 128)"}},{types:["comment"],style:{color:"rgb(106, 153, 85)"}},{types:["builtin","changed","keyword","interpolation-punctuation"],style:{color:"rgb(86, 156, 214)"}},{types:["number","inserted"],style:{color:"rgb(181, 206, 168)"}},{types:["constant"],style:{color:"rgb(100, 102, 149)"}},{types:["attr-name","variable"],style:{color:"rgb(156, 220, 254)"}},{types:["deleted","string","attr-value","template-punctuation"],style:{color:"rgb(206, 145, 120)"}},{types:["selector"],style:{color:"rgb(215, 186, 125)"}},{types:["tag"],style:{color:"rgb(78, 201, 176)"}},{types:["tag"],languages:["markup"],style:{color:"rgb(86, 156, 214)"}},{types:["punctuation","operator"],style:{color:"rgb(212, 212, 212)"}},{types:["punctuation"],languages:["markup"],style:{color:"#808080"}},{types:["function"],style:{color:"rgb(220, 220, 170)"}},{types:["class-name"],style:{color:"rgb(78, 201, 176)"}},{types:["char"],style:{color:"rgb(209, 105, 105)"}}]},q={plain:{color:"#000000",backgroundColor:"#ffffff"},styles:[{types:["comment"],style:{color:"rgb(0, 128, 0)"}},{types:["builtin"],style:{color:"rgb(0, 112, 193)"}},{types:["number","variable","inserted"],style:{color:"rgb(9, 134, 88)"}},{types:["operator"],style:{color:"rgb(0, 0, 0)"}},{types:["constant","char"],style:{color:"rgb(129, 31, 63)"}},{types:["tag"],style:{color:"rgb(128, 0, 0)"}},{types:["attr-name"],style:{color:"rgb(255, 0, 0)"}},{types:["deleted","string"],style:{color:"rgb(163, 21, 21)"}},{types:["changed","punctuation"],style:{color:"rgb(4, 81, 165)"}},{types:["function","keyword"],style:{color:"rgb(0, 0, 255)"}},{types:["class-name"],style:{color:"rgb(38, 127, 153)"}}]},H={plain:{color:"#f8fafc",backgroundColor:"#011627"},styles:[{types:["prolog"],style:{color:"#000080"}},{types:["comment"],style:{color:"#6A9955"}},{types:["builtin","changed","keyword","interpolation-punctuation"],style:{color:"#569CD6"}},{types:["number","inserted"],style:{color:"#B5CEA8"}},{types:["constant"],style:{color:"#f8fafc"}},{types:["attr-name","variable"],style:{color:"#9CDCFE"}},{types:["deleted","string","attr-value","template-punctuation"],style:{color:"#cbd5e1"}},{types:["selector"],style:{color:"#D7BA7D"}},{types:["tag"],style:{color:"#0ea5e9"}},{types:["tag"],languages:["markup"],style:{color:"#0ea5e9"}},{types:["punctuation","operator"],style:{color:"#D4D4D4"}},{types:["punctuation"],languages:["markup"],style:{color:"#808080"}},{types:["function"],style:{color:"#7dd3fc"}},{types:["class-name"],style:{color:"#0ea5e9"}},{types:["char"],style:{color:"#D16969"}}]},G={plain:{color:"#0f172a",backgroundColor:"#f1f5f9"},styles:[{types:["prolog"],style:{color:"#000080"}},{types:["comment"],style:{color:"#6A9955"}},{types:["builtin","changed","keyword","interpolation-punctuation"],style:{color:"#0c4a6e"}},{types:["number","inserted"],style:{color:"#B5CEA8"}},{types:["constant"],style:{color:"#0f172a"}},{types:["attr-name","variable"],style:{color:"#0c4a6e"}},{types:["deleted","string","attr-value","template-punctuation"],style:{color:"#64748b"}},{types:["selector"],style:{color:"#D7BA7D"}},{types:["tag"],style:{color:"#0ea5e9"}},{types:["tag"],languages:["markup"],style:{color:"#0ea5e9"}},{types:["punctuation","operator"],style:{color:"#475569"}},{types:["punctuation"],languages:["markup"],style:{color:"#808080"}},{types:["function"],style:{color:"#0e7490"}},{types:["class-name"],style:{color:"#0ea5e9"}},{types:["char"],style:{color:"#D16969"}}]},V={plain:{backgroundColor:"hsl(220, 13%, 18%)",color:"hsl(220, 14%, 71%)",textShadow:"0 1px rgba(0, 0, 0, 0.3)"},styles:[{types:["comment","prolog","cdata"],style:{color:"hsl(220, 10%, 40%)"}},{types:["doctype","punctuation","entity"],style:{color:"hsl(220, 14%, 71%)"}},{types:["attr-name","class-name","maybe-class-name","boolean","constant","number","atrule"],style:{color:"hsl(29, 54%, 61%)"}},{types:["keyword"],style:{color:"hsl(286, 60%, 67%)"}},{types:["property","tag","symbol","deleted","important"],style:{color:"hsl(355, 65%, 65%)"}},{types:["selector","string","char","builtin","inserted","regex","attr-value"],style:{color:"hsl(95, 38%, 62%)"}},{types:["variable","operator","function"],style:{color:"hsl(207, 82%, 66%)"}},{types:["url"],style:{color:"hsl(187, 47%, 55%)"}},{types:["deleted"],style:{textDecorationLine:"line-through"}},{types:["inserted"],style:{textDecorationLine:"underline"}},{types:["italic"],style:{fontStyle:"italic"}},{types:["important","bold"],style:{fontWeight:"bold"}},{types:["important"],style:{color:"hsl(220, 14%, 71%)"}}]},W={plain:{backgroundColor:"hsl(230, 1%, 98%)",color:"hsl(230, 8%, 24%)"},styles:[{types:["comment","prolog","cdata"],style:{color:"hsl(230, 4%, 64%)"}},{types:["doctype","punctuation","entity"],style:{color:"hsl(230, 8%, 24%)"}},{types:["attr-name","class-name","boolean","constant","number","atrule"],style:{color:"hsl(35, 99%, 36%)"}},{types:["keyword"],style:{color:"hsl(301, 63%, 40%)"}},{types:["property","tag","symbol","deleted","important"],style:{color:"hsl(5, 74%, 59%)"}},{types:["selector","string","char","builtin","inserted","regex","attr-value","punctuation"],style:{color:"hsl(119, 34%, 47%)"}},{types:["variable","operator","function"],style:{color:"hsl(221, 87%, 60%)"}},{types:["url"],style:{color:"hsl(198, 99%, 37%)"}},{types:["deleted"],style:{textDecorationLine:"line-through"}},{types:["inserted"],style:{textDecorationLine:"underline"}},{types:["italic"],style:{fontStyle:"italic"}},{types:["important","bold"],style:{fontWeight:"bold"}},{types:["important"],style:{color:"hsl(230, 8%, 24%)"}}]},Q={plain:{color:"#ebdbb2",backgroundColor:"#292828"},styles:[{types:["imports","class-name","maybe-class-name","constant","doctype","builtin","function"],style:{color:"#d8a657"}},{types:["property-access"],style:{color:"#7daea3"}},{types:["tag"],style:{color:"#e78a4e"}},{types:["attr-name","char","url","regex"],style:{color:"#a9b665"}},{types:["attr-value","string"],style:{color:"#89b482"}},{types:["comment","prolog","cdata","operator","inserted"],style:{color:"#a89984"}},{types:["delimiter","boolean","keyword","selector","important","atrule","property","variable","deleted"],style:{color:"#ea6962"}},{types:["entity","number","symbol"],style:{color:"#d3869b"}}]},K={plain:{color:"#654735",backgroundColor:"#f9f5d7"},styles:[{types:["delimiter","boolean","keyword","selector","important","atrule","property","variable","deleted"],style:{color:"#af2528"}},{types:["imports","class-name","maybe-class-name","constant","doctype","builtin"],style:{color:"#b4730e"}},{types:["string","attr-value"],style:{color:"#477a5b"}},{types:["property-access"],style:{color:"#266b79"}},{types:["function","attr-name","char","url"],style:{color:"#72761e"}},{types:["tag"],style:{color:"#b94c07"}},{types:["comment","prolog","cdata","operator","inserted"],style:{color:"#a89984"}},{types:["entity","number","symbol"],style:{color:"#924f79"}}]},Y=/\r\n|\r|\n/,X=e=>{0===e.length?e.push({types:["plain"],content:"\n",empty:!0}):1===e.length&&""===e[0].content&&(e[0].content="\n",e[0].empty=!0)},Z=(e,t)=>{const n=e.length;return n>0&&e[n-1]===t?e:e.concat(t)},J=e=>{const t=[[]],n=[e],r=[0],a=[e.length];let o=0,i=0,l=[];const s=[l];for(;i>-1;){for(;(o=r[i]++)<a[i];){let e,u=t[i];const c=n[i][o];if("string"==typeof c?(u=i>0?u:["plain"],e=c):(u=Z(u,c.type),c.alias&&(u=Z(u,c.alias)),e=c.content),"string"!=typeof e){i++,t.push(u),n.push(e),r.push(0),a.push(e.length);continue}const d=e.split(Y),f=d.length;l.push({types:u,content:d[0]});for(let t=1;t<f;t++)X(l),s.push(l=[]),l.push({types:u,content:d[t]})}i--,t.pop(),n.pop(),r.pop(),a.pop()}return X(l),s},ee=(e,t)=>{const{plain:n}=e,r=e.styles.reduce(((e,n)=>{const{languages:r,style:a}=n;return r&&!r.includes(t)||n.types.forEach((t=>{const n=x(x({},e[t]),a);e[t]=n})),e}),{});return r.root=n,r.plain=_(x({},n),{backgroundColor:void 0}),r},te=({children:e,language:t,code:n,theme:r,prism:a})=>{const o=t.toLowerCase(),i=ee(r,o),l=(e=>(0,c.useCallback)((t=>{var n=t,{className:r,style:a,line:o}=n,i=E(n,["className","style","line"]);const l=_(x({},i),{className:(0,d.A)("token-line",r)});return"object"==typeof e&&"plain"in e&&(l.style=e.plain),"object"==typeof a&&(l.style=x(x({},l.style||{}),a)),l}),[e]))(i),s=(e=>{const t=(0,c.useCallback)((({types:t,empty:n})=>{if(null!=e)return 1===t.length&&"plain"===t[0]?null!=n?{display:"inline-block"}:void 0:1===t.length&&null!=n?e[t[0]]:Object.assign(null!=n?{display:"inline-block"}:{},...t.map((t=>e[t])))}),[e]);return(0,c.useCallback)((e=>{var n=e,{token:r,className:a,style:o}=n,i=E(n,["token","className","style"]);const l=_(x({},i),{className:(0,d.A)("token",...r.types,a),children:r.content,style:t(r)});return null!=o&&(l.style=x(x({},l.style||{}),o)),l}),[t])})(i),u=(({prism:e,code:t,grammar:n,language:r})=>(0,c.useMemo)((()=>{if(null==n)return J([t]);const a={code:t,grammar:n,language:r,tokens:[]};return e.hooks.run("before-tokenize",a),a.tokens=e.tokenize(t,n),e.hooks.run("after-tokenize",a),J(a.tokens)}),[t,n,r,e]))({prism:a,language:o,code:n,grammar:a.languages[o]});return e({tokens:u,className:`prism-code language-${o}`,style:null!=i?i.root:{},getLineProps:l,getTokenProps:s})},ne=e=>(0,c.createElement)(te,_(x({},e),{prism:e.prism||A,theme:e.theme||U,code:e.code,language:e.language}))},2131:(e,t,n)=>{"use strict";n.d(t,{o:()=>i});var r=n(4586),a=n(6347),o=n(440);function i(){const{siteConfig:{baseUrl:e,url:t,trailingSlash:n},i18n:{defaultLocale:i,currentLocale:l}}=(0,r.A)(),{pathname:s}=(0,a.zy)(),u=(0,o.Ks)(s,{trailingSlash:n,baseUrl:e}),c=l===i?e:e.replace(`/${l}/`,"/"),d=u.replace(e,"");return{createUrl:function({locale:e,fullyQualified:n}){return`${n?t:""}${function(e){return e===i?`${c}`:`${c}${e}/`}(e)}${d}`}}}},2303:(e,t,n)=>{"use strict";n.d(t,{A:()=>o});var r=n(6540),a=n(6125);function o(){return(0,r.useContext)(a.o)}},2566:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.addPrefix=function(e,t){return e.startsWith(t)?e:`${t}${e}`},t.removeSuffix=function(e,t){if(""===t)return e;return e.endsWith(t)?e.slice(0,-t.length):e},t.addSuffix=function(e,t){return e.endsWith(t)?e:`${e}${t}`},t.removePrefix=function(e,t){return e.startsWith(t)?e.slice(t.length):e}},2654:e=>{"use strict";e.exports={}},2694:(e,t,n)=>{"use strict";var r=n(6925);function a(){}function o(){}o.resetWarningCache=a,e.exports=function(){function e(e,t,n,a,o,i){if(i!==r){var l=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw l.name="Invariant Violation",l}}function t(){return e}e.isRequired=e;var n={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:o,resetWarningCache:a};return n.PropTypes=n,n}},2799:(e,t)=>{"use strict";var n="function"==typeof Symbol&&Symbol.for,r=n?Symbol.for("react.element"):60103,a=n?Symbol.for("react.portal"):60106,o=n?Symbol.for("react.fragment"):60107,i=n?Symbol.for("react.strict_mode"):60108,l=n?Symbol.for("react.profiler"):60114,s=n?Symbol.for("react.provider"):60109,u=n?Symbol.for("react.context"):60110,c=n?Symbol.for("react.async_mode"):60111,d=n?Symbol.for("react.concurrent_mode"):60111,f=n?Symbol.for("react.forward_ref"):60112,p=n?Symbol.for("react.suspense"):60113,m=n?Symbol.for("react.suspense_list"):60120,h=n?Symbol.for("react.memo"):60115,g=n?Symbol.for("react.lazy"):60116,y=n?Symbol.for("react.block"):60121,b=n?Symbol.for("react.fundamental"):60117,v=n?Symbol.for("react.responder"):60118,w=n?Symbol.for("react.scope"):60119;function k(e){if("object"==typeof e&&null!==e){var t=e.$$typeof;switch(t){case r:switch(e=e.type){case c:case d:case o:case l:case i:case p:return e;default:switch(e=e&&e.$$typeof){case u:case f:case g:case h:case s:return e;default:return t}}case a:return t}}}function S(e){return k(e)===d}t.AsyncMode=c,t.ConcurrentMode=d,t.ContextConsumer=u,t.ContextProvider=s,t.Element=r,t.ForwardRef=f,t.Fragment=o,t.Lazy=g,t.Memo=h,t.Portal=a,t.Profiler=l,t.StrictMode=i,t.Suspense=p,t.isAsyncMode=function(e){return S(e)||k(e)===c},t.isConcurrentMode=S,t.isContextConsumer=function(e){return k(e)===u},t.isContextProvider=function(e){return k(e)===s},t.isElement=function(e){return"object"==typeof e&&null!==e&&e.$$typeof===r},t.isForwardRef=function(e){return k(e)===f},t.isFragment=function(e){return k(e)===o},t.isLazy=function(e){return k(e)===g},t.isMemo=function(e){return k(e)===h},t.isPortal=function(e){return k(e)===a},t.isProfiler=function(e){return k(e)===l},t.isStrictMode=function(e){return k(e)===i},t.isSuspense=function(e){return k(e)===p},t.isValidElementType=function(e){return"string"==typeof e||"function"==typeof e||e===o||e===d||e===l||e===i||e===p||e===m||"object"==typeof e&&null!==e&&(e.$$typeof===g||e.$$typeof===h||e.$$typeof===s||e.$$typeof===u||e.$$typeof===f||e.$$typeof===b||e.$$typeof===v||e.$$typeof===w||e.$$typeof===y)},t.typeOf=k},2831:(e,t,n)=>{"use strict";n.d(t,{u:()=>i,v:()=>l});var r=n(6347),a=n(8168),o=n(6540);function i(e,t,n){return void 0===n&&(n=[]),e.some((function(e){var a=e.path?(0,r.B6)(t,e):n.length?n[n.length-1].match:r.Ix.computeRootMatch(t);return a&&(n.push({route:e,match:a}),e.routes&&i(e.routes,t,n)),a})),n}function l(e,t,n){return void 0===t&&(t={}),void 0===n&&(n={}),e?o.createElement(r.dO,n,e.map((function(e,n){return o.createElement(r.qh,{key:e.key||n,path:e.path,exact:e.exact,strict:e.strict,render:function(n){return e.render?e.render((0,a.A)({},n,{},t,{route:e})):o.createElement(e.component,(0,a.A)({},n,t,{route:e}))}})}))):null}},2833:e=>{e.exports=function(e,t,n,r){var a=n?n.call(r,e,t):void 0;if(void 0!==a)return!!a;if(e===t)return!0;if("object"!=typeof e||!e||"object"!=typeof t||!t)return!1;var o=Object.keys(e),i=Object.keys(t);if(o.length!==i.length)return!1;for(var l=Object.prototype.hasOwnProperty.bind(t),s=0;s<o.length;s++){var u=o[s];if(!l(u))return!1;var c=e[u],d=t[u];if(!1===(a=n?n.call(r,c,d,u):void 0)||void 0===a&&c!==d)return!1}return!0}},2892:(e,t,n)=>{"use strict";function r(e,t){return r=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e},r(e,t)}function a(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,r(e,t)}n.d(t,{A:()=>a})},2983:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.addTrailingSlash=a,t.default=function(e,t){const{trailingSlash:n,baseUrl:r}=t;if(e.startsWith("#"))return e;if(void 0===n)return e;const[i]=e.split(/[#?]/),l="/"===i||i===r?i:(s=i,u=n,u?a(s):o(s));var s,u;return e.replace(i,l)},t.addLeadingSlash=function(e){return(0,r.addPrefix)(e,"/")},t.removeTrailingSlash=o;const r=n(2566);function a(e){return e.endsWith("/")?e:`${e}/`}function o(e){return(0,r.removeSuffix)(e,"/")}},3001:(e,t,n)=>{"use strict";n.r(t)},3025:(e,t,n)=>{"use strict";n.d(t,{n:()=>l,r:()=>s});var r=n(6540),a=n(9532),o=n(4848);const i=r.createContext(null);function l({children:e,version:t}){return(0,o.jsx)(i.Provider,{value:t,children:e})}function s(){const e=(0,r.useContext)(i);if(null===e)throw new a.dV("DocsVersionProvider");return e}},3102:(e,t,n)=>{"use strict";n.d(t,{W:()=>i,o:()=>o});var r=n(6540),a=n(4848);const o=r.createContext(null);function i({children:e,value:t}){const n=r.useContext(o),i=(0,r.useMemo)((()=>function({parent:e,value:t}){if(!e){if(!t)throw new Error("Unexpected: no Docusaurus route context found");if(!("plugin"in t))throw new Error("Unexpected: Docusaurus topmost route context has no `plugin` attribute");return t}const n={...e.data,...t?.data};return{plugin:e.plugin,data:n}}({parent:n,value:t})),[n,t]);return(0,a.jsx)(o.Provider,{value:i,children:e})}},3104:(e,t,n)=>{"use strict";n.d(t,{Mq:()=>f,Tv:()=>u,gk:()=>p});var r=n(6540),a=n(8193),o=n(2303),i=(n(205),n(9532)),l=n(4848);const s=r.createContext(void 0);function u({children:e}){const t=function(){const e=(0,r.useRef)(!0);return(0,r.useMemo)((()=>({scrollEventsEnabledRef:e,enableScrollEvents:()=>{e.current=!0},disableScrollEvents:()=>{e.current=!1}})),[])}();return(0,l.jsx)(s.Provider,{value:t,children:e})}function c(){const e=(0,r.useContext)(s);if(null==e)throw new i.dV("ScrollControllerProvider");return e}const d=()=>a.A.canUseDOM?{scrollX:window.pageXOffset,scrollY:window.pageYOffset}:null;function f(e,t=[]){const{scrollEventsEnabledRef:n}=c(),a=(0,r.useRef)(d()),o=(0,i._q)(e);(0,r.useEffect)((()=>{const e=()=>{if(!n.current)return;const e=d();o(e,a.current),a.current=e},t={passive:!0};return e(),window.addEventListener("scroll",e,t),()=>window.removeEventListener("scroll",e,t)}),[o,n,...t])}function p(){const e=(0,r.useRef)(null),t=(0,o.A)()&&"smooth"===getComputedStyle(document.documentElement).scrollBehavior;return{startScroll:n=>{e.current=t?function(e){return window.scrollTo({top:e,behavior:"smooth"}),()=>{}}(n):function(e){let t=null;const n=document.documentElement.scrollTop>e;return function r(){const a=document.documentElement.scrollTop;(n&&a>e||!n&&a<e)&&(t=requestAnimationFrame(r),window.scrollTo(0,Math.floor(.85*(a-e))+e))}(),()=>t&&cancelAnimationFrame(t)}(n)},cancelScroll:()=>e.current?.()}}},3109:(e,t,n)=>{"use strict";function r(){return window.matchMedia("(prefers-reduced-motion: reduce)").matches}n.d(t,{O:()=>r})},3157:(e,t,n)=>{var r={"./":8722};function a(e){var t=o(e);return n(t)}function o(e){if(!n.o(r,e)){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}return r[e]}a.keys=function(){return Object.keys(r)},a.resolve=o,e.exports=a,a.id=3157},3186:(e,t,n)=>{"use strict";n.d(t,{A:()=>i});n(6540);const r={iconExternalLink:"iconExternalLink_nPIU"};var a=n(4848);const o="#theme-svg-external-link";function i({width:e=13.5,height:t=13.5}){return(0,a.jsx)("svg",{width:e,height:t,"aria-hidden":"true",className:r.iconExternalLink,children:(0,a.jsx)("use",{href:o})})}},3259:(e,t,n)=>{"use strict";function r(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,e.__proto__=t}function a(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function o(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(){return i=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},i.apply(this,arguments)}var l=n(6540),s=[],u=[];var c=l.createContext(null);function d(e){var t=e(),n={loading:!0,loaded:null,error:null};return n.promise=t.then((function(e){return n.loading=!1,n.loaded=e,e})).catch((function(e){throw n.loading=!1,n.error=e,e})),n}function f(e){var t={loading:!1,loaded:{},error:null},n=[];try{Object.keys(e).forEach((function(r){var a=d(e[r]);a.loading?t.loading=!0:(t.loaded[r]=a.loaded,t.error=a.error),n.push(a.promise),a.promise.then((function(e){t.loaded[r]=e})).catch((function(e){t.error=e}))}))}catch(r){t.error=r}return t.promise=Promise.all(n).then((function(e){return t.loading=!1,e})).catch((function(e){throw t.loading=!1,e})),t}function p(e,t){return l.createElement((n=e)&&n.__esModule?n.default:n,t);var n}function m(e,t){var d,f;if(!t.loading)throw new Error("react-loadable requires a `loading` component");var m=i({loader:null,loading:null,delay:200,timeout:null,render:p,webpack:null,modules:null},t),h=null;function g(){return h||(h=e(m.loader)),h.promise}return s.push(g),"function"==typeof m.webpack&&u.push((function(){if((0,m.webpack)().every((function(e){return void 0!==e&&void 0!==n.m[e]})))return g()})),f=d=function(t){function n(n){var r;return o(a(a(r=t.call(this,n)||this)),"retry",(function(){r.setState({error:null,loading:!0,timedOut:!1}),h=e(m.loader),r._loadModule()})),g(),r.state={error:h.error,pastDelay:!1,timedOut:!1,loading:h.loading,loaded:h.loaded},r}r(n,t),n.preload=function(){return g()};var i=n.prototype;return i.UNSAFE_componentWillMount=function(){this._loadModule()},i.componentDidMount=function(){this._mounted=!0},i._loadModule=function(){var e=this;if(this.context&&Array.isArray(m.modules)&&m.modules.forEach((function(t){e.context.report(t)})),h.loading){var t=function(t){e._mounted&&e.setState(t)};"number"==typeof m.delay&&(0===m.delay?this.setState({pastDelay:!0}):this._delay=setTimeout((function(){t({pastDelay:!0})}),m.delay)),"number"==typeof m.timeout&&(this._timeout=setTimeout((function(){t({timedOut:!0})}),m.timeout));var n=function(){t({error:h.error,loaded:h.loaded,loading:h.loading}),e._clearTimeouts()};h.promise.then((function(){return n(),null})).catch((function(e){return n(),null}))}},i.componentWillUnmount=function(){this._mounted=!1,this._clearTimeouts()},i._clearTimeouts=function(){clearTimeout(this._delay),clearTimeout(this._timeout)},i.render=function(){return this.state.loading||this.state.error?l.createElement(m.loading,{isLoading:this.state.loading,pastDelay:this.state.pastDelay,timedOut:this.state.timedOut,error:this.state.error,retry:this.retry}):this.state.loaded?m.render(this.state.loaded,this.props):null},n}(l.Component),o(d,"contextType",c),f}function h(e){return m(d,e)}h.Map=function(e){if("function"!=typeof e.render)throw new Error("LoadableMap requires a `render(loaded, props)` function");return m(f,e)};var g=function(e){function t(){return e.apply(this,arguments)||this}return r(t,e),t.prototype.render=function(){return l.createElement(c.Provider,{value:{report:this.props.report}},l.Children.only(this.props.children))},t}(l.Component);function y(e){for(var t=[];e.length;){var n=e.pop();t.push(n())}return Promise.all(t).then((function(){if(e.length)return y(e)}))}h.Capture=g,h.preloadAll=function(){return new Promise((function(e,t){y(s).then(e,t)}))},h.preloadReady=function(){return new Promise((function(e,t){y(u).then(e,e)}))},e.exports=h},3427:(e,t,n)=>{"use strict";n.d(t,{A:()=>i});var r=n(6540);n(4848);const a=r.createContext({collectAnchor:()=>{},collectLink:()=>{}}),o=()=>(0,r.useContext)(a);function i(){return o()}},3465:(e,t,n)=>{"use strict";n.d(t,{A:()=>c});n(6540);var r=n(8774),a=n(6025),o=n(4586),i=n(6342),l=n(1122),s=n(4848);function u({logo:e,alt:t,imageClassName:n}){const r={light:(0,a.Ay)(e.src),dark:(0,a.Ay)(e.srcDark||e.src)},o=(0,s.jsx)(l.A,{className:e.className,sources:r,height:e.height,width:e.width,alt:t,style:e.style});return n?(0,s.jsx)("div",{className:n,children:o}):o}function c(e){const{siteConfig:{title:t}}=(0,o.A)(),{navbar:{title:n,logo:l}}=(0,i.p)(),{imageClassName:c,titleClassName:d,...f}=e,p=(0,a.Ay)(l?.href||"/"),m=n?"":t,h=l?.alt??m;return(0,s.jsxs)(r.A,{to:p,...f,...l?.target&&{target:l.target},children:[l&&(0,s.jsx)(u,{logo:l,alt:h,imageClassName:c}),null!=n&&(0,s.jsx)("b",{className:d,children:n})]})}},3886:(e,t,n)=>{"use strict";n.d(t,{VQ:()=>g,g1:()=>b});var r=n(6540),a=n(4070),o=n(7065),i=n(6342),l=n(679),s=n(9532),u=n(4848);const c=e=>`docs-preferred-version-${e}`,d={save:(e,t,n)=>{(0,l.Wf)(c(e),{persistence:t}).set(n)},read:(e,t)=>(0,l.Wf)(c(e),{persistence:t}).get(),clear:(e,t)=>{(0,l.Wf)(c(e),{persistence:t}).del()}},f=e=>Object.fromEntries(e.map((e=>[e,{preferredVersionName:null}])));const p=r.createContext(null);function m(){const e=(0,a.Gy)(),t=(0,i.p)().docs.versionPersistence,n=(0,r.useMemo)((()=>Object.keys(e)),[e]),[o,l]=(0,r.useState)((()=>f(n)));(0,r.useEffect)((()=>{l(function({pluginIds:e,versionPersistence:t,allDocsData:n}){function r(e){const r=d.read(e,t);return n[e].versions.some((e=>e.name===r))?{preferredVersionName:r}:(d.clear(e,t),{preferredVersionName:null})}return Object.fromEntries(e.map((e=>[e,r(e)])))}({allDocsData:e,versionPersistence:t,pluginIds:n}))}),[e,t,n]);return[o,(0,r.useMemo)((()=>({savePreferredVersion:function(e,n){d.save(e,t,n),l((t=>({...t,[e]:{preferredVersionName:n}})))}})),[t])]}function h({children:e}){const t=m();return(0,u.jsx)(p.Provider,{value:t,children:e})}function g({children:e}){return(0,u.jsx)(h,{children:e})}function y(){const e=(0,r.useContext)(p);if(!e)throw new s.dV("DocsPreferredVersionContextProvider");return e}function b(e=o.W){const t=(0,a.ht)(e),[n,i]=y(),{preferredVersionName:l}=n[e];return{preferredVersion:t.versions.find((e=>e.name===l))??null,savePreferredVersionName:(0,r.useCallback)((t=>{i.savePreferredVersion(e,t)}),[i,e])}}},4054:e=>{"use strict";e.exports=JSON.parse('{"/BharatMLStack/blog-170":{"__comp":"a6aa9e1f","__context":{"plugin":"36994c47"},"sidebar":"814f3328","items":[{"content":"f2c141e4"}],"__props":"f994c8da"},"/BharatMLStack/blog/archive-dde":{"__comp":"9e4087bc","__context":{"plugin":"36994c47"},"__props":"6479fb86"},"/BharatMLStack/blog/authors-f47":{"__comp":"621db11d","__context":{"data":{"blogMetadata":"acecf23e"},"plugin":"36994c47"},"sidebar":"814f3328","__props":"2d865531"},"/BharatMLStack/blog/post-one-e5f":{"__comp":"ccc49370","__context":{"data":{"blogMetadata":"acecf23e"},"plugin":"36994c47"},"sidebar":"814f3328","content":"09dd5be9"},"/BharatMLStack/blog/tags-8af":{"__comp":"01a85c17","__context":{"plugin":"36994c47"},"sidebar":"814f3328","__props":"7fa80e1c"},"/BharatMLStack/blog/tags/interaction-store-4b6":{"__comp":"6875c492","__context":{"plugin":"36994c47"},"sidebar":"814f3328","items":[{"content":"f2c141e4"}],"__props":"3980073a"},"/BharatMLStack/blog/tags/meesho-316":{"__comp":"6875c492","__context":{"plugin":"36994c47"},"sidebar":"814f3328","items":[{"content":"f2c141e4"}],"__props":"1a64de69"},"/BharatMLStack/blog/tags/mlplatform-48f":{"__comp":"6875c492","__context":{"plugin":"36994c47"},"sidebar":"814f3328","items":[{"content":"f2c141e4"}],"__props":"479eb034"},"/BharatMLStack/blog/tags/online-feature-store-44b":{"__comp":"6875c492","__context":{"plugin":"36994c47"},"sidebar":"814f3328","items":[{"content":"f2c141e4"}],"__props":"3e1c5046"},"/BharatMLStack/markdown-page-747":{"__comp":"1f391b9e","__context":{"plugin":"a7456010"},"content":"393be207"},"/BharatMLStack/-e34":{"__comp":"c4f5d8e4","__context":{"plugin":"a7456010"},"config":"5e9f5e1a"},"/BharatMLStack/-fd6":{"__comp":"5e95c892","__context":{"plugin":"aba21aa0"}},"/BharatMLStack/-098":{"__comp":"a7bd4aaa","__props":"4137b431"},"/BharatMLStack/-925":{"__comp":"a94703ab"},"/BharatMLStack/category/go-sdk-6b0":{"__comp":"14eb3368","__props":"c7b64fcc"},"/BharatMLStack/category/online-feature-store-7ee":{"__comp":"14eb3368","__props":"8ac6191a"},"/BharatMLStack/category/python-sdk-1fd":{"__comp":"14eb3368","__props":"44d1c015"},"/BharatMLStack/category/quick-start-dff":{"__comp":"14eb3368","__props":"14064408"},"/BharatMLStack/category/sdks-532":{"__comp":"14eb3368","__props":"616111d3"},"/BharatMLStack/category/trufflebox-ui-5f5":{"__comp":"14eb3368","__props":"fcf4f6ca"},"/BharatMLStack/category/v100-ddd":{"__comp":"14eb3368","__props":"fa31f022"},"/BharatMLStack/online-feature-store/v1.0.0-218":{"__comp":"14eb3368","__props":"72dc5b25"},"/BharatMLStack/online-feature-store/v1.0.0/architecture-0af":{"__comp":"17896441","content":"e66382f6"},"/BharatMLStack/online-feature-store/v1.0.0/benchmarks-889":{"__comp":"17896441","content":"67d4782a"},"/BharatMLStack/online-feature-store/v1.0.0/data-formats-46e":{"__comp":"17896441","content":"4caa95bf"},"/BharatMLStack/online-feature-store/v1.0.0/functionalities-415":{"__comp":"17896441","content":"c4822c4f"},"/BharatMLStack/online-feature-store/v1.0.0/release-notes-36c":{"__comp":"17896441","content":"d152284c"},"/BharatMLStack/quick-start/v1.0.0/quick-start-b19":{"__comp":"17896441","content":"0fff8dc8"},"/BharatMLStack/sdks/go/v1.0.0/feature_client-1df":{"__comp":"17896441","content":"4af50aac"},"/BharatMLStack/sdks/python/v1.0.0/grpc_feature_client-9dc":{"__comp":"17896441","content":"0413d9af"},"/BharatMLStack/sdks/python/v1.0.0/spark_feature_push_client-1bc":{"__comp":"17896441","content":"ac51638e"},"/BharatMLStack/trufflebox-ui/v1.0.0/userguide-65e":{"__comp":"17896441","content":"176d210f"}}')},4070:(e,t,n)=>{"use strict";n.d(t,{zK:()=>h,vT:()=>f,Gy:()=>c,HW:()=>g,ht:()=>d,r7:()=>m,jh:()=>p});var r=n(6347),a=n(4586),o=n(7065);function i(e,t={}){const n=function(){const{globalData:e}=(0,a.A)();return e}()[e];if(!n&&t.failfast)throw new Error(`Docusaurus plugin global data not found for "${e}" plugin.`);return n}const l=e=>e.versions.find((e=>e.isLast));function s(e,t){const n=function(e,t){return[...e.versions].sort(((e,t)=>e.path===t.path?0:e.path.includes(t.path)?-1:t.path.includes(e.path)?1:0)).find((e=>!!(0,r.B6)(t,{path:e.path,exact:!1,strict:!1})))}(e,t),a=n?.docs.find((e=>!!(0,r.B6)(t,{path:e.path,exact:!0,strict:!1})));return{activeVersion:n,activeDoc:a,alternateDocVersions:a?function(t){const n={};return e.versions.forEach((e=>{e.docs.forEach((r=>{r.id===t&&(n[e.name]=r)}))})),n}(a.id):{}}}const u={},c=()=>i("docusaurus-plugin-content-docs")??u,d=e=>{try{return function(e,t=o.W,n={}){const r=i(e),a=r?.[t];if(!a&&n.failfast)throw new Error(`Docusaurus plugin global data not found for "${e}" plugin with id "${t}".`);return a}("docusaurus-plugin-content-docs",e,{failfast:!0})}catch(t){throw new Error("You are using a feature of the Docusaurus docs plugin, but this plugin does not seem to be enabled"+("Default"===e?"":` (pluginId=${e}`),{cause:t})}};function f(e={}){const t=c(),{pathname:n}=(0,r.zy)();return function(e,t,n={}){const a=Object.entries(e).sort(((e,t)=>t[1].path.localeCompare(e[1].path))).find((([,e])=>!!(0,r.B6)(t,{path:e.path,exact:!1,strict:!1}))),o=a?{pluginId:a[0],pluginData:a[1]}:void 0;if(!o&&n.failfast)throw new Error(`Can't find active docs plugin for "${t}" pathname, while it was expected to be found. Maybe you tried to use a docs feature that can only be used on a docs-related page? Existing docs plugin paths are: ${Object.values(e).map((e=>e.path)).join(", ")}`);return o}(t,n,e)}function p(e){return d(e).versions}function m(e){const t=d(e);return l(t)}function h(e){const t=d(e),{pathname:n}=(0,r.zy)();return s(t,n)}function g(e){const t=d(e),{pathname:n}=(0,r.zy)();return function(e,t){const n=l(e);return{latestDocSuggestion:s(e,t).alternateDocVersions[n.name],latestVersionSuggestion:n}}(t,n)}},4090:(e,t,n)=>{"use strict";n.d(t,{w:()=>a,J:()=>o});var r=n(6540);const a="navigation-with-keyboard";function o(){(0,r.useEffect)((()=>{function e(e){"keydown"===e.type&&"Tab"===e.key&&document.body.classList.add(a),"mousedown"===e.type&&document.body.classList.remove(a)}return document.addEventListener("keydown",e),document.addEventListener("mousedown",e),()=>{document.body.classList.remove(a),document.removeEventListener("keydown",e),document.removeEventListener("mousedown",e)}}),[])}},4146:(e,t,n)=>{"use strict";var r=n(4363),a={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},o={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},i={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},l={};function s(e){return r.isMemo(e)?i:l[e.$$typeof]||a}l[r.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},l[r.Memo]=i;var u=Object.defineProperty,c=Object.getOwnPropertyNames,d=Object.getOwnPropertySymbols,f=Object.getOwnPropertyDescriptor,p=Object.getPrototypeOf,m=Object.prototype;e.exports=function e(t,n,r){if("string"!=typeof n){if(m){var a=p(n);a&&a!==m&&e(t,a,r)}var i=c(n);d&&(i=i.concat(d(n)));for(var l=s(t),h=s(n),g=0;g<i.length;++g){var y=i[g];if(!(o[y]||r&&r[y]||h&&h[y]||l&&l[y])){var b=f(n,y);try{u(t,y,b)}catch(v){}}}}return t}},4164:(e,t,n)=>{"use strict";function r(e){var t,n,a="";if("string"==typeof e||"number"==typeof e)a+=e;else if("object"==typeof e)if(Array.isArray(e)){var o=e.length;for(t=0;t<o;t++)e[t]&&(n=r(e[t]))&&(a&&(a+=" "),a+=n)}else for(n in e)e[n]&&(a&&(a+=" "),a+=n);return a}n.d(t,{A:()=>a});const a=function(){for(var e,t,n=0,a="",o=arguments.length;n<o;n++)(e=arguments[n])&&(t=r(e))&&(a&&(a+=" "),a+=t);return a}},4363:(e,t,n)=>{"use strict";e.exports=n(2799)},4477:(e,t)=>{"use strict";function n(e,t){var n=e.length;e.push(t);e:for(;0<n;){var r=n-1>>>1,a=e[r];if(!(0<o(a,t)))break e;e[r]=t,e[n]=a,n=r}}function r(e){return 0===e.length?null:e[0]}function a(e){if(0===e.length)return null;var t=e[0],n=e.pop();if(n!==t){e[0]=n;e:for(var r=0,a=e.length,i=a>>>1;r<i;){var l=2*(r+1)-1,s=e[l],u=l+1,c=e[u];if(0>o(s,n))u<a&&0>o(c,s)?(e[r]=c,e[u]=n,r=u):(e[r]=s,e[l]=n,r=l);else{if(!(u<a&&0>o(c,n)))break e;e[r]=c,e[u]=n,r=u}}}return t}function o(e,t){var n=e.sortIndex-t.sortIndex;return 0!==n?n:e.id-t.id}if(t.unstable_now=void 0,"object"==typeof performance&&"function"==typeof performance.now){var i=performance;t.unstable_now=function(){return i.now()}}else{var l=Date,s=l.now();t.unstable_now=function(){return l.now()-s}}var u=[],c=[],d=1,f=null,p=3,m=!1,h=!1,g=!1,y=!1,b="function"==typeof setTimeout?setTimeout:null,v="function"==typeof clearTimeout?clearTimeout:null,w="undefined"!=typeof setImmediate?setImmediate:null;function k(e){for(var t=r(c);null!==t;){if(null===t.callback)a(c);else{if(!(t.startTime<=e))break;a(c),t.sortIndex=t.expirationTime,n(u,t)}t=r(c)}}function S(e){if(g=!1,k(e),!h)if(null!==r(u))h=!0,_||(_=!0,x());else{var t=r(c);null!==t&&N(S,t.startTime-e)}}var x,_=!1,E=-1,C=5,A=-1;function T(){return!!y||!(t.unstable_now()-A<C)}function L(){if(y=!1,_){var e=t.unstable_now();A=e;var n=!0;try{e:{h=!1,g&&(g=!1,v(E),E=-1),m=!0;var o=p;try{t:{for(k(e),f=r(u);null!==f&&!(f.expirationTime>e&&T());){var i=f.callback;if("function"==typeof i){f.callback=null,p=f.priorityLevel;var l=i(f.expirationTime<=e);if(e=t.unstable_now(),"function"==typeof l){f.callback=l,k(e),n=!0;break t}f===r(u)&&a(u),k(e)}else a(u);f=r(u)}if(null!==f)n=!0;else{var s=r(c);null!==s&&N(S,s.startTime-e),n=!1}}break e}finally{f=null,p=o,m=!1}n=void 0}}finally{n?x():_=!1}}}if("function"==typeof w)x=function(){w(L)};else if("undefined"!=typeof MessageChannel){var j=new MessageChannel,P=j.port2;j.port1.onmessage=L,x=function(){P.postMessage(null)}}else x=function(){b(L,0)};function N(e,n){E=b((function(){e(t.unstable_now())}),n)}t.unstable_IdlePriority=5,t.unstable_ImmediatePriority=1,t.unstable_LowPriority=4,t.unstable_NormalPriority=3,t.unstable_Profiling=null,t.unstable_UserBlockingPriority=2,t.unstable_cancelCallback=function(e){e.callback=null},t.unstable_forceFrameRate=function(e){0>e||125<e?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):C=0<e?Math.floor(1e3/e):5},t.unstable_getCurrentPriorityLevel=function(){return p},t.unstable_next=function(e){switch(p){case 1:case 2:case 3:var t=3;break;default:t=p}var n=p;p=t;try{return e()}finally{p=n}},t.unstable_requestPaint=function(){y=!0},t.unstable_runWithPriority=function(e,t){switch(e){case 1:case 2:case 3:case 4:case 5:break;default:e=3}var n=p;p=e;try{return t()}finally{p=n}},t.unstable_scheduleCallback=function(e,a,o){var i=t.unstable_now();switch("object"==typeof o&&null!==o?o="number"==typeof(o=o.delay)&&0<o?i+o:i:o=i,e){case 1:var l=-1;break;case 2:l=250;break;case 5:l=1073741823;break;case 4:l=1e4;break;default:l=5e3}return e={id:d++,callback:a,priorityLevel:e,startTime:o,expirationTime:l=o+l,sortIndex:-1},o>i?(e.sortIndex=o,n(c,e),null===r(u)&&e===r(c)&&(g?(v(E),E=-1):g=!0,N(S,o-i))):(e.sortIndex=l,n(u,e),h||m||(h=!0,_||(_=!0,x()))),e},t.unstable_shouldYield=T,t.unstable_wrapCallback=function(e){var t=p;return function(){var n=p;p=t;try{return e.apply(this,arguments)}finally{p=n}}}},4563:(e,t,n)=>{"use strict";n.d(t,{AL:()=>c,s$:()=>d});var r=n(6540),a=n(4586),o=n(6803),i=n(9532),l=n(4848);const s=({title:e,siteTitle:t,titleDelimiter:n})=>{const r=e?.trim();return r&&r!==t?`${r} ${n} ${t}`:t},u=(0,r.createContext)(null);function c({formatter:e,children:t}){return(0,l.jsx)(u.Provider,{value:e,children:t})}function d(){const e=function(){const e=(0,r.useContext)(u);if(null===e)throw new i.dV("TitleFormatterProvider");return e}(),{siteConfig:t}=(0,a.A)(),{title:n,titleDelimiter:l}=t,{plugin:c}=(0,o.A)();return{format:t=>e({title:t,siteTitle:n,titleDelimiter:l,plugin:c,defaultFormatter:s})}}},4581:(e,t,n)=>{"use strict";n.d(t,{l:()=>l});var r=n(6540),a=n(8193);const o={desktop:"desktop",mobile:"mobile",ssr:"ssr"},i=996;function l({desktopBreakpoint:e=i}={}){const[t,n]=(0,r.useState)((()=>"ssr"));return(0,r.useEffect)((()=>{function t(){n(function(e){if(!a.A.canUseDOM)throw new Error("getWindowSize() should only be called after React hydration");return window.innerWidth>e?o.desktop:o.mobile}(e))}return t(),window.addEventListener("resize",t),()=>{window.removeEventListener("resize",t)}}),[e]),t}},4586:(e,t,n)=>{"use strict";n.d(t,{A:()=>o});var r=n(6540),a=n(6988);function o(){return(0,r.useContext)(a.o)}},4625:(e,t,n)=>{"use strict";n.d(t,{I9:()=>d,Kd:()=>c,N_:()=>y,k2:()=>w});var r=n(6347),a=n(2892),o=n(6540),i=n(1513),l=n(8168),s=n(8587),u=n(1561),c=function(e){function t(){for(var t,n=arguments.length,r=new Array(n),a=0;a<n;a++)r[a]=arguments[a];return(t=e.call.apply(e,[this].concat(r))||this).history=(0,i.zR)(t.props),t}return(0,a.A)(t,e),t.prototype.render=function(){return o.createElement(r.Ix,{history:this.history,children:this.props.children})},t}(o.Component);var d=function(e){function t(){for(var t,n=arguments.length,r=new Array(n),a=0;a<n;a++)r[a]=arguments[a];return(t=e.call.apply(e,[this].concat(r))||this).history=(0,i.TM)(t.props),t}return(0,a.A)(t,e),t.prototype.render=function(){return o.createElement(r.Ix,{history:this.history,children:this.props.children})},t}(o.Component);var f=function(e,t){return"function"==typeof e?e(t):e},p=function(e,t){return"string"==typeof e?(0,i.yJ)(e,null,null,t):e},m=function(e){return e},h=o.forwardRef;void 0===h&&(h=m);var g=h((function(e,t){var n=e.innerRef,r=e.navigate,a=e.onClick,i=(0,s.A)(e,["innerRef","navigate","onClick"]),u=i.target,c=(0,l.A)({},i,{onClick:function(e){try{a&&a(e)}catch(t){throw e.preventDefault(),t}e.defaultPrevented||0!==e.button||u&&"_self"!==u||function(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}(e)||(e.preventDefault(),r())}});return c.ref=m!==h&&t||n,o.createElement("a",c)}));var y=h((function(e,t){var n=e.component,a=void 0===n?g:n,c=e.replace,d=e.to,y=e.innerRef,b=(0,s.A)(e,["component","replace","to","innerRef"]);return o.createElement(r.XZ.Consumer,null,(function(e){e||(0,u.A)(!1);var n=e.history,r=p(f(d,e.location),e.location),s=r?n.createHref(r):"",g=(0,l.A)({},b,{href:s,navigate:function(){var t=f(d,e.location),r=(0,i.AO)(e.location)===(0,i.AO)(p(t));(c||r?n.replace:n.push)(t)}});return m!==h?g.ref=t||y:g.innerRef=y,o.createElement(a,g)}))})),b=function(e){return e},v=o.forwardRef;void 0===v&&(v=b);var w=v((function(e,t){var n=e["aria-current"],a=void 0===n?"page":n,i=e.activeClassName,c=void 0===i?"active":i,d=e.activeStyle,m=e.className,h=e.exact,g=e.isActive,w=e.location,k=e.sensitive,S=e.strict,x=e.style,_=e.to,E=e.innerRef,C=(0,s.A)(e,["aria-current","activeClassName","activeStyle","className","exact","isActive","location","sensitive","strict","style","to","innerRef"]);return o.createElement(r.XZ.Consumer,null,(function(e){e||(0,u.A)(!1);var n=w||e.location,i=p(f(_,n),n),s=i.pathname,A=s&&s.replace(/([.+*?=^!:${}()[\]|/\\])/g,"\\$1"),T=A?(0,r.B6)(n.pathname,{path:A,exact:h,sensitive:k,strict:S}):null,L=!!(g?g(T,n):T),j="function"==typeof m?m(L):m,P="function"==typeof x?x(L):x;L&&(j=function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return t.filter((function(e){return e})).join(" ")}(j,c),P=(0,l.A)({},P,d));var N=(0,l.A)({"aria-current":L&&a||null,className:j,style:P,to:i},C);return b!==v?N.ref=t||E:N.innerRef=E,o.createElement(y,N)}))}))},4634:e=>{e.exports=Array.isArray||function(e){return"[object Array]"==Object.prototype.toString.call(e)}},4784:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r={title:"BharatMLStack",tagline:"BharatMLStack is a comprehensive, production-ready machine learning infrastructure platform designed to democratize ML capabilities across India and beyond. Our mission is to provide a robust, scalable, and accessible ML stack that empowers organizations to build, deploy, and manage machine learning solutions at massive scale.",favicon:"img/favicon.ico",future:{v4:{removeLegacyPostBuildHeadAttribute:!0,useCssCascadeLayers:!0},experimental_faster:{swcJsLoader:!1,swcJsMinimizer:!1,swcHtmlMinimizer:!1,lightningCssMinimizer:!1,mdxCrossCompilerCache:!1,rspackBundler:!1,rspackPersistentCache:!1,ssgWorkerThreads:!1},experimental_storage:{type:"localStorage",namespace:!1},experimental_router:"browser"},url:"https://meesho.github.io",baseUrl:"/BharatMLStack/",organizationName:"Meesho Ltd.",projectName:"BharatMLStack",onBrokenLinks:"throw",onBrokenMarkdownLinks:"warn",i18n:{defaultLocale:"en",locales:["en"],path:"i18n",localeConfigs:{}},presets:[["classic",{docs:{sidebarPath:"./sidebars.js",editUrl:"https://github.com/Meesho/BharatMLStack/tree/main/docs",routeBasePath:"/"},blog:{showReadingTime:!0,feedOptions:{type:["rss","atom"],xslt:!0},editUrl:"https://github.com/Meesho/BharatMLStack/tree/main/docs",onInlineTags:"warn",onInlineAuthors:"warn",onUntruncatedBlogPosts:"warn"},theme:{customCss:"./src/css/custom.css"}}]],themeConfig:{image:"img/docusaurus-social-card.jpg",navbar:{title:"BharatMLStack",items:[{type:"docSidebar",sidebarId:"tutorialSidebar",position:"left",label:"Docs"},{to:"/blog",label:"Blog",position:"left"},{href:"https://github.com/Meesho/BharatMLStack",label:"GitHub",position:"right"}],hideOnScroll:!1},footer:{style:"dark",links:[{title:"Community",items:[{label:"Github Discussions",href:"https://github.com/Meesho/BharatMLStack/discussions"},{label:"Discord",href:"https://discord.gg/XkT7XsV2AU"}]},{title:"More",items:[{label:"Blog",to:"/blog"},{label:"GitHub",href:"https://github.com/Meesho/BharatMLStack"}]}],copyright:"Copyright \xa9 2025 Meesho Ltd. Built with Docusaurus."},prism:{theme:{plain:{color:"#393A34",backgroundColor:"#f6f8fa"},styles:[{types:["comment","prolog","doctype","cdata"],style:{color:"#999988",fontStyle:"italic"}},{types:["namespace"],style:{opacity:.7}},{types:["string","attr-value"],style:{color:"#e3116c"}},{types:["punctuation","operator"],style:{color:"#393A34"}},{types:["entity","url","symbol","number","boolean","variable","constant","property","regex","inserted"],style:{color:"#36acaa"}},{types:["atrule","keyword","attr-name","selector"],style:{color:"#00a4db"}},{types:["function","deleted","tag"],style:{color:"#d73a49"}},{types:["function-variable"],style:{color:"#6f42c1"}},{types:["tag","selector","keyword"],style:{color:"#00009f"}}]},darkTheme:{plain:{color:"#F8F8F2",backgroundColor:"#282A36"},styles:[{types:["prolog","constant","builtin"],style:{color:"rgb(189, 147, 249)"}},{types:["inserted","function"],style:{color:"rgb(80, 250, 123)"}},{types:["deleted"],style:{color:"rgb(255, 85, 85)"}},{types:["changed"],style:{color:"rgb(255, 184, 108)"}},{types:["punctuation","symbol"],style:{color:"rgb(248, 248, 242)"}},{types:["string","char","tag","selector"],style:{color:"rgb(255, 121, 198)"}},{types:["keyword","variable"],style:{color:"rgb(189, 147, 249)",fontStyle:"italic"}},{types:["comment"],style:{color:"rgb(98, 114, 164)"}},{types:["attr-name"],style:{color:"rgb(241, 250, 140)"}}]},additionalLanguages:[],magicComments:[{className:"theme-code-block-highlighted-line",line:"highlight-next-line",block:{start:"highlight-start",end:"highlight-end"}}]},colorMode:{defaultMode:"light",disableSwitch:!1,respectPrefersColorScheme:!1},docs:{versionPersistence:"localStorage",sidebar:{hideable:!1,autoCollapseCategories:!1}},blog:{sidebar:{groupByYear:!0}},metadata:[],tableOfContents:{minHeadingLevel:2,maxHeadingLevel:3}},baseUrlIssueBanner:!0,onBrokenAnchors:"warn",onDuplicateRoutes:"warn",staticDirectories:["static"],customFields:{},plugins:[],themes:[],scripts:[],headTags:[],stylesheets:[],clientModules:[],titleDelimiter:"|",noIndex:!1,markdown:{format:"mdx",mermaid:!1,mdx1Compat:{comments:!0,admonitions:!0,headingIds:!0},anchors:{maintainCase:!1}}}},4848:(e,t,n)=>{"use strict";e.exports=n(9698)},5041:(e,t,n)=>{"use strict";n.d(t,{M:()=>h,o:()=>m});var r=n(6540),a=n(2303),o=n(679),i=n(9532),l=n(6342),s=n(4848);const u=(0,o.Wf)("docusaurus.announcement.dismiss"),c=(0,o.Wf)("docusaurus.announcement.id"),d=()=>"true"===u.get(),f=e=>u.set(String(e)),p=r.createContext(null);function m({children:e}){const t=function(){const{announcementBar:e}=(0,l.p)(),t=(0,a.A)(),[n,o]=(0,r.useState)((()=>!!t&&d()));(0,r.useEffect)((()=>{o(d())}),[]);const i=(0,r.useCallback)((()=>{f(!0),o(!0)}),[]);return(0,r.useEffect)((()=>{if(!e)return;const{id:t}=e;let n=c.get();"annoucement-bar"===n&&(n="announcement-bar");const r=t!==n;c.set(t),r&&f(!1),!r&&d()||o(!1)}),[e]),(0,r.useMemo)((()=>({isActive:!!e&&!n,close:i})),[e,n,i])}();return(0,s.jsx)(p.Provider,{value:t,children:e})}function h(){const e=(0,r.useContext)(p);if(!e)throw new i.dV("AnnouncementBarProvider");return e}},5062:(e,t,n)=>{"use strict";n.d(t,{$:()=>i});var r=n(6540),a=n(6347),o=n(9532);function i(e){const t=(0,a.zy)(),n=(0,o.ZC)(t),i=(0,o._q)(e);(0,r.useEffect)((()=>{n&&t!==n&&i({location:t,previousLocation:n})}),[i,t,n])}},5260:(e,t,n)=>{"use strict";n.d(t,{A:()=>o});n(6540);var r=n(545),a=n(4848);function o(e){return(0,a.jsx)(r.mg,{...e})}},5293:(e,t,n)=>{"use strict";n.d(t,{G:()=>w,a:()=>v});var r=n(6540),a=n(9532),o=n(679),i=n(6342),l=n(4848);function s(){return window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}function u(e){return function(e,t){const n=window.matchMedia(e);return n.addEventListener("change",t),()=>n.removeEventListener("change",t)}("(prefers-color-scheme: dark)",(()=>e(s())))}const c=r.createContext(void 0),d=(0,o.Wf)("theme"),f="system",p=e=>"dark"===e?"dark":"light",m=e=>null===e||e===f?null:p(e),h={get:()=>p(document.documentElement.getAttribute("data-theme")),set:e=>{document.documentElement.setAttribute("data-theme",p(e))}},g={get:()=>m(document.documentElement.getAttribute("data-theme-choice")),set:e=>{document.documentElement.setAttribute("data-theme-choice",m(e)??f)}},y=e=>{null===e?d.del():d.set(p(e))};function b(){const{colorMode:{defaultMode:e,disableSwitch:t,respectPrefersColorScheme:n}}=(0,i.p)(),{colorMode:a,setColorModeState:o,colorModeChoice:l,setColorModeChoiceState:c}=function(){const{colorMode:{defaultMode:e}}=(0,i.p)(),[t,n]=(0,r.useState)(e),[a,o]=(0,r.useState)(null);return(0,r.useEffect)((()=>{n(h.get()),o(g.get())}),[]),{colorMode:t,setColorModeState:n,colorModeChoice:a,setColorModeChoiceState:o}}();(0,r.useEffect)((()=>{t&&d.del()}),[t]);const f=(0,r.useCallback)(((t,r={})=>{const{persist:a=!0}=r;if(null===t){const t=n?s():e;h.set(t),o(t),g.set(null),c(null)}else h.set(t),g.set(t),o(t),c(t);a&&y(t)}),[o,c,n,e]);return(0,r.useEffect)((()=>d.listen((e=>{f(m(e.newValue))}))),[f]),(0,r.useEffect)((()=>{if(null===l&&n)return u((e=>{o(e),h.set(e)}))}),[n,l,o]),(0,r.useMemo)((()=>({colorMode:a,colorModeChoice:l,setColorMode:f,get isDarkTheme(){return"dark"===a},setLightTheme(){f("light")},setDarkTheme(){f("dark")}})),[a,l,f])}function v({children:e}){const t=b();return(0,l.jsx)(c.Provider,{value:t,children:e})}function w(){const e=(0,r.useContext)(c);if(null==e)throw new a.dV("ColorModeProvider","Please see https://docusaurus.io/docs/api/themes/configuration#use-color-mode.");return e}},5302:(e,t,n)=>{var r=n(4634);e.exports=m,e.exports.parse=o,e.exports.compile=function(e,t){return s(o(e,t),t)},e.exports.tokensToFunction=s,e.exports.tokensToRegExp=p;var a=new RegExp(["(\\\\.)","([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?|(\\*))"].join("|"),"g");function o(e,t){for(var n,r=[],o=0,l=0,s="",u=t&&t.delimiter||"/";null!=(n=a.exec(e));){var d=n[0],f=n[1],p=n.index;if(s+=e.slice(l,p),l=p+d.length,f)s+=f[1];else{var m=e[l],h=n[2],g=n[3],y=n[4],b=n[5],v=n[6],w=n[7];s&&(r.push(s),s="");var k=null!=h&&null!=m&&m!==h,S="+"===v||"*"===v,x="?"===v||"*"===v,_=h||u,E=y||b,C=h||("string"==typeof r[r.length-1]?r[r.length-1]:"");r.push({name:g||o++,prefix:h||"",delimiter:_,optional:x,repeat:S,partial:k,asterisk:!!w,pattern:E?c(E):w?".*":i(_,C)})}}return l<e.length&&(s+=e.substr(l)),s&&r.push(s),r}function i(e,t){return!t||t.indexOf(e)>-1?"[^"+u(e)+"]+?":u(t)+"|(?:(?!"+u(t)+")[^"+u(e)+"])+?"}function l(e){return encodeURI(e).replace(/[\/?#]/g,(function(e){return"%"+e.charCodeAt(0).toString(16).toUpperCase()}))}function s(e,t){for(var n=new Array(e.length),a=0;a<e.length;a++)"object"==typeof e[a]&&(n[a]=new RegExp("^(?:"+e[a].pattern+")$",f(t)));return function(t,a){for(var o="",i=t||{},s=(a||{}).pretty?l:encodeURIComponent,u=0;u<e.length;u++){var c=e[u];if("string"!=typeof c){var d,f=i[c.name];if(null==f){if(c.optional){c.partial&&(o+=c.prefix);continue}throw new TypeError('Expected "'+c.name+'" to be defined')}if(r(f)){if(!c.repeat)throw new TypeError('Expected "'+c.name+'" to not repeat, but received `'+JSON.stringify(f)+"`");if(0===f.length){if(c.optional)continue;throw new TypeError('Expected "'+c.name+'" to not be empty')}for(var p=0;p<f.length;p++){if(d=s(f[p]),!n[u].test(d))throw new TypeError('Expected all "'+c.name+'" to match "'+c.pattern+'", but received `'+JSON.stringify(d)+"`");o+=(0===p?c.prefix:c.delimiter)+d}}else{if(d=c.asterisk?encodeURI(f).replace(/[?#]/g,(function(e){return"%"+e.charCodeAt(0).toString(16).toUpperCase()})):s(f),!n[u].test(d))throw new TypeError('Expected "'+c.name+'" to match "'+c.pattern+'", but received "'+d+'"');o+=c.prefix+d}}else o+=c}return o}}function u(e){return e.replace(/([.+*?=^!:${}()[\]|\/\\])/g,"\\$1")}function c(e){return e.replace(/([=!:$\/()])/g,"\\$1")}function d(e,t){return e.keys=t,e}function f(e){return e&&e.sensitive?"":"i"}function p(e,t,n){r(t)||(n=t||n,t=[]);for(var a=(n=n||{}).strict,o=!1!==n.end,i="",l=0;l<e.length;l++){var s=e[l];if("string"==typeof s)i+=u(s);else{var c=u(s.prefix),p="(?:"+s.pattern+")";t.push(s),s.repeat&&(p+="(?:"+c+p+")*"),i+=p=s.optional?s.partial?c+"("+p+")?":"(?:"+c+"("+p+"))?":c+"("+p+")"}}var m=u(n.delimiter||"/"),h=i.slice(-m.length)===m;return a||(i=(h?i.slice(0,-m.length):i)+"(?:"+m+"(?=$))?"),i+=o?"$":a&&h?"":"(?="+m+"|$)",d(new RegExp("^"+i,f(n)),t)}function m(e,t,n){return r(t)||(n=t||n,t=[]),n=n||{},e instanceof RegExp?function(e,t){var n=e.source.match(/\((?!\?)/g);if(n)for(var r=0;r<n.length;r++)t.push({name:r,prefix:null,delimiter:null,optional:!1,repeat:!1,partial:!1,asterisk:!1,pattern:null});return d(e,t)}(e,t):r(e)?function(e,t,n){for(var r=[],a=0;a<e.length;a++)r.push(m(e[a],t,n).source);return d(new RegExp("(?:"+r.join("|")+")",f(n)),t)}(e,t,n):function(e,t,n){return p(o(e,n),t,n)}(e,t,n)}},5338:(e,t,n)=>{"use strict";!function e(){if("undefined"!=typeof __REACT_DEVTOOLS_GLOBAL_HOOK__&&"function"==typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE)try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(t){console.error(t)}}(),e.exports=n(1247)},5500:(e,t,n)=>{"use strict";n.d(t,{Jx:()=>y,be:()=>m,e3:()=>g});var r=n(6540),a=n(4164),o=n(5260),i=n(6803),l=n(6025),s=n(4563),u=n(4848);function c({title:e}){const t=(0,s.s$)().format(e);return(0,u.jsxs)(o.A,{children:[(0,u.jsx)("title",{children:t}),(0,u.jsx)("meta",{property:"og:title",content:t})]})}function d({description:e}){return(0,u.jsxs)(o.A,{children:[(0,u.jsx)("meta",{name:"description",content:e}),(0,u.jsx)("meta",{property:"og:description",content:e})]})}function f({image:e}){const{withBaseUrl:t}=(0,l.hH)(),n=t(e,{absolute:!0});return(0,u.jsxs)(o.A,{children:[(0,u.jsx)("meta",{property:"og:image",content:n}),(0,u.jsx)("meta",{name:"twitter:image",content:n})]})}function p({keywords:e}){return(0,u.jsx)(o.A,{children:(0,u.jsx)("meta",{name:"keywords",content:Array.isArray(e)?e.join(","):e})})}function m({title:e,description:t,keywords:n,image:r,children:a}){return(0,u.jsxs)(u.Fragment,{children:[e&&(0,u.jsx)(c,{title:e}),t&&(0,u.jsx)(d,{description:t}),n&&(0,u.jsx)(p,{keywords:n}),r&&(0,u.jsx)(f,{image:r}),a&&(0,u.jsx)(o.A,{children:a})]})}const h=r.createContext(void 0);function g({className:e,children:t}){const n=r.useContext(h),i=(0,a.A)(n,e);return(0,u.jsxs)(h.Provider,{value:i,children:[(0,u.jsx)(o.A,{children:(0,u.jsx)("html",{className:i})}),t]})}function y({children:e}){const t=(0,i.A)(),n=`plugin-${t.plugin.name.replace(/docusaurus-(?:plugin|theme)-(?:content-)?/gi,"")}`;const r=`plugin-id-${t.plugin.id}`;return(0,u.jsx)(g,{className:(0,a.A)(n,r),children:e})}},5556:(e,t,n)=>{e.exports=n(2694)()},5600:(e,t,n)=>{"use strict";n.d(t,{GX:()=>u,YL:()=>s,y_:()=>l});var r=n(6540),a=n(9532),o=n(4848);const i=r.createContext(null);function l({children:e}){const t=(0,r.useState)({component:null,props:null});return(0,o.jsx)(i.Provider,{value:t,children:e})}function s(){const e=(0,r.useContext)(i);if(!e)throw new a.dV("NavbarSecondaryMenuContentProvider");return e[0]}function u({component:e,props:t}){const n=(0,r.useContext)(i);if(!n)throw new a.dV("NavbarSecondaryMenuContentProvider");const[,o]=n,l=(0,a.Be)(t);return(0,r.useEffect)((()=>{o({component:e,props:l})}),[o,e,l]),(0,r.useEffect)((()=>()=>o({component:null,props:null})),[o]),null}},5947:function(e,t,n){var r,a;r=function(){var e,t,n={version:"0.2.0"},r=n.settings={minimum:.08,easing:"ease",positionUsing:"",speed:200,trickle:!0,trickleRate:.02,trickleSpeed:800,showSpinner:!0,barSelector:'[role="bar"]',spinnerSelector:'[role="spinner"]',parent:"body",template:'<div class="bar" role="bar"><div class="peg"></div></div><div class="spinner" role="spinner"><div class="spinner-icon"></div></div>'};function a(e,t,n){return e<t?t:e>n?n:e}function o(e){return 100*(-1+e)}function i(e,t,n){var a;return(a="translate3d"===r.positionUsing?{transform:"translate3d("+o(e)+"%,0,0)"}:"translate"===r.positionUsing?{transform:"translate("+o(e)+"%,0)"}:{"margin-left":o(e)+"%"}).transition="all "+t+"ms "+n,a}n.configure=function(e){var t,n;for(t in e)void 0!==(n=e[t])&&e.hasOwnProperty(t)&&(r[t]=n);return this},n.status=null,n.set=function(e){var t=n.isStarted();e=a(e,r.minimum,1),n.status=1===e?null:e;var o=n.render(!t),u=o.querySelector(r.barSelector),c=r.speed,d=r.easing;return o.offsetWidth,l((function(t){""===r.positionUsing&&(r.positionUsing=n.getPositioningCSS()),s(u,i(e,c,d)),1===e?(s(o,{transition:"none",opacity:1}),o.offsetWidth,setTimeout((function(){s(o,{transition:"all "+c+"ms linear",opacity:0}),setTimeout((function(){n.remove(),t()}),c)}),c)):setTimeout(t,c)})),this},n.isStarted=function(){return"number"==typeof n.status},n.start=function(){n.status||n.set(0);var e=function(){setTimeout((function(){n.status&&(n.trickle(),e())}),r.trickleSpeed)};return r.trickle&&e(),this},n.done=function(e){return e||n.status?n.inc(.3+.5*Math.random()).set(1):this},n.inc=function(e){var t=n.status;return t?("number"!=typeof e&&(e=(1-t)*a(Math.random()*t,.1,.95)),t=a(t+e,0,.994),n.set(t)):n.start()},n.trickle=function(){return n.inc(Math.random()*r.trickleRate)},e=0,t=0,n.promise=function(r){return r&&"resolved"!==r.state()?(0===t&&n.start(),e++,t++,r.always((function(){0===--t?(e=0,n.done()):n.set((e-t)/e)})),this):this},n.render=function(e){if(n.isRendered())return document.getElementById("nprogress");c(document.documentElement,"nprogress-busy");var t=document.createElement("div");t.id="nprogress",t.innerHTML=r.template;var a,i=t.querySelector(r.barSelector),l=e?"-100":o(n.status||0),u=document.querySelector(r.parent);return s(i,{transition:"all 0 linear",transform:"translate3d("+l+"%,0,0)"}),r.showSpinner||(a=t.querySelector(r.spinnerSelector))&&p(a),u!=document.body&&c(u,"nprogress-custom-parent"),u.appendChild(t),t},n.remove=function(){d(document.documentElement,"nprogress-busy"),d(document.querySelector(r.parent),"nprogress-custom-parent");var e=document.getElementById("nprogress");e&&p(e)},n.isRendered=function(){return!!document.getElementById("nprogress")},n.getPositioningCSS=function(){var e=document.body.style,t="WebkitTransform"in e?"Webkit":"MozTransform"in e?"Moz":"msTransform"in e?"ms":"OTransform"in e?"O":"";return t+"Perspective"in e?"translate3d":t+"Transform"in e?"translate":"margin"};var l=function(){var e=[];function t(){var n=e.shift();n&&n(t)}return function(n){e.push(n),1==e.length&&t()}}(),s=function(){var e=["Webkit","O","Moz","ms"],t={};function n(e){return e.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,(function(e,t){return t.toUpperCase()}))}function r(t){var n=document.body.style;if(t in n)return t;for(var r,a=e.length,o=t.charAt(0).toUpperCase()+t.slice(1);a--;)if((r=e[a]+o)in n)return r;return t}function a(e){return e=n(e),t[e]||(t[e]=r(e))}function o(e,t,n){t=a(t),e.style[t]=n}return function(e,t){var n,r,a=arguments;if(2==a.length)for(n in t)void 0!==(r=t[n])&&t.hasOwnProperty(n)&&o(e,n,r);else o(e,a[1],a[2])}}();function u(e,t){return("string"==typeof e?e:f(e)).indexOf(" "+t+" ")>=0}function c(e,t){var n=f(e),r=n+t;u(n,t)||(e.className=r.substring(1))}function d(e,t){var n,r=f(e);u(e,t)&&(n=r.replace(" "+t+" "," "),e.className=n.substring(1,n.length-1))}function f(e){return(" "+(e.className||"")+" ").replace(/\s+/gi," ")}function p(e){e&&e.parentNode&&e.parentNode.removeChild(e)}return n},void 0===(a="function"==typeof r?r.call(t,n,t,e):r)||(e.exports=a)},6025:(e,t,n)=>{"use strict";n.d(t,{Ay:()=>l,hH:()=>i});var r=n(6540),a=n(4586),o=n(6654);function i(){const{siteConfig:e}=(0,a.A)(),{baseUrl:t,url:n}=e,i=e.future.experimental_router,l=(0,r.useCallback)(((e,r)=>function({siteUrl:e,baseUrl:t,url:n,options:{forcePrependBaseUrl:r=!1,absolute:a=!1}={},router:i}){if(!n||n.startsWith("#")||(0,o.z)(n))return n;if("hash"===i)return n.startsWith("/")?`.${n}`:`./${n}`;if(r)return t+n.replace(/^\//,"");if(n===t.replace(/\/$/,""))return t;const l=n.startsWith(t)?n:t+n.replace(/^\//,"");return a?e+l:l}({siteUrl:n,baseUrl:t,url:e,options:r,router:i})),[n,t,i]);return{withBaseUrl:l}}function l(e,t={}){const{withBaseUrl:n}=i();return n(e,t)}},6125:(e,t,n)=>{"use strict";n.d(t,{o:()=>o,x:()=>i});var r=n(6540),a=n(4848);const o=r.createContext(!1);function i({children:e}){const[t,n]=(0,r.useState)(!1);return(0,r.useEffect)((()=>{n(!0)}),[]),(0,a.jsx)(o.Provider,{value:t,children:e})}},6134:(e,t,n)=>{"use strict";var r=n(1765),a=n(4784);!function(e){const{themeConfig:{prism:t}}=a.default,{additionalLanguages:r}=t,o=globalThis.Prism;globalThis.Prism=e,r.forEach((e=>{"php"===e&&n(9700),n(8692)(`./prism-${e}`)})),delete globalThis.Prism,void 0!==o&&(globalThis.Prism=e)}(r.My)},6221:(e,t,n)=>{"use strict";var r=n(6540);function a(e){var t="https://react.dev/errors/"+e;if(1<arguments.length){t+="?args[]="+encodeURIComponent(arguments[1]);for(var n=2;n<arguments.length;n++)t+="&args[]="+encodeURIComponent(arguments[n])}return"Minified React error #"+e+"; visit "+t+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}function o(){}var i={d:{f:o,r:function(){throw Error(a(522))},D:o,C:o,L:o,m:o,X:o,S:o,M:o},p:0,findDOMNode:null},l=Symbol.for("react.portal");var s=r.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;function u(e,t){return"font"===e?"":"string"==typeof t?"use-credentials"===t?t:"":void 0}t.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=i,t.createPortal=function(e,t){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:null;if(!t||1!==t.nodeType&&9!==t.nodeType&&11!==t.nodeType)throw Error(a(299));return function(e,t,n){var r=3<arguments.length&&void 0!==arguments[3]?arguments[3]:null;return{$$typeof:l,key:null==r?null:""+r,children:e,containerInfo:t,implementation:n}}(e,t,null,n)},t.flushSync=function(e){var t=s.T,n=i.p;try{if(s.T=null,i.p=2,e)return e()}finally{s.T=t,i.p=n,i.d.f()}},t.preconnect=function(e,t){"string"==typeof e&&(t?t="string"==typeof(t=t.crossOrigin)?"use-credentials"===t?t:"":void 0:t=null,i.d.C(e,t))},t.prefetchDNS=function(e){"string"==typeof e&&i.d.D(e)},t.preinit=function(e,t){if("string"==typeof e&&t&&"string"==typeof t.as){var n=t.as,r=u(n,t.crossOrigin),a="string"==typeof t.integrity?t.integrity:void 0,o="string"==typeof t.fetchPriority?t.fetchPriority:void 0;"style"===n?i.d.S(e,"string"==typeof t.precedence?t.precedence:void 0,{crossOrigin:r,integrity:a,fetchPriority:o}):"script"===n&&i.d.X(e,{crossOrigin:r,integrity:a,fetchPriority:o,nonce:"string"==typeof t.nonce?t.nonce:void 0})}},t.preinitModule=function(e,t){if("string"==typeof e)if("object"==typeof t&&null!==t){if(null==t.as||"script"===t.as){var n=u(t.as,t.crossOrigin);i.d.M(e,{crossOrigin:n,integrity:"string"==typeof t.integrity?t.integrity:void 0,nonce:"string"==typeof t.nonce?t.nonce:void 0})}}else null==t&&i.d.M(e)},t.preload=function(e,t){if("string"==typeof e&&"object"==typeof t&&null!==t&&"string"==typeof t.as){var n=t.as,r=u(n,t.crossOrigin);i.d.L(e,n,{crossOrigin:r,integrity:"string"==typeof t.integrity?t.integrity:void 0,nonce:"string"==typeof t.nonce?t.nonce:void 0,type:"string"==typeof t.type?t.type:void 0,fetchPriority:"string"==typeof t.fetchPriority?t.fetchPriority:void 0,referrerPolicy:"string"==typeof t.referrerPolicy?t.referrerPolicy:void 0,imageSrcSet:"string"==typeof t.imageSrcSet?t.imageSrcSet:void 0,imageSizes:"string"==typeof t.imageSizes?t.imageSizes:void 0,media:"string"==typeof t.media?t.media:void 0})}},t.preloadModule=function(e,t){if("string"==typeof e)if(t){var n=u(t.as,t.crossOrigin);i.d.m(e,{as:"string"==typeof t.as&&"script"!==t.as?t.as:void 0,crossOrigin:n,integrity:"string"==typeof t.integrity?t.integrity:void 0})}else i.d.m(e)},t.requestFormReset=function(e){i.d.r(e)},t.unstable_batchedUpdates=function(e,t){return e(t)},t.useFormState=function(e,t,n){return s.H.useFormState(e,t,n)},t.useFormStatus=function(){return s.H.useHostTransitionStatus()},t.version="19.1.0"},6294:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(5947),a=n.n(r);a().configure({showSpinner:!1});const o={onRouteUpdate({location:e,previousLocation:t}){if(t&&e.pathname!==t.pathname){const e=window.setTimeout((()=>{a().start()}),200);return()=>window.clearTimeout(e)}},onRouteDidUpdate(){a().done()}}},6342:(e,t,n)=>{"use strict";n.d(t,{p:()=>a});var r=n(4586);function a(){return(0,r.A)().siteConfig.themeConfig}},6347:(e,t,n)=>{"use strict";n.d(t,{B6:()=>x,Ix:()=>v,W6:()=>N,XZ:()=>b,dO:()=>j,qh:()=>_,zy:()=>O});var r=n(2892),a=n(6540),o=n(5556),i=n.n(o),l=n(1513),s=n(1561),u=n(8168),c=n(5302),d=n.n(c),f=(n(4363),n(8587)),p=(n(4146),1073741823),m="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:void 0!==n.g?n.g:{};var h=a.createContext||function(e,t){var n,o,l="__create-react-context-"+function(){var e="__global_unique_id__";return m[e]=(m[e]||0)+1}()+"__",s=function(e){function n(){for(var t,n,r,a=arguments.length,o=new Array(a),i=0;i<a;i++)o[i]=arguments[i];return(t=e.call.apply(e,[this].concat(o))||this).emitter=(n=t.props.value,r=[],{on:function(e){r.push(e)},off:function(e){r=r.filter((function(t){return t!==e}))},get:function(){return n},set:function(e,t){n=e,r.forEach((function(e){return e(n,t)}))}}),t}(0,r.A)(n,e);var a=n.prototype;return a.getChildContext=function(){var e;return(e={})[l]=this.emitter,e},a.componentWillReceiveProps=function(e){if(this.props.value!==e.value){var n,r=this.props.value,a=e.value;((o=r)===(i=a)?0!==o||1/o==1/i:o!=o&&i!=i)?n=0:(n="function"==typeof t?t(r,a):p,0!==(n|=0)&&this.emitter.set(e.value,n))}var o,i},a.render=function(){return this.props.children},n}(a.Component);s.childContextTypes=((n={})[l]=i().object.isRequired,n);var u=function(t){function n(){for(var e,n=arguments.length,r=new Array(n),a=0;a<n;a++)r[a]=arguments[a];return(e=t.call.apply(t,[this].concat(r))||this).observedBits=void 0,e.state={value:e.getValue()},e.onUpdate=function(t,n){0!==((0|e.observedBits)&n)&&e.setState({value:e.getValue()})},e}(0,r.A)(n,t);var a=n.prototype;return a.componentWillReceiveProps=function(e){var t=e.observedBits;this.observedBits=null==t?p:t},a.componentDidMount=function(){this.context[l]&&this.context[l].on(this.onUpdate);var e=this.props.observedBits;this.observedBits=null==e?p:e},a.componentWillUnmount=function(){this.context[l]&&this.context[l].off(this.onUpdate)},a.getValue=function(){return this.context[l]?this.context[l].get():e},a.render=function(){return(e=this.props.children,Array.isArray(e)?e[0]:e)(this.state.value);var e},n}(a.Component);return u.contextTypes=((o={})[l]=i().object,o),{Provider:s,Consumer:u}},g=function(e){var t=h();return t.displayName=e,t},y=g("Router-History"),b=g("Router"),v=function(e){function t(t){var n;return(n=e.call(this,t)||this).state={location:t.history.location},n._isMounted=!1,n._pendingLocation=null,t.staticContext||(n.unlisten=t.history.listen((function(e){n._pendingLocation=e}))),n}(0,r.A)(t,e),t.computeRootMatch=function(e){return{path:"/",url:"/",params:{},isExact:"/"===e}};var n=t.prototype;return n.componentDidMount=function(){var e=this;this._isMounted=!0,this.unlisten&&this.unlisten(),this.props.staticContext||(this.unlisten=this.props.history.listen((function(t){e._isMounted&&e.setState({location:t})}))),this._pendingLocation&&this.setState({location:this._pendingLocation})},n.componentWillUnmount=function(){this.unlisten&&(this.unlisten(),this._isMounted=!1,this._pendingLocation=null)},n.render=function(){return a.createElement(b.Provider,{value:{history:this.props.history,location:this.state.location,match:t.computeRootMatch(this.state.location.pathname),staticContext:this.props.staticContext}},a.createElement(y.Provider,{children:this.props.children||null,value:this.props.history}))},t}(a.Component);a.Component;a.Component;var w={},k=1e4,S=0;function x(e,t){void 0===t&&(t={}),("string"==typeof t||Array.isArray(t))&&(t={path:t});var n=t,r=n.path,a=n.exact,o=void 0!==a&&a,i=n.strict,l=void 0!==i&&i,s=n.sensitive,u=void 0!==s&&s;return[].concat(r).reduce((function(t,n){if(!n&&""!==n)return null;if(t)return t;var r=function(e,t){var n=""+t.end+t.strict+t.sensitive,r=w[n]||(w[n]={});if(r[e])return r[e];var a=[],o={regexp:d()(e,a,t),keys:a};return S<k&&(r[e]=o,S++),o}(n,{end:o,strict:l,sensitive:u}),a=r.regexp,i=r.keys,s=a.exec(e);if(!s)return null;var c=s[0],f=s.slice(1),p=e===c;return o&&!p?null:{path:n,url:"/"===n&&""===c?"/":c,isExact:p,params:i.reduce((function(e,t,n){return e[t.name]=f[n],e}),{})}}),null)}var _=function(e){function t(){return e.apply(this,arguments)||this}return(0,r.A)(t,e),t.prototype.render=function(){var e=this;return a.createElement(b.Consumer,null,(function(t){t||(0,s.A)(!1);var n=e.props.location||t.location,r=e.props.computedMatch?e.props.computedMatch:e.props.path?x(n.pathname,e.props):t.match,o=(0,u.A)({},t,{location:n,match:r}),i=e.props,l=i.children,c=i.component,d=i.render;return Array.isArray(l)&&function(e){return 0===a.Children.count(e)}(l)&&(l=null),a.createElement(b.Provider,{value:o},o.match?l?"function"==typeof l?l(o):l:c?a.createElement(c,o):d?d(o):null:"function"==typeof l?l(o):null)}))},t}(a.Component);function E(e){return"/"===e.charAt(0)?e:"/"+e}function C(e,t){if(!e)return t;var n=E(e);return 0!==t.pathname.indexOf(n)?t:(0,u.A)({},t,{pathname:t.pathname.substr(n.length)})}function A(e){return"string"==typeof e?e:(0,l.AO)(e)}function T(e){return function(){(0,s.A)(!1)}}function L(){}a.Component;var j=function(e){function t(){return e.apply(this,arguments)||this}return(0,r.A)(t,e),t.prototype.render=function(){var e=this;return a.createElement(b.Consumer,null,(function(t){t||(0,s.A)(!1);var n,r,o=e.props.location||t.location;return a.Children.forEach(e.props.children,(function(e){if(null==r&&a.isValidElement(e)){n=e;var i=e.props.path||e.props.from;r=i?x(o.pathname,(0,u.A)({},e.props,{path:i})):t.match}})),r?a.cloneElement(n,{location:o,computedMatch:r}):null}))},t}(a.Component);var P=a.useContext;function N(){return P(y)}function O(){return P(b).location}},6540:(e,t,n)=>{"use strict";e.exports=n(9869)},6654:(e,t,n)=>{"use strict";function r(e){return/^(?:\w*:|\/\/)/.test(e)}function a(e){return void 0!==e&&!r(e)}n.d(t,{A:()=>a,z:()=>r})},6803:(e,t,n)=>{"use strict";n.d(t,{A:()=>o});var r=n(6540),a=n(3102);function o(){const e=r.useContext(a.o);if(!e)throw new Error("Unexpected: no Docusaurus route context found");return e}},6921:(e,t,n)=>{"use strict";n.d(t,{A:()=>a});const r=e=>"object"==typeof e&&!!e&&Object.keys(e).length>0;function a(e){const t={};return function e(n,a){Object.entries(n).forEach((([n,o])=>{const i=a?`${a}.${n}`:n;r(o)?e(o,i):t[i]=o}))}(e),t}},6925:e=>{"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},6969:e=>{e.exports&&(e.exports={core:{meta:{path:"components/prism-core.js",option:"mandatory"},core:"Core"},themes:{meta:{path:"themes/{id}.css",link:"index.html?theme={id}",exclusive:!0},prism:{title:"Default",option:"default"},"prism-dark":"Dark","prism-funky":"Funky","prism-okaidia":{title:"Okaidia",owner:"ocodia"},"prism-twilight":{title:"Twilight",owner:"remybach"},"prism-coy":{title:"Coy",owner:"tshedor"},"prism-solarizedlight":{title:"Solarized Light",owner:"hectormatos2011 "},"prism-tomorrow":{title:"Tomorrow Night",owner:"Rosey"}},languages:{meta:{path:"components/prism-{id}",noCSS:!0,examplesPath:"examples/prism-{id}",addCheckAll:!0},markup:{title:"Markup",alias:["html","xml","svg","mathml","ssml","atom","rss"],aliasTitles:{html:"HTML",xml:"XML",svg:"SVG",mathml:"MathML",ssml:"SSML",atom:"Atom",rss:"RSS"},option:"default"},css:{title:"CSS",option:"default",modify:"markup"},clike:{title:"C-like",option:"default"},javascript:{title:"JavaScript",require:"clike",modify:"markup",optional:"regex",alias:"js",option:"default"},abap:{title:"ABAP",owner:"dellagustin"},abnf:{title:"ABNF",owner:"RunDevelopment"},actionscript:{title:"ActionScript",require:"javascript",modify:"markup",owner:"Golmote"},ada:{title:"Ada",owner:"Lucretia"},agda:{title:"Agda",owner:"xy-ren"},al:{title:"AL",owner:"RunDevelopment"},antlr4:{title:"ANTLR4",alias:"g4",owner:"RunDevelopment"},apacheconf:{title:"Apache Configuration",owner:"GuiTeK"},apex:{title:"Apex",require:["clike","sql"],owner:"RunDevelopment"},apl:{title:"APL",owner:"ngn"},applescript:{title:"AppleScript",owner:"Golmote"},aql:{title:"AQL",owner:"RunDevelopment"},arduino:{title:"Arduino",require:"cpp",alias:"ino",owner:"dkern"},arff:{title:"ARFF",owner:"Golmote"},armasm:{title:"ARM Assembly",alias:"arm-asm",owner:"RunDevelopment"},arturo:{title:"Arturo",alias:"art",optional:["bash","css","javascript","markup","markdown","sql"],owner:"drkameleon"},asciidoc:{alias:"adoc",title:"AsciiDoc",owner:"Golmote"},aspnet:{title:"ASP.NET (C#)",require:["markup","csharp"],owner:"nauzilus"},asm6502:{title:"6502 Assembly",owner:"kzurawel"},asmatmel:{title:"Atmel AVR Assembly",owner:"cerkit"},autohotkey:{title:"AutoHotkey",owner:"aviaryan"},autoit:{title:"AutoIt",owner:"Golmote"},avisynth:{title:"AviSynth",alias:"avs",owner:"Zinfidel"},"avro-idl":{title:"Avro IDL",alias:"avdl",owner:"RunDevelopment"},awk:{title:"AWK",alias:"gawk",aliasTitles:{gawk:"GAWK"},owner:"RunDevelopment"},bash:{title:"Bash",alias:["sh","shell"],aliasTitles:{sh:"Shell",shell:"Shell"},owner:"zeitgeist87"},basic:{title:"BASIC",owner:"Golmote"},batch:{title:"Batch",owner:"Golmote"},bbcode:{title:"BBcode",alias:"shortcode",aliasTitles:{shortcode:"Shortcode"},owner:"RunDevelopment"},bbj:{title:"BBj",owner:"hyyan"},bicep:{title:"Bicep",owner:"johnnyreilly"},birb:{title:"Birb",require:"clike",owner:"Calamity210"},bison:{title:"Bison",require:"c",owner:"Golmote"},bnf:{title:"BNF",alias:"rbnf",aliasTitles:{rbnf:"RBNF"},owner:"RunDevelopment"},bqn:{title:"BQN",owner:"yewscion"},brainfuck:{title:"Brainfuck",owner:"Golmote"},brightscript:{title:"BrightScript",owner:"RunDevelopment"},bro:{title:"Bro",owner:"wayward710"},bsl:{title:"BSL (1C:Enterprise)",alias:"oscript",aliasTitles:{oscript:"OneScript"},owner:"Diversus23"},c:{title:"C",require:"clike",owner:"zeitgeist87"},csharp:{title:"C#",require:"clike",alias:["cs","dotnet"],owner:"mvalipour"},cpp:{title:"C++",require:"c",owner:"zeitgeist87"},cfscript:{title:"CFScript",require:"clike",alias:"cfc",owner:"mjclemente"},chaiscript:{title:"ChaiScript",require:["clike","cpp"],owner:"RunDevelopment"},cil:{title:"CIL",owner:"sbrl"},cilkc:{title:"Cilk/C",require:"c",alias:"cilk-c",owner:"OpenCilk"},cilkcpp:{title:"Cilk/C++",require:"cpp",alias:["cilk-cpp","cilk"],owner:"OpenCilk"},clojure:{title:"Clojure",owner:"troglotit"},cmake:{title:"CMake",owner:"mjrogozinski"},cobol:{title:"COBOL",owner:"RunDevelopment"},coffeescript:{title:"CoffeeScript",require:"javascript",alias:"coffee",owner:"R-osey"},concurnas:{title:"Concurnas",alias:"conc",owner:"jasontatton"},csp:{title:"Content-Security-Policy",owner:"ScottHelme"},cooklang:{title:"Cooklang",owner:"ahue"},coq:{title:"Coq",owner:"RunDevelopment"},crystal:{title:"Crystal",require:"ruby",owner:"MakeNowJust"},"css-extras":{title:"CSS Extras",require:"css",modify:"css",owner:"milesj"},csv:{title:"CSV",owner:"RunDevelopment"},cue:{title:"CUE",owner:"RunDevelopment"},cypher:{title:"Cypher",owner:"RunDevelopment"},d:{title:"D",require:"clike",owner:"Golmote"},dart:{title:"Dart",require:"clike",owner:"Golmote"},dataweave:{title:"DataWeave",owner:"machaval"},dax:{title:"DAX",owner:"peterbud"},dhall:{title:"Dhall",owner:"RunDevelopment"},diff:{title:"Diff",owner:"uranusjr"},django:{title:"Django/Jinja2",require:"markup-templating",alias:"jinja2",owner:"romanvm"},"dns-zone-file":{title:"DNS zone file",owner:"RunDevelopment",alias:"dns-zone"},docker:{title:"Docker",alias:"dockerfile",owner:"JustinBeckwith"},dot:{title:"DOT (Graphviz)",alias:"gv",optional:"markup",owner:"RunDevelopment"},ebnf:{title:"EBNF",owner:"RunDevelopment"},editorconfig:{title:"EditorConfig",owner:"osipxd"},eiffel:{title:"Eiffel",owner:"Conaclos"},ejs:{title:"EJS",require:["javascript","markup-templating"],owner:"RunDevelopment",alias:"eta",aliasTitles:{eta:"Eta"}},elixir:{title:"Elixir",owner:"Golmote"},elm:{title:"Elm",owner:"zwilias"},etlua:{title:"Embedded Lua templating",require:["lua","markup-templating"],owner:"RunDevelopment"},erb:{title:"ERB",require:["ruby","markup-templating"],owner:"Golmote"},erlang:{title:"Erlang",owner:"Golmote"},"excel-formula":{title:"Excel Formula",alias:["xlsx","xls"],owner:"RunDevelopment"},fsharp:{title:"F#",require:"clike",owner:"simonreynolds7"},factor:{title:"Factor",owner:"catb0t"},false:{title:"False",owner:"edukisto"},"firestore-security-rules":{title:"Firestore security rules",require:"clike",owner:"RunDevelopment"},flow:{title:"Flow",require:"javascript",owner:"Golmote"},fortran:{title:"Fortran",owner:"Golmote"},ftl:{title:"FreeMarker Template Language",require:"markup-templating",owner:"RunDevelopment"},gml:{title:"GameMaker Language",alias:"gamemakerlanguage",require:"clike",owner:"LiarOnce"},gap:{title:"GAP (CAS)",owner:"RunDevelopment"},gcode:{title:"G-code",owner:"RunDevelopment"},gdscript:{title:"GDScript",owner:"RunDevelopment"},gedcom:{title:"GEDCOM",owner:"Golmote"},gettext:{title:"gettext",alias:"po",owner:"RunDevelopment"},gherkin:{title:"Gherkin",owner:"hason"},git:{title:"Git",owner:"lgiraudel"},glsl:{title:"GLSL",require:"c",owner:"Golmote"},gn:{title:"GN",alias:"gni",owner:"RunDevelopment"},"linker-script":{title:"GNU Linker Script",alias:"ld",owner:"RunDevelopment"},go:{title:"Go",require:"clike",owner:"arnehormann"},"go-module":{title:"Go module",alias:"go-mod",owner:"RunDevelopment"},gradle:{title:"Gradle",require:"clike",owner:"zeabdelkhalek-badido18"},graphql:{title:"GraphQL",optional:"markdown",owner:"Golmote"},groovy:{title:"Groovy",require:"clike",owner:"robfletcher"},haml:{title:"Haml",require:"ruby",optional:["css","css-extras","coffeescript","erb","javascript","less","markdown","scss","textile"],owner:"Golmote"},handlebars:{title:"Handlebars",require:"markup-templating",alias:["hbs","mustache"],aliasTitles:{mustache:"Mustache"},owner:"Golmote"},haskell:{title:"Haskell",alias:"hs",owner:"bholst"},haxe:{title:"Haxe",require:"clike",optional:"regex",owner:"Golmote"},hcl:{title:"HCL",owner:"outsideris"},hlsl:{title:"HLSL",require:"c",owner:"RunDevelopment"},hoon:{title:"Hoon",owner:"matildepark"},http:{title:"HTTP",optional:["csp","css","hpkp","hsts","javascript","json","markup","uri"],owner:"danielgtaylor"},hpkp:{title:"HTTP Public-Key-Pins",owner:"ScottHelme"},hsts:{title:"HTTP Strict-Transport-Security",owner:"ScottHelme"},ichigojam:{title:"IchigoJam",owner:"BlueCocoa"},icon:{title:"Icon",owner:"Golmote"},"icu-message-format":{title:"ICU Message Format",owner:"RunDevelopment"},idris:{title:"Idris",alias:"idr",owner:"KeenS",require:"haskell"},ignore:{title:".ignore",owner:"osipxd",alias:["gitignore","hgignore","npmignore"],aliasTitles:{gitignore:".gitignore",hgignore:".hgignore",npmignore:".npmignore"}},inform7:{title:"Inform 7",owner:"Golmote"},ini:{title:"Ini",owner:"aviaryan"},io:{title:"Io",owner:"AlesTsurko"},j:{title:"J",owner:"Golmote"},java:{title:"Java",require:"clike",owner:"sherblot"},javadoc:{title:"JavaDoc",require:["markup","java","javadoclike"],modify:"java",optional:"scala",owner:"RunDevelopment"},javadoclike:{title:"JavaDoc-like",modify:["java","javascript","php"],owner:"RunDevelopment"},javastacktrace:{title:"Java stack trace",owner:"RunDevelopment"},jexl:{title:"Jexl",owner:"czosel"},jolie:{title:"Jolie",require:"clike",owner:"thesave"},jq:{title:"JQ",owner:"RunDevelopment"},jsdoc:{title:"JSDoc",require:["javascript","javadoclike","typescript"],modify:"javascript",optional:["actionscript","coffeescript"],owner:"RunDevelopment"},"js-extras":{title:"JS Extras",require:"javascript",modify:"javascript",optional:["actionscript","coffeescript","flow","n4js","typescript"],owner:"RunDevelopment"},json:{title:"JSON",alias:"webmanifest",aliasTitles:{webmanifest:"Web App Manifest"},owner:"CupOfTea696"},json5:{title:"JSON5",require:"json",owner:"RunDevelopment"},jsonp:{title:"JSONP",require:"json",owner:"RunDevelopment"},jsstacktrace:{title:"JS stack trace",owner:"sbrl"},"js-templates":{title:"JS Templates",require:"javascript",modify:"javascript",optional:["css","css-extras","graphql","markdown","markup","sql"],owner:"RunDevelopment"},julia:{title:"Julia",owner:"cdagnino"},keepalived:{title:"Keepalived Configure",owner:"dev-itsheng"},keyman:{title:"Keyman",owner:"mcdurdin"},kotlin:{title:"Kotlin",alias:["kt","kts"],aliasTitles:{kts:"Kotlin Script"},require:"clike",owner:"Golmote"},kumir:{title:"KuMir (\u041a\u0443\u041c\u0438\u0440)",alias:"kum",owner:"edukisto"},kusto:{title:"Kusto",owner:"RunDevelopment"},latex:{title:"LaTeX",alias:["tex","context"],aliasTitles:{tex:"TeX",context:"ConTeXt"},owner:"japborst"},latte:{title:"Latte",require:["clike","markup-templating","php"],owner:"nette"},less:{title:"Less",require:"css",optional:"css-extras",owner:"Golmote"},lilypond:{title:"LilyPond",require:"scheme",alias:"ly",owner:"RunDevelopment"},liquid:{title:"Liquid",require:"markup-templating",owner:"cinhtau"},lisp:{title:"Lisp",alias:["emacs","elisp","emacs-lisp"],owner:"JuanCaicedo"},livescript:{title:"LiveScript",owner:"Golmote"},llvm:{title:"LLVM IR",owner:"porglezomp"},log:{title:"Log file",optional:"javastacktrace",owner:"RunDevelopment"},lolcode:{title:"LOLCODE",owner:"Golmote"},lua:{title:"Lua",owner:"Golmote"},magma:{title:"Magma (CAS)",owner:"RunDevelopment"},makefile:{title:"Makefile",owner:"Golmote"},markdown:{title:"Markdown",require:"markup",optional:"yaml",alias:"md",owner:"Golmote"},"markup-templating":{title:"Markup templating",require:"markup",owner:"Golmote"},mata:{title:"Mata",owner:"RunDevelopment"},matlab:{title:"MATLAB",owner:"Golmote"},maxscript:{title:"MAXScript",owner:"RunDevelopment"},mel:{title:"MEL",owner:"Golmote"},mermaid:{title:"Mermaid",owner:"RunDevelopment"},metafont:{title:"METAFONT",owner:"LaeriExNihilo"},mizar:{title:"Mizar",owner:"Golmote"},mongodb:{title:"MongoDB",owner:"airs0urce",require:"javascript"},monkey:{title:"Monkey",owner:"Golmote"},moonscript:{title:"MoonScript",alias:"moon",owner:"RunDevelopment"},n1ql:{title:"N1QL",owner:"TMWilds"},n4js:{title:"N4JS",require:"javascript",optional:"jsdoc",alias:"n4jsd",owner:"bsmith-n4"},"nand2tetris-hdl":{title:"Nand To Tetris HDL",owner:"stephanmax"},naniscript:{title:"Naninovel Script",owner:"Elringus",alias:"nani"},nasm:{title:"NASM",owner:"rbmj"},neon:{title:"NEON",owner:"nette"},nevod:{title:"Nevod",owner:"nezaboodka"},nginx:{title:"nginx",owner:"volado"},nim:{title:"Nim",owner:"Golmote"},nix:{title:"Nix",owner:"Golmote"},nsis:{title:"NSIS",owner:"idleberg"},objectivec:{title:"Objective-C",require:"c",alias:"objc",owner:"uranusjr"},ocaml:{title:"OCaml",owner:"Golmote"},odin:{title:"Odin",owner:"edukisto"},opencl:{title:"OpenCL",require:"c",modify:["c","cpp"],owner:"Milania1"},openqasm:{title:"OpenQasm",alias:"qasm",owner:"RunDevelopment"},oz:{title:"Oz",owner:"Golmote"},parigp:{title:"PARI/GP",owner:"Golmote"},parser:{title:"Parser",require:"markup",owner:"Golmote"},pascal:{title:"Pascal",alias:"objectpascal",aliasTitles:{objectpascal:"Object Pascal"},owner:"Golmote"},pascaligo:{title:"Pascaligo",owner:"DefinitelyNotAGoat"},psl:{title:"PATROL Scripting Language",owner:"bertysentry"},pcaxis:{title:"PC-Axis",alias:"px",owner:"RunDevelopment"},peoplecode:{title:"PeopleCode",alias:"pcode",owner:"RunDevelopment"},perl:{title:"Perl",owner:"Golmote"},php:{title:"PHP",require:"markup-templating",owner:"milesj"},phpdoc:{title:"PHPDoc",require:["php","javadoclike"],modify:"php",owner:"RunDevelopment"},"php-extras":{title:"PHP Extras",require:"php",modify:"php",owner:"milesj"},"plant-uml":{title:"PlantUML",alias:"plantuml",owner:"RunDevelopment"},plsql:{title:"PL/SQL",require:"sql",owner:"Golmote"},powerquery:{title:"PowerQuery",alias:["pq","mscript"],owner:"peterbud"},powershell:{title:"PowerShell",owner:"nauzilus"},processing:{title:"Processing",require:"clike",owner:"Golmote"},prolog:{title:"Prolog",owner:"Golmote"},promql:{title:"PromQL",owner:"arendjr"},properties:{title:".properties",owner:"Golmote"},protobuf:{title:"Protocol Buffers",require:"clike",owner:"just-boris"},pug:{title:"Pug",require:["markup","javascript"],optional:["coffeescript","ejs","handlebars","less","livescript","markdown","scss","stylus","twig"],owner:"Golmote"},puppet:{title:"Puppet",owner:"Golmote"},pure:{title:"Pure",optional:["c","cpp","fortran"],owner:"Golmote"},purebasic:{title:"PureBasic",require:"clike",alias:"pbfasm",owner:"HeX0R101"},purescript:{title:"PureScript",require:"haskell",alias:"purs",owner:"sriharshachilakapati"},python:{title:"Python",alias:"py",owner:"multipetros"},qsharp:{title:"Q#",require:"clike",alias:"qs",owner:"fedonman"},q:{title:"Q (kdb+ database)",owner:"Golmote"},qml:{title:"QML",require:"javascript",owner:"RunDevelopment"},qore:{title:"Qore",require:"clike",owner:"temnroegg"},r:{title:"R",owner:"Golmote"},racket:{title:"Racket",require:"scheme",alias:"rkt",owner:"RunDevelopment"},cshtml:{title:"Razor C#",alias:"razor",require:["markup","csharp"],optional:["css","css-extras","javascript","js-extras"],owner:"RunDevelopment"},jsx:{title:"React JSX",require:["markup","javascript"],optional:["jsdoc","js-extras","js-templates"],owner:"vkbansal"},tsx:{title:"React TSX",require:["jsx","typescript"]},reason:{title:"Reason",require:"clike",owner:"Golmote"},regex:{title:"Regex",owner:"RunDevelopment"},rego:{title:"Rego",owner:"JordanSh"},renpy:{title:"Ren'py",alias:"rpy",owner:"HyuchiaDiego"},rescript:{title:"ReScript",alias:"res",owner:"vmarcosp"},rest:{title:"reST (reStructuredText)",owner:"Golmote"},rip:{title:"Rip",owner:"ravinggenius"},roboconf:{title:"Roboconf",owner:"Golmote"},robotframework:{title:"Robot Framework",alias:"robot",owner:"RunDevelopment"},ruby:{title:"Ruby",require:"clike",alias:"rb",owner:"samflores"},rust:{title:"Rust",owner:"Golmote"},sas:{title:"SAS",optional:["groovy","lua","sql"],owner:"Golmote"},sass:{title:"Sass (Sass)",require:"css",optional:"css-extras",owner:"Golmote"},scss:{title:"Sass (SCSS)",require:"css",optional:"css-extras",owner:"MoOx"},scala:{title:"Scala",require:"java",owner:"jozic"},scheme:{title:"Scheme",owner:"bacchus123"},"shell-session":{title:"Shell session",require:"bash",alias:["sh-session","shellsession"],owner:"RunDevelopment"},smali:{title:"Smali",owner:"RunDevelopment"},smalltalk:{title:"Smalltalk",owner:"Golmote"},smarty:{title:"Smarty",require:"markup-templating",optional:"php",owner:"Golmote"},sml:{title:"SML",alias:"smlnj",aliasTitles:{smlnj:"SML/NJ"},owner:"RunDevelopment"},solidity:{title:"Solidity (Ethereum)",alias:"sol",require:"clike",owner:"glachaud"},"solution-file":{title:"Solution file",alias:"sln",owner:"RunDevelopment"},soy:{title:"Soy (Closure Template)",require:"markup-templating",owner:"Golmote"},sparql:{title:"SPARQL",require:"turtle",owner:"Triply-Dev",alias:"rq"},"splunk-spl":{title:"Splunk SPL",owner:"RunDevelopment"},sqf:{title:"SQF: Status Quo Function (Arma 3)",require:"clike",owner:"RunDevelopment"},sql:{title:"SQL",owner:"multipetros"},squirrel:{title:"Squirrel",require:"clike",owner:"RunDevelopment"},stan:{title:"Stan",owner:"RunDevelopment"},stata:{title:"Stata Ado",require:["mata","java","python"],owner:"RunDevelopment"},iecst:{title:"Structured Text (IEC 61131-3)",owner:"serhioromano"},stylus:{title:"Stylus",owner:"vkbansal"},supercollider:{title:"SuperCollider",alias:"sclang",owner:"RunDevelopment"},swift:{title:"Swift",owner:"chrischares"},systemd:{title:"Systemd configuration file",owner:"RunDevelopment"},"t4-templating":{title:"T4 templating",owner:"RunDevelopment"},"t4-cs":{title:"T4 Text Templates (C#)",require:["t4-templating","csharp"],alias:"t4",owner:"RunDevelopment"},"t4-vb":{title:"T4 Text Templates (VB)",require:["t4-templating","vbnet"],owner:"RunDevelopment"},tap:{title:"TAP",owner:"isaacs",require:"yaml"},tcl:{title:"Tcl",owner:"PeterChaplin"},tt2:{title:"Template Toolkit 2",require:["clike","markup-templating"],owner:"gflohr"},textile:{title:"Textile",require:"markup",optional:"css",owner:"Golmote"},toml:{title:"TOML",owner:"RunDevelopment"},tremor:{title:"Tremor",alias:["trickle","troy"],owner:"darach",aliasTitles:{trickle:"trickle",troy:"troy"}},turtle:{title:"Turtle",alias:"trig",aliasTitles:{trig:"TriG"},owner:"jakubklimek"},twig:{title:"Twig",require:"markup-templating",owner:"brandonkelly"},typescript:{title:"TypeScript",require:"javascript",optional:"js-templates",alias:"ts",owner:"vkbansal"},typoscript:{title:"TypoScript",alias:"tsconfig",aliasTitles:{tsconfig:"TSConfig"},owner:"dkern"},unrealscript:{title:"UnrealScript",alias:["uscript","uc"],owner:"RunDevelopment"},uorazor:{title:"UO Razor Script",owner:"jaseowns"},uri:{title:"URI",alias:"url",aliasTitles:{url:"URL"},owner:"RunDevelopment"},v:{title:"V",require:"clike",owner:"taggon"},vala:{title:"Vala",require:"clike",optional:"regex",owner:"TemplarVolk"},vbnet:{title:"VB.Net",require:"basic",owner:"Bigsby"},velocity:{title:"Velocity",require:"markup",owner:"Golmote"},verilog:{title:"Verilog",owner:"a-rey"},vhdl:{title:"VHDL",owner:"a-rey"},vim:{title:"vim",owner:"westonganger"},"visual-basic":{title:"Visual Basic",alias:["vb","vba"],aliasTitles:{vba:"VBA"},owner:"Golmote"},warpscript:{title:"WarpScript",owner:"RunDevelopment"},wasm:{title:"WebAssembly",owner:"Golmote"},"web-idl":{title:"Web IDL",alias:"webidl",owner:"RunDevelopment"},wgsl:{title:"WGSL",owner:"Dr4gonthree"},wiki:{title:"Wiki markup",require:"markup",owner:"Golmote"},wolfram:{title:"Wolfram language",alias:["mathematica","nb","wl"],aliasTitles:{mathematica:"Mathematica",nb:"Mathematica Notebook"},owner:"msollami"},wren:{title:"Wren",owner:"clsource"},xeora:{title:"Xeora",require:"markup",alias:"xeoracube",aliasTitles:{xeoracube:"XeoraCube"},owner:"freakmaxi"},"xml-doc":{title:"XML doc (.net)",require:"markup",modify:["csharp","fsharp","vbnet"],owner:"RunDevelopment"},xojo:{title:"Xojo (REALbasic)",owner:"Golmote"},xquery:{title:"XQuery",require:"markup",owner:"Golmote"},yaml:{title:"YAML",alias:"yml",owner:"hason"},yang:{title:"YANG",owner:"RunDevelopment"},zig:{title:"Zig",owner:"RunDevelopment"}},plugins:{meta:{path:"plugins/{id}/prism-{id}",link:"plugins/{id}/"},"line-highlight":{title:"Line Highlight",description:"Highlights specific lines and/or line ranges."},"line-numbers":{title:"Line Numbers",description:"Line number at the beginning of code lines.",owner:"kuba-kubula"},"show-invisibles":{title:"Show Invisibles",description:"Show hidden characters such as tabs and line breaks.",optional:["autolinker","data-uri-highlight"]},autolinker:{title:"Autolinker",description:"Converts URLs and emails in code to clickable links. Parses Markdown links in comments."},wpd:{title:"WebPlatform Docs",description:'Makes tokens link to <a href="https://webplatform.github.io/docs/">WebPlatform.org documentation</a>. The links open in a new tab.'},"custom-class":{title:"Custom Class",description:"This plugin allows you to prefix Prism's default classes (<code>.comment</code> can become <code>.namespace--comment</code>) or replace them with your defined ones (like <code>.editor__comment</code>). You can even add new classes.",owner:"dvkndn",noCSS:!0},"file-highlight":{title:"File Highlight",description:"Fetch external files and highlight them with Prism. Used on the Prism website itself.",noCSS:!0},"show-language":{title:"Show Language",description:"Display the highlighted language in code blocks (inline code does not show the label).",owner:"nauzilus",noCSS:!0,require:"toolbar"},"jsonp-highlight":{title:"JSONP Highlight",description:"Fetch content with JSONP and highlight some interesting content (e.g. GitHub/Gists or Bitbucket API).",noCSS:!0,owner:"nauzilus"},"highlight-keywords":{title:"Highlight Keywords",description:"Adds special CSS classes for each keyword for fine-grained highlighting.",owner:"vkbansal",noCSS:!0},"remove-initial-line-feed":{title:"Remove initial line feed",description:"Removes the initial line feed in code blocks.",owner:"Golmote",noCSS:!0},"inline-color":{title:"Inline color",description:"Adds a small inline preview for colors in style sheets.",require:"css-extras",owner:"RunDevelopment"},previewers:{title:"Previewers",description:"Previewers for angles, colors, gradients, easing and time.",require:"css-extras",owner:"Golmote"},autoloader:{title:"Autoloader",description:"Automatically loads the needed languages to highlight the code blocks.",owner:"Golmote",noCSS:!0},"keep-markup":{title:"Keep Markup",description:"Prevents custom markup from being dropped out during highlighting.",owner:"Golmote",optional:"normalize-whitespace",noCSS:!0},"command-line":{title:"Command Line",description:"Display a command line with a prompt and, optionally, the output/response from the commands.",owner:"chriswells0"},"unescaped-markup":{title:"Unescaped Markup",description:"Write markup without having to escape anything."},"normalize-whitespace":{title:"Normalize Whitespace",description:"Supports multiple operations to normalize whitespace in code blocks.",owner:"zeitgeist87",optional:"unescaped-markup",noCSS:!0},"data-uri-highlight":{title:"Data-URI Highlight",description:"Highlights data-URI contents.",owner:"Golmote",noCSS:!0},toolbar:{title:"Toolbar",description:"Attach a toolbar for plugins to easily register buttons on the top of a code block.",owner:"mAAdhaTTah"},"copy-to-clipboard":{title:"Copy to Clipboard Button",description:"Add a button that copies the code block to the clipboard when clicked.",owner:"mAAdhaTTah",require:"toolbar",noCSS:!0},"download-button":{title:"Download Button",description:"A button in the toolbar of a code block adding a convenient way to download a code file.",owner:"Golmote",require:"toolbar",noCSS:!0},"match-braces":{title:"Match braces",description:"Highlights matching braces.",owner:"RunDevelopment"},"diff-highlight":{title:"Diff Highlight",description:"Highlights the code inside diff blocks.",owner:"RunDevelopment",require:"diff"},"filter-highlight-all":{title:"Filter highlightAll",description:"Filters the elements the <code>highlightAll</code> and <code>highlightAllUnder</code> methods actually highlight.",owner:"RunDevelopment",noCSS:!0},treeview:{title:"Treeview",description:"A language with special styles to highlight file system tree structures.",owner:"Golmote"}}})},6972:(e,t,n)=>{"use strict";n.d(t,{$S:()=>m,B5:()=>C,Nr:()=>p,OF:()=>S,QB:()=>E,Vd:()=>x,Y:()=>w,a4:()=>h,cC:()=>f,d1:()=>A,fW:()=>_,w8:()=>b});var r=n(6540),a=n(6347),o=n(2831),i=n(4070),l=n(9169),s=n(1682),u=n(3886),c=n(3025),d=n(609);function f(e){const t=(0,c.r)();if(!e)return;const n=t.docs[e];if(!n)throw new Error(`no version doc found by id=${e}`);return n}function p(e){return"link"!==e.type||e.unlisted?"category"===e.type?function(e){if(e.href&&!e.linkUnlisted)return e.href;for(const t of e.items){const e=p(t);if(e)return e}}(e):void 0:e.href}function m(){const{pathname:e}=(0,a.zy)(),t=(0,d.t)();if(!t)throw new Error("Unexpected: cant find current sidebar in context");const n=k({sidebarItems:t.items,pathname:e,onlyCategories:!0}).slice(-1)[0];if(!n)throw new Error(`${e} is not associated with a category. useCurrentSidebarCategory() should only be used on category index pages.`);return n}function h(){const{pathname:e}=(0,a.zy)(),t=(0,d.t)();if(!t)throw new Error("Unexpected: cant find current sidebar in context");const n=k({sidebarItems:t.items,pathname:e,onlyCategories:!0}).slice(-1)[0];return n?.items??t.items}const g=(e,t)=>void 0!==e&&(0,l.ys)(e,t),y=(e,t)=>e.some((e=>b(e,t)));function b(e,t){return"link"===e.type?g(e.href,t):"category"===e.type&&(g(e.href,t)||y(e.items,t))}function v(e,t){switch(e.type){case"category":return b(e,t)||void 0!==e.href&&!e.linkUnlisted||e.items.some((e=>v(e,t)));case"link":return!e.unlisted||b(e,t);default:return!0}}function w(e,t){return(0,r.useMemo)((()=>e.filter((e=>v(e,t)))),[e,t])}function k({sidebarItems:e,pathname:t,onlyCategories:n=!1}){const r=[];return function e(a){for(const o of a)if("category"===o.type&&((0,l.ys)(o.href,t)||e(o.items))||"link"===o.type&&(0,l.ys)(o.href,t)){return n&&"category"!==o.type||r.unshift(o),!0}return!1}(e),r}function S(){const e=(0,d.t)(),{pathname:t}=(0,a.zy)(),n=(0,i.vT)()?.pluginData.breadcrumbs;return!1!==n&&e?k({sidebarItems:e.items,pathname:t}):null}function x(e){const{activeVersion:t}=(0,i.zK)(e),{preferredVersion:n}=(0,u.g1)(e),a=(0,i.r7)(e);return(0,r.useMemo)((()=>(0,s.sb)([t,n,a].filter(Boolean))),[t,n,a])}function _(e,t){const n=x(t);return(0,r.useMemo)((()=>{const t=n.flatMap((e=>e.sidebars?Object.entries(e.sidebars):[])),r=t.find((t=>t[0]===e));if(!r)throw new Error(`Can't find any sidebar with id "${e}" in version${n.length>1?"s":""} ${n.map((e=>e.name)).join(", ")}".\nAvailable sidebar ids are:\n- ${t.map((e=>e[0])).join("\n- ")}`);return r[1]}),[e,n])}function E(e,t){const n=x(t);return(0,r.useMemo)((()=>{const t=n.flatMap((e=>e.docs)),r=t.find((t=>t.id===e));if(!r){if(n.flatMap((e=>e.draftIds)).includes(e))return null;throw new Error(`Couldn't find any doc with id "${e}" in version${n.length>1?"s":""} "${n.map((e=>e.name)).join(", ")}".\nAvailable doc ids are:\n- ${(0,s.sb)(t.map((e=>e.id))).join("\n- ")}`)}return r}),[e,n])}function C({route:e}){const t=(0,a.zy)(),n=(0,c.r)(),r=e.routes,i=r.find((e=>(0,a.B6)(t.pathname,e)));if(!i)return null;const l=i.sidebar,s=l?n.docsSidebars[l]:void 0;return{docElement:(0,o.v)(r),sidebarName:l,sidebarItems:s}}function A(e){return e.filter((e=>!("category"===e.type||"link"===e.type)||!!p(e)))}},6988:(e,t,n)=>{"use strict";n.d(t,{o:()=>d,l:()=>f});var r=n(6540),a=n(4784);const o=JSON.parse('{"docusaurus-plugin-content-docs":{"default":{"path":"/BharatMLStack/","versions":[{"name":"current","label":"Next","isLast":true,"path":"/BharatMLStack/","mainDocId":"online-feature-store/v1.0.0/architecture","docs":[{"id":"online-feature-store/v1.0.0/architecture","path":"/BharatMLStack/online-feature-store/v1.0.0/architecture","sidebar":"tutorialSidebar"},{"id":"online-feature-store/v1.0.0/benchmarks","path":"/BharatMLStack/online-feature-store/v1.0.0/benchmarks","sidebar":"tutorialSidebar"},{"id":"online-feature-store/v1.0.0/data-formats","path":"/BharatMLStack/online-feature-store/v1.0.0/data-formats","sidebar":"tutorialSidebar"},{"id":"online-feature-store/v1.0.0/functionalities","path":"/BharatMLStack/online-feature-store/v1.0.0/functionalities","sidebar":"tutorialSidebar"},{"id":"online-feature-store/v1.0.0/release-notes","path":"/BharatMLStack/online-feature-store/v1.0.0/release-notes","sidebar":"tutorialSidebar"},{"id":"quick-start/v1.0.0/quick-start","path":"/BharatMLStack/quick-start/v1.0.0/quick-start","sidebar":"tutorialSidebar"},{"id":"sdks/go/v1.0.0/feature_client","path":"/BharatMLStack/sdks/go/v1.0.0/feature_client","sidebar":"tutorialSidebar"},{"id":"sdks/python/v1.0.0/grpc_feature_client","path":"/BharatMLStack/sdks/python/v1.0.0/grpc_feature_client","sidebar":"tutorialSidebar"},{"id":"sdks/python/v1.0.0/spark_feature_push_client","path":"/BharatMLStack/sdks/python/v1.0.0/spark_feature_push_client","sidebar":"tutorialSidebar"},{"id":"trufflebox-ui/v1.0.0/userguide","path":"/BharatMLStack/trufflebox-ui/v1.0.0/userguide","sidebar":"tutorialSidebar"},{"id":"/category/online-feature-store","path":"/BharatMLStack/category/online-feature-store","sidebar":"tutorialSidebar"},{"id":"/online-feature-store/v1.0.0","path":"/BharatMLStack/online-feature-store/v1.0.0","sidebar":"tutorialSidebar"},{"id":"/category/quick-start","path":"/BharatMLStack/category/quick-start","sidebar":"tutorialSidebar"},{"id":"/category/trufflebox-ui","path":"/BharatMLStack/category/trufflebox-ui","sidebar":"tutorialSidebar"},{"id":"/category/sdks","path":"/BharatMLStack/category/sdks","sidebar":"tutorialSidebar"},{"id":"/category/go-sdk","path":"/BharatMLStack/category/go-sdk","sidebar":"tutorialSidebar"},{"id":"/category/python-sdk","path":"/BharatMLStack/category/python-sdk","sidebar":"tutorialSidebar"},{"id":"/category/v100","path":"/BharatMLStack/category/v100","sidebar":"tutorialSidebar"}],"draftIds":[],"sidebars":{"tutorialSidebar":{"link":{"path":"/BharatMLStack/category/online-feature-store","label":"Online Feature Store"}}}}],"breadcrumbs":true}}}'),i=JSON.parse('{"defaultLocale":"en","locales":["en"],"path":"i18n","currentLocale":"en","localeConfigs":{"en":{"label":"English","direction":"ltr","htmlLang":"en","calendar":"gregory","path":"en"}}}');var l=n(2654);const s=JSON.parse('{"docusaurusVersion":"3.8.1","siteVersion":"0.0.0","pluginVersions":{"docusaurus-plugin-css-cascade-layers":{"type":"package","name":"@docusaurus/plugin-css-cascade-layers","version":"3.8.1"},"docusaurus-plugin-content-docs":{"type":"package","name":"@docusaurus/plugin-content-docs","version":"3.8.1"},"docusaurus-plugin-content-blog":{"type":"package","name":"@docusaurus/plugin-content-blog","version":"3.8.1"},"docusaurus-plugin-content-pages":{"type":"package","name":"@docusaurus/plugin-content-pages","version":"3.8.1"},"docusaurus-plugin-sitemap":{"type":"package","name":"@docusaurus/plugin-sitemap","version":"3.8.1"},"docusaurus-plugin-svgr":{"type":"package","name":"@docusaurus/plugin-svgr","version":"3.8.1"},"docusaurus-theme-classic":{"type":"package","name":"@docusaurus/theme-classic","version":"3.8.1"}}}');var u=n(4848);const c={siteConfig:a.default,siteMetadata:s,globalData:o,i18n:i,codeTranslations:l},d=r.createContext(c);function f({children:e}){return(0,u.jsx)(d.Provider,{value:c,children:e})}},7065:(e,t,n)=>{"use strict";n.d(t,{W:()=>r});const r="default"},7489:(e,t,n)=>{"use strict";n.d(t,{A:()=>h});var r=n(6540),a=n(8193),o=n(5260),i=n(440),l=n(1656),s=n(3102),u=n(4848);function c({error:e,tryAgain:t}){return(0,u.jsxs)("div",{style:{display:"flex",flexDirection:"column",justifyContent:"center",alignItems:"flex-start",minHeight:"100vh",width:"100%",maxWidth:"80ch",fontSize:"20px",margin:"0 auto",padding:"1rem"},children:[(0,u.jsx)("h1",{style:{fontSize:"3rem"},children:"This page crashed"}),(0,u.jsx)("button",{type:"button",onClick:t,style:{margin:"1rem 0",fontSize:"2rem",cursor:"pointer",borderRadius:20,padding:"1rem"},children:"Try again"}),(0,u.jsx)(d,{error:e})]})}function d({error:e}){const t=(0,i.rA)(e).map((e=>e.message)).join("\n\nCause:\n");return(0,u.jsx)("p",{style:{whiteSpace:"pre-wrap"},children:t})}function f({children:e}){return(0,u.jsx)(s.W,{value:{plugin:{name:"docusaurus-core-error-boundary",id:"default"}},children:e})}function p({error:e,tryAgain:t}){return(0,u.jsx)(f,{children:(0,u.jsxs)(h,{fallback:()=>(0,u.jsx)(c,{error:e,tryAgain:t}),children:[(0,u.jsx)(o.A,{children:(0,u.jsx)("title",{children:"Page Error"})}),(0,u.jsx)(l.A,{children:(0,u.jsx)(c,{error:e,tryAgain:t})})]})})}const m=e=>(0,u.jsx)(p,{...e});class h extends r.Component{constructor(e){super(e),this.state={error:null}}componentDidCatch(e){a.A.canUseDOM&&this.setState({error:e})}render(){const{children:e}=this.props,{error:t}=this.state;if(t){const e={error:t,tryAgain:()=>this.setState({error:null})};return(this.props.fallback??m)(e)}return e??null}}},7559:(e,t,n)=>{"use strict";n.d(t,{G:()=>r});const r={page:{blogListPage:"blog-list-page",blogPostPage:"blog-post-page",blogTagsListPage:"blog-tags-list-page",blogTagPostListPage:"blog-tags-post-list-page",blogAuthorsListPage:"blog-authors-list-page",blogAuthorsPostsPage:"blog-authors-posts-page",docsDocPage:"docs-doc-page",docsTagsListPage:"docs-tags-list-page",docsTagDocListPage:"docs-tags-doc-list-page",mdxPage:"mdx-page"},wrapper:{main:"main-wrapper",blogPages:"blog-wrapper",docsPages:"docs-wrapper",mdxPages:"mdx-wrapper"},common:{editThisPage:"theme-edit-this-page",lastUpdated:"theme-last-updated",backToTopButton:"theme-back-to-top-button",codeBlock:"theme-code-block",admonition:"theme-admonition",unlistedBanner:"theme-unlisted-banner",draftBanner:"theme-draft-banner",admonitionType:e=>`theme-admonition-${e}`},announcementBar:{container:"theme-announcement-bar"},layout:{navbar:{container:"theme-layout-navbar",containerLeft:"theme-layout-navbar-left",containerRight:"theme-layout-navbar-right",mobileSidebar:{container:"theme-layout-navbar-sidebar",panel:"theme-layout-navbar-sidebar-panel"}},main:{container:"theme-layout-main"},footer:{container:"theme-layout-footer",column:"theme-layout-footer-column"}},docs:{docVersionBanner:"theme-doc-version-banner",docVersionBadge:"theme-doc-version-badge",docBreadcrumbs:"theme-doc-breadcrumbs",docMarkdown:"theme-doc-markdown",docTocMobile:"theme-doc-toc-mobile",docTocDesktop:"theme-doc-toc-desktop",docFooter:"theme-doc-footer",docFooterTagsRow:"theme-doc-footer-tags-row",docFooterEditMetaRow:"theme-doc-footer-edit-meta-row",docSidebarContainer:"theme-doc-sidebar-container",docSidebarMenu:"theme-doc-sidebar-menu",docSidebarItemCategory:"theme-doc-sidebar-item-category",docSidebarItemLink:"theme-doc-sidebar-item-link",docSidebarItemCategoryLevel:e=>`theme-doc-sidebar-item-category-level-${e}`,docSidebarItemLinkLevel:e=>`theme-doc-sidebar-item-link-level-${e}`},blog:{blogFooterTagsRow:"theme-blog-footer-tags-row",blogFooterEditMetaRow:"theme-blog-footer-edit-meta-row"},pages:{pageFooterEditMetaRow:"theme-pages-footer-edit-meta-row"}}},8168:(e,t,n)=>{"use strict";function r(){return r=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)({}).hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},r.apply(null,arguments)}n.d(t,{A:()=>r})},8193:(e,t,n)=>{"use strict";n.d(t,{A:()=>a});const r="undefined"!=typeof window&&"document"in window&&"createElement"in window.document,a={canUseDOM:r,canUseEventListeners:r&&("addEventListener"in window||"attachEvent"in window),canUseIntersectionObserver:r&&"IntersectionObserver"in window,canUseViewport:r&&"screen"in window}},8328:(e,t,n)=>{"use strict";n.d(t,{A:()=>f});n(6540);var r=n(3259),a=n.n(r),o=n(4054);const i={"01a85c17":[()=>Promise.all([n.e(1869),n.e(8209)]).then(n.bind(n,9158)),"@theme/BlogTagsListPage",9158],"0413d9af":[()=>n.e(9919).then(n.bind(n,7114)),"@site/docs/sdks/python/v1.0.0/grpc_feature_client.md",7114],"09dd5be9":[()=>n.e(6273).then(n.bind(n,8831)),"@site/blog/bharatmlstack-history/post-one/index.md",8831],"0fff8dc8":[()=>n.e(9596).then(n.bind(n,5958)),"@site/docs/quick-start/v1.0.0/quick-start.md",5958],14064408:[()=>n.e(4582).then(n.t.bind(n,9416,19)),"@generated/docusaurus-plugin-content-docs/default/p/bharat-ml-stack-category-quick-start-b0e.json",9416],"14eb3368":[()=>Promise.all([n.e(1869),n.e(6969)]).then(n.bind(n,477)),"@theme/DocCategoryGeneratedIndexPage",477],"176d210f":[()=>n.e(6100).then(n.bind(n,753)),"@site/docs/trufflebox-ui/v1.0.0/userguide.md",753],17896441:[()=>Promise.all([n.e(1869),n.e(6870),n.e(8401)]).then(n.bind(n,833)),"@theme/DocItem",833],"1a64de69":[()=>n.e(3645).then(n.t.bind(n,1694,19)),"@generated/docusaurus-plugin-content-blog/default/p/bharat-ml-stack-blog-tags-meesho-214.json",1694],"1f391b9e":[()=>Promise.all([n.e(1869),n.e(6870),n.e(6061)]).then(n.bind(n,7973)),"@theme/MDXPage",7973],"2d865531":[()=>n.e(9197).then(n.t.bind(n,4153,19)),"@generated/docusaurus-plugin-content-blog/default/p/bharat-ml-stack-blog-authors-eb6.json",4153],"36994c47":[()=>n.e(9858).then(n.t.bind(n,5516,19)),"@generated/docusaurus-plugin-content-blog/default/__plugin.json",5516],"393be207":[()=>n.e(4134).then(n.bind(n,591)),"@site/src/pages/markdown-page.md",591],"3980073a":[()=>n.e(940).then(n.t.bind(n,3840,19)),"@generated/docusaurus-plugin-content-blog/default/p/bharat-ml-stack-blog-tags-interaction-store-62d.json",3840],"3e1c5046":[()=>n.e(690).then(n.t.bind(n,8750,19)),"@generated/docusaurus-plugin-content-blog/default/p/bharat-ml-stack-blog-tags-online-feature-store-e01.json",8750],"4137b431":[()=>n.e(6054).then(n.t.bind(n,4019,19)),"@generated/docusaurus-plugin-content-docs/default/p/bharat-ml-stack-aad.json",4019],"44d1c015":[()=>n.e(1065).then(n.t.bind(n,6725,19)),"@generated/docusaurus-plugin-content-docs/default/p/bharat-ml-stack-category-python-sdk-f96.json",6725],"479eb034":[()=>n.e(5425).then(n.t.bind(n,9341,19)),"@generated/docusaurus-plugin-content-blog/default/p/bharat-ml-stack-blog-tags-mlplatform-b63.json",9341],"4af50aac":[()=>n.e(1964).then(n.bind(n,6220)),"@site/docs/sdks/go/v1.0.0/feature_client.md",6220],"4caa95bf":[()=>n.e(2344).then(n.bind(n,9584)),"@site/docs/online-feature-store/v1.0.0/data-formats.md",9584],"5e95c892":[()=>n.e(9647).then(n.bind(n,7121)),"@theme/DocsRoot",7121],"5e9f5e1a":[()=>Promise.resolve().then(n.bind(n,4784)),"@generated/docusaurus.config",4784],"616111d3":[()=>n.e(9158).then(n.t.bind(n,9470,19)),"@generated/docusaurus-plugin-content-docs/default/p/bharat-ml-stack-category-sdks-291.json",9470],"621db11d":[()=>Promise.all([n.e(1869),n.e(7518),n.e(4212)]).then(n.bind(n,3250)),"@theme/Blog/Pages/BlogAuthorsListPage",3250],"6479fb86":[()=>n.e(5579).then(n.t.bind(n,3751,19)),"@generated/docusaurus-plugin-content-blog/default/p/bharat-ml-stack-blog-archive-553.json",3751],"67d4782a":[()=>n.e(8588).then(n.bind(n,8769)),"@site/docs/online-feature-store/v1.0.0/benchmarks.md",8769],"6875c492":[()=>Promise.all([n.e(1869),n.e(6870),n.e(7518),n.e(4813)]).then(n.bind(n,3069)),"@theme/BlogTagsPostsPage",3069],"72dc5b25":[()=>n.e(8261).then(n.t.bind(n,3613,19)),"@generated/docusaurus-plugin-content-docs/default/p/bharat-ml-stack-online-feature-store-v-1-0-0-a94.json",3613],"7fa80e1c":[()=>n.e(3322).then(n.t.bind(n,9189,19)),"@generated/docusaurus-plugin-content-blog/default/p/bharat-ml-stack-blog-tags-853.json",9189],"814f3328":[()=>n.e(7472).then(n.t.bind(n,5513,19)),"~blog/default/blog-post-list-prop-default.json",5513],"8ac6191a":[()=>n.e(8465).then(n.t.bind(n,4540,19)),"@generated/docusaurus-plugin-content-docs/default/p/bharat-ml-stack-category-online-feature-store-8eb.json",4540],"9e4087bc":[()=>n.e(2711).then(n.bind(n,9331)),"@theme/BlogArchivePage",9331],a6aa9e1f:[()=>Promise.all([n.e(1869),n.e(6870),n.e(7518),n.e(7643)]).then(n.bind(n,5124)),"@theme/BlogListPage",5124],a7456010:[()=>n.e(1235).then(n.t.bind(n,8552,19)),"@generated/docusaurus-plugin-content-pages/default/__plugin.json",8552],a7bd4aaa:[()=>n.e(7098).then(n.bind(n,1723)),"@theme/DocVersionRoot",1723],a94703ab:[()=>Promise.all([n.e(1869),n.e(9048)]).then(n.bind(n,1377)),"@theme/DocRoot",1377],aba21aa0:[()=>n.e(5742).then(n.t.bind(n,7093,19)),"@generated/docusaurus-plugin-content-docs/default/__plugin.json",7093],ac51638e:[()=>n.e(9473).then(n.bind(n,6692)),"@site/docs/sdks/python/v1.0.0/spark_feature_push_client.md",6692],acecf23e:[()=>n.e(1903).then(n.t.bind(n,1912,19)),"~blog/default/blogMetadata-default.json",1912],c4822c4f:[()=>n.e(1915).then(n.bind(n,3649)),"@site/docs/online-feature-store/v1.0.0/functionalities.md",3649],c4f5d8e4:[()=>Promise.all([n.e(1869),n.e(2634)]).then(n.bind(n,6467)),"@site/src/pages/index.js",6467],c7b64fcc:[()=>n.e(8933).then(n.t.bind(n,9997,19)),"@generated/docusaurus-plugin-content-docs/default/p/bharat-ml-stack-category-go-sdk-b5b.json",9997],ccc49370:[()=>Promise.all([n.e(1869),n.e(6870),n.e(7518),n.e(3249)]).then(n.bind(n,3858)),"@theme/BlogPostPage",3858],d152284c:[()=>n.e(1606).then(n.bind(n,5876)),"@site/docs/online-feature-store/v1.0.0/release-notes.md",5876],e66382f6:[()=>n.e(1405).then(n.bind(n,9563)),"@site/docs/online-feature-store/v1.0.0/architecture.md",9563],f2c141e4:[()=>n.e(1909).then(n.bind(n,161)),"@site/blog/bharatmlstack-history/post-one/index.md?truncated=true",161],f994c8da:[()=>n.e(1999).then(n.t.bind(n,38,19)),"@generated/docusaurus-plugin-content-blog/default/p/bharat-ml-stack-blog-7a3.json",38],fa31f022:[()=>n.e(6062).then(n.t.bind(n,6096,19)),"@generated/docusaurus-plugin-content-docs/default/p/bharat-ml-stack-category-v-100-ae3.json",6096],fcf4f6ca:[()=>n.e(7720).then(n.t.bind(n,4041,19)),"@generated/docusaurus-plugin-content-docs/default/p/bharat-ml-stack-category-trufflebox-ui-b39.json",4041]};var l=n(4848);function s({error:e,retry:t,pastDelay:n}){return e?(0,l.jsxs)("div",{style:{textAlign:"center",color:"#fff",backgroundColor:"#fa383e",borderColor:"#fa383e",borderStyle:"solid",borderRadius:"0.25rem",borderWidth:"1px",boxSizing:"border-box",display:"block",padding:"1rem",flex:"0 0 50%",marginLeft:"25%",marginRight:"25%",marginTop:"5rem",maxWidth:"50%",width:"100%"},children:[(0,l.jsx)("p",{children:String(e)}),(0,l.jsx)("div",{children:(0,l.jsx)("button",{type:"button",onClick:t,children:"Retry"})})]}):n?(0,l.jsx)("div",{style:{display:"flex",justifyContent:"center",alignItems:"center",height:"100vh"},children:(0,l.jsx)("svg",{id:"loader",style:{width:128,height:110,position:"absolute",top:"calc(100vh - 64%)"},viewBox:"0 0 45 45",xmlns:"http://www.w3.org/2000/svg",stroke:"#61dafb",children:(0,l.jsxs)("g",{fill:"none",fillRule:"evenodd",transform:"translate(1 1)",strokeWidth:"2",children:[(0,l.jsxs)("circle",{cx:"22",cy:"22",r:"6",strokeOpacity:"0",children:[(0,l.jsx)("animate",{attributeName:"r",begin:"1.5s",dur:"3s",values:"6;22",calcMode:"linear",repeatCount:"indefinite"}),(0,l.jsx)("animate",{attributeName:"stroke-opacity",begin:"1.5s",dur:"3s",values:"1;0",calcMode:"linear",repeatCount:"indefinite"}),(0,l.jsx)("animate",{attributeName:"stroke-width",begin:"1.5s",dur:"3s",values:"2;0",calcMode:"linear",repeatCount:"indefinite"})]}),(0,l.jsxs)("circle",{cx:"22",cy:"22",r:"6",strokeOpacity:"0",children:[(0,l.jsx)("animate",{attributeName:"r",begin:"3s",dur:"3s",values:"6;22",calcMode:"linear",repeatCount:"indefinite"}),(0,l.jsx)("animate",{attributeName:"stroke-opacity",begin:"3s",dur:"3s",values:"1;0",calcMode:"linear",repeatCount:"indefinite"}),(0,l.jsx)("animate",{attributeName:"stroke-width",begin:"3s",dur:"3s",values:"2;0",calcMode:"linear",repeatCount:"indefinite"})]}),(0,l.jsx)("circle",{cx:"22",cy:"22",r:"8",children:(0,l.jsx)("animate",{attributeName:"r",begin:"0s",dur:"1.5s",values:"6;1;2;3;4;5;6",calcMode:"linear",repeatCount:"indefinite"})})]})})}):null}var u=n(6921),c=n(3102);function d(e,t){if("*"===e)return a()({loading:s,loader:()=>n.e(2237).then(n.bind(n,2237)),modules:["@theme/NotFound"],webpack:()=>[2237],render(e,t){const n=e.default;return(0,l.jsx)(c.W,{value:{plugin:{name:"native",id:"default"}},children:(0,l.jsx)(n,{...t})})}});const r=o[`${e}-${t}`],d={},f=[],p=[],m=(0,u.A)(r);return Object.entries(m).forEach((([e,t])=>{const n=i[t];n&&(d[e]=n[0],f.push(n[1]),p.push(n[2]))})),a().Map({loading:s,loader:d,modules:f,webpack:()=>p,render(t,n){const a=JSON.parse(JSON.stringify(r));Object.entries(t).forEach((([t,n])=>{const r=n.default;if(!r)throw new Error(`The page component at ${e} doesn't have a default export. This makes it impossible to render anything. Consider default-exporting a React component.`);"object"!=typeof r&&"function"!=typeof r||Object.keys(n).filter((e=>"default"!==e)).forEach((e=>{r[e]=n[e]}));let o=a;const i=t.split(".");i.slice(0,-1).forEach((e=>{o=o[e]})),o[i[i.length-1]]=r}));const o=a.__comp;delete a.__comp;const i=a.__context;delete a.__context;const s=a.__props;return delete a.__props,(0,l.jsx)(c.W,{value:i,children:(0,l.jsx)(o,{...a,...s,...n})})}})}const f=[{path:"/BharatMLStack/blog",component:d("/BharatMLStack/blog","170"),exact:!0},{path:"/BharatMLStack/blog/archive",component:d("/BharatMLStack/blog/archive","dde"),exact:!0},{path:"/BharatMLStack/blog/authors",component:d("/BharatMLStack/blog/authors","f47"),exact:!0},{path:"/BharatMLStack/blog/post-one",component:d("/BharatMLStack/blog/post-one","e5f"),exact:!0},{path:"/BharatMLStack/blog/tags",component:d("/BharatMLStack/blog/tags","8af"),exact:!0},{path:"/BharatMLStack/blog/tags/interaction-store",component:d("/BharatMLStack/blog/tags/interaction-store","4b6"),exact:!0},{path:"/BharatMLStack/blog/tags/meesho",component:d("/BharatMLStack/blog/tags/meesho","316"),exact:!0},{path:"/BharatMLStack/blog/tags/mlplatform",component:d("/BharatMLStack/blog/tags/mlplatform","48f"),exact:!0},{path:"/BharatMLStack/blog/tags/online-feature-store",component:d("/BharatMLStack/blog/tags/online-feature-store","44b"),exact:!0},{path:"/BharatMLStack/markdown-page",component:d("/BharatMLStack/markdown-page","747"),exact:!0},{path:"/BharatMLStack/",component:d("/BharatMLStack/","e34"),exact:!0},{path:"/BharatMLStack/",component:d("/BharatMLStack/","fd6"),routes:[{path:"/BharatMLStack/",component:d("/BharatMLStack/","098"),routes:[{path:"/BharatMLStack/",component:d("/BharatMLStack/","925"),routes:[{path:"/BharatMLStack/category/go-sdk",component:d("/BharatMLStack/category/go-sdk","6b0"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/category/online-feature-store",component:d("/BharatMLStack/category/online-feature-store","7ee"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/category/python-sdk",component:d("/BharatMLStack/category/python-sdk","1fd"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/category/quick-start",component:d("/BharatMLStack/category/quick-start","dff"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/category/sdks",component:d("/BharatMLStack/category/sdks","532"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/category/trufflebox-ui",component:d("/BharatMLStack/category/trufflebox-ui","5f5"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/category/v100",component:d("/BharatMLStack/category/v100","ddd"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/online-feature-store/v1.0.0",component:d("/BharatMLStack/online-feature-store/v1.0.0","218"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/online-feature-store/v1.0.0/architecture",component:d("/BharatMLStack/online-feature-store/v1.0.0/architecture","0af"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/online-feature-store/v1.0.0/benchmarks",component:d("/BharatMLStack/online-feature-store/v1.0.0/benchmarks","889"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/online-feature-store/v1.0.0/data-formats",component:d("/BharatMLStack/online-feature-store/v1.0.0/data-formats","46e"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/online-feature-store/v1.0.0/functionalities",component:d("/BharatMLStack/online-feature-store/v1.0.0/functionalities","415"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/online-feature-store/v1.0.0/release-notes",component:d("/BharatMLStack/online-feature-store/v1.0.0/release-notes","36c"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/quick-start/v1.0.0/quick-start",component:d("/BharatMLStack/quick-start/v1.0.0/quick-start","b19"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/sdks/go/v1.0.0/feature_client",component:d("/BharatMLStack/sdks/go/v1.0.0/feature_client","1df"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/sdks/python/v1.0.0/grpc_feature_client",component:d("/BharatMLStack/sdks/python/v1.0.0/grpc_feature_client","9dc"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/sdks/python/v1.0.0/spark_feature_push_client",component:d("/BharatMLStack/sdks/python/v1.0.0/spark_feature_push_client","1bc"),exact:!0,sidebar:"tutorialSidebar"},{path:"/BharatMLStack/trufflebox-ui/v1.0.0/userguide",component:d("/BharatMLStack/trufflebox-ui/v1.0.0/userguide","65e"),exact:!0,sidebar:"tutorialSidebar"}]}]}]},{path:"*",component:d("*")}]},8380:e=>{"use strict";var t=function(){var e=function(){};function t(e,t){Array.isArray(e)?e.forEach(t):null!=e&&t(e,0)}function n(e){for(var t={},n=0,r=e.length;n<r;n++)t[e[n]]=!0;return t}function r(e){var n={},r=[];function a(r,o){if(!(r in n)){o.push(r);var i=o.indexOf(r);if(i<o.length-1)throw new Error("Circular dependency: "+o.slice(i).join(" -> "));var l={},s=e[r];if(s){function u(t){if(!(t in e))throw new Error(r+" depends on an unknown component "+t);if(!(t in l))for(var i in a(t,o),l[t]=!0,n[t])l[i]=!0}t(s.require,u),t(s.optional,u),t(s.modify,u)}n[r]=l,o.pop()}}return function(e){var t=n[e];return t||(a(e,r),t=n[e]),t}}function a(e){for(var t in e)return!0;return!1}return function(o,i,l){var s=function(e){var t={};for(var n in e){var r=e[n];for(var a in r)if("meta"!=a){var o=r[a];t[a]="string"==typeof o?{title:o}:o}}return t}(o),u=function(e){var n;return function(r){if(r in e)return r;if(!n)for(var a in n={},e){var o=e[a];t(o&&o.alias,(function(t){if(t in n)throw new Error(t+" cannot be alias for both "+a+" and "+n[t]);if(t in e)throw new Error(t+" cannot be alias of "+a+" because it is a component.");n[t]=a}))}return n[r]||r}}(s);i=i.map(u),l=(l||[]).map(u);var c=n(i),d=n(l);i.forEach((function e(n){var r=s[n];t(r&&r.require,(function(t){t in d||(c[t]=!0,e(t))}))}));for(var f,p=r(s),m=c;a(m);){for(var h in f={},m){var g=s[h];t(g&&g.modify,(function(e){e in d&&(f[e]=!0)}))}for(var y in d)if(!(y in c))for(var b in p(y))if(b in c){f[y]=!0;break}for(var v in m=f)c[v]=!0}var w={getIds:function(){var e=[];return w.load((function(t){e.push(t)})),e},load:function(t,n){return function(t,n,r,a){var o=a?a.series:void 0,i=a?a.parallel:e,l={},s={};function u(e){if(e in l)return l[e];s[e]=!0;var a,c=[];for(var d in t(e))d in n&&c.push(d);if(0===c.length)a=r(e);else{var f=i(c.map((function(e){var t=u(e);return delete s[e],t})));o?a=o(f,(function(){return r(e)})):r(e)}return l[e]=a}for(var c in n)u(c);var d=[];for(var f in s)d.push(l[f]);return i(d)}(p,c,t,n)}};return w}}();e.exports=t},8587:(e,t,n)=>{"use strict";function r(e,t){if(null==e)return{};var n={};for(var r in e)if({}.hasOwnProperty.call(e,r)){if(-1!==t.indexOf(r))continue;n[r]=e[r]}return n}n.d(t,{A:()=>r})},8600:(e,t,n)=>{"use strict";var r=n(6540),a=n(5338),o=n(545),i=n(4625),l=n(4784),s=n(8193);const u=[n(3001),n(119),n(6134),n(6294),n(1043)];var c=n(8328),d=n(6347),f=n(2831),p=n(4848);function m({children:e}){return(0,p.jsx)(p.Fragment,{children:e})}var h=n(4563);const g=e=>e.defaultFormatter(e);function y({children:e}){return(0,p.jsx)(h.AL,{formatter:g,children:e})}function b({children:e}){return(0,p.jsx)(y,{children:e})}var v=n(5260),w=n(4586),k=n(6025),S=n(6342),x=n(5500),_=n(2131),E=n(4090);var C=n(440),A=n(1463);function T(){const{i18n:{currentLocale:e,defaultLocale:t,localeConfigs:n}}=(0,w.A)(),r=(0,_.o)(),a=n[e].htmlLang,o=e=>e.replace("-","_");return(0,p.jsxs)(v.A,{children:[Object.entries(n).map((([e,{htmlLang:t}])=>(0,p.jsx)("link",{rel:"alternate",href:r.createUrl({locale:e,fullyQualified:!0}),hrefLang:t},e))),(0,p.jsx)("link",{rel:"alternate",href:r.createUrl({locale:t,fullyQualified:!0}),hrefLang:"x-default"}),(0,p.jsx)("meta",{property:"og:locale",content:o(a)}),Object.values(n).filter((e=>a!==e.htmlLang)).map((e=>(0,p.jsx)("meta",{property:"og:locale:alternate",content:o(e.htmlLang)},`meta-og-${e.htmlLang}`)))]})}function L({permalink:e}){const{siteConfig:{url:t}}=(0,w.A)(),n=function(){const{siteConfig:{url:e,baseUrl:t,trailingSlash:n}}=(0,w.A)(),{pathname:r}=(0,d.zy)();return e+(0,C.Ks)((0,k.Ay)(r),{trailingSlash:n,baseUrl:t})}(),r=e?`${t}${e}`:n;return(0,p.jsxs)(v.A,{children:[(0,p.jsx)("meta",{property:"og:url",content:r}),(0,p.jsx)("link",{rel:"canonical",href:r})]})}function j(){const{i18n:{currentLocale:e}}=(0,w.A)(),{metadata:t,image:n}=(0,S.p)();return(0,p.jsxs)(p.Fragment,{children:[(0,p.jsxs)(v.A,{children:[(0,p.jsx)("meta",{name:"twitter:card",content:"summary_large_image"}),(0,p.jsx)("body",{className:E.w})]}),n&&(0,p.jsx)(x.be,{image:n}),(0,p.jsx)(L,{}),(0,p.jsx)(T,{}),(0,p.jsx)(A.A,{tag:"default",locale:e}),(0,p.jsx)(v.A,{children:t.map(((e,t)=>(0,p.jsx)("meta",{...e},t)))})]})}const P=new Map;var N=n(6125),O=n(6988),R=n(205);function M(e,...t){const n=u.map((n=>{const r=n.default?.[e]??n[e];return r?.(...t)}));return()=>n.forEach((e=>e?.()))}const D=function({children:e,location:t,previousLocation:n}){return(0,R.A)((()=>{n!==t&&(!function({location:e,previousLocation:t}){if(!t)return;const n=e.pathname===t.pathname,r=e.hash===t.hash,a=e.search===t.search;if(n&&r&&!a)return;const{hash:o}=e;if(o){const e=decodeURIComponent(o.substring(1)),t=document.getElementById(e);t?.scrollIntoView()}else window.scrollTo(0,0)}({location:t,previousLocation:n}),M("onRouteDidUpdate",{previousLocation:n,location:t}))}),[n,t]),e};function F(e){const t=Array.from(new Set([e,decodeURI(e)])).map((e=>(0,f.u)(c.A,e))).flat();return Promise.all(t.map((e=>e.route.component.preload?.())))}class I extends r.Component{previousLocation;routeUpdateCleanupCb;constructor(e){super(e),this.previousLocation=null,this.routeUpdateCleanupCb=s.A.canUseDOM?M("onRouteUpdate",{previousLocation:null,location:this.props.location}):()=>{},this.state={nextRouteHasLoaded:!0}}shouldComponentUpdate(e,t){if(e.location===this.props.location)return t.nextRouteHasLoaded;const n=e.location;return this.previousLocation=this.props.location,this.setState({nextRouteHasLoaded:!1}),this.routeUpdateCleanupCb=M("onRouteUpdate",{previousLocation:this.previousLocation,location:n}),F(n.pathname).then((()=>{this.routeUpdateCleanupCb(),this.setState({nextRouteHasLoaded:!0})})).catch((e=>{console.warn(e),window.location.reload()})),!1}render(){const{children:e,location:t}=this.props;return(0,p.jsx)(D,{previousLocation:this.previousLocation,location:t,children:(0,p.jsx)(d.qh,{location:t,render:()=>e})})}}const B=I,z="__docusaurus-base-url-issue-banner-suggestion-container";function $(e){return`\ndocument.addEventListener('DOMContentLoaded', function maybeInsertBanner() {\n var shouldInsert = typeof window['docusaurus'] === 'undefined';\n shouldInsert && insertBanner();\n});\n\nfunction insertBanner() {\n var bannerContainer = document.createElement('div');\n bannerContainer.id = '__docusaurus-base-url-issue-banner-container';\n var bannerHtml = ${JSON.stringify(function(e){return`\n<div id="__docusaurus-base-url-issue-banner" style="border: thick solid red; background-color: rgb(255, 230, 179); margin: 20px; padding: 20px; font-size: 20px;">\n <p style="font-weight: bold; font-size: 30px;">Your Docusaurus site did not load properly.</p>\n <p>A very common reason is a wrong site <a href="https://docusaurus.io/docs/docusaurus.config.js/#baseUrl" style="font-weight: bold;">baseUrl configuration</a>.</p>\n <p>Current configured baseUrl = <span style="font-weight: bold; color: red;">${e}</span> ${"/"===e?" (default value)":""}</p>\n <p>We suggest trying baseUrl = <span id="${z}" style="font-weight: bold; color: green;"></span></p>\n</div>\n`}(e)).replace(/</g,"\\<")};\n bannerContainer.innerHTML = bannerHtml;\n document.body.prepend(bannerContainer);\n var suggestionContainer = document.getElementById('${z}');\n var actualHomePagePath = window.location.pathname;\n var suggestedBaseUrl = actualHomePagePath.substr(-1) === '/'\n ? actualHomePagePath\n : actualHomePagePath + '/';\n suggestionContainer.innerHTML = suggestedBaseUrl;\n}\n`}function U(){const{siteConfig:{baseUrl:e}}=(0,w.A)();return(0,p.jsx)(p.Fragment,{children:!s.A.canUseDOM&&(0,p.jsx)(v.A,{children:(0,p.jsx)("script",{children:$(e)})})})}function q(){const{siteConfig:{baseUrl:e,baseUrlIssueBanner:t}}=(0,w.A)(),{pathname:n}=(0,d.zy)();return t&&n===e?(0,p.jsx)(U,{}):null}function H(){const{siteConfig:{favicon:e,title:t,noIndex:n},i18n:{currentLocale:r,localeConfigs:a}}=(0,w.A)(),o=(0,k.Ay)(e),{htmlLang:i,direction:l}=a[r];return(0,p.jsxs)(v.A,{children:[(0,p.jsx)("html",{lang:i,dir:l}),(0,p.jsx)("title",{children:t}),(0,p.jsx)("meta",{property:"og:title",content:t}),(0,p.jsx)("meta",{name:"viewport",content:"width=device-width, initial-scale=1.0"}),n&&(0,p.jsx)("meta",{name:"robots",content:"noindex, nofollow"}),e&&(0,p.jsx)("link",{rel:"icon",href:o})]})}var G=n(7489),V=n(2303);function W(){const e=(0,V.A)();return(0,p.jsx)(v.A,{children:(0,p.jsx)("html",{"data-has-hydrated":e})})}const Q=(0,f.v)(c.A);function K(){const e=function(e){if(P.has(e.pathname))return{...e,pathname:P.get(e.pathname)};if((0,f.u)(c.A,e.pathname).some((({route:e})=>!0===e.exact)))return P.set(e.pathname,e.pathname),e;const t=e.pathname.trim().replace(/(?:\/index)?\.html$/,"")||"/";return P.set(e.pathname,t),{...e,pathname:t}}((0,d.zy)());return(0,p.jsx)(B,{location:e,children:Q})}function Y(){return(0,p.jsx)(G.A,{children:(0,p.jsx)(O.l,{children:(0,p.jsxs)(N.x,{children:[(0,p.jsx)(m,{children:(0,p.jsxs)(b,{children:[(0,p.jsx)(H,{}),(0,p.jsx)(j,{}),(0,p.jsx)(q,{}),(0,p.jsx)(K,{})]})}),(0,p.jsx)(W,{})]})})})}var X=n(4054);const Z=function(e){try{return document.createElement("link").relList.supports(e)}catch{return!1}}("prefetch")?function(e){return new Promise(((t,n)=>{if("undefined"==typeof document)return void n();const r=document.createElement("link");r.setAttribute("rel","prefetch"),r.setAttribute("href",e),r.onload=()=>t(),r.onerror=()=>n();const a=document.getElementsByTagName("head")[0]??document.getElementsByName("script")[0]?.parentNode;a?.appendChild(r)}))}:function(e){return new Promise(((t,n)=>{const r=new XMLHttpRequest;r.open("GET",e,!0),r.withCredentials=!0,r.onload=()=>{200===r.status?t():n()},r.send(null)}))};var J=n(6921);const ee=new Set,te=new Set,ne=()=>navigator.connection?.effectiveType.includes("2g")||navigator.connection?.saveData,re={prefetch:e=>{if(!(e=>!ne()&&!te.has(e)&&!ee.has(e))(e))return!1;ee.add(e);const t=(0,f.u)(c.A,e).flatMap((e=>{return t=e.route.path,Object.entries(X).filter((([e])=>e.replace(/-[^-]+$/,"")===t)).flatMap((([,e])=>Object.values((0,J.A)(e))));var t}));return Promise.all(t.map((e=>{const t=n.gca(e);return t&&!t.includes("undefined")?Z(t).catch((()=>{})):Promise.resolve()})))},preload:e=>!!(e=>!ne()&&!te.has(e))(e)&&(te.add(e),F(e))},ae=Object.freeze(re);function oe({children:e}){return"hash"===l.default.future.experimental_router?(0,p.jsx)(i.I9,{children:e}):(0,p.jsx)(i.Kd,{children:e})}const ie=Boolean(!0);if(s.A.canUseDOM){window.docusaurus=ae;const e=document.getElementById("__docusaurus"),t=(0,p.jsx)(o.vd,{children:(0,p.jsx)(oe,{children:(0,p.jsx)(Y,{})})}),n=(e,t)=>{console.error("Docusaurus React Root onRecoverableError:",e,t)},i=()=>{if(window.docusaurusRoot)window.docusaurusRoot.render(t);else if(ie)window.docusaurusRoot=a.hydrateRoot(e,t,{onRecoverableError:n});else{const r=a.createRoot(e,{onRecoverableError:n});r.render(t),window.docusaurusRoot=r}};F(window.location.pathname).then((()=>{(0,r.startTransition)(i)}))}},8692:(e,t,n)=>{var r={"./":8722};function a(e){var t=o(e);return n(t)}function o(e){if(!n.o(r,e)){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}return r[e]}a.keys=function(){return Object.keys(r)},a.resolve=o,e.exports=a,a.id=8692},8722:(e,t,n)=>{const r=n(6969),a=n(8380),o=new Set;function i(e){void 0===e?e=Object.keys(r.languages).filter((e=>"meta"!=e)):Array.isArray(e)||(e=[e]);const t=[...o,...Object.keys(Prism.languages)];a(r,e,t).load((e=>{if(!(e in r.languages))return void(i.silent||console.warn("Language does not exist: "+e));const t="./prism-"+e;delete n.c[n(3157).resolve(t)],delete Prism.languages[e],n(3157)(t),o.add(e)}))}i.silent=!1,e.exports=i},8774:(e,t,n)=>{"use strict";n.d(t,{A:()=>p});var r=n(6540),a=n(4625),o=n(440),i=n(4586),l=n(6654),s=n(8193),u=n(3427),c=n(6025),d=n(4848);function f({isNavLink:e,to:t,href:n,activeClassName:f,isActive:p,"data-noBrokenLinkCheck":m,autoAddBaseUrl:h=!0,...g},y){const{siteConfig:b}=(0,i.A)(),{trailingSlash:v,baseUrl:w}=b,k=b.future.experimental_router,{withBaseUrl:S}=(0,c.hH)(),x=(0,u.A)(),_=(0,r.useRef)(null);(0,r.useImperativeHandle)(y,(()=>_.current));const E=t||n;const C=(0,l.A)(E),A=E?.replace("pathname://","");let T=void 0!==A?(L=A,h&&(e=>e.startsWith("/"))(L)?S(L):L):void 0;var L;"hash"===k&&T?.startsWith("./")&&(T=T?.slice(1)),T&&C&&(T=(0,o.Ks)(T,{trailingSlash:v,baseUrl:w}));const j=(0,r.useRef)(!1),P=e?a.k2:a.N_,N=s.A.canUseIntersectionObserver,O=(0,r.useRef)(),R=()=>{j.current||null==T||(window.docusaurus.preload(T),j.current=!0)};(0,r.useEffect)((()=>(!N&&C&&s.A.canUseDOM&&null!=T&&window.docusaurus.prefetch(T),()=>{N&&O.current&&O.current.disconnect()})),[O,T,N,C]);const M=T?.startsWith("#")??!1,D=!g.target||"_self"===g.target,F=!T||!C||!D||M&&"hash"!==k;m||!M&&F||x.collectLink(T),g.id&&x.collectAnchor(g.id);const I={};return F?(0,d.jsx)("a",{ref:_,href:T,...E&&!C&&{target:"_blank",rel:"noopener noreferrer"},...g,...I}):(0,d.jsx)(P,{...g,onMouseEnter:R,onTouchStart:R,innerRef:e=>{_.current=e,N&&e&&C&&(O.current=new window.IntersectionObserver((t=>{t.forEach((t=>{e===t.target&&(t.isIntersecting||t.intersectionRatio>0)&&(O.current.unobserve(e),O.current.disconnect(),null!=T&&window.docusaurus.prefetch(T))}))})),O.current.observe(e))},to:T,...e&&{isActive:p,activeClassName:f},...I})}const p=r.forwardRef(f)},9169:(e,t,n)=>{"use strict";n.d(t,{Dt:()=>l,ys:()=>i});var r=n(6540),a=n(8328),o=n(4586);function i(e,t){const n=e=>(!e||e.endsWith("/")?e:`${e}/`)?.toLowerCase();return n(e)===n(t)}function l(){const{baseUrl:e}=(0,o.A)().siteConfig;return(0,r.useMemo)((()=>function({baseUrl:e,routes:t}){function n(t){return t.path===e&&!0===t.exact}function r(t){return t.path===e&&!t.exact}return function e(t){if(0===t.length)return;return t.find(n)||e(t.filter(r).flatMap((e=>e.routes??[])))}(t)}({routes:a.A,baseUrl:e})),[e])}},9532:(e,t,n)=>{"use strict";n.d(t,{Be:()=>u,ZC:()=>l,_q:()=>i,dV:()=>s,fM:()=>c});var r=n(6540),a=n(205),o=n(4848);function i(e){const t=(0,r.useRef)(e);return(0,a.A)((()=>{t.current=e}),[e]),(0,r.useCallback)(((...e)=>t.current(...e)),[])}function l(e){const t=(0,r.useRef)();return(0,a.A)((()=>{t.current=e})),t.current}class s extends Error{constructor(e,t){super(),this.name="ReactContextError",this.message=`Hook ${this.stack?.split("\n")[1]?.match(/at (?:\w+\.)?(?<name>\w+)/)?.groups.name??""} is called outside the <${e}>. ${t??""}`}}function u(e){const t=Object.entries(e);return t.sort(((e,t)=>e[0].localeCompare(t[0]))),(0,r.useMemo)((()=>e),t.flat())}function c(e){return({children:t})=>(0,o.jsx)(o.Fragment,{children:e.reduceRight(((e,t)=>(0,o.jsx)(t,{children:e})),t)})}},9698:(e,t)=>{"use strict";var n=Symbol.for("react.transitional.element"),r=Symbol.for("react.fragment");function a(e,t,r){var a=null;if(void 0!==r&&(a=""+r),void 0!==t.key&&(a=""+t.key),"key"in t)for(var o in r={},t)"key"!==o&&(r[o]=t[o]);else r=t;return t=r.ref,{$$typeof:n,type:e,key:a,ref:void 0!==t?t:null,props:r}}t.Fragment=r,t.jsx=a,t.jsxs=a},9700:()=>{!function(e){function t(e,t){return"___"+e.toUpperCase()+t+"___"}Object.defineProperties(e.languages["markup-templating"]={},{buildPlaceholders:{value:function(n,r,a,o){if(n.language===r){var i=n.tokenStack=[];n.code=n.code.replace(a,(function(e){if("function"==typeof o&&!o(e))return e;for(var a,l=i.length;-1!==n.code.indexOf(a=t(r,l));)++l;return i[l]=e,a})),n.grammar=e.languages.markup}}},tokenizePlaceholders:{value:function(n,r){if(n.language===r&&n.tokenStack){n.grammar=e.languages[r];var a=0,o=Object.keys(n.tokenStack);!function i(l){for(var s=0;s<l.length&&!(a>=o.length);s++){var u=l[s];if("string"==typeof u||u.content&&"string"==typeof u.content){var c=o[a],d=n.tokenStack[c],f="string"==typeof u?u:u.content,p=t(r,c),m=f.indexOf(p);if(m>-1){++a;var h=f.substring(0,m),g=new e.Token(r,e.tokenize(d,n.grammar),"language-"+r,d),y=f.substring(m+p.length),b=[];h&&b.push.apply(b,i([h])),b.push(g),y&&b.push.apply(b,i([y])),"string"==typeof u?l.splice.apply(l,[s,1].concat(b)):u.content=b}}else u.content&&i(u.content)}return l}(n.tokens)}}}})}(Prism)},9869:(e,t)=>{"use strict";var n=Symbol.for("react.transitional.element"),r=Symbol.for("react.portal"),a=Symbol.for("react.fragment"),o=Symbol.for("react.strict_mode"),i=Symbol.for("react.profiler"),l=Symbol.for("react.consumer"),s=Symbol.for("react.context"),u=Symbol.for("react.forward_ref"),c=Symbol.for("react.suspense"),d=Symbol.for("react.memo"),f=Symbol.for("react.lazy"),p=Symbol.iterator;var m={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},h=Object.assign,g={};function y(e,t,n){this.props=e,this.context=t,this.refs=g,this.updater=n||m}function b(){}function v(e,t,n){this.props=e,this.context=t,this.refs=g,this.updater=n||m}y.prototype.isReactComponent={},y.prototype.setState=function(e,t){if("object"!=typeof e&&"function"!=typeof e&&null!=e)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")},y.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")},b.prototype=y.prototype;var w=v.prototype=new b;w.constructor=v,h(w,y.prototype),w.isPureReactComponent=!0;var k=Array.isArray,S={H:null,A:null,T:null,S:null,V:null},x=Object.prototype.hasOwnProperty;function _(e,t,r,a,o,i){return r=i.ref,{$$typeof:n,type:e,key:t,ref:void 0!==r?r:null,props:i}}function E(e){return"object"==typeof e&&null!==e&&e.$$typeof===n}var C=/\/+/g;function A(e,t){return"object"==typeof e&&null!==e&&null!=e.key?(n=""+e.key,r={"=":"=0",":":"=2"},"$"+n.replace(/[=:]/g,(function(e){return r[e]}))):t.toString(36);var n,r}function T(){}function L(e,t,a,o,i){var l=typeof e;"undefined"!==l&&"boolean"!==l||(e=null);var s,u,c=!1;if(null===e)c=!0;else switch(l){case"bigint":case"string":case"number":c=!0;break;case"object":switch(e.$$typeof){case n:case r:c=!0;break;case f:return L((c=e._init)(e._payload),t,a,o,i)}}if(c)return i=i(e),c=""===o?"."+A(e,0):o,k(i)?(a="",null!=c&&(a=c.replace(C,"$&/")+"/"),L(i,t,a,"",(function(e){return e}))):null!=i&&(E(i)&&(s=i,u=a+(null==i.key||e&&e.key===i.key?"":(""+i.key).replace(C,"$&/")+"/")+c,i=_(s.type,u,void 0,0,0,s.props)),t.push(i)),1;c=0;var d,m=""===o?".":o+":";if(k(e))for(var h=0;h<e.length;h++)c+=L(o=e[h],t,a,l=m+A(o,h),i);else if("function"==typeof(h=null===(d=e)||"object"!=typeof d?null:"function"==typeof(d=p&&d[p]||d["@@iterator"])?d:null))for(e=h.call(e),h=0;!(o=e.next()).done;)c+=L(o=o.value,t,a,l=m+A(o,h++),i);else if("object"===l){if("function"==typeof e.then)return L(function(e){switch(e.status){case"fulfilled":return e.value;case"rejected":throw e.reason;default:switch("string"==typeof e.status?e.then(T,T):(e.status="pending",e.then((function(t){"pending"===e.status&&(e.status="fulfilled",e.value=t)}),(function(t){"pending"===e.status&&(e.status="rejected",e.reason=t)}))),e.status){case"fulfilled":return e.value;case"rejected":throw e.reason}}throw e}(e),t,a,o,i);throw t=String(e),Error("Objects are not valid as a React child (found: "+("[object Object]"===t?"object with keys {"+Object.keys(e).join(", ")+"}":t)+"). If you meant to render a collection of children, use an array instead.")}return c}function j(e,t,n){if(null==e)return e;var r=[],a=0;return L(e,r,"","",(function(e){return t.call(n,e,a++)})),r}function P(e){if(-1===e._status){var t=e._result;(t=t()).then((function(t){0!==e._status&&-1!==e._status||(e._status=1,e._result=t)}),(function(t){0!==e._status&&-1!==e._status||(e._status=2,e._result=t)})),-1===e._status&&(e._status=0,e._result=t)}if(1===e._status)return e._result.default;throw e._result}var N="function"==typeof reportError?reportError:function(e){if("object"==typeof window&&"function"==typeof window.ErrorEvent){var t=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:"object"==typeof e&&null!==e&&"string"==typeof e.message?String(e.message):String(e),error:e});if(!window.dispatchEvent(t))return}else if("object"==typeof process&&"function"==typeof process.emit)return void process.emit("uncaughtException",e);console.error(e)};function O(){}t.Children={map:j,forEach:function(e,t,n){j(e,(function(){t.apply(this,arguments)}),n)},count:function(e){var t=0;return j(e,(function(){t++})),t},toArray:function(e){return j(e,(function(e){return e}))||[]},only:function(e){if(!E(e))throw Error("React.Children.only expected to receive a single React element child.");return e}},t.Component=y,t.Fragment=a,t.Profiler=i,t.PureComponent=v,t.StrictMode=o,t.Suspense=c,t.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=S,t.__COMPILER_RUNTIME={__proto__:null,c:function(e){return S.H.useMemoCache(e)}},t.cache=function(e){return function(){return e.apply(null,arguments)}},t.cloneElement=function(e,t,n){if(null==e)throw Error("The argument must be a React element, but you passed "+e+".");var r=h({},e.props),a=e.key;if(null!=t)for(o in void 0!==t.ref&&void 0,void 0!==t.key&&(a=""+t.key),t)!x.call(t,o)||"key"===o||"__self"===o||"__source"===o||"ref"===o&&void 0===t.ref||(r[o]=t[o]);var o=arguments.length-2;if(1===o)r.children=n;else if(1<o){for(var i=Array(o),l=0;l<o;l++)i[l]=arguments[l+2];r.children=i}return _(e.type,a,void 0,0,0,r)},t.createContext=function(e){return(e={$$typeof:s,_currentValue:e,_currentValue2:e,_threadCount:0,Provider:null,Consumer:null}).Provider=e,e.Consumer={$$typeof:l,_context:e},e},t.createElement=function(e,t,n){var r,a={},o=null;if(null!=t)for(r in void 0!==t.key&&(o=""+t.key),t)x.call(t,r)&&"key"!==r&&"__self"!==r&&"__source"!==r&&(a[r]=t[r]);var i=arguments.length-2;if(1===i)a.children=n;else if(1<i){for(var l=Array(i),s=0;s<i;s++)l[s]=arguments[s+2];a.children=l}if(e&&e.defaultProps)for(r in i=e.defaultProps)void 0===a[r]&&(a[r]=i[r]);return _(e,o,void 0,0,0,a)},t.createRef=function(){return{current:null}},t.forwardRef=function(e){return{$$typeof:u,render:e}},t.isValidElement=E,t.lazy=function(e){return{$$typeof:f,_payload:{_status:-1,_result:e},_init:P}},t.memo=function(e,t){return{$$typeof:d,type:e,compare:void 0===t?null:t}},t.startTransition=function(e){var t=S.T,n={};S.T=n;try{var r=e(),a=S.S;null!==a&&a(n,r),"object"==typeof r&&null!==r&&"function"==typeof r.then&&r.then(O,N)}catch(o){N(o)}finally{S.T=t}},t.unstable_useCacheRefresh=function(){return S.H.useCacheRefresh()},t.use=function(e){return S.H.use(e)},t.useActionState=function(e,t,n){return S.H.useActionState(e,t,n)},t.useCallback=function(e,t){return S.H.useCallback(e,t)},t.useContext=function(e){return S.H.useContext(e)},t.useDebugValue=function(){},t.useDeferredValue=function(e,t){return S.H.useDeferredValue(e,t)},t.useEffect=function(e,t,n){var r=S.H;if("function"==typeof n)throw Error("useEffect CRUD overload is not enabled in this build of React.");return r.useEffect(e,t)},t.useId=function(){return S.H.useId()},t.useImperativeHandle=function(e,t,n){return S.H.useImperativeHandle(e,t,n)},t.useInsertionEffect=function(e,t){return S.H.useInsertionEffect(e,t)},t.useLayoutEffect=function(e,t){return S.H.useLayoutEffect(e,t)},t.useMemo=function(e,t){return S.H.useMemo(e,t)},t.useOptimistic=function(e,t){return S.H.useOptimistic(e,t)},t.useReducer=function(e,t,n){return S.H.useReducer(e,t,n)},t.useRef=function(e){return S.H.useRef(e)},t.useState=function(e){return S.H.useState(e)},t.useSyncExternalStore=function(e,t,n){return S.H.useSyncExternalStore(e,t,n)},t.useTransition=function(){return S.H.useTransition()},t.version="19.1.0"},9876:(e,t,n)=>{"use strict";n.d(t,{e:()=>m,M:()=>h});var r=n(6540),a=n(5600),o=n(4581),i=n(6347),l=n(9532);function s(e){!function(e){const t=(0,i.W6)(),n=(0,l._q)(e);(0,r.useEffect)((()=>t.block(((e,t)=>n(e,t)))),[t,n])}(((t,n)=>{if("POP"===n)return e(t,n)}))}var u=n(6342),c=n(4848);const d=r.createContext(void 0);function f(){const e=function(){const e=(0,a.YL)(),{items:t}=(0,u.p)().navbar;return 0===t.length&&!e.component}(),t=(0,o.l)(),n=!e&&"mobile"===t,[i,l]=(0,r.useState)(!1),s=(0,r.useCallback)((()=>{l((e=>!e))}),[]);return(0,r.useEffect)((()=>{"desktop"===t&&l(!1)}),[t]),(0,r.useMemo)((()=>({disabled:e,shouldRender:n,toggle:s,shown:i})),[e,n,s,i])}function p({handler:e}){return s(e),null}function m({children:e}){const t=f();return(0,c.jsxs)(c.Fragment,{children:[t.shown&&(0,c.jsx)(p,{handler:()=>(t.toggle(),!1)}),(0,c.jsx)(d.Provider,{value:t,children:e})]})}function h(){const e=r.useContext(d);if(void 0===e)throw new l.dV("NavbarMobileSidebarProvider");return e}},9982:(e,t,n)=>{"use strict";e.exports=n(4477)}},e=>{e.O(0,[1869],(()=>{return t=8600,e(e.s=t);var t}));e.O()}]); \ No newline at end of file diff --git a/docs/assets/js/runtime~main.a356c557.js b/docs/assets/js/runtime~main.a356c557.js new file mode 100644 index 00000000..ec433a43 --- /dev/null +++ b/docs/assets/js/runtime~main.a356c557.js @@ -0,0 +1 @@ +(()=>{"use strict";var e,a,c,f,d,b={},t={};function r(e){var a=t[e];if(void 0!==a)return a.exports;var c=t[e]={id:e,loaded:!1,exports:{}};return b[e].call(c.exports,c,c.exports,r),c.loaded=!0,c.exports}r.m=b,r.c=t,e=[],r.O=(a,c,f,d)=>{if(!c){var b=1/0;for(i=0;i<e.length;i++){c=e[i][0],f=e[i][1],d=e[i][2];for(var t=!0,o=0;o<c.length;o++)(!1&d||b>=d)&&Object.keys(r.O).every(e=>r.O[e](c[o]))?c.splice(o--,1):(t=!1,d<b&&(b=d));if(t){e.splice(i--,1);var n=f();void 0!==n&&(a=n)}}return a}d=d||0;for(var i=e.length;i>0&&e[i-1][2]>d;i--)e[i]=e[i-1];e[i]=[c,f,d]},r.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return r.d(a,{a:a}),a},c=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,r.t=function(e,f){if(1&f&&(e=this(e)),8&f)return e;if("object"==typeof e&&e){if(4&f&&e.__esModule)return e;if(16&f&&"function"==typeof e.then)return e}var d=Object.create(null);r.r(d);var b={};a=a||[null,c({}),c([]),c(c)];for(var t=2&f&&e;("object"==typeof t||"function"==typeof t)&&!~a.indexOf(t);t=c(t))Object.getOwnPropertyNames(t).forEach(a=>b[a]=()=>e[a]);return b.default=()=>e,r.d(d,b),d},r.d=(e,a)=>{for(var c in a)r.o(a,c)&&!r.o(e,c)&&Object.defineProperty(e,c,{enumerable:!0,get:a[c]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((a,c)=>(r.f[c](e,a),a),[])),r.u=e=>"assets/js/"+({9:"845957d4",74:"340c7c5f",149:"e8321834",690:"3e1c5046",770:"aaabe254",940:"3980073a",1009:"50899a24",1065:"44d1c015",1235:"a7456010",1405:"e66382f6",1508:"56eef1be",1537:"8dd2df60",1606:"d152284c",1686:"08daf6b6",1782:"3650a837",1903:"acecf23e",1915:"c4822c4f",1964:"4af50aac",1965:"b0267ac9",1966:"3039fa8c",1999:"f994c8da",2092:"0dae2a8b",2344:"4caa95bf",2379:"4df0e30b",2576:"2303959d",2634:"c4f5d8e4",2711:"9e4087bc",2771:"e8202a51",2951:"9aed321e",3068:"d853e668",3212:"c31e69d4",3239:"23d02069",3249:"ccc49370",3322:"7fa80e1c",3645:"1a64de69",3976:"0e384e19",4064:"99009a21",4134:"393be207",4164:"bcee635f",4197:"a1ba6e62",4212:"621db11d",4416:"93f344c7",4424:"252a9097",4582:"14064408",4797:"4dd73b28",4813:"6875c492",5425:"479eb034",5430:"9796f4b8",5579:"6479fb86",5742:"aba21aa0",5801:"2c62ead1",6027:"1a4fe2b7",6054:"4137b431",6061:"1f391b9e",6063:"bd5b7851",6088:"df502808",6100:"176d210f",6267:"769c1945",6812:"bba9e323",6969:"14eb3368",7098:"a7bd4aaa",7290:"982cae12",7472:"814f3328",7508:"0a89f5c9",7609:"adb039a4",7643:"a6aa9e1f",7720:"fcf4f6ca",7795:"3c208a5b",7813:"74783256",7871:"be9e6e2d",8014:"9d13045e",8209:"01a85c17",8241:"4d1a2db0",8276:"c621f852",8401:"17896441",8439:"23a1b8fc",8465:"8ac6191a",8588:"67d4782a",8591:"ae7a6e8a",8593:"d01bc907",8643:"a2d4c71d",8933:"c7b64fcc",9048:"a94703ab",9095:"4b01b88a",9158:"616111d3",9197:"2d865531",9226:"6bb91276",9473:"ac51638e",9596:"0fff8dc8",9647:"5e95c892",9688:"bf2864cf",9795:"d9861b0f",9824:"8ea48c46",9858:"36994c47",9919:"0413d9af"}[e]||e)+"."+{9:"8290eece",74:"a496fe54",149:"dbcc9814",690:"22a78085",770:"ba3e9f5f",940:"43116f8b",1009:"53577092",1065:"880095c2",1235:"5f9bbb01",1405:"ad26fd04",1508:"ad4e065e",1537:"f10b075c",1606:"0800e671",1686:"852abb6b",1782:"fd1a89f8",1903:"4b2b5a9c",1915:"c80625fe",1964:"59b38fde",1965:"2ed3e1de",1966:"6e21a8e8",1999:"ba88b6c0",2092:"a19d1bf1",2237:"bfceba09",2344:"ca3bb1d0",2379:"00b3a0ff",2576:"3e6bfee0",2634:"3bbeb6fa",2711:"342bf9bc",2771:"49541ad2",2951:"0ede45c0",3068:"e1234cf0",3212:"0061b133",3239:"48cf6bc2",3249:"471f68d9",3322:"b2123398",3645:"844e372c",3976:"cb894d32",4064:"1a57fa22",4134:"6e979fd2",4164:"b2209c62",4197:"00332bec",4212:"1a835b77",4416:"7cebeb9e",4424:"294bfc5c",4582:"9ca9709f",4797:"06495428",4813:"1403aab6",5425:"fc01692f",5430:"34772e40",5579:"431b9ea8",5742:"ed09cce9",5801:"fc5c1b17",6027:"286ed6de",6054:"eda97697",6061:"4acd5995",6063:"1913daf9",6088:"a61c45a0",6100:"cd62be36",6267:"d427d253",6812:"c204228f",6870:"25f53758",6969:"60af715e",7098:"8da7b7a1",7290:"44c377b8",7472:"9351096c",7508:"190be82b",7518:"9525ffbe",7609:"88a13880",7643:"7671586a",7720:"8b12d88e",7795:"9fedc403",7813:"0fa34723",7871:"944ea2f0",8014:"3f255bd8",8209:"9618aedf",8241:"1e2d0e16",8276:"beef9a06",8401:"72377930",8439:"29176600",8465:"8ac511aa",8588:"96733ef0",8591:"58ed54b2",8593:"3a0113c2",8643:"c8f92a2a",8933:"70fc6828",9048:"3a38a667",9095:"e818c95d",9158:"9f2925b1",9197:"30aab314",9226:"d5be09e8",9473:"0b4da379",9596:"70193857",9647:"6dda521c",9688:"6fc085c5",9795:"449cd5bc",9824:"119645ab",9858:"337a7516",9919:"fc3050c7"}[e]+".js",r.miniCssF=e=>{},r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),r.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),f={},d="docs:",r.l=(e,a,c,b)=>{if(f[e])f[e].push(a);else{var t,o;if(void 0!==c)for(var n=document.getElementsByTagName("script"),i=0;i<n.length;i++){var u=n[i];if(u.getAttribute("src")==e||u.getAttribute("data-webpack")==d+c){t=u;break}}t||(o=!0,(t=document.createElement("script")).charset="utf-8",t.timeout=120,r.nc&&t.setAttribute("nonce",r.nc),t.setAttribute("data-webpack",d+c),t.src=e),f[e]=[a];var l=(a,c)=>{t.onerror=t.onload=null,clearTimeout(s);var d=f[e];if(delete f[e],t.parentNode&&t.parentNode.removeChild(t),d&&d.forEach(e=>e(c)),a)return a(c)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:t}),12e4);t.onerror=l.bind(null,t.onerror),t.onload=l.bind(null,t.onload),o&&document.head.appendChild(t)}},r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.p="/BharatMLStack/",r.gca=function(e){return e={14064408:"4582",17896441:"8401",74783256:"7813","845957d4":"9","340c7c5f":"74",e8321834:"149","3e1c5046":"690",aaabe254:"770","3980073a":"940","50899a24":"1009","44d1c015":"1065",a7456010:"1235",e66382f6:"1405","56eef1be":"1508","8dd2df60":"1537",d152284c:"1606","08daf6b6":"1686","3650a837":"1782",acecf23e:"1903",c4822c4f:"1915","4af50aac":"1964",b0267ac9:"1965","3039fa8c":"1966",f994c8da:"1999","0dae2a8b":"2092","4caa95bf":"2344","4df0e30b":"2379","2303959d":"2576",c4f5d8e4:"2634","9e4087bc":"2711",e8202a51:"2771","9aed321e":"2951",d853e668:"3068",c31e69d4:"3212","23d02069":"3239",ccc49370:"3249","7fa80e1c":"3322","1a64de69":"3645","0e384e19":"3976","99009a21":"4064","393be207":"4134",bcee635f:"4164",a1ba6e62:"4197","621db11d":"4212","93f344c7":"4416","252a9097":"4424","4dd73b28":"4797","6875c492":"4813","479eb034":"5425","9796f4b8":"5430","6479fb86":"5579",aba21aa0:"5742","2c62ead1":"5801","1a4fe2b7":"6027","4137b431":"6054","1f391b9e":"6061",bd5b7851:"6063",df502808:"6088","176d210f":"6100","769c1945":"6267",bba9e323:"6812","14eb3368":"6969",a7bd4aaa:"7098","982cae12":"7290","814f3328":"7472","0a89f5c9":"7508",adb039a4:"7609",a6aa9e1f:"7643",fcf4f6ca:"7720","3c208a5b":"7795",be9e6e2d:"7871","9d13045e":"8014","01a85c17":"8209","4d1a2db0":"8241",c621f852:"8276","23a1b8fc":"8439","8ac6191a":"8465","67d4782a":"8588",ae7a6e8a:"8591",d01bc907:"8593",a2d4c71d:"8643",c7b64fcc:"8933",a94703ab:"9048","4b01b88a":"9095","616111d3":"9158","2d865531":"9197","6bb91276":"9226",ac51638e:"9473","0fff8dc8":"9596","5e95c892":"9647",bf2864cf:"9688",d9861b0f:"9795","8ea48c46":"9824","36994c47":"9858","0413d9af":"9919"}[e]||e,r.p+r.u(e)},(()=>{var e={5354:0,1869:0};r.f.j=(a,c)=>{var f=r.o(e,a)?e[a]:void 0;if(0!==f)if(f)c.push(f[2]);else if(/^(1869|5354)$/.test(a))e[a]=0;else{var d=new Promise((c,d)=>f=e[a]=[c,d]);c.push(f[2]=d);var b=r.p+r.u(a),t=new Error;r.l(b,c=>{if(r.o(e,a)&&(0!==(f=e[a])&&(e[a]=void 0),f)){var d=c&&("load"===c.type?"missing":c.type),b=c&&c.target&&c.target.src;t.message="Loading chunk "+a+" failed.\n("+d+": "+b+")",t.name="ChunkLoadError",t.type=d,t.request=b,f[1](t)}},"chunk-"+a,a)}},r.O.j=a=>0===e[a];var a=(a,c)=>{var f,d,b=c[0],t=c[1],o=c[2],n=0;if(b.some(a=>0!==e[a])){for(f in t)r.o(t,f)&&(r.m[f]=t[f]);if(o)var i=o(r)}for(a&&a(c);n<b.length;n++)d=b[n],r.o(e,d)&&e[d]&&e[d][0](),e[d]=0;return r.O(i)},c=self.webpackChunkdocs=self.webpackChunkdocs||[];c.forEach(a.bind(null,0)),c.push=a.bind(null,c.push.bind(c))})()})(); \ No newline at end of file diff --git a/docs/assets/js/runtime~main.ac134f18.js b/docs/assets/js/runtime~main.ac134f18.js deleted file mode 100644 index 941cca7e..00000000 --- a/docs/assets/js/runtime~main.ac134f18.js +++ /dev/null @@ -1 +0,0 @@ -(()=>{"use strict";var e,a,c,t,r,f={},d={};function o(e){var a=d[e];if(void 0!==a)return a.exports;var c=d[e]={id:e,loaded:!1,exports:{}};return f[e].call(c.exports,c,c.exports,o),c.loaded=!0,c.exports}o.m=f,o.c=d,e=[],o.O=(a,c,t,r)=>{if(!c){var f=1/0;for(i=0;i<e.length;i++){c=e[i][0],t=e[i][1],r=e[i][2];for(var d=!0,n=0;n<c.length;n++)(!1&r||f>=r)&&Object.keys(o.O).every((e=>o.O[e](c[n])))?c.splice(n--,1):(d=!1,r<f&&(f=r));if(d){e.splice(i--,1);var b=t();void 0!==b&&(a=b)}}return a}r=r||0;for(var i=e.length;i>0&&e[i-1][2]>r;i--)e[i]=e[i-1];e[i]=[c,t,r]},o.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return o.d(a,{a:a}),a},c=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,o.t=function(e,t){if(1&t&&(e=this(e)),8&t)return e;if("object"==typeof e&&e){if(4&t&&e.__esModule)return e;if(16&t&&"function"==typeof e.then)return e}var r=Object.create(null);o.r(r);var f={};a=a||[null,c({}),c([]),c(c)];for(var d=2&t&&e;"object"==typeof d&&!~a.indexOf(d);d=c(d))Object.getOwnPropertyNames(d).forEach((a=>f[a]=()=>e[a]));return f.default=()=>e,o.d(r,f),r},o.d=(e,a)=>{for(var c in a)o.o(a,c)&&!o.o(e,c)&&Object.defineProperty(e,c,{enumerable:!0,get:a[c]})},o.f={},o.e=e=>Promise.all(Object.keys(o.f).reduce(((a,c)=>(o.f[c](e,a),a)),[])),o.u=e=>"assets/js/"+({690:"3e1c5046",940:"3980073a",1065:"44d1c015",1235:"a7456010",1405:"e66382f6",1606:"d152284c",1903:"acecf23e",1909:"f2c141e4",1915:"c4822c4f",1964:"4af50aac",1999:"f994c8da",2344:"4caa95bf",2634:"c4f5d8e4",2711:"9e4087bc",3249:"ccc49370",3322:"7fa80e1c",3645:"1a64de69",4134:"393be207",4212:"621db11d",4582:"14064408",4813:"6875c492",5425:"479eb034",5579:"6479fb86",5742:"aba21aa0",6054:"4137b431",6061:"1f391b9e",6062:"fa31f022",6100:"176d210f",6273:"09dd5be9",6969:"14eb3368",7098:"a7bd4aaa",7472:"814f3328",7643:"a6aa9e1f",7720:"fcf4f6ca",8209:"01a85c17",8261:"72dc5b25",8401:"17896441",8465:"8ac6191a",8588:"67d4782a",8933:"c7b64fcc",9048:"a94703ab",9158:"616111d3",9197:"2d865531",9473:"ac51638e",9596:"0fff8dc8",9647:"5e95c892",9858:"36994c47",9919:"0413d9af"}[e]||e)+"."+{690:"22a78085",940:"4ab85476",1065:"4db6c425",1235:"5f9bbb01",1405:"aaa6c9c6",1606:"8adb699d",1903:"4b2b5a9c",1909:"e3b70339",1915:"9fe3ec4e",1964:"a113ce2d",1999:"90063f83",2237:"bfceba09",2344:"2e5bda05",2634:"f5d4db47",2711:"b154716b",3249:"8f9e0351",3322:"b5f726bf",3645:"22893b6e",4134:"81b456e5",4212:"515621df",4582:"be0f96be",4813:"72d20027",5425:"0c88dc68",5579:"d4723af3",5742:"ed09cce9",6054:"2e5cd4ca",6061:"8e4379a0",6062:"c62034f4",6100:"47d21595",6273:"9cd209bb",6870:"6d10e1d8",6969:"e9006523",7098:"8da7b7a1",7472:"b45803b6",7518:"6ac3b679",7643:"a34fe105",7720:"d9bac5e5",8209:"5487421a",8261:"e57720a7",8401:"e023d99a",8465:"6f3973a2",8588:"09ac845b",8933:"9ff95135",9048:"50a268a2",9158:"9f2925b1",9197:"a5005531",9473:"cd23cca0",9596:"7a470540",9647:"a6c239e7",9858:"337a7516",9919:"cb9ba41f"}[e]+".js",o.miniCssF=e=>{},o.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),o.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),t={},r="docs:",o.l=(e,a,c,f)=>{if(t[e])t[e].push(a);else{var d,n;if(void 0!==c)for(var b=document.getElementsByTagName("script"),i=0;i<b.length;i++){var u=b[i];if(u.getAttribute("src")==e||u.getAttribute("data-webpack")==r+c){d=u;break}}d||(n=!0,(d=document.createElement("script")).charset="utf-8",d.timeout=120,o.nc&&d.setAttribute("nonce",o.nc),d.setAttribute("data-webpack",r+c),d.src=e),t[e]=[a];var l=(a,c)=>{d.onerror=d.onload=null,clearTimeout(s);var r=t[e];if(delete t[e],d.parentNode&&d.parentNode.removeChild(d),r&&r.forEach((e=>e(c))),a)return a(c)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:d}),12e4);d.onerror=l.bind(null,d.onerror),d.onload=l.bind(null,d.onload),n&&document.head.appendChild(d)}},o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.p="/BharatMLStack/",o.gca=function(e){return e={14064408:"4582",17896441:"8401","3e1c5046":"690","3980073a":"940","44d1c015":"1065",a7456010:"1235",e66382f6:"1405",d152284c:"1606",acecf23e:"1903",f2c141e4:"1909",c4822c4f:"1915","4af50aac":"1964",f994c8da:"1999","4caa95bf":"2344",c4f5d8e4:"2634","9e4087bc":"2711",ccc49370:"3249","7fa80e1c":"3322","1a64de69":"3645","393be207":"4134","621db11d":"4212","6875c492":"4813","479eb034":"5425","6479fb86":"5579",aba21aa0:"5742","4137b431":"6054","1f391b9e":"6061",fa31f022:"6062","176d210f":"6100","09dd5be9":"6273","14eb3368":"6969",a7bd4aaa:"7098","814f3328":"7472",a6aa9e1f:"7643",fcf4f6ca:"7720","01a85c17":"8209","72dc5b25":"8261","8ac6191a":"8465","67d4782a":"8588",c7b64fcc:"8933",a94703ab:"9048","616111d3":"9158","2d865531":"9197",ac51638e:"9473","0fff8dc8":"9596","5e95c892":"9647","36994c47":"9858","0413d9af":"9919"}[e]||e,o.p+o.u(e)},(()=>{var e={5354:0,1869:0};o.f.j=(a,c)=>{var t=o.o(e,a)?e[a]:void 0;if(0!==t)if(t)c.push(t[2]);else if(/^(1869|5354)$/.test(a))e[a]=0;else{var r=new Promise(((c,r)=>t=e[a]=[c,r]));c.push(t[2]=r);var f=o.p+o.u(a),d=new Error;o.l(f,(c=>{if(o.o(e,a)&&(0!==(t=e[a])&&(e[a]=void 0),t)){var r=c&&("load"===c.type?"missing":c.type),f=c&&c.target&&c.target.src;d.message="Loading chunk "+a+" failed.\n("+r+": "+f+")",d.name="ChunkLoadError",d.type=r,d.request=f,t[1](d)}}),"chunk-"+a,a)}},o.O.j=a=>0===e[a];var a=(a,c)=>{var t,r,f=c[0],d=c[1],n=c[2],b=0;if(f.some((a=>0!==e[a]))){for(t in d)o.o(d,t)&&(o.m[t]=d[t]);if(n)var i=n(o)}for(a&&a(c);b<f.length;b++)r=f[b],o.o(e,r)&&e[r]&&e[r][0](),e[r]=0;return o.O(i)},c=self.webpackChunkdocs=self.webpackChunkdocs||[];c.forEach(a.bind(null,0)),c.push=a.bind(null,c.push.bind(c))})()})(); \ No newline at end of file diff --git a/docs/blog/archive/index.html b/docs/blog/archive/index.html index a0076431..0f37cf62 100644 --- a/docs/blog/archive/index.html +++ b/docs/blog/archive/index.html @@ -4,14 +4,14 @@ <meta charset="UTF-8"> <meta name="generator" content="Docusaurus v3.8.1"> <title data-rh="true">Archive | BharatMLStack - - - + + + - + \ No newline at end of file diff --git a/docs/blog/atom.xml b/docs/blog/atom.xml index 84514334..01db1346 100644 --- a/docs/blog/atom.xml +++ b/docs/blog/atom.xml @@ -2,20 +2,672 @@ https://meesho.github.io/BharatMLStack/blog BharatMLStack Blog - 2022-11-15T00:00:00.000Z + 2026-02-19T00:00:00.000Z https://github.com/jpmonette/feed BharatMLStack Blog https://meesho.github.io/BharatMLStack/img/favicon.ico + + <![CDATA[Beyond Vector RAG: Building Agent Memory That Learns From Experience.]]> + https://meesho.github.io/BharatMLStack/blog/episodic-memory-for-agents + + 2026-02-19T00:00:00.000Z + + BharatMLStack +Agent memory has come a long way. Persistent context, vector retrieval, knowledge graphs — the building blocks are real and getting better fast.

+

But most of what we call "memory" today is still closer to search: chunk text, embed it, retrieve whatever looks similar at query time. That works well for recalling facts and preferences. It starts to break down when you need an agent to recall what happened last time, learn from a mistake, or avoid repeating a failed approach.

+

We are trying to experiment something different. An episodic memory system where a frozen LLM — same weights, no retraining — produces increasingly better decisions over time because the memory feeding it context is continuously evolving. +Then we tested it. The results were interesting.

+

The Gap Nobody Talks About

+

Here's a scenario every engineering team has encountered: AI agent hits a Redis connection pool exhaustion issue. It misdiagnoses it as a database problem. You correct it. Next week, a different service has the exact same failure pattern. The agent makes the exact same mistake.

+

Why? Because LLMs don't learn at inference time. Corrections adjust behavior within a conversation. Once the session ends, the lesson is gone. The model weights haven't changed. The next conversation starts from zero.

+

Current "memory" systems don't fully address this. They store facts — user preferences, document chunks, conversation summaries. But facts aren't experience. Knowing that "Redis connection pools can exhaust under load" is different from remembering "last time I saw 500 errors under load, I assumed it was the database, I was wrong, it was actually the connection pool, and here's the correction I received."

+

The first is a fact. The second is an episode. The difference matters.

+

What's Wrong With Vector RAG as Memory

+

We identified five structural gaps in how current agent frameworks handle memory:

+

No concept of time. Two events are either semantically similar or they're not. The system can't represent "this happened after that" without distorting similarity scores. An agent can't reason about sequence or causality.

+

No concept of situation. A production incident and a design review might use the same technical vocabulary. Flat vector search can't distinguish them. Your agent retrieves planning notes when it should be retrieving incident postmortems.

+

No outcome tracking. The system stores what happened but not whether it worked. A failed approach and a successful one are equally retrievable. The agent has no way to prefer strategies that worked over strategies that didn't.

+

Summaries destroy evidence. Summarization-based memory compresses experience but discards the reasoning chain. The agent loses the ability to explain how it arrived at a conclusion. The audit trail is gone.

+

No causal links. Each memory chunk is independent. There's no way to express that incident A caused decision B, which led to outcome C, which was corrected by approach D. Without this structure, the agent can't traverse chains of reasoning.

+

These gaps compound. As an agent accumulates more experience, flat vector memory gets noisier, more contradictory, and less useful. The system degrades precisely when it should be improving.

+

The Architecture: Episodic Memory

+

We are building a memory system modeled on how human episodic memory works — not as a metaphor, but as an engineering specification.

+

The system has four layers:

+

Layer 1: Immutable Timeline

+

Every piece of agent experience is recorded as an append-only timeline entry. Each entry carries a semantic embedding (what it means), a timestamp (when it happened), and a state label (what situation the agent was in — debugging, planning, code review, incident response). Entries are never modified, never deleted, never summarized. This is the source of truth.

+

Layer 2: Episode Segmentation

+

The system watches the timeline and detects when one coherent unit of experience ends and another begins — via state transitions, semantic shifts, temporal gaps, or explicit signals. Each episode is a reference into the timeline (not a copy) with a generated summary, an outcome (SUCCESS, FAILURE, PARTIAL, UNKNOWN), decisions made, assumptions held, and corrections received.

+

The outcome field is the most important thing that doesn't exist in any current memory system. Without it, you can't learn from mistakes.

+

Layer 3: Episodic Graph

+

Episodes are connected through typed, weighted links: CAUSED_BY, LED_TO, RETRY_OF, LEARNED_FROM, CONTINUATION, CONTRADICTED. Over time, this forms a directed graph that enables traversal by meaning and causality. You can follow the chain: "this incident caused that investigation, which led to a failed fix, which was corrected by this approach."

+

Layer 4: Generalized Facts

+

When multiple episodes exhibit consistent patterns, the system extracts reasoning heuristics: "When services fail immediately after deployment with no traffic change, investigate configuration errors before connection pool problems." Facts are versioned, never overwritten, and maintain links back to supporting and contradicting episodes. When contradicting evidence accumulates, confidence decreases. When confidence drops below a threshold, the fact is revised — but the old version is preserved.

+

The LLM sits above all four layers. At query time, the system assembles structured context — relevant episodes with outcomes, applicable facts with confidence scores, causal narratives — and passes it to the LLM for reasoning. The model reasons over structured memory. It doesn't store or manage memory.

+

The Reinforcement Loop

+

This is where it comes together:

+
    +
  1. Agent reasons using retrieved episodes and facts
  2. +
  3. Outcome is detected (CI pass/fail, user correction, test result)
  4. +
  5. New episode is created with outcome tracking
  6. +
  7. Links are created between the retrieved episodes and the new episode
  8. +
  9. Facts are reinforced (if outcome aligned) or contradicted (if outcome conflicted)
  10. +
  11. If the decision was wrong and corrected, a LEARNED_FROM link is created
  12. +
+

The model weights never change. The memory structure evolves continuously. A frozen LLM produces better decisions over time because it receives better context from richer memory.

+

The Experiment

+

We built the full system in Python (~1,000 lines) and tested it head-to-head against a baseline flat-vector RAG agent across a 9-round synthetic debugging scenario. Both agents used the identical LLM (Claude Sonnet 4) for reasoning. The only variable was the memory system.

+

The scenario was designed to test five capabilities:

+
Round TypeWhat It TestsRounds
LEARNCan the agent build experience from failures?1, 2, 4
RED HERRINGCan the agent resist applying a pattern when it doesn't fit?3
TESTCan the agent apply learned patterns to new services?5, 6
SUBTLECan the agent generalize to different symptoms, same root cause?7
CORRECTIONAfter being corrected, does the agent adapt?8, 9
+

Rounds 1-4 build experience: three connection pool failures across different services, plus one red herring (a deployment config error that looks like a connection pool issue). Rounds 5-7 test whether the agent applies the learned pattern to unfamiliar services and subtle symptom variations. Rounds 8-9 are the critical test: the agent is corrected after misdiagnosing a deployment-correlated error, then tested on a near-identical scenario to see if it adapts.

+

Results

+

Decision Accuracy

+
RoundTypeEpisodic AgentBaseline Agent
1LEARN
2LEARN
3RED HERRING
4LEARN
5TEST
6TEST
7SUBTLE
8CORRECTION
9CORRECTION
Total7/9 (78%)5/9 (56%)
+

The episodic agent won 7-5. A 40% relative improvement in decision accuracy using the exact same LLM.

+

Where the Gap Opened

+

The episodic agent's advantage concentrated in exactly the rounds designed to test memory quality:

+

Rounds 5-6 (pattern application): The episodic agent cited 4 past failure episodes with connection pool exhaustion as root cause, complete with correction annotations. It correctly identified pool exhaustion in new services. The baseline retrieved disconnected chunks and suggested checking timeout configurations — a pattern it picked up from the Round 3 red herring.

+

Round 7 (subtle symptoms — latency increase, no errors): Both agents had the same evidence available. The episodic agent's retrieval surfaced a diverse set of episodes (thanks to MMR diversity filtering) including the Redis pool exhaustion from Round 6, which primed it to recognize that latency without errors can still be pool contention. The baseline defaulted to "check recent config changes."

+

Round 9 (adaptation after correction): This is the result we're most proud of. Look at the episodic agent's reasoning:

+
+

"Episode 1 directly parallels this situation — errors spiking immediately after a deployment (v2.4.1 then, v3.1.0 now) with no traffic change. In that case, the root cause was a database migration that dropped an index. The generalized fact confirms that deployment-related issues with immediate onset after version changes are more likely caused by configuration errors or missing dependencies than by connection pool problems."

+
+

It cited a specific past episode by analogy, quoted a generalized fact, and explained why this situation matches the deployment pattern rather than the connection pool pattern. The baseline gave a vaguer assessment.

+

Retrieval Quality

+

This is where the structural difference is most visible:

+
MetricEpisodic AgentBaseline Agent
Retrieved items with explicit outcome labels100%25%
Correct pattern applications (Rounds 4-7)4/41/4
False positives (Rounds 8-9)00
+

Every item the episodic agent retrieved carried a structured outcome label (SUCCESS or FAILURE) with correction details. Only 25% of the baseline's chunks contained any outcome information — and those were incidental text mentions, not structured labels.

+

The episodic agent correctly applied the connection pool pattern in all four rounds where it was the root cause, and correctly avoided it in both rounds where it wasn't. The baseline applied it correctly once.

+

What Didn't Work

+

Two things didn't work as anticipated:

+

Round 3 (red herring): Both agents failed. The symptoms looked like connection pool issues, but the root cause was a deployment config change. At this point, the episodic agent had only seen connection pool episodes — it had no counter-evidence for deployment-correlated errors. You can't distinguish patterns you've only seen one side of. After Round 8 introduced a correction, the agent successfully avoided this mistake in Round 9.

+

Fact quality variance. Some extracted facts were specific and actionable ("Deployment-related issues with immediate onset are more likely configuration errors"). Others were vague ("Initial symptom-based diagnosis often leads to misidentifying the root cause"). A production system needs a usefulness filter, not just a confidence score.

+

What This Means

+

The most important finding isn't the accuracy improvement. It's that the reinforcement loop closes without retraining.

+

In the POC, we observed:

+
    +
  • Rounds 1-4: Agent encounters failures, episodes recorded with outcomes and corrections
  • +
  • After Round 4: Fact extracted — "Connection pool exhaustion is a common root cause under load"
  • +
  • Rounds 5-7: Agent applies the pattern with increasing confidence (fact support count grows)
  • +
  • Round 8: Agent encounters a deployment error, correctly identifies it as config, gets corrected
  • +
  • After Round 8: New fact — "Deployment-related issues with immediate onset are more likely configuration errors"
  • +
  • Round 9: Agent receives near-identical scenario, correctly avoids connection pool pattern, cites the Round 8 correction
  • +
+

The model didn't change. The memory evolved. That's the whole point.

+

How It Compares to Existing Solutions

+

Agent memory is a fast-moving space with several strong systems, each solving a different slice of the problem:

+

Mem0 excels at persistent personalization — extracting user preferences, managing session context, and reducing token costs through intelligent compression. It's the most production-ready memory layer available and integrates with nearly every agent framework. Its focus is on remembering about users and conversations rather than learning from task-level outcomes, which is a different problem than the one we're exploring here.

+

Zep/Graphiti is doing some of the most interesting work in temporal knowledge graphs. Their bi-temporal model — tracking both when an event occurred and when it was ingested — addresses a real structural gap in how agent memory handles changing facts over time. Their episode and entity subgraphs share some philosophical DNA with our approach. Where our work diverges is in outcome tracking and reinforcement: we're specifically focused on whether a decision worked, and using that signal to update memory structure.

+

Letta (formerly MemGPT) pioneered self-editing memory — giving the LLM tools to manage its own memory blocks. This is a powerful paradigm, and their recent work on "Context Repositories" and sleep-time compute suggests they're actively pushing toward agents that learn over time. Their team has been transparent that experiential learning is an unsolved problem, which is part of what motivated our exploration.

+

MemRL (Jan 2026 paper) is the closest to our work academically. It shares the core insight of decoupling stable LLM reasoning from plastic, evolving memory. Their approach uses reinforcement learning to assign utility Q-values to memories, which is elegant but requires training a value function. Our approach is purely structural — no training step, no Q-values, just graph evolution and LLM-based reasoning over outcomes.

+

The common thread: most existing systems focus on knowledge persistence — remembering facts, preferences, and conversation history across sessions. The problem we're exploring is experiential learning — tracking whether past decisions worked, forming causal chains between episodes, and extracting reasoning heuristics that improve over time. These are complementary capabilities that would be needed by an ideal production system.

+

Try It Yourself

+

The prototype is available in our experiments directory:

+
experiments/episodic-memory-prototype/
├── memory/ # Timeline, encoder, episodes, graph, facts, retriever, reinforcer
├── agent/ # Episodic memory agent
├── baseline/ # Flat vector RAG agent (comparison)
├── simulator/ # 9-round debugging scenario
├── eval/ # Head-to-head comparison + scoring
└── tests/
+

To run the comparison:

+
cd experiments/episodic-memory-prototype
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
export ANTHROPIC_API_KEY=sk-ant-...
python -m eval.compare
+

Without an API key, it runs in heuristic mode (keyword-based decisions). With a key, both agents use Claude Sonnet for reasoning — that's where the quality gap becomes visible.

+

Conclusion

+

This is a 9-round synthetic scenario we designed. It demonstrates the poc architecture works end-to-end and shows where episodic memory provides qualitatively different reasoning. It is not a peer-reviewed benchmark and should not be interpreted as a statistically rigorous claim. We're publishing the prototype so others can reproduce and extend the evaluation. +If this sparks interest do trigger github discussion.

+
+

The episodic memory prototype is available in BharatMLStack repo at /experiments/episodic-memory-prototype

]]>
+ + Adarsha Das + https://github.com/a0d00kc + + + + + + +
+ + <![CDATA[LLM Inference Optimization Techniques: Engineering Sub-Second Latency at Scale]]> + https://meesho.github.io/BharatMLStack/blog/llm-inference-optimization-sub-sec-latency + + 2025-06-02T00:00:00.000Z + + BharatMLStack +Raw execution of Large Language Models is inherently expensive and memory-intensive. To achieve sub-second latency and high throughput, we implement a multi-layered optimization strategy that targets the entire inference stack—from memory management to kernel execution.

+

1. Advanced Memory Management: Paged & Prefix KV Caching

+

The most significant bottleneck in LLM inference is not always compute, but memory bandwidth—specifically managing the Key-Value (KV) cache.

+

Paged KV caching

+

Standard caching suffers from fragmentation. We use Paged KV caching, which operates similarly to an operating system's virtual memory: the KV cache is divided into non-contiguous blocks. This lets us serve larger batch sizes without running out of memory.

+

KV cache quantization

+

To further maximize available memory, we implement KV cache quantization (e.g., FP8). By compressing stored attention keys and values from 16-bit to 8-bit, we nearly double the effective context window capacity of the GPU, allowing longer conversations or larger batches without materially degrading quality.

+

Prefix caching (the "voice bot" optimizer)

+

For use cases like GenAI voice bots where the system prompt (e.g., "You are a helpful assistant...") is static across thousands of requests, we enable prefix caching.

+
    +
  • Impact: By reusing pre-computed KV states for common prefixes, we achieve a cache hit rate of ~90%. This reduces Time To First Token (TTFT) by skipping redundant computation of the system prompt.
  • +
+

2. Aggressive Quantization (INT4 AWQ & FP8)

+

Running models in their native 16-bit precision (BF16) restricts maximum batch size and throughput. We use quantization to shrink model weights without sacrificing accuracy.

+

INT4 AWQ (Activation-aware Weight Quantization)

+

For the Llama 3 family, we use AWQ to compress weights to 4 bits. This reduces model size by ~75%, allowing larger models to fit into L4 GPU memory and significantly improving token generation speed.

+

FP8 precision

+

For NVIDIA Hopper (H100) architectures, we are exploring FP8 quantization, leveraging native FP8 tensor cores to accelerate matrix multiplications while maintaining a higher dynamic range than integer quantization.

+
    +
  • Verification: We validate quantized models by comparing dot-product similarity of embeddings against the FP16 baseline, consistently achieving >99% similarity.
  • +
+

3. Kernel Fusion & Custom Plugins

+

To minimize overhead from launching thousands of small GPU operations, we fuse them into monolithic kernels using NVIDIA TensorRT plugins.

+
    +
  • Flash attention & FMHA: We enable Fused Multi-Head Attention (FMHA) combined with flash attention to reduce memory reads/writes.
  • +
  • GEMM plugins: We use specialized GEMM plugins to accelerate transformer linear layers.
  • +
  • Removing input padding: Instead of padding short sequences to match the longest, we remove input padding so the GPU processes only valid tokens.
  • +
+

4. Inflight (Continuous) Batching

+

Traditional static batching waits for all requests in a batch to finish before returning results—so one long response delays everyone else.

+

We implement inflight batching: as soon as one request completes, its slot is freed and filled by a new request from the queue. This keeps GPUs saturated and decouples latency of short queries from long ones.

+

5. Parallelism Strategies: Scaling Beyond One GPU

+

For large models (e.g., 70B+ parameters) that cannot fit into the VRAM of a single GPU, we use parallelism strategies.

+
    +
  • Tensor parallelism (TP): Split weight matrices across multiple GPUs (e.g., 4× L4 or 8× A100). Each GPU computes a shard and outputs are reduced at every layer.
  • +
  • Pipeline parallelism (PP): Split model layers across GPUs to pipeline compute (e.g., while one GPU computes later layers for Request A, another starts early layers for Request B).
  • +
+

6. Speculative Decoding

+

To reduce inter-token latency (ITL), we explore speculative decoding.

+
    +
  • Mechanism: A smaller, faster "draft" model speculatively generates a short token sequence (e.g., 5 tokens).
  • +
  • Verification: The larger target model verifies those tokens in one parallel forward pass. If correct, we effectively generate multiple tokens per large-model step; if not, we discard and regenerate. This is effective for predictable text, improving perceived generation speed.
  • +
+

Few Benchmarks

+

Below are a couple of representative use cases and performance numbers.

+

Search query rewriting

+
    +
  • LLM: Fine-tuned llama-3.2-1B
  • +
  • Input & output token length: ~10–20
  • +
  • Response type: Non-streaming
  • +
+
Inference runtimeHardwareMax requests/secMax p99 latency
TensorRT-LLM4 × L4 GPUs (multi-GPU)100095 ms
TensorRT-LLM1 × A100 40 GB GPU100069 ms
+

Voice bot query

+
    +
  • LLM: Llama-3.1-8B
  • +
  • Input token length: ~1900–2000
  • +
  • Output token length: ~200
  • +
  • Response type: Streaming
  • +
+
Inference runtimeConcurrencyp99 TTFT (ms)p99 ITL (ms)Token throughput (tokens/sec)Request throughput (req/sec)Hardware
TensorRT-LLM136.2722.7845.660.23L4
TensorRT-LLM249.8123.2189.370.45L4
TensorRT-LLM455.3336.62153.390.78L4
TensorRT-LLM866.539.11279.881.47L4
TensorRT-LLM16131.830.39547.82.77L4
TensorRT-LLM32277.2248.02925.74.78L4
TensorRT-LLM64498.5271.621,164.406.2L4
TensorRT-LLM128677.31120.371,445.187.69L4
TensorRT-LLM2561,926.31216.881,600.818.52L4
TensorRT-LLM121.179.24130.050.68A100
TensorRT-LLM225.789.21264.51.35A100
TensorRT-LLM428.5210.99437.692.27A100
TensorRT-LLM834.412.61760.493.96A100
TensorRT-LLM1668.0314.321,343.807.01A100
TensorRT-LLM32185.9616.822,287.3011.92A100
TensorRT-LLM64136.8721.173,625.2218.89A100
TensorRT-LLM128463.7834.154,456.5123.24A100
TensorRT-LLM256890.1259.185,188.2427.05A100
+

Conclusion

+

High-performance LLM inference is fundamentally a systems engineering problem: memory efficiency, kernel execution, batching strategy, and parallelism determine real-world latency and throughput. Techniques such as paged KV caching, aggressive quantization, kernel fusion, and inflight batching improve GPU utilization while reducing latency and memory pressure.

+

These optimizations enable the platform to deliver sub-second responses, sustain high concurrency, and efficiently serve both lightweight and long-context workloads. By continuously optimizing across the full inference stack, we keep LLM serving scalable, cost-efficient, and production-ready for real-time AI applications.

]]>
+ + Jaya Kumar + https://github.com/jayakommuru + + + + + + + +
+ + <![CDATA[Designing a Production-Grade LLM Inference Platform: From Model Weights to Scalable GPU Serving]]> + https://meesho.github.io/BharatMLStack/blog/multi-engine-llm-inferencing-platform + + 2025-03-29T00:00:00.000Z + + BharatMLStack +Serving large language models in production introduces new challenges across infrastructure, performance optimization, and operational lifecycle management. The LLM Inference Platform addresses these challenges by providing a unified system for deploying and managing open-source and fine-tuned LLMs at scale.

+

The platform implements a complete LLMOps lifecycle — from model registration and automated compilation to deployment, runtime optimization, and monitoring. Designed as a self-service environment, users can onboard models directly from open repositories such as Hugging Face or upload custom fine-tuned models, and deploy them using a single-click workflow with no manual infrastructure or configuration steps required.

+

In addition to fully automated deployment, the platform allows users to select and apply custom inference optimization techniques — such as quantization strategies, batching configurations, and runtime-specific performance enhancements — enabling teams to balance latency, throughput, and cost based on their use case. The goal is to reduce operational friction while enabling high-performance, production-grade LLM inference.

+

Why LLM Inference Is not just bigger ML model serving

+

Large language model (LLM) inference introduces a fundamentally different set of challenges compared to traditional machine learning inference. While classical ML models typically perform a single forward pass to produce a fixed prediction, LLMs operate as autoregressive systems, generating outputs token by token based on previously generated context. This difference dramatically changes how inference systems must be designed, optimized, and scaled.

+

Autoregressive Generation and Sequential Computation:

+

Unlike traditional models such as classifiers or recommenders — where inference cost is relatively constant — LLMs generate responses incrementally. Each new token depends on all previously generated tokens, making inference inherently sequential and dynamic. This means latency and compute requirements vary significantly depending on prompt length and output size, introducing complexity in scheduling and resource allocation. +Because tokens cannot be generated fully in parallel during decoding, GPUs may become underutilized without specialized batching and scheduling strategies. This has led to the development of dedicated LLM inference engines optimized for token-level execution.

+

Prefill and Decode Phases:

+

LLM inference typically consists of two distinct stages:

+
    +
  • Prefill phase — the model processes the input prompt and builds internal representations. This stage is compute-heavy and highly parallelizable.
  • +
  • Decode phase — the model generates tokens sequentially, predicting one token at a time using previously generated context.
  • +
+

The decode stage often becomes memory-bound rather than compute-bound, which creates new performance bottlenecks compared to traditional ML workloads.

+

Context Management and KV Caching:

+

Another fundamental difference lies in how LLMs maintain context. Transformer-based models rely on attention mechanisms that require access to past token representations. To avoid recomputing these representations repeatedly, inference engines use key-value (KV) caching, which stores intermediate activations from previous tokens. +KV caching significantly improves performance by eliminating redundant computation, but it introduces new challenges:

+
    +
  • Memory consumption grows with sequence length and batch size
  • +
  • GPU memory becomes a critical bottleneck
  • +
  • Efficient memory management becomes essential for scaling concurrent requests
  • +
+

This tradeoff between compute efficiency and memory usage is unique to LLM inference workloads.

+

Dynamic and Irregular Workloads:

+

Traditional ML inference typically operates on fixed-size inputs with predictable latency. In contrast, LLM requests vary widely in prompt length, output length, and runtime behavior. As a result:

+
    +
  • Batch sizes must be dynamic rather than static
  • +
  • Requests may enter and leave batches asynchronously
  • +
  • Scheduling systems must continuously rebalance workloads to maximize GPU utilization
  • +
+

These characteristics require specialized serving architectures that differ significantly from standard ML serving pipelines.

+

Streaming and User Experience Constraints:

+

Another distinguishing factor is the expectation of real-time streaming responses. Instead of returning a single output, LLM systems often stream tokens to users as they are generated. +Because of these differences — sequential generation, growing memory requirements, dynamic workloads, and streaming constraints — LLM inference cannot be treated as a simple extension of existing ML serving systems. Production platforms must incorporate specialized runtime engines, advanced optimization techniques, and observability tailored specifically to LLM workloads.

+

LLMOps: High-Level Architecture

+

LLM Architecture

+

The LLM Inference Framework is designed as a fully automated, end-to-end system for deploying and operating open-source and fine-tuned large language models at scale. The architecture abstracts the complexity of model optimization, hardware selection, deployment, and runtime management into a unified workflow that enables users to move from raw model weights to production-ready inference endpoints with minimal manual intervention.

+

Our LLM Inference Framework is architected not just as a serving engine, but as a complete lifecycle management system. As illustrated in the high-level design below, the platform automates the journey of a model through seven distinct stages, ensuring reproducibility, performance, and scalability.

+
    +
  1. +

    Onboarding & Registration (The Source of Truth)

    +

    The lifecycle begins with the Data Scientist or engineer.

    +
      +
    • Model Ingestion: Users onboard models—whether open-source (Hugging Face, NeMo) or internally fine-tuned—via the Truffle Box SDK/UI.
    • +
    • LLM + Prompt Registry: Unlike traditional systems that only track model weights, our registry is a unified control plane. It stores both the Model Artifacts and the Prompt Templates. This allows Data Scientists to register and version-control prompts (e.g., "customer_support_v2") independently of the application code.
    • +
    +
  2. +
  3. +

    The "Black Box" Build Engine

    +

    Once a model is registered, the Automated LLM Compiler + Quantizer Module kicks off a background job on ephemeral GPU resources.

    +
      +
    • Transformation: The raw model is converted into a TRT-LLM Checkpoint.
    • +
    • Quantization: The system automatically applies quantization algorithms (like INT4 AWQ or FP8) to reduce memory footprint.
    • +
    • Engine Building: Finally, it compiles a highly optimized TRT Engine specifically tuned for the target hardware.
    • +
    +
  4. +
  5. +

    Intelligent Profiling & Validation

    +

    Before deployment, the new engine passes through the Hardware & Inference Runtime Profiler.

    +
      +
    • Benchmarking: This module empirically tests the engine against various hardware configurations (L4 vs. A100) and runtimes (TRT-LLM vs. vLLM).
    • +
    • Optimization: It recommends the optimal configuration that meets latency SLAs (Time-To-First-Token) while minimizing cost.
    • +
    +
  6. +
  7. +

    Smart Artifact Generation & Distribution

    +

    To solve the Kubernetes "Cold Start" problem, the LLM Serving Artifacts Generation module packages the model using a bifurcated strategy:

    +
      +
    • Standard Models: Artifacts are uploaded to Cloud Storage (GCS) and downloaded by pods at startup.
    • +
    • Very Large Models: For massive models (>8GB) where network downloads are too slow, the system pre-caches the model onto Secondary Boot Disks. These disks are attached directly to new GPU nodes during autoscaling, eliminating download wait times.
    • +
    +
  8. +
  9. +

    Image Streaming & Deployment

    +

    Simultaneously, the inference runtime container images are pulled from the Artifact Registry.

    +
      +
    • Image Streaming: We utilize container image streaming to allow pods to start initializing while the massive Triton/Dynamo container layers are still downloading, further shaving seconds off the startup time. link
    • +
    +
  10. +
  11. +

    The Inference Runtime (Kubernetes)

    +

    The workload lands on Kubernetes with Autoscaling.

    +
      +
    • Dynamic Backends: Depending on the profile generated in Stage 3, the pod initializes either TensorRT-LLM (for throughput) or vLLM (for flexibility), or spins up a Dynamo worker for distributed inference.
    • +
    • Data Loading: The pod either downloads the model from Cloud Storage or mounts the pre-warmed Secondary Boot Disk ("Pull from Disk").
    • +
    +
  12. +
  13. +

    Client Interaction & Observability

    +

    Finally, the LLM Inference Client executes the request.

    +
      +
    • Prompt Injection: The client pulls the specific prompt template ID from the Registry, ensuring the exact versioned instructions are used.
    • +
    • Streaming Response: The request is sent via gRPC, and tokens are streamed back to the user in real-time.
    • +
    +
  14. +
  15. +

    Observability: Monitoring the Pulse of GenAI

    +

    In traditional microservices, success is measured by CPU utilization and request latency (p99). For Large Language Models, these metrics are insufficient. A user doesn't care if the GPU is at 80% utilization; they care about how fast the first word appears and how smoothly the rest of the sentence follows.

    +

    To capture the true user experience, our platform instrumentation focuses on three critical LLM-specific metrics:

    +
      +
    1. +

      Time to First Token (TTFT)

      +
        +
      • Definition: TTFT measures the time elapsed from the moment a request is received until the very first token is generated and streamed back to the user.
      • +
      • Why it matters: This represents the "Prefill Phase" latency—the time the model takes to process the input prompt and load weights. A high TTFT makes the application feel unresponsive or "hung."
      • +
      • Optimization: We closely monitor TTFT to ensure our Prefix Caching is effective (aiming for high cache hitrates), which drastically lowers this metric by skipping redundant prompt processing.
      • +
      +
    2. +
    3. +

      Inter-Token Latency (ITL)

      +
        +
      • Definition: ITL measures the average time interval between the generation of consecutive tokens during the "Decode Phase".
      • +
      • Why it matters: This defines the "perceived speed" of reading. Even if the first token is fast (low TTFT), high ITL makes the text generation look "jerky" or slow to the user.
      • +
      • Benchmarks: In our testing with Llama 3.1, we track p99 ITL to ensure it stays below human reading speeds to maintain a natural conversational flow.
      • +
      +
    4. +
    5. +

      Token Throughput vs. Request Throughput

      +
        +
      • We distinguish between two types of throughput to balance system efficiency with user load:
      • +
      • Token Throughput (tokens/sec): The total number of tokens generated across all concurrent requests. This measures the raw compute efficiency of the GPU and the effectiveness of batching.
      • +
      • Request Throughput (req/sec): The number of distinct user queries served per second. We use this to determine autoscaling thresholds, ensuring we scale out before the queue depth impacts ITL.
      • +
      +
    6. +
    7. +

      The Monitoring Stack

      +
        +
      • Real-time Dashboards: We utilize Grafana to visualize these streaming metrics in real-time, allowing on-call engineers to spot "slow generation" incidents that generic "500 error" alerts would miss.
      • +
      • Request Tracing: Since Triton Inference Server does not log request payloads by default, we integrate a Helix Client to asynchronously publish request logs to Log Tables. This allows us to trace a specific "slow" request back to its prompt to understand if a complex input caused the latency spike.
      • +
      +
    8. +
    +
  16. +
+

Supported Inference backends (TensorRT LLM, Dynamo & vLLM)

+

Tailored for the Use Case: We do not believe in a "one-size-fits-all" approach to inference. Different use cases—whether a real-time voice bot requiring ultra-lowsub-second latency or a massive reasoning task requiring huge context windows—demand different runtime characteristics. Our platform is designed to be runtime-agnostic, allowing us to automatically select and tailor the best engine based on the specific requirements of the application:

+
    +
  1. +

    TensorRT-LLM: The High-Performance Standard

    +

    Suitable for: High-throughput production workloads where latency is critical (e.g., customer support chat, real-time voice bots).

    +

    TensorRT-LLM serves as our default backend for these scenarios. Our internal benchmarks on Llama 3.1 and 3.2 models demonstrated that a tuned TensorRT-LLM engine significantly outperforms standard runtimes, especially when utilizing INT4 AWQ and FP8 quantization .

    +

    Key optimizations we tailor for these high-load cases include:

    +
      +
    • Optimized execution via TensorRT engine compilation
    • +
    • Quantization-aware execution for reduced memory usage and improved throughput
    • +
    • Inflight Batching: Allowing requests to be processed continuously without waiting for the entire batch to finish, drastically improving GPU utilization .
    • +
    • Custom Plugins: Enabling specific NVIDIA plugins like the GEMM plugin and GPT Attention plugin to accelerate matrix multiplications and attention mechanisms .
    • +
    +
  2. +
  3. +

    Dynamo: Distributed Inference for Reasoning Models

    +

    Suitable for: Very large "reasoning" models (70B+) or scenarios requiring massive context windows where a single GPU's memory is insufficient.

    +

    For these memory-bound tasks, we utilize Dynamo, a low-latency distributed inference framework . Unlike monolithic servers, Dynamo disaggregates the inference process to scale resources horizontally:

    +
      +
    • KV Aware Routing: A specialized router directs requests to workers that already hold the relevant Key-Value (KV) cache, minimizing redundant computation .
    • +
    • Prefill vs. Decode Split: The workload is divided into Prefill Workers (processing the prompt) and Decode Workers (generating tokens), allowing us to scale the compute-heavy "reading" phase independently from the memory-heavy "writing" phase .
    • +
    • Distributed execution across multiple GPU resources
    • +
    +
  4. +
  5. +

    vLLM: The Flexible Baseline

    +

    Suitable for: Rapid prototyping, testing new model architectures, or low-traffic internal tools where ease of deployment outweighs raw throughput.

    +

    While TensorRT-LLM is optimized for maximum speed, vLLM provides a robust and flexible baseline .

    +
      +
    • High throughput through dynamic batching and efficient memory utilization
    • +
    • Paged KV cache management for handling long contexts and concurrent requests
    • +
    • Strong support for open-source model ecosystems
    • +
    • Rapid Adoption: It allows us to onboard new model architectures immediately without waiting for a custom TensorRT build.
    • +
    • Benchmarking Insight: In our internal tests, vLLM provided a strong baseline but often lacked the specific max-token optimizations present in our custom TRT engines . We use it strategically for initial testing before committing to a full TensorRT optimization pipeline.
    • +
    +
  6. +
+

Conclusion

+

Large language model inference introduces a fundamentally new class of infrastructure challenges—where performance is governed not just by raw compute, but by memory efficiency, intelligent scheduling, runtime specialization, and lifecycle automation. Unlike traditional ML serving, LLM inference requires systems that understand token-level execution, manage rapidly growing context state, and continuously balance latency, throughput, and cost under highly dynamic workloads.

+

The LLM Inference Framework addresses these challenges by transforming inference into a fully automated, reproducible lifecycle—from model onboarding and compilation to deployment, optimization, and observability. By integrating automated quantization and engine compilation, intelligent runtime selection, cold-start mitigation strategies, and LLM-specific observability metrics such as Time-to-First-Token and Inter-Token Latency, the platform ensures both high performance and operational simplicity.

+

Equally important, the framework is designed with flexibility and future evolution in mind. Its runtime-agnostic architecture enables seamless adoption of emerging inference engines, hardware accelerators, and optimization techniques without requiring platform redesign. This ensures that teams can continuously leverage advancements in the rapidly evolving LLM ecosystem while maintaining consistent operational workflows.

+

Ultimately, the goal of the platform is to make production-scale LLM deployment as seamless and reliable as traditional software deployment—allowing teams to focus on building intelligent applications rather than managing infrastructure complexity. By combining lifecycle automation, runtime optimization, and deep observability, the LLM Inference Framework provides a scalable foundation for delivering fast, cost-efficient, and production-ready LLM experiences.

+

Future Explorations

+

While we have achieved significant milestones in latency and throughput, the landscape of GenAI is evolving rapidly. Our roadmap focuses on increasing flexibility, reducing costs, and enhancing reliability for enterprise-grade workloads. Here is what we are building next:

+
    +
  • TPU Support: To diversify our hardware supply chain and further optimize cost-per-token, we are evaluating Google Cloud TPUs to bake it into our platform. By leveraging the JAX and PyTorch/XLA ecosystems, we aim to unlock the massive throughput potential of TPU v5e chips, particularly for our open-source Llama models. This will allow the hardware profiler to dynamically choose between NVIDIA GPUs and Google TPUs based on real-time availability and price-performance metrics.
  • +
  • Multi-LoRA Serving (Serverless Experience): Currently, deploying a fine-tuned model requires a dedicated GPU. We are building support for Multi-LoRA serving, which will allow us to serve hundreds of unique, fine-tuned adapters on top of a single frozen base model. This will drastically reduce costs for multi-tenant applications, enabling a "serverless" experience where specific fine-tunes are hot-swapped instantly per request.
  • +
  • Spot Instance Orchestration: To further optimize cloud costs, we are developing fault-tolerant mechanisms to run inference workloads on Spot Instances. By implementing aggressive checkpointing and seamless request draining, we aim to leverage cheaper, preemptible compute capacity without interrupting the user's streaming experience.
  • +
  • Semantic Caching Layer: We plan to move beyond standard Prefix Caching to implement Semantic Caching. By using a vector database to fetch responses for semantically similar queries (e.g., "How do I reset my password?" vs. "Password reset steps"), we can bypass the GPU entirely for repetitive queries, reducing latency to near-zero.
  • +
  • Context-Aware Autoscaling: Standard CPU/GPU utilization metrics are often insufficient signals for scaling LLMs. We are working on KV-cache pressure metrics for autoscaling. This ensures that we scale out before the memory fills up, preventing eviction-based slowdowns during traffic spikes.
  • +
  • Online Evaluation & Guardrails: We are integrating a lightweight "Trust Layer" into the proxy. This will allow for low-latency input/output filtering (Guardrails) and asynchronous "LLM-as-a-Judge" evaluation pipelines to monitor response quality in production, not just system health.
  • +
]]>
+ + Jaya Kumar + https://github.com/jayakommuru + + + + + + + +
+ + <![CDATA[Cracking the Code: Scaling Model Inference & Real-Time Embedding Search]]> + https://meesho.github.io/BharatMLStack/blog/scaling-model-inference-and-embedding-search + + 2024-05-21T00:00:00.000Z + + BharatMLStack +By mid-2023, we had transformed our ML stack—building a real-time feature store, optimizing model retrieval, and fine-tuning ranking. But two critical gaps remained:

+
    +
  • 🔹 Scaling model inference without hitting infrastructure roadblocks
  • +
  • 🔹 Moving embedding search from batch to real-time for candidate generation
  • +
+

Here’s how we tackled these last-mile challenges, broke free from infrastructure constraints, and built a cost-efficient, high-performance system.

+

Breaking Free from the Scalability Ceiling

+

The Model Serving Bottleneck—A Wake-Up Call

+

July 2023. With just months left for the Mega Blockbuster Sale (MBS), we noticed a serious issue—scaling our model-serving infrastructure was taking 10–15 minutes. In real-time ML, that’s an eternity. +In one of our war rooms, we ran a quick experiment:

+
    +
  • 🚀 We deployed an XGBoost model on a self-hosted Triton Inference Server running on a 16-core machine.
  • +
  • 🚀 Fired requests and compared the outputs with our existing cloud-hosted setup.
  • +
  • 🚀 The results matched—perfectly.
  • +
+

That moment changed everything. We prepped a backup Triton setup on EKS, just in case our cloud provider couldn't allocate enough compute resources in time. Luckily, they did—but the seed was planted. +Then in October, just two weeks before MBS, we got an alarming response from our infrastructure team: +"Node availability may be an issue." +With no time to waste, we moved 30% of real-time ML traffic to our self-hosted Triton cluster. The results?

+
    +
  • ✅ p99 latency dropped from 90–100ms to 30–40ms
  • +
  • ✅ Triton handled significantly higher throughput on fewer resources
  • +
  • ✅ No model changes were needed
  • +
+

MBS ran without a hitch, proving that self-hosted inference was the way forward.

+

Scaling Triton on GKE

+

This left us with two choices:

+
    +
  • 1️⃣ Port models to a managed cloud inference service, investing time in learning a new deployment stack
  • +
  • 2️⃣ Scale our existing Triton setup on GKE, optimizing for cost and performance
  • +
+

We went with Option 2—and it slashed inference costs to 35% of what we previously paid, while giving us full control over scaling and optimizations.

+

Fixing the Cold Start Problem

+

As we onboarded more deep learning (DL) models, we hit a new bottleneck, new inference pods took 7–9 minutes to spin up.

+

After profiling, we found the culprits:

+
    +
  • Triton’s base image—a massive 5GB
  • +
  • Model binaries—often 1GB+
  • +
  • Startup delay—mostly due to downloading and initializing these assets
  • +
+

To fix this, we built a lightweight Triton image, stripping unused components and shrinking the size to 900MB. This cut cold start times drastically, making auto-scaling faster and smoother.

+

Embedding Search: The Last Piece of the Puzzle

+

By mid-2023, most of our ML stack had gone real-time—except for Candidate Generation (CG), which still ran in batch mode. To truly power real-time recommendations, we needed an online embedding search system.

+

Choosing the Right Vector Database

+

We benchmarked three production-ready vector DBs across key parameters:

+
    +
  • Milvus
  • +
  • Qdrant
  • +
  • Weaviate
  • +
+

After extensive POCs, Qdrant stood out for its:

+
    +
  • ✅ Blazing-fast search latency on high-dimensional vectors
  • +
  • ✅ Efficient memory usage, crucial for in-memory workloads
  • +
  • ✅ Support for upserts and soft deletes, vital for Ads use cases
  • +
  • ✅ gRPC + REST APIs, making integration seamless
  • +
  • ✅ Powerful filtering, allowing fine-tuned retrieval (e.g., filtering Ads by category, active status, etc.)
  • +
+

At its core, Qdrant uses HNSW indexing, delivering both high recall and low-latency nearest-neighbor search—a perfect fit for our needs.

+

Embedding Freshness & Real-Time Updates

+

To ensure embeddings stayed up to date, we built a dual ingestion pipeline:

+
    +
  • 📌 Daily Refresh: A bulk pipeline updated embeddings overnight
  • +
  • 📌 Real-Time Updates: Ads events triggered immediate upserts/deletes
  • +
+

This setup powered real-time "Similar Products" recommendations on the product page and became the foundation for Ads Candidate Generation, ensuring the right ads surfaced in milliseconds.

+

Skye

+

Final Takeaways: Scaling Smartly for Real-Time ML

+
    +
  • 🚀 Self-hosted inference on Triton gave us lower cost, faster scaling, and better performance than managed services
  • +
  • 🚀 Building a custom Triton image reduced cold starts, improving responsiveness
  • +
  • 🚀 Qdrant-based embedding search enabled real-time personalization at scale
  • +
  • 🚀 Real-time updates for embeddings unlocked dynamic, up-to-date recommendations
  • +
+

By early 2024, Meesho’s ML stack had evolved into a fully real-time, scalable, and cost-efficient system, setting the foundation for even bigger leaps ahead.

]]>
+ + Aditya Kumar + https://github.com/Adit2607 + + + Jaya Kumar + https://github.com/jayakommuru + + + Adarsha Das + https://github.com/a0d00kc + + + + + + +
+ + <![CDATA[Building Meesho’s ML Platform: Lessons from the First-Gen System (Part 2)]]> + https://meesho.github.io/BharatMLStack/blog/building-meeshos-mlplatform-lessons-from-first-gen + + 2023-04-10T00:00:00.000Z + + BharatMLStack +By late 2022, we had built something we were truly proud of—a real-time ML serving system with a DAG-based executor, a feature store, and an interaction store powering key ranking and personalization models. It was a major milestone, the culmination of months of effort from data scientists, ML engineers, and backend teams. Our system was live, and we were ready to push the boundaries of experimentation. +And it worked. Mostly. +But soon, cracks appeared. Every new model needed custom feature retrieval logic, DAGs became dense and unmanageable, and scaling turned into a constant firefight. Costs surged, and infra bottlenecks slowed experimentation. Our system worked, but it wasn’t built for scale. +This is the story of how we tackled these challenges—building Inferflow for seamless feature retrieval, optimizing real-time infra, and cutting costs while scaling to millions of QPS.

+

The Cost of Success

+

Every new Ranker model required its own feature set, often pulling from different entities. Each addition meant:

+
    +
  • Adding new DAG nodes in IOP
  • +
  • Writing custom logic to fetch features from multiple sources (e.g., user, product, user × category)
  • +
  • Inferring intermediate features (e.g., extracting category from a product to fetch user × category data)
  • +
  • Optimizing I/O and dealing with the inevitable bugs
  • +
+

What began as clean DAGs soon turned into a tangled web of cross-dependent graphs. Every experimentation cycle meant new nodes, new dependencies, and slower iterations.

+

Scaling Pains (and Cassandra’s Limits)

+

At some point, we were hitting:

+
    +
  • 250–300K reads/sec
  • +
  • 1M writes/sec (during lean hours)
  • +
+

All of this ran on Cassandra. While its distributed architecture had been proven in production, operating large-scale clusters came with considerable infrastructure overhead. Our proof-of-concept (POC) demonstrated throughput of around 100K ops/sec, but as we scaled further, the challenges grew. Ensuring node health, optimizing compaction, and maintaining storage balance became increasingly demanding. We also observed latency spikes under heavy load, alongside a sharp increase in total cost of ownership.

+

Interaction Store Woes

+

Our interaction store was another ticking time bomb:

+
    +
  • 🚨 Clusters kept growing in size and cost
  • +
  • 🚨 Latency spikes became increasingly frequent
  • +
  • 🚨 The DMC proxy occasionally lost locality of nodes against shards, causing cross-node communication and degraded performance
  • +
+

Each time this happened, we had to manually rebalance shards just to restore stable latency, making operations unsustainable at scale.

+

Silver Linings

+

Despite the chaos, the system was live and delivering value:

+
    +
  • Real-time infrastructure was in production
  • +
  • Costs dropped by 60–70% compared to offline personalization
  • +
  • New experiments rolled out faster and more successfully
  • +
  • User engagement metrics improved
  • +
+

It wasn’t perfect. It was far from easy. But it worked—and that counted for a lot.

+

Round Two: Solving the Top 2 Bottlenecks

+

With the first-gen system stretched to its limits, we stepped back. Conversations with data scientists and backend engineers revealed three recurring pain points:

+
    +
  1. Coding feature retrieval logic for every new model was becoming unsustainable
  2. +
  3. ML scale was exploding—bringing rising infra costs with it
  4. +
  5. Real-time embedding search was the next big unlock
  6. +
+

We tackled them one by one—starting with the biggest pain point.

+

Problem 1: No-Code Feature Retrieval for Model Inference

+

We noticed a pattern: for personalized ranking, models needed features from:

+
    +
  • ✅ Product
  • +
  • ✅ User
  • +
  • ✅ User × Category
  • +
  • ✅ Region, cohort, sub-category, etc.
  • +
+

A key insight emerged: Entities that contribute features for a model always map back to the context entities.

+

MP Dag

+

With this, we designed Inferflow, a graph-driven feature retrieval and model orchestration system:

+
    +
  • 1️⃣ Inferflow takes a modelId and context IDs (e.g., userId, productIds)
  • +
  • 2️⃣ Loads a pre-defined feature retrieval graph from ZooKeeper
  • +
  • 3️⃣ Executes the graph to resolve entity relationships dynamically
  • +
  • 4️⃣ Outputs a 2D matrix of feature vectors
  • +
+

💡 The impact?

+
    +
  • 🚀 No more custom feature retrieval code—just graph updates in config
  • +
  • 🚀 Feature consistency across experiments
  • +
  • 🚀 Faster iteration cycles for ranking, fraud detection, and beyond
  • +
+

Here’s a visual example that shows how this graph plays out during execution. We further extended the graph to call multiple models as needed: +MP matrix +We built Inferflow in GoLang, using gRPC and Proto3 serialization for efficiency.

+

Problem 2: Scaling Without Breaking the Bank

+

With more ML use cases coming online, we needed to cut costs without compromising performance. We focused on:

+
    +
  • 🔹 Online Feature Store
  • +
  • 🔹 Interaction Store
  • +
+

Optimizing the Online Feature Store

+

Our costs were concentrated in:

+
    +
  • 📌 Database (Cassandra)
  • +
  • 📌 Cache (Redis)
  • +
  • 📌 Running Pods (Java services)
  • +
+

1️⃣ Replacing Cassandra with ScyllaDB +As we hit the operational limits of large Cassandra clusters, we transitioned to ScyllaDB, which offered a seamless drop-in replacement without major code changes. The switch brought significant benefits:

+
    +
  • Throughput: Matched or exceeded Cassandra's performance under identical workloads, even under high concurrency.
  • +
  • Latency: Achieved consistently lower P99 latencies due to ScyllaDB's shard-per-core architecture and better I/O utilization.
  • +
  • Cost Efficiency: Reduced infra footprint by ~70% through better CPU and memory efficiency, eliminating the need for over-provisioned nodes.
  • +
+

2️⃣ Finding the Right Cache +To reduce backend load and improve response times, we benchmarked multiple caching solutions—Memcached, KeyDB, and Dragonfly—under real production traffic patterns. Dragonfly stood out due to its robust architecture and operational simplicity:

+
    +
  • Data Skew Handling: Efficiently managed extreme key hotness and uneven access patterns without performance degradation.
  • +
  • Throughput: Delivered consistently high throughput, even with large object sizes and concurrent access.
  • +
  • Ease of Adoption: Acted as a drop-in Redis replacement with full protocol compatibility—no changes needed in application code or client libraries.
  • +
+

3️⃣ Moving to GoLang for Cost-Efficient Serving +Java services were memory-heavy—so we rewrote core services in GoLang. The results?

+

✅ Memory usage dropped by ~80% +✅ CPU utilization was significantly lower +✅ Faster, more efficient deployments

+

Optimizing the Interaction Store

+

We realized that we only need a user’s interaction data in Redis when they open the app. So, we implemented a tiered storage approach:

+
    +
  • 📌 Cold Tier (ScyllaDB)—Stores click, order, wishlist events
  • +
  • 📌 Hot Tier (Redis)—Loads a user’s past interactions only when they open the app
  • +
+

Smart Offloading: We introduced an inactivity tracker to detect when a user session ends. At that point, Redis data was flushed back to Scylla, reducing unnecessary writes.

+

InteractionStore

+

Results

+
    +
  • Online Feature Store hit 1M QPS for the first time during the 2023 Mega Blockbuster Sale—without breaking a sweat
  • +
  • Infra costs for Online Feature Store and Interaction Store dropped by ~60%
  • +
+

The Catch: Our ML Hosting Hit a Hard Limit

+

While planning for 2023 MBS, we ran into a critical scalability bottleneck:

+
    +
  • ❌ Insufficient compute availability in our region for ML instances
  • +
  • ❌ Couldn’t provision enough nodes to handle real-time inference at scale
  • +
+

This forced us to rethink where and how we hosted our models. The existing setup was great for prototyping—but it wasn’t built to handle the bursty, high-QPS demands of real-world production workloads.

+

Conclusion: From Firefighting to Future-Proofing

+

What started as an ambitious experiment turned into a real-time ML infrastructure that powered millions of requests per second. We battled scaling pains, rethought feature retrieval with Inferflow, and rebuilt our infra stack for efficiency—driving down costs while improving experimentation velocity. +But new challenges emerged. Our infrastructure could now handle scale, but our ML model hosting setup hit a hard limit. With compute availability bottlenecks threatening real-time inference, we faced a critical decision: how do we make model serving as scalable and cost-efficient as the rest of our stack? That’s the next piece of the puzzle—and the story of Part 3.

]]>
+ + Bhawani Singh + https://github.com/singh-bhawani + + + Jigar Dave + https://github.com/jigarpatel26 + + + Adarsha Das + https://github.com/a0d00kc + + + + + + +
<![CDATA[Building Meesho’s ML Platform: From Chaos to Cutting-Edge (Part 1)]]> - https://meesho.github.io/BharatMLStack/blog/post-one - + https://meesho.github.io/BharatMLStack/blog/building-meeshos-mlplatform + 2022-11-15T00:00:00.000Z - - BharatMLStack

-

The Genesis: How a Friday Night Roast Sparked Meesho’s ML Platform

-

It all started in early 2022, over a casual Friday evening catch-up. Like many great origin stories, this one began with friendly banter between a group of backend engineers and data scientists. As the conversations unfolded, so did the roasting—until one remark hit a little too close to home:

+ + BharatMLStack +It all started in early 2022, over a casual Friday evening catch-up. Like many great origin stories, this one began with friendly banter between a group of backend engineers and data scientists. As the conversations unfolded, so did the roasting—until one remark hit a little too close to home:

"Why are we still crunching data for Monthly Active Users (MAU) when the next day it’s all about Daily Active Users (DAU)?"

The laughter died down, and the question lingered. When we regrouped on Monday—clear-headed and slightly reflective—we decided to dig into the numbers. What they discovered was quite revealing: a large portion of compute resources wasn’t being put to good use. Much of the system’s effort was spent supporting users who weren’t actively engaging, and even for new users, the experience wasn’t optimized to make a meaningful impact.

@@ -30,7 +682,7 @@ Much of the system’s effort was spent supporting users who weren’t actively
  • Serving: A microservice retrieved ranked recommendations from an in-memory data store via exposed APIs, delivering personalized listings across key surfaces such as "For You" and Category Landing Pages (CLP).
  • This approach held up well—until Meesho started seeing a significant surge in traffic.

    -

    The Turning Point: From Batch to Real-Time

    +

    The Turning Point: From Batch to Real-Time

    At this time, the team was iterating on new Ranker models, and real-time inference seemed like the next logical step. But Rankers needed real-time feature retrieval, which meant an online feature store had to be built first.

    Exploring open-source options led to cost vs. performance trade-offs, but Meesho’s surging traffic meant that latency and stability were non-negotiable. After multiple debates and stakeholder discussions, a bold decision was made:

    We would build our own feature store.

    @@ -40,11 +692,11 @@ Much of the system’s effort was spent supporting users who weren’t actively

    That’s when the idea struck:
    We needed a framework for real-time DAG execution—one that preserved the same flexibility as Airflow but worked for streaming data.

    This moment shaped the next phase of our journey.

    -

    First Generation Design

    +

    First Generation Design

    Alt Text

    Laying the Groundwork: The First-Gen ML Platform

    To solve these challenges, the team built three foundational components:

    -

    1. IOP Framework: A Real-Time DAG Executor

    +

    1. IOP Framework: A Real-Time DAG Executor

    • Reusable Nodes: Each DAG node (e.g., an invocation to a CG service, a ranker, or a filter) had to be implemented only once. After that, it could be reused across any workflow by referencing it in config.
    • Config-driven Dynamic Graphs: Execution graphs were defined as adjacency lists stored in ZooKeeper, allowing teams to modify the sequence or structure of operations without touching application code.
    • @@ -52,13 +704,13 @@ Much of the system’s effort was spent supporting users who weren’t actively
    • Production-Grade DAGs: DAGs were designed to execute in low-latency real-time environments, with support for parallel execution, retries, and branching.
    More about IOP DAG -

    2. Online Feature Store - 0th Version

    +

    2. Online Feature Store - 0th Version

    • Used Cassandra and Redis for low-latency feature serving.
    • Maintained feature consistency using Feature Groups with TTL-based expiry.
    • A hybrid schema was used: feature keys stored in ZooKeeper, data stored in compact arrays.
    -

    3. Interaction Store - 0th Version

    +

    3. Interaction Store - 0th Version

    • Captured real-time user interactions like clicks, orders, and add-to-cart events.
    • Stored event data in Redis ZSETs (sorted sets) to enable fast lookups for recommendation engines.
    • @@ -66,15 +718,15 @@ Much of the system’s effort was spent supporting users who weren’t actively

    With these components in place, real-time ML at Meesho became a reality.

    This was just the beginning.

    -

    Building the Online Feature Store - 0th Version

    +

    Building the Online Feature Store - 0th Version

    Alt text

    -

    Choosing the Right Tech Stack

    +

    Choosing the Right Tech Stack

    We spent considerable time evaluating various databases, caches, and communication protocols for our online feature store. After carefully weighing cost, latency, throughput, and operational stability, we settled on a combination of:

    • Cassandra and Redis for storage
    • gRPC + Proto3 as our communication layer
    -

    Streamlining the Data Flow

    +

    Streamlining the Data Flow

    To keep things simple in the initial version:

    • Feature engineering jobs wrote raw outputs to an S3 bucket
    • @@ -91,27 +743,27 @@ Much of the system’s effort was spent supporting users who weren’t actively
    • Ad-hoc jobs computed features in higher frequency
    • These jobs pushed to both Kafka and S3 (S3 preserved historical data for future model training)
    -

    The Challenges: Data Format and Storage

    +

    The Challenges: Data Format and Storage

    One of the most critical design challenges was how to store feature data efficiently and consistently, especially in databases like Cassandra and Redis, which come with unique storage constraints.

    We had to solve for three key requirements:

    • -

      Feature Consistency

      +

      Feature Consistency

      When a feature group contains features like order_count_1h and click_count_1h, both must reflect the same time window. Inconsistent updates would lead to unreliable model predictions.

    • -

      TTL Granularity

      +

      TTL Granularity

      Each feature group required an expiry timestamp, so that all features within it expired together—preserving consistency during reads.

    • -

      Extensibility Across Databases

      +

      Extensibility Across Databases

      We anticipated that infra needs would evolve. To future-proof our system, the data format was designed to be decoupled from DB-specific layouts, enabling portability to systems like ScyllaDB, DynamoDB, HBase, or BigTable.


    -

    Overcoming Technical Constraints

    +

    Overcoming Technical Constraints

    At the time, we were using Cassandra, which not only imposed a soft limit of 75 columns per row, but also exhibited significant performance degradation as the number of columns increased further, particularly in memory constrained machines. Wide rows caused high memory usage during reads, unpredictable latencies due to heavy deserialization overhead, and inefficiencies during compactions and repairs. This ruled out the naive "one column per feature" approach. We needed a format that was compact, minimized the number of columns, and remained efficient and portable across different storage systems.

    -

    The Solution: Schema Separation

    +

    The Solution: Schema Separation

    We introduced the concept of Feature Groups—logical groupings of features that must remain consistent with one another. To represent these groups efficiently, we adopted a layered storage approach:

      @@ -120,7 +772,7 @@ To represent these groups efficiently, we adopted a layered storage approach:

      Expiry Timestamp and Schema Version were appended using a semi-colon delimiter at the end of the string.

    Example:

    -
    feature_1_value,feature_2_value,feature_3_value;expiry_ts
    +
    feature_1_value,feature_2_value,feature_3_value;expiry_ts

    This format allowed:

    • Consistent writes and reads at the group level
    • @@ -128,34 +780,34 @@ To represent these groups efficiently, we adopted a layered storage approach:

      Efficient storage with minimal DB column usage
    • Support for per-group TTLs and schema evolution
    -

    Tracking Changes in Feature Groups

    +

    Tracking Changes in Feature Groups

    Feature groups don’t stay static. As models evolve, features get added, renamed, or removed. But schema changes often go live before the data is ready—and stopping ingestion just to wait for everything to align isn't feasible.

    -

    Common Real-World Scenarios:

    +

    Common Real-World Scenarios:

    • A new feature is added to the schema, but ingestion jobs still use the older schema version.
    • Ongoing writes don’t include the newly added feature, and stopping ingestion would break freshness for existing features.
    • During serving, models request a mix of old and new features, depending on rollout stages.
    -

    The Solution: Schema Versioning

    +

    The Solution: Schema Versioning

    We solved this with versioned feature group schemas, which unlocked several capabilities:

    • -

      Backward Compatibility

      +

      Backward Compatibility

      Older ingestion jobs can continue writing using older schema versions. During reads, the system uses the schema version embedded in the value to interpret the data correctly.
    • -

      Partial Availability Handling

      +

      Partial Availability Handling

      During inference, if some features in the request aren’t available (due to rollout delays or missing data), the system serves default values, ensuring the inference call doesn’t fail.
    • -

      Safe Writes Without Pipeline Pauses

      +

      Safe Writes Without Pipeline Pauses

      With schema versioning, we no longer had to stop ingestion pipelines for schema updates. Writes using previous versions can continue safely, and downstream consumers evolve independently. This design gave us the flexibility to move fast without breaking things—preserving data quality, enabling experimentation, and ensuring reliability at scale.

    Alt Text

    -

    Interaction Store - 0th Version

    +

    Interaction Store - 0th Version

    Alt Text

    To power real-time Candidate Generators (CGs), we needed fast access to user behavior signals—like what a user recently clicked, ordered, or added to their cart. These interactions form the basis for many real-time recommendations, such as Similar Products, People Also Viewed, or Recently Ordered Again. For the 0th version of the Interaction Store, we focused on a design that was simple, fast, and reliable — optimized for high-throughput ingestion and low-latency lookups.

    -

    Event Ingestion

    +

    Event Ingestion

    We instrumented our backend services to emit key user interaction events to Kafka in real time. These included:

    • Click
    • @@ -171,9 +823,9 @@ For the 0th version of the Interaction Store, we focused on a d
    • timestamp — the moment the interaction occurred

    This decoupled the interaction logging from storage, allowing ingestion and consumption to scale independently.

    -

    Storage Design

    +

    Storage Design

    To store these events, we built Kafka consumers that processed the incoming streams and wrote the data into Redis, using sorted sets (ZSETs) as the primary data structure.

    -

    Why Redis?

    +

    Why Redis?

    Redis gave us:

    • Low-latency reads and writes
    • @@ -181,9 +833,9 @@ For the 0th version of the Interaction Store, we focused on a d
    • Native TTL support, if needed in later versions
    • In-memory performance —ideal for real-time CGs
    -

    Storage Structure

    +

    Storage Structure

    Each user’s interactions were stored using a composite key format, uniquely identifying the user and interaction type. This structure allowed efficient organization and quick retrieval of recent activity for recommendation generation:

    -
    userId_eventType → ZSET[...(pid, ts)...]
    +
    userId_eventType → ZSET[...(pid, ts)...]

    Within each ZSET:

    • The timestamp served as the score, maintaining temporal order
    • @@ -194,9 +846,9 @@ For the 0th version of the Interaction Store, we focused on a d
    • Fetch the last k interactions of a specific type for a given user with ZREVRANGE(userId_eventType, count)
    • Retrieve all interactions within a time range (e.g., last 24 hours) with ZREVRANGEBYSCORE(userId_eventType, timeRange)
    -

    Built-in Guardrails

    +

    Built-in Guardrails

    Since Redis was the sole store, we implemented High Availability (HA) to prevent data loss. To optimize memory usage, we also enforced size limits per event type—only storing the last k interactions per user, with older entries getting truncated.

    -

    Conclusion: Laying the Foundation for Real-Time ML

    +

    Conclusion: Laying the Foundation for Real-Time ML

    In this first phase, we tackled the fundamentals—shifting from batch-based recommendations to a real-time Recommendation using ML platform that could keep up with Meesho’s growth.

    With the IOP Framework, Online Feature Store, and Interaction Store, we built the core infrastructure to support real-time personalization at scale. These wins have already unlocked:

      diff --git a/docs/blog/authors/index.html b/docs/blog/authors/index.html index af01797d..347173f8 100644 --- a/docs/blog/authors/index.html +++ b/docs/blog/authors/index.html @@ -4,14 +4,14 @@ Authors | BharatMLStack - - - + + + -

      Authors

      + \ No newline at end of file diff --git a/docs/blog/building-meeshos-mlplatform-lessons-from-first-gen/index.html b/docs/blog/building-meeshos-mlplatform-lessons-from-first-gen/index.html new file mode 100644 index 00000000..94c130a3 --- /dev/null +++ b/docs/blog/building-meeshos-mlplatform-lessons-from-first-gen/index.html @@ -0,0 +1,143 @@ + + + + + +Building Meesho’s ML Platform: Lessons from the First-Gen System (Part 2) | BharatMLStack + + + + + + + + +

      Building Meesho’s ML Platform: Lessons from the First-Gen System (Part 2)

      · 7 min read
      Bhawani Singh
      Architect @ Meesho
      Jigar Dave
      Lead Software Engineer @ Meesho
      Adarsha Das
      Senior Architect @ Meesho

      BharatMLStack +By late 2022, we had built something we were truly proud of—a real-time ML serving system with a DAG-based executor, a feature store, and an interaction store powering key ranking and personalization models. It was a major milestone, the culmination of months of effort from data scientists, ML engineers, and backend teams. Our system was live, and we were ready to push the boundaries of experimentation. +And it worked. Mostly. +But soon, cracks appeared. Every new model needed custom feature retrieval logic, DAGs became dense and unmanageable, and scaling turned into a constant firefight. Costs surged, and infra bottlenecks slowed experimentation. Our system worked, but it wasn’t built for scale. +This is the story of how we tackled these challenges—building Inferflow for seamless feature retrieval, optimizing real-time infra, and cutting costs while scaling to millions of QPS.

      +

      The Cost of Success

      +

      Every new Ranker model required its own feature set, often pulling from different entities. Each addition meant:

      +
        +
      • Adding new DAG nodes in IOP
      • +
      • Writing custom logic to fetch features from multiple sources (e.g., user, product, user × category)
      • +
      • Inferring intermediate features (e.g., extracting category from a product to fetch user × category data)
      • +
      • Optimizing I/O and dealing with the inevitable bugs
      • +
      +

      What began as clean DAGs soon turned into a tangled web of cross-dependent graphs. Every experimentation cycle meant new nodes, new dependencies, and slower iterations.

      +

      Scaling Pains (and Cassandra’s Limits)

      +

      At some point, we were hitting:

      +
        +
      • 250–300K reads/sec
      • +
      • 1M writes/sec (during lean hours)
      • +
      +

      All of this ran on Cassandra. While its distributed architecture had been proven in production, operating large-scale clusters came with considerable infrastructure overhead. Our proof-of-concept (POC) demonstrated throughput of around 100K ops/sec, but as we scaled further, the challenges grew. Ensuring node health, optimizing compaction, and maintaining storage balance became increasingly demanding. We also observed latency spikes under heavy load, alongside a sharp increase in total cost of ownership.

      +

      Interaction Store Woes

      +

      Our interaction store was another ticking time bomb:

      +
        +
      • 🚨 Clusters kept growing in size and cost
      • +
      • 🚨 Latency spikes became increasingly frequent
      • +
      • 🚨 The DMC proxy occasionally lost locality of nodes against shards, causing cross-node communication and degraded performance
      • +
      +

      Each time this happened, we had to manually rebalance shards just to restore stable latency, making operations unsustainable at scale.

      +

      Silver Linings

      +

      Despite the chaos, the system was live and delivering value:

      +
        +
      • Real-time infrastructure was in production
      • +
      • Costs dropped by 60–70% compared to offline personalization
      • +
      • New experiments rolled out faster and more successfully
      • +
      • User engagement metrics improved
      • +
      +

      It wasn’t perfect. It was far from easy. But it worked—and that counted for a lot.

      +

      Round Two: Solving the Top 2 Bottlenecks

      +

      With the first-gen system stretched to its limits, we stepped back. Conversations with data scientists and backend engineers revealed three recurring pain points:

      +
        +
      1. Coding feature retrieval logic for every new model was becoming unsustainable
      2. +
      3. ML scale was exploding—bringing rising infra costs with it
      4. +
      5. Real-time embedding search was the next big unlock
      6. +
      +

      We tackled them one by one—starting with the biggest pain point.

      +

      Problem 1: No-Code Feature Retrieval for Model Inference

      +

      We noticed a pattern: for personalized ranking, models needed features from:

      +
        +
      • ✅ Product
      • +
      • ✅ User
      • +
      • ✅ User × Category
      • +
      • ✅ Region, cohort, sub-category, etc.
      • +
      +

      A key insight emerged: Entities that contribute features for a model always map back to the context entities.

      +

      MP Dag

      +

      With this, we designed Inferflow, a graph-driven feature retrieval and model orchestration system:

      +
        +
      • 1️⃣ Inferflow takes a modelId and context IDs (e.g., userId, productIds)
      • +
      • 2️⃣ Loads a pre-defined feature retrieval graph from ZooKeeper
      • +
      • 3️⃣ Executes the graph to resolve entity relationships dynamically
      • +
      • 4️⃣ Outputs a 2D matrix of feature vectors
      • +
      +

      💡 The impact?

      +
        +
      • 🚀 No more custom feature retrieval code—just graph updates in config
      • +
      • 🚀 Feature consistency across experiments
      • +
      • 🚀 Faster iteration cycles for ranking, fraud detection, and beyond
      • +
      +

      Here’s a visual example that shows how this graph plays out during execution. We further extended the graph to call multiple models as needed: +MP matrix +We built Inferflow in GoLang, using gRPC and Proto3 serialization for efficiency.

      +

      Problem 2: Scaling Without Breaking the Bank

      +

      With more ML use cases coming online, we needed to cut costs without compromising performance. We focused on:

      +
        +
      • 🔹 Online Feature Store
      • +
      • 🔹 Interaction Store
      • +
      +

      Optimizing the Online Feature Store

      +

      Our costs were concentrated in:

      +
        +
      • 📌 Database (Cassandra)
      • +
      • 📌 Cache (Redis)
      • +
      • 📌 Running Pods (Java services)
      • +
      +

      1️⃣ Replacing Cassandra with ScyllaDB +As we hit the operational limits of large Cassandra clusters, we transitioned to ScyllaDB, which offered a seamless drop-in replacement without major code changes. The switch brought significant benefits:

      +
        +
      • Throughput: Matched or exceeded Cassandra's performance under identical workloads, even under high concurrency.
      • +
      • Latency: Achieved consistently lower P99 latencies due to ScyllaDB's shard-per-core architecture and better I/O utilization.
      • +
      • Cost Efficiency: Reduced infra footprint by ~70% through better CPU and memory efficiency, eliminating the need for over-provisioned nodes.
      • +
      +

      2️⃣ Finding the Right Cache +To reduce backend load and improve response times, we benchmarked multiple caching solutions—Memcached, KeyDB, and Dragonfly—under real production traffic patterns. Dragonfly stood out due to its robust architecture and operational simplicity:

      +
        +
      • Data Skew Handling: Efficiently managed extreme key hotness and uneven access patterns without performance degradation.
      • +
      • Throughput: Delivered consistently high throughput, even with large object sizes and concurrent access.
      • +
      • Ease of Adoption: Acted as a drop-in Redis replacement with full protocol compatibility—no changes needed in application code or client libraries.
      • +
      +

      3️⃣ Moving to GoLang for Cost-Efficient Serving +Java services were memory-heavy—so we rewrote core services in GoLang. The results?

      +

      ✅ Memory usage dropped by ~80% +✅ CPU utilization was significantly lower +✅ Faster, more efficient deployments

      +

      Optimizing the Interaction Store

      +

      We realized that we only need a user’s interaction data in Redis when they open the app. So, we implemented a tiered storage approach:

      +
        +
      • 📌 Cold Tier (ScyllaDB)—Stores click, order, wishlist events
      • +
      • 📌 Hot Tier (Redis)—Loads a user’s past interactions only when they open the app
      • +
      +

      Smart Offloading: We introduced an inactivity tracker to detect when a user session ends. At that point, Redis data was flushed back to Scylla, reducing unnecessary writes.

      +

      InteractionStore

      +

      Results

      +
        +
      • Online Feature Store hit 1M QPS for the first time during the 2023 Mega Blockbuster Sale—without breaking a sweat
      • +
      • Infra costs for Online Feature Store and Interaction Store dropped by ~60%
      • +
      +

      The Catch: Our ML Hosting Hit a Hard Limit

      +

      While planning for 2023 MBS, we ran into a critical scalability bottleneck:

      +
        +
      • ❌ Insufficient compute availability in our region for ML instances
      • +
      • ❌ Couldn’t provision enough nodes to handle real-time inference at scale
      • +
      +

      This forced us to rethink where and how we hosted our models. The existing setup was great for prototyping—but it wasn’t built to handle the bursty, high-QPS demands of real-world production workloads.

      +

      Conclusion: From Firefighting to Future-Proofing

      +

      What started as an ambitious experiment turned into a real-time ML infrastructure that powered millions of requests per second. We battled scaling pains, rethought feature retrieval with Inferflow, and rebuilt our infra stack for efficiency—driving down costs while improving experimentation velocity. +But new challenges emerged. Our infrastructure could now handle scale, but our ML model hosting setup hit a hard limit. With compute availability bottlenecks threatening real-time inference, we faced a critical decision: how do we make model serving as scalable and cost-efficient as the rest of our stack? That’s the next piece of the puzzle—and the story of Part 3.

      + + \ No newline at end of file diff --git a/docs/blog/building-meeshos-mlplatform/index.html b/docs/blog/building-meeshos-mlplatform/index.html new file mode 100644 index 00000000..dd8b65b9 --- /dev/null +++ b/docs/blog/building-meeshos-mlplatform/index.html @@ -0,0 +1,208 @@ + + + + + +Building Meesho’s ML Platform: From Chaos to Cutting-Edge (Part 1) | BharatMLStack + + + + + + + + +

      Building Meesho’s ML Platform: From Chaos to Cutting-Edge (Part 1)

      · 11 min read
      Adarsha Das
      Senior Architect @ Meesho
      Aditya Kumar
      Lead Software Engineer @ Meesho
      Bhawani Singh
      Architect @ Meesho
      Jigar Dave
      Lead Software Engineer @ Meesho

      BharatMLStack +It all started in early 2022, over a casual Friday evening catch-up. Like many great origin stories, this one began with friendly banter between a group of backend engineers and data scientists. As the conversations unfolded, so did the roasting—until one remark hit a little too close to home:

      +

      "Why are we still crunching data for Monthly Active Users (MAU) when the next day it’s all about Daily Active Users (DAU)?"

      +

      The laughter died down, and the question lingered. When we regrouped on Monday—clear-headed and slightly reflective—we decided to dig into the numbers. What they discovered was quite revealing: a large portion of compute resources wasn’t being put to good use. +Much of the system’s effort was spent supporting users who weren’t actively engaging, and even for new users, the experience wasn’t optimized to make a meaningful impact.

      +

      At the same time, Meesho had just launched a company-wide initiative to reduce costs—and every team had to contribute. This realization sparked the journey that would eventually lead to the Meesho ML Platform, known today as BharatMLStack.

      +

      Alt Text

      +

      Before the ML Platform, our recommendation and ranking pipelines followed a batch processing approach:

      +
        +
      • Data Ingestion: The Data Platform team executed ETL jobs to ingest raw user data—including user profiles, interaction logs, and product impressions—into designated S3 buckets.
      • +
      • Layer 1: Embedding Generation: On the Data Science side, Spark jobs pulled data from multiple S3 sources, cleaned and preprocessed it, and applied matrix factorization to generate user and item embeddings. The processed data and embeddings were then stored back in S3 in a structured format.
      • +
      • Layer 2: Candidate Generation (CG): In this stage, Spark jobs leveraged embeddings and historical interaction data to generate candidate recommendations for users. These candidate lists were subsequently written to S3.
      • +
      • Layer 3: Ranking and Merging – A final round of processing ranked the generated candidates using ML models, combined different candidate lists, and stored the final ranked recommendations in a caching system.
      • +
      • Serving: A microservice retrieved ranked recommendations from an in-memory data store via exposed APIs, delivering personalized listings across key surfaces such as "For You" and Category Landing Pages (CLP).
      • +
      +

      This approach held up well—until Meesho started seeing a significant surge in traffic.

      +

      The Turning Point: From Batch to Real-Time

      +

      At this time, the team was iterating on new Ranker models, and real-time inference seemed like the next logical step. But Rankers needed real-time feature retrieval, which meant an online feature store had to be built first.

      +

      Exploring open-source options led to cost vs. performance trade-offs, but Meesho’s surging traffic meant that latency and stability were non-negotiable. After multiple debates and stakeholder discussions, a bold decision was made:

      +

      We would build our own feature store.

      +

      Meanwhile, efforts began to bring Candidate Generators (CGs) to real-time. The challenge? Storing and retrieving user interactions quickly enough to power real-time recommendations.

      +

      As the team dove deeper, a new roadblock emerged:
      +Our ML jobs were orchestrated using Airflow DAGs, giving data scientists flexibility in experimentation. But transitioning to real-time execution threatened this agility. Every change would now require backend engineering support, slowing down iteration cycles.

      +

      That’s when the idea struck:
      +We needed a framework for real-time DAG execution—one that preserved the same flexibility as Airflow but worked for streaming data.

      +

      This moment shaped the next phase of our journey.

      +

      First Generation Design

      +

      Alt Text

      +

      Laying the Groundwork: The First-Gen ML Platform

      +

      To solve these challenges, the team built three foundational components:

      +

      1. IOP Framework: A Real-Time DAG Executor

      +
        +
      • Reusable Nodes: Each DAG node (e.g., an invocation to a CG service, a ranker, or a filter) had to be implemented only once. After that, it could be reused across any workflow by referencing it in config.
      • +
      • Config-driven Dynamic Graphs: Execution graphs were defined as adjacency lists stored in ZooKeeper, allowing teams to modify the sequence or structure of operations without touching application code.
      • +
      • Plug-and-play CGs: The Candidate Generator interface was preserved, so a single CG node could call any CG service by passing cg_name in the request. This drastically reduced the code surface area and improved maintainability.
      • +
      • Production-Grade DAGs: DAGs were designed to execute in low-latency real-time environments, with support for parallel execution, retries, and branching.
      • +
      +More about IOP DAG +

      2. Online Feature Store - 0th Version

      +
        +
      • Used Cassandra and Redis for low-latency feature serving.
      • +
      • Maintained feature consistency using Feature Groups with TTL-based expiry.
      • +
      • A hybrid schema was used: feature keys stored in ZooKeeper, data stored in compact arrays.
      • +
      +

      3. Interaction Store - 0th Version

      +
        +
      • Captured real-time user interactions like clicks, orders, and add-to-cart events.
      • +
      • Stored event data in Redis ZSETs (sorted sets) to enable fast lookups for recommendation engines.
      • +
      • Provided an API to fetch a user's last k interactions or interactions within a time window.
      • +
      +

      With these components in place, real-time ML at Meesho became a reality.

      +

      This was just the beginning.

      +

      Building the Online Feature Store - 0th Version

      +

      Alt text

      +

      Choosing the Right Tech Stack

      +

      We spent considerable time evaluating various databases, caches, and communication protocols for our online feature store. After carefully weighing cost, latency, throughput, and operational stability, we settled on a combination of:

      +
        +
      • Cassandra and Redis for storage
      • +
      • gRPC + Proto3 as our communication layer
      • +
      +

      Streamlining the Data Flow

      +

      To keep things simple in the initial version:

      +
        +
      • Feature engineering jobs wrote raw outputs to an S3 bucket
      • +
      • A daily feature push job: +
          +
        • Read from S3
        • +
        • Grouped related features into Feature Groups (ensuring consistency)
        • +
        • Pushed them to Kafka
        • +
        +
      • +
      +

      For features requiring frequent updates:

      +
        +
      • Ad-hoc jobs computed features in higher frequency
      • +
      • These jobs pushed to both Kafka and S3 (S3 preserved historical data for future model training)
      • +
      +

      The Challenges: Data Format and Storage

      +

      One of the most critical design challenges was how to store feature data efficiently and consistently, especially in databases like Cassandra and Redis, which come with unique storage constraints.

      +

      We had to solve for three key requirements:

      +
        +
      • +

        Feature Consistency

        +

        When a feature group contains features like order_count_1h and click_count_1h, both must reflect the same time window. Inconsistent updates would lead to unreliable model predictions.

        +
      • +
      • +

        TTL Granularity

        +

        Each feature group required an expiry timestamp, so that all features within it expired together—preserving consistency during reads.

        +
      • +
      • +

        Extensibility Across Databases

        +

        We anticipated that infra needs would evolve. To future-proof our system, the data format was designed to be decoupled from DB-specific layouts, enabling portability to systems like ScyllaDB, DynamoDB, HBase, or BigTable.

        +
      • +
      +
      +

      Overcoming Technical Constraints

      +

      At the time, we were using Cassandra, which not only imposed a soft limit of 75 columns per row, but also exhibited significant performance degradation as the number of columns increased further, particularly in memory constrained machines. Wide rows caused high memory usage during reads, unpredictable latencies due to heavy deserialization overhead, and inefficiencies during compactions and repairs. This ruled out the naive "one column per feature" approach. We needed a format that was compact, minimized the number of columns, and remained efficient and portable across different storage systems.

      +

      The Solution: Schema Separation

      +

      We introduced the concept of Feature Groups—logical groupings of features that must remain consistent with one another. +To represent these groups efficiently, we adopted a layered storage approach:

      +
        +
      • Feature Labels (Keys) were stored in ZooKeeper, serving as the schema.
      • +
      • Feature Values were stored as a comma-separated string array in Cassandra or Redis.
      • +
      • Expiry Timestamp and Schema Version were appended using a semi-colon delimiter at the end of the string.
      • +
      +

      Example:

      +
      feature_1_value,feature_2_value,feature_3_value;expiry_ts
      +

      This format allowed:

      +
        +
      • Consistent writes and reads at the group level
      • +
      • Easy parsing of feature values using the schema lookup from ZooKeeper
      • +
      • Efficient storage with minimal DB column usage
      • +
      • Support for per-group TTLs and schema evolution
      • +
      +

      Tracking Changes in Feature Groups

      +

      Feature groups don’t stay static. As models evolve, features get added, renamed, or removed. But schema changes often go live before the data is ready—and stopping ingestion just to wait for everything to align isn't feasible.

      +

      Common Real-World Scenarios:

      +
        +
      • A new feature is added to the schema, but ingestion jobs still use the older schema version.
      • +
      • Ongoing writes don’t include the newly added feature, and stopping ingestion would break freshness for existing features.
      • +
      • During serving, models request a mix of old and new features, depending on rollout stages.
      • +
      +

      The Solution: Schema Versioning

      +

      We solved this with versioned feature group schemas, which unlocked several capabilities:

      +
        +
      • +

        Backward Compatibility

        +Older ingestion jobs can continue writing using older schema versions. During reads, the system uses the schema version embedded in the value to interpret the data correctly.
      • +
      • +

        Partial Availability Handling

        +During inference, if some features in the request aren’t available (due to rollout delays or missing data), the system serves default values, ensuring the inference call doesn’t fail.
      • +
      • +

        Safe Writes Without Pipeline Pauses

        +With schema versioning, we no longer had to stop ingestion pipelines for schema updates. Writes using previous versions can continue safely, and downstream consumers evolve independently. +This design gave us the flexibility to move fast without breaking things—preserving data quality, enabling experimentation, and ensuring reliability at scale.
      • +
      +

      Alt Text

      +

      Interaction Store - 0th Version

      +

      Alt Text

      +

      To power real-time Candidate Generators (CGs), we needed fast access to user behavior signals—like what a user recently clicked, ordered, or added to their cart. These interactions form the basis for many real-time recommendations, such as Similar Products, People Also Viewed, or Recently Ordered Again. +For the 0th version of the Interaction Store, we focused on a design that was simple, fast, and reliable — optimized for high-throughput ingestion and low-latency lookups.

      +

      Event Ingestion

      +

      We instrumented our backend services to emit key user interaction events to Kafka in real time. These included:

      +
        +
      • Click
      • +
      • Order
      • +
      • Add to Cart
      • +
      • Wishlist
      • +
      • Share
      • +
      +

      Each event carried essential metadata:

      +
        +
      • userId — uniquely identifies the user
      • +
      • productId — the item being interacted with
      • +
      • timestamp — the moment the interaction occurred
      • +
      +

      This decoupled the interaction logging from storage, allowing ingestion and consumption to scale independently.

      +

      Storage Design

      +

      To store these events, we built Kafka consumers that processed the incoming streams and wrote the data into Redis, using sorted sets (ZSETs) as the primary data structure.

      +

      Why Redis?

      +

      Redis gave us:

      +
        +
      • Low-latency reads and writes
      • +
      • Time-ordered data using ZSETs (via score = timestamp)
      • +
      • Native TTL support, if needed in later versions
      • +
      • In-memory performance —ideal for real-time CGs
      • +
      +

      Storage Structure

      +

      Each user’s interactions were stored using a composite key format, uniquely identifying the user and interaction type. This structure allowed efficient organization and quick retrieval of recent activity for recommendation generation:

      +
      userId_eventType → ZSET[...(pid, ts)...]
      +

      Within each ZSET:

      +
        +
      • The timestamp served as the score, maintaining temporal order
      • +
      • The productId (optionally with metadata) was the value
      • +
      +

      This allowed us to efficiently retrieve the interactions with HTTP-based API server with two query modes:

      +
        +
      • Fetch the last k interactions of a specific type for a given user with ZREVRANGE(userId_eventType, count)
      • +
      • Retrieve all interactions within a time range (e.g., last 24 hours) with ZREVRANGEBYSCORE(userId_eventType, timeRange)
      • +
      +

      Built-in Guardrails

      +

      Since Redis was the sole store, we implemented High Availability (HA) to prevent data loss. To optimize memory usage, we also enforced size limits per event type—only storing the last k interactions per user, with older entries getting truncated.

      +

      Conclusion: Laying the Foundation for Real-Time ML

      +

      In this first phase, we tackled the fundamentals—shifting from batch-based recommendations to a real-time Recommendation using ML platform that could keep up with Meesho’s growth.

      +

      With the IOP Framework, Online Feature Store, and Interaction Store, we built the core infrastructure to support real-time personalization at scale. These wins have already unlocked:

      +
        +
      • ✅ Faster, more dynamic recommendations for millions of users.
      • +
      • ✅ Better infrastructure efficiency, reducing wasted compute power.
      • +
      • ✅ A flexible, modular system that allows for further experimentation.
      • +
      +

      But this is just the beginning. While we've solved key challenges, certain roadblocks remain —from optimizing cost-performance trade-offs to seamlessly evolving schemas.

      +

      This foundational work laid the path for a reliable and scalable real-time feature serving layer.

      + + \ No newline at end of file diff --git a/docs/blog/episodic-memory-for-agents/index.html b/docs/blog/episodic-memory-for-agents/index.html new file mode 100644 index 00000000..da6e2e68 --- /dev/null +++ b/docs/blog/episodic-memory-for-agents/index.html @@ -0,0 +1,115 @@ + + + + + +Beyond Vector RAG: Building Agent Memory That Learns From Experience. | BharatMLStack + + + + + + + + +

      Beyond Vector RAG: Building Agent Memory That Learns From Experience.

      · 12 min read
      Adarsha Das
      Senior Architect @ Meesho

      BharatMLStack +Agent memory has come a long way. Persistent context, vector retrieval, knowledge graphs — the building blocks are real and getting better fast.

      +

      But most of what we call "memory" today is still closer to search: chunk text, embed it, retrieve whatever looks similar at query time. That works well for recalling facts and preferences. It starts to break down when you need an agent to recall what happened last time, learn from a mistake, or avoid repeating a failed approach.

      +

      We are trying to experiment something different. An episodic memory system where a frozen LLM — same weights, no retraining — produces increasingly better decisions over time because the memory feeding it context is continuously evolving. +Then we tested it. The results were interesting.

      +

      The Gap Nobody Talks About

      +

      Here's a scenario every engineering team has encountered: AI agent hits a Redis connection pool exhaustion issue. It misdiagnoses it as a database problem. You correct it. Next week, a different service has the exact same failure pattern. The agent makes the exact same mistake.

      +

      Why? Because LLMs don't learn at inference time. Corrections adjust behavior within a conversation. Once the session ends, the lesson is gone. The model weights haven't changed. The next conversation starts from zero.

      +

      Current "memory" systems don't fully address this. They store facts — user preferences, document chunks, conversation summaries. But facts aren't experience. Knowing that "Redis connection pools can exhaust under load" is different from remembering "last time I saw 500 errors under load, I assumed it was the database, I was wrong, it was actually the connection pool, and here's the correction I received."

      +

      The first is a fact. The second is an episode. The difference matters.

      +

      What's Wrong With Vector RAG as Memory

      +

      We identified five structural gaps in how current agent frameworks handle memory:

      +

      No concept of time. Two events are either semantically similar or they're not. The system can't represent "this happened after that" without distorting similarity scores. An agent can't reason about sequence or causality.

      +

      No concept of situation. A production incident and a design review might use the same technical vocabulary. Flat vector search can't distinguish them. Your agent retrieves planning notes when it should be retrieving incident postmortems.

      +

      No outcome tracking. The system stores what happened but not whether it worked. A failed approach and a successful one are equally retrievable. The agent has no way to prefer strategies that worked over strategies that didn't.

      +

      Summaries destroy evidence. Summarization-based memory compresses experience but discards the reasoning chain. The agent loses the ability to explain how it arrived at a conclusion. The audit trail is gone.

      +

      No causal links. Each memory chunk is independent. There's no way to express that incident A caused decision B, which led to outcome C, which was corrected by approach D. Without this structure, the agent can't traverse chains of reasoning.

      +

      These gaps compound. As an agent accumulates more experience, flat vector memory gets noisier, more contradictory, and less useful. The system degrades precisely when it should be improving.

      +

      The Architecture: Episodic Memory

      +

      We are building a memory system modeled on how human episodic memory works — not as a metaphor, but as an engineering specification.

      +

      The system has four layers:

      +

      Layer 1: Immutable Timeline

      +

      Every piece of agent experience is recorded as an append-only timeline entry. Each entry carries a semantic embedding (what it means), a timestamp (when it happened), and a state label (what situation the agent was in — debugging, planning, code review, incident response). Entries are never modified, never deleted, never summarized. This is the source of truth.

      +

      Layer 2: Episode Segmentation

      +

      The system watches the timeline and detects when one coherent unit of experience ends and another begins — via state transitions, semantic shifts, temporal gaps, or explicit signals. Each episode is a reference into the timeline (not a copy) with a generated summary, an outcome (SUCCESS, FAILURE, PARTIAL, UNKNOWN), decisions made, assumptions held, and corrections received.

      +

      The outcome field is the most important thing that doesn't exist in any current memory system. Without it, you can't learn from mistakes.

      +

      Layer 3: Episodic Graph

      +

      Episodes are connected through typed, weighted links: CAUSED_BY, LED_TO, RETRY_OF, LEARNED_FROM, CONTINUATION, CONTRADICTED. Over time, this forms a directed graph that enables traversal by meaning and causality. You can follow the chain: "this incident caused that investigation, which led to a failed fix, which was corrected by this approach."

      +

      Layer 4: Generalized Facts

      +

      When multiple episodes exhibit consistent patterns, the system extracts reasoning heuristics: "When services fail immediately after deployment with no traffic change, investigate configuration errors before connection pool problems." Facts are versioned, never overwritten, and maintain links back to supporting and contradicting episodes. When contradicting evidence accumulates, confidence decreases. When confidence drops below a threshold, the fact is revised — but the old version is preserved.

      +

      The LLM sits above all four layers. At query time, the system assembles structured context — relevant episodes with outcomes, applicable facts with confidence scores, causal narratives — and passes it to the LLM for reasoning. The model reasons over structured memory. It doesn't store or manage memory.

      +

      The Reinforcement Loop

      +

      This is where it comes together:

      +
        +
      1. Agent reasons using retrieved episodes and facts
      2. +
      3. Outcome is detected (CI pass/fail, user correction, test result)
      4. +
      5. New episode is created with outcome tracking
      6. +
      7. Links are created between the retrieved episodes and the new episode
      8. +
      9. Facts are reinforced (if outcome aligned) or contradicted (if outcome conflicted)
      10. +
      11. If the decision was wrong and corrected, a LEARNED_FROM link is created
      12. +
      +

      The model weights never change. The memory structure evolves continuously. A frozen LLM produces better decisions over time because it receives better context from richer memory.

      +

      The Experiment

      +

      We built the full system in Python (~1,000 lines) and tested it head-to-head against a baseline flat-vector RAG agent across a 9-round synthetic debugging scenario. Both agents used the identical LLM (Claude Sonnet 4) for reasoning. The only variable was the memory system.

      +

      The scenario was designed to test five capabilities:

      +
      Round TypeWhat It TestsRounds
      LEARNCan the agent build experience from failures?1, 2, 4
      RED HERRINGCan the agent resist applying a pattern when it doesn't fit?3
      TESTCan the agent apply learned patterns to new services?5, 6
      SUBTLECan the agent generalize to different symptoms, same root cause?7
      CORRECTIONAfter being corrected, does the agent adapt?8, 9
      +

      Rounds 1-4 build experience: three connection pool failures across different services, plus one red herring (a deployment config error that looks like a connection pool issue). Rounds 5-7 test whether the agent applies the learned pattern to unfamiliar services and subtle symptom variations. Rounds 8-9 are the critical test: the agent is corrected after misdiagnosing a deployment-correlated error, then tested on a near-identical scenario to see if it adapts.

      +

      Results

      +

      Decision Accuracy

      +
      RoundTypeEpisodic AgentBaseline Agent
      1LEARN
      2LEARN
      3RED HERRING
      4LEARN
      5TEST
      6TEST
      7SUBTLE
      8CORRECTION
      9CORRECTION
      Total7/9 (78%)5/9 (56%)
      +

      The episodic agent won 7-5. A 40% relative improvement in decision accuracy using the exact same LLM.

      +

      Where the Gap Opened

      +

      The episodic agent's advantage concentrated in exactly the rounds designed to test memory quality:

      +

      Rounds 5-6 (pattern application): The episodic agent cited 4 past failure episodes with connection pool exhaustion as root cause, complete with correction annotations. It correctly identified pool exhaustion in new services. The baseline retrieved disconnected chunks and suggested checking timeout configurations — a pattern it picked up from the Round 3 red herring.

      +

      Round 7 (subtle symptoms — latency increase, no errors): Both agents had the same evidence available. The episodic agent's retrieval surfaced a diverse set of episodes (thanks to MMR diversity filtering) including the Redis pool exhaustion from Round 6, which primed it to recognize that latency without errors can still be pool contention. The baseline defaulted to "check recent config changes."

      +

      Round 9 (adaptation after correction): This is the result we're most proud of. Look at the episodic agent's reasoning:

      +
      +

      "Episode 1 directly parallels this situation — errors spiking immediately after a deployment (v2.4.1 then, v3.1.0 now) with no traffic change. In that case, the root cause was a database migration that dropped an index. The generalized fact confirms that deployment-related issues with immediate onset after version changes are more likely caused by configuration errors or missing dependencies than by connection pool problems."

      +
      +

      It cited a specific past episode by analogy, quoted a generalized fact, and explained why this situation matches the deployment pattern rather than the connection pool pattern. The baseline gave a vaguer assessment.

      +

      Retrieval Quality

      +

      This is where the structural difference is most visible:

      +
      MetricEpisodic AgentBaseline Agent
      Retrieved items with explicit outcome labels100%25%
      Correct pattern applications (Rounds 4-7)4/41/4
      False positives (Rounds 8-9)00
      +

      Every item the episodic agent retrieved carried a structured outcome label (SUCCESS or FAILURE) with correction details. Only 25% of the baseline's chunks contained any outcome information — and those were incidental text mentions, not structured labels.

      +

      The episodic agent correctly applied the connection pool pattern in all four rounds where it was the root cause, and correctly avoided it in both rounds where it wasn't. The baseline applied it correctly once.

      +

      What Didn't Work

      +

      Two things didn't work as anticipated:

      +

      Round 3 (red herring): Both agents failed. The symptoms looked like connection pool issues, but the root cause was a deployment config change. At this point, the episodic agent had only seen connection pool episodes — it had no counter-evidence for deployment-correlated errors. You can't distinguish patterns you've only seen one side of. After Round 8 introduced a correction, the agent successfully avoided this mistake in Round 9.

      +

      Fact quality variance. Some extracted facts were specific and actionable ("Deployment-related issues with immediate onset are more likely configuration errors"). Others were vague ("Initial symptom-based diagnosis often leads to misidentifying the root cause"). A production system needs a usefulness filter, not just a confidence score.

      +

      What This Means

      +

      The most important finding isn't the accuracy improvement. It's that the reinforcement loop closes without retraining.

      +

      In the POC, we observed:

      +
        +
      • Rounds 1-4: Agent encounters failures, episodes recorded with outcomes and corrections
      • +
      • After Round 4: Fact extracted — "Connection pool exhaustion is a common root cause under load"
      • +
      • Rounds 5-7: Agent applies the pattern with increasing confidence (fact support count grows)
      • +
      • Round 8: Agent encounters a deployment error, correctly identifies it as config, gets corrected
      • +
      • After Round 8: New fact — "Deployment-related issues with immediate onset are more likely configuration errors"
      • +
      • Round 9: Agent receives near-identical scenario, correctly avoids connection pool pattern, cites the Round 8 correction
      • +
      +

      The model didn't change. The memory evolved. That's the whole point.

      +

      How It Compares to Existing Solutions

      +

      Agent memory is a fast-moving space with several strong systems, each solving a different slice of the problem:

      +

      Mem0 excels at persistent personalization — extracting user preferences, managing session context, and reducing token costs through intelligent compression. It's the most production-ready memory layer available and integrates with nearly every agent framework. Its focus is on remembering about users and conversations rather than learning from task-level outcomes, which is a different problem than the one we're exploring here.

      +

      Zep/Graphiti is doing some of the most interesting work in temporal knowledge graphs. Their bi-temporal model — tracking both when an event occurred and when it was ingested — addresses a real structural gap in how agent memory handles changing facts over time. Their episode and entity subgraphs share some philosophical DNA with our approach. Where our work diverges is in outcome tracking and reinforcement: we're specifically focused on whether a decision worked, and using that signal to update memory structure.

      +

      Letta (formerly MemGPT) pioneered self-editing memory — giving the LLM tools to manage its own memory blocks. This is a powerful paradigm, and their recent work on "Context Repositories" and sleep-time compute suggests they're actively pushing toward agents that learn over time. Their team has been transparent that experiential learning is an unsolved problem, which is part of what motivated our exploration.

      +

      MemRL (Jan 2026 paper) is the closest to our work academically. It shares the core insight of decoupling stable LLM reasoning from plastic, evolving memory. Their approach uses reinforcement learning to assign utility Q-values to memories, which is elegant but requires training a value function. Our approach is purely structural — no training step, no Q-values, just graph evolution and LLM-based reasoning over outcomes.

      +

      The common thread: most existing systems focus on knowledge persistence — remembering facts, preferences, and conversation history across sessions. The problem we're exploring is experiential learning — tracking whether past decisions worked, forming causal chains between episodes, and extracting reasoning heuristics that improve over time. These are complementary capabilities that would be needed by an ideal production system.

      +

      Try It Yourself

      +

      The prototype is available in our experiments directory:

      +
      experiments/episodic-memory-prototype/
      ├── memory/ # Timeline, encoder, episodes, graph, facts, retriever, reinforcer
      ├── agent/ # Episodic memory agent
      ├── baseline/ # Flat vector RAG agent (comparison)
      ├── simulator/ # 9-round debugging scenario
      ├── eval/ # Head-to-head comparison + scoring
      └── tests/
      +

      To run the comparison:

      +
      cd experiments/episodic-memory-prototype
      python -m venv .venv && source .venv/bin/activate
      pip install -r requirements.txt
      export ANTHROPIC_API_KEY=sk-ant-...
      python -m eval.compare
      +

      Without an API key, it runs in heuristic mode (keyword-based decisions). With a key, both agents use Claude Sonnet for reasoning — that's where the quality gap becomes visible.

      +

      Conclusion

      +

      This is a 9-round synthetic scenario we designed. It demonstrates the poc architecture works end-to-end and shows where episodic memory provides qualitatively different reasoning. It is not a peer-reviewed benchmark and should not be interpreted as a statistically rigorous claim. We're publishing the prototype so others can reproduce and extend the evaluation. +If this sparks interest do trigger github discussion.

      +
      +

      The episodic memory prototype is available in BharatMLStack repo at /experiments/episodic-memory-prototype

      + + \ No newline at end of file diff --git a/docs/blog/index.html b/docs/blog/index.html index b033836d..3a5bed61 100644 --- a/docs/blog/index.html +++ b/docs/blog/index.html @@ -3,18 +3,473 @@ -Blog | BharatMLStack - - - +Blog | BharatMLStack + + + -

      Building Meesho’s ML Platform: From Chaos to Cutting-Edge (Part 1)

      · 11 min read
      Adarsha Das
      Senior Architect @ Meesho
      Aditya Kumar
      SDE-III @ Meesho
      Bhawani Singh
      SDE-IV @ Meesho
      Jigar Dave
      SDE-IV @ Meesho

      BharatMLStack

      -

      The Genesis: How a Friday Night Roast Sparked Meesho’s ML Platform

      -

      It all started in early 2022, over a casual Friday evening catch-up. Like many great origin stories, this one began with friendly banter between a group of backend engineers and data scientists. As the conversations unfolded, so did the roasting—until one remark hit a little too close to home:

      +

      Beyond Vector RAG: Building Agent Memory That Learns From Experience.

      · 12 min read
      Adarsha Das
      Senior Architect @ Meesho

      BharatMLStack +Agent memory has come a long way. Persistent context, vector retrieval, knowledge graphs — the building blocks are real and getting better fast.

      +

      But most of what we call "memory" today is still closer to search: chunk text, embed it, retrieve whatever looks similar at query time. That works well for recalling facts and preferences. It starts to break down when you need an agent to recall what happened last time, learn from a mistake, or avoid repeating a failed approach.

      +

      We are trying to experiment something different. An episodic memory system where a frozen LLM — same weights, no retraining — produces increasingly better decisions over time because the memory feeding it context is continuously evolving. +Then we tested it. The results were interesting.

      LLM Inference Optimization Techniques: Engineering Sub-Second Latency at Scale

      · 5 min read
      Jaya Kumar
      Lead ML Engineer @ Meesho

      BharatMLStack +Raw execution of Large Language Models is inherently expensive and memory-intensive. To achieve sub-second latency and high throughput, we implement a multi-layered optimization strategy that targets the entire inference stack—from memory management to kernel execution.

      +

      1. Advanced Memory Management: Paged & Prefix KV Caching

      +

      The most significant bottleneck in LLM inference is not always compute, but memory bandwidth—specifically managing the Key-Value (KV) cache.

      +

      Paged KV caching

      +

      Standard caching suffers from fragmentation. We use Paged KV caching, which operates similarly to an operating system's virtual memory: the KV cache is divided into non-contiguous blocks. This lets us serve larger batch sizes without running out of memory.

      +

      KV cache quantization

      +

      To further maximize available memory, we implement KV cache quantization (e.g., FP8). By compressing stored attention keys and values from 16-bit to 8-bit, we nearly double the effective context window capacity of the GPU, allowing longer conversations or larger batches without materially degrading quality.

      +

      Prefix caching (the "voice bot" optimizer)

      +

      For use cases like GenAI voice bots where the system prompt (e.g., "You are a helpful assistant...") is static across thousands of requests, we enable prefix caching.

      +
        +
      • Impact: By reusing pre-computed KV states for common prefixes, we achieve a cache hit rate of ~90%. This reduces Time To First Token (TTFT) by skipping redundant computation of the system prompt.
      • +
      +

      2. Aggressive Quantization (INT4 AWQ & FP8)

      +

      Running models in their native 16-bit precision (BF16) restricts maximum batch size and throughput. We use quantization to shrink model weights without sacrificing accuracy.

      +

      INT4 AWQ (Activation-aware Weight Quantization)

      +

      For the Llama 3 family, we use AWQ to compress weights to 4 bits. This reduces model size by ~75%, allowing larger models to fit into L4 GPU memory and significantly improving token generation speed.

      +

      FP8 precision

      +

      For NVIDIA Hopper (H100) architectures, we are exploring FP8 quantization, leveraging native FP8 tensor cores to accelerate matrix multiplications while maintaining a higher dynamic range than integer quantization.

      +
        +
      • Verification: We validate quantized models by comparing dot-product similarity of embeddings against the FP16 baseline, consistently achieving >99% similarity.
      • +
      +

      3. Kernel Fusion & Custom Plugins

      +

      To minimize overhead from launching thousands of small GPU operations, we fuse them into monolithic kernels using NVIDIA TensorRT plugins.

      +
        +
      • Flash attention & FMHA: We enable Fused Multi-Head Attention (FMHA) combined with flash attention to reduce memory reads/writes.
      • +
      • GEMM plugins: We use specialized GEMM plugins to accelerate transformer linear layers.
      • +
      • Removing input padding: Instead of padding short sequences to match the longest, we remove input padding so the GPU processes only valid tokens.
      • +
      +

      4. Inflight (Continuous) Batching

      +

      Traditional static batching waits for all requests in a batch to finish before returning results—so one long response delays everyone else.

      +

      We implement inflight batching: as soon as one request completes, its slot is freed and filled by a new request from the queue. This keeps GPUs saturated and decouples latency of short queries from long ones.

      +

      5. Parallelism Strategies: Scaling Beyond One GPU

      +

      For large models (e.g., 70B+ parameters) that cannot fit into the VRAM of a single GPU, we use parallelism strategies.

      +
        +
      • Tensor parallelism (TP): Split weight matrices across multiple GPUs (e.g., 4× L4 or 8× A100). Each GPU computes a shard and outputs are reduced at every layer.
      • +
      • Pipeline parallelism (PP): Split model layers across GPUs to pipeline compute (e.g., while one GPU computes later layers for Request A, another starts early layers for Request B).
      • +
      +

      6. Speculative Decoding

      +

      To reduce inter-token latency (ITL), we explore speculative decoding.

      +
        +
      • Mechanism: A smaller, faster "draft" model speculatively generates a short token sequence (e.g., 5 tokens).
      • +
      • Verification: The larger target model verifies those tokens in one parallel forward pass. If correct, we effectively generate multiple tokens per large-model step; if not, we discard and regenerate. This is effective for predictable text, improving perceived generation speed.
      • +
      +

      Few Benchmarks

      +

      Below are a couple of representative use cases and performance numbers.

      +

      Search query rewriting

      +
        +
      • LLM: Fine-tuned llama-3.2-1B
      • +
      • Input & output token length: ~10–20
      • +
      • Response type: Non-streaming
      • +
      +
      Inference runtimeHardwareMax requests/secMax p99 latency
      TensorRT-LLM4 × L4 GPUs (multi-GPU)100095 ms
      TensorRT-LLM1 × A100 40 GB GPU100069 ms
      +

      Voice bot query

      +
        +
      • LLM: Llama-3.1-8B
      • +
      • Input token length: ~1900–2000
      • +
      • Output token length: ~200
      • +
      • Response type: Streaming
      • +
      +
      Inference runtimeConcurrencyp99 TTFT (ms)p99 ITL (ms)Token throughput (tokens/sec)Request throughput (req/sec)Hardware
      TensorRT-LLM136.2722.7845.660.23L4
      TensorRT-LLM249.8123.2189.370.45L4
      TensorRT-LLM455.3336.62153.390.78L4
      TensorRT-LLM866.539.11279.881.47L4
      TensorRT-LLM16131.830.39547.82.77L4
      TensorRT-LLM32277.2248.02925.74.78L4
      TensorRT-LLM64498.5271.621,164.406.2L4
      TensorRT-LLM128677.31120.371,445.187.69L4
      TensorRT-LLM2561,926.31216.881,600.818.52L4
      TensorRT-LLM121.179.24130.050.68A100
      TensorRT-LLM225.789.21264.51.35A100
      TensorRT-LLM428.5210.99437.692.27A100
      TensorRT-LLM834.412.61760.493.96A100
      TensorRT-LLM1668.0314.321,343.807.01A100
      TensorRT-LLM32185.9616.822,287.3011.92A100
      TensorRT-LLM64136.8721.173,625.2218.89A100
      TensorRT-LLM128463.7834.154,456.5123.24A100
      TensorRT-LLM256890.1259.185,188.2427.05A100
      +

      Conclusion

      +

      High-performance LLM inference is fundamentally a systems engineering problem: memory efficiency, kernel execution, batching strategy, and parallelism determine real-world latency and throughput. Techniques such as paged KV caching, aggressive quantization, kernel fusion, and inflight batching improve GPU utilization while reducing latency and memory pressure.

      +

      These optimizations enable the platform to deliver sub-second responses, sustain high concurrency, and efficiently serve both lightweight and long-context workloads. By continuously optimizing across the full inference stack, we keep LLM serving scalable, cost-efficient, and production-ready for real-time AI applications.

      Designing a Production-Grade LLM Inference Platform: From Model Weights to Scalable GPU Serving

      · 14 min read
      Jaya Kumar
      Lead ML Engineer @ Meesho

      BharatMLStack +Serving large language models in production introduces new challenges across infrastructure, performance optimization, and operational lifecycle management. The LLM Inference Platform addresses these challenges by providing a unified system for deploying and managing open-source and fine-tuned LLMs at scale.

      +

      The platform implements a complete LLMOps lifecycle — from model registration and automated compilation to deployment, runtime optimization, and monitoring. Designed as a self-service environment, users can onboard models directly from open repositories such as Hugging Face or upload custom fine-tuned models, and deploy them using a single-click workflow with no manual infrastructure or configuration steps required.

      +

      In addition to fully automated deployment, the platform allows users to select and apply custom inference optimization techniques — such as quantization strategies, batching configurations, and runtime-specific performance enhancements — enabling teams to balance latency, throughput, and cost based on their use case. The goal is to reduce operational friction while enabling high-performance, production-grade LLM inference.

      +

      Why LLM Inference Is not just bigger ML model serving

      +

      Large language model (LLM) inference introduces a fundamentally different set of challenges compared to traditional machine learning inference. While classical ML models typically perform a single forward pass to produce a fixed prediction, LLMs operate as autoregressive systems, generating outputs token by token based on previously generated context. This difference dramatically changes how inference systems must be designed, optimized, and scaled.

      +

      Autoregressive Generation and Sequential Computation:

      +

      Unlike traditional models such as classifiers or recommenders — where inference cost is relatively constant — LLMs generate responses incrementally. Each new token depends on all previously generated tokens, making inference inherently sequential and dynamic. This means latency and compute requirements vary significantly depending on prompt length and output size, introducing complexity in scheduling and resource allocation. +Because tokens cannot be generated fully in parallel during decoding, GPUs may become underutilized without specialized batching and scheduling strategies. This has led to the development of dedicated LLM inference engines optimized for token-level execution.

      +

      Prefill and Decode Phases:

      +

      LLM inference typically consists of two distinct stages:

      +
        +
      • Prefill phase — the model processes the input prompt and builds internal representations. This stage is compute-heavy and highly parallelizable.
      • +
      • Decode phase — the model generates tokens sequentially, predicting one token at a time using previously generated context.
      • +
      +

      The decode stage often becomes memory-bound rather than compute-bound, which creates new performance bottlenecks compared to traditional ML workloads.

      +

      Context Management and KV Caching:

      +

      Another fundamental difference lies in how LLMs maintain context. Transformer-based models rely on attention mechanisms that require access to past token representations. To avoid recomputing these representations repeatedly, inference engines use key-value (KV) caching, which stores intermediate activations from previous tokens. +KV caching significantly improves performance by eliminating redundant computation, but it introduces new challenges:

      +
        +
      • Memory consumption grows with sequence length and batch size
      • +
      • GPU memory becomes a critical bottleneck
      • +
      • Efficient memory management becomes essential for scaling concurrent requests
      • +
      +

      This tradeoff between compute efficiency and memory usage is unique to LLM inference workloads.

      +

      Dynamic and Irregular Workloads:

      +

      Traditional ML inference typically operates on fixed-size inputs with predictable latency. In contrast, LLM requests vary widely in prompt length, output length, and runtime behavior. As a result:

      +
        +
      • Batch sizes must be dynamic rather than static
      • +
      • Requests may enter and leave batches asynchronously
      • +
      • Scheduling systems must continuously rebalance workloads to maximize GPU utilization
      • +
      +

      These characteristics require specialized serving architectures that differ significantly from standard ML serving pipelines.

      +

      Streaming and User Experience Constraints:

      +

      Another distinguishing factor is the expectation of real-time streaming responses. Instead of returning a single output, LLM systems often stream tokens to users as they are generated. +Because of these differences — sequential generation, growing memory requirements, dynamic workloads, and streaming constraints — LLM inference cannot be treated as a simple extension of existing ML serving systems. Production platforms must incorporate specialized runtime engines, advanced optimization techniques, and observability tailored specifically to LLM workloads.

      +

      LLMOps: High-Level Architecture

      +

      LLM Architecture

      +

      The LLM Inference Framework is designed as a fully automated, end-to-end system for deploying and operating open-source and fine-tuned large language models at scale. The architecture abstracts the complexity of model optimization, hardware selection, deployment, and runtime management into a unified workflow that enables users to move from raw model weights to production-ready inference endpoints with minimal manual intervention.

      +

      Our LLM Inference Framework is architected not just as a serving engine, but as a complete lifecycle management system. As illustrated in the high-level design below, the platform automates the journey of a model through seven distinct stages, ensuring reproducibility, performance, and scalability.

      +
        +
      1. +

        Onboarding & Registration (The Source of Truth)

        +

        The lifecycle begins with the Data Scientist or engineer.

        +
          +
        • Model Ingestion: Users onboard models—whether open-source (Hugging Face, NeMo) or internally fine-tuned—via the Truffle Box SDK/UI.
        • +
        • LLM + Prompt Registry: Unlike traditional systems that only track model weights, our registry is a unified control plane. It stores both the Model Artifacts and the Prompt Templates. This allows Data Scientists to register and version-control prompts (e.g., "customer_support_v2") independently of the application code.
        • +
        +
      2. +
      3. +

        The "Black Box" Build Engine

        +

        Once a model is registered, the Automated LLM Compiler + Quantizer Module kicks off a background job on ephemeral GPU resources.

        +
          +
        • Transformation: The raw model is converted into a TRT-LLM Checkpoint.
        • +
        • Quantization: The system automatically applies quantization algorithms (like INT4 AWQ or FP8) to reduce memory footprint.
        • +
        • Engine Building: Finally, it compiles a highly optimized TRT Engine specifically tuned for the target hardware.
        • +
        +
      4. +
      5. +

        Intelligent Profiling & Validation

        +

        Before deployment, the new engine passes through the Hardware & Inference Runtime Profiler.

        +
          +
        • Benchmarking: This module empirically tests the engine against various hardware configurations (L4 vs. A100) and runtimes (TRT-LLM vs. vLLM).
        • +
        • Optimization: It recommends the optimal configuration that meets latency SLAs (Time-To-First-Token) while minimizing cost.
        • +
        +
      6. +
      7. +

        Smart Artifact Generation & Distribution

        +

        To solve the Kubernetes "Cold Start" problem, the LLM Serving Artifacts Generation module packages the model using a bifurcated strategy:

        +
          +
        • Standard Models: Artifacts are uploaded to Cloud Storage (GCS) and downloaded by pods at startup.
        • +
        • Very Large Models: For massive models (>8GB) where network downloads are too slow, the system pre-caches the model onto Secondary Boot Disks. These disks are attached directly to new GPU nodes during autoscaling, eliminating download wait times.
        • +
        +
      8. +
      9. +

        Image Streaming & Deployment

        +

        Simultaneously, the inference runtime container images are pulled from the Artifact Registry.

        +
          +
        • Image Streaming: We utilize container image streaming to allow pods to start initializing while the massive Triton/Dynamo container layers are still downloading, further shaving seconds off the startup time. link
        • +
        +
      10. +
      11. +

        The Inference Runtime (Kubernetes)

        +

        The workload lands on Kubernetes with Autoscaling.

        +
          +
        • Dynamic Backends: Depending on the profile generated in Stage 3, the pod initializes either TensorRT-LLM (for throughput) or vLLM (for flexibility), or spins up a Dynamo worker for distributed inference.
        • +
        • Data Loading: The pod either downloads the model from Cloud Storage or mounts the pre-warmed Secondary Boot Disk ("Pull from Disk").
        • +
        +
      12. +
      13. +

        Client Interaction & Observability

        +

        Finally, the LLM Inference Client executes the request.

        +
          +
        • Prompt Injection: The client pulls the specific prompt template ID from the Registry, ensuring the exact versioned instructions are used.
        • +
        • Streaming Response: The request is sent via gRPC, and tokens are streamed back to the user in real-time.
        • +
        +
      14. +
      15. +

        Observability: Monitoring the Pulse of GenAI

        +

        In traditional microservices, success is measured by CPU utilization and request latency (p99). For Large Language Models, these metrics are insufficient. A user doesn't care if the GPU is at 80% utilization; they care about how fast the first word appears and how smoothly the rest of the sentence follows.

        +

        To capture the true user experience, our platform instrumentation focuses on three critical LLM-specific metrics:

        +
          +
        1. +

          Time to First Token (TTFT)

          +
            +
          • Definition: TTFT measures the time elapsed from the moment a request is received until the very first token is generated and streamed back to the user.
          • +
          • Why it matters: This represents the "Prefill Phase" latency—the time the model takes to process the input prompt and load weights. A high TTFT makes the application feel unresponsive or "hung."
          • +
          • Optimization: We closely monitor TTFT to ensure our Prefix Caching is effective (aiming for high cache hitrates), which drastically lowers this metric by skipping redundant prompt processing.
          • +
          +
        2. +
        3. +

          Inter-Token Latency (ITL)

          +
            +
          • Definition: ITL measures the average time interval between the generation of consecutive tokens during the "Decode Phase".
          • +
          • Why it matters: This defines the "perceived speed" of reading. Even if the first token is fast (low TTFT), high ITL makes the text generation look "jerky" or slow to the user.
          • +
          • Benchmarks: In our testing with Llama 3.1, we track p99 ITL to ensure it stays below human reading speeds to maintain a natural conversational flow.
          • +
          +
        4. +
        5. +

          Token Throughput vs. Request Throughput

          +
            +
          • We distinguish between two types of throughput to balance system efficiency with user load:
          • +
          • Token Throughput (tokens/sec): The total number of tokens generated across all concurrent requests. This measures the raw compute efficiency of the GPU and the effectiveness of batching.
          • +
          • Request Throughput (req/sec): The number of distinct user queries served per second. We use this to determine autoscaling thresholds, ensuring we scale out before the queue depth impacts ITL.
          • +
          +
        6. +
        7. +

          The Monitoring Stack

          +
            +
          • Real-time Dashboards: We utilize Grafana to visualize these streaming metrics in real-time, allowing on-call engineers to spot "slow generation" incidents that generic "500 error" alerts would miss.
          • +
          • Request Tracing: Since Triton Inference Server does not log request payloads by default, we integrate a Helix Client to asynchronously publish request logs to Log Tables. This allows us to trace a specific "slow" request back to its prompt to understand if a complex input caused the latency spike.
          • +
          +
        8. +
        +
      16. +
      +

      Supported Inference backends (TensorRT LLM, Dynamo & vLLM)

      +

      Tailored for the Use Case: We do not believe in a "one-size-fits-all" approach to inference. Different use cases—whether a real-time voice bot requiring ultra-lowsub-second latency or a massive reasoning task requiring huge context windows—demand different runtime characteristics. Our platform is designed to be runtime-agnostic, allowing us to automatically select and tailor the best engine based on the specific requirements of the application:

      +
        +
      1. +

        TensorRT-LLM: The High-Performance Standard

        +

        Suitable for: High-throughput production workloads where latency is critical (e.g., customer support chat, real-time voice bots).

        +

        TensorRT-LLM serves as our default backend for these scenarios. Our internal benchmarks on Llama 3.1 and 3.2 models demonstrated that a tuned TensorRT-LLM engine significantly outperforms standard runtimes, especially when utilizing INT4 AWQ and FP8 quantization .

        +

        Key optimizations we tailor for these high-load cases include:

        +
          +
        • Optimized execution via TensorRT engine compilation
        • +
        • Quantization-aware execution for reduced memory usage and improved throughput
        • +
        • Inflight Batching: Allowing requests to be processed continuously without waiting for the entire batch to finish, drastically improving GPU utilization .
        • +
        • Custom Plugins: Enabling specific NVIDIA plugins like the GEMM plugin and GPT Attention plugin to accelerate matrix multiplications and attention mechanisms .
        • +
        +
      2. +
      3. +

        Dynamo: Distributed Inference for Reasoning Models

        +

        Suitable for: Very large "reasoning" models (70B+) or scenarios requiring massive context windows where a single GPU's memory is insufficient.

        +

        For these memory-bound tasks, we utilize Dynamo, a low-latency distributed inference framework . Unlike monolithic servers, Dynamo disaggregates the inference process to scale resources horizontally:

        +
          +
        • KV Aware Routing: A specialized router directs requests to workers that already hold the relevant Key-Value (KV) cache, minimizing redundant computation .
        • +
        • Prefill vs. Decode Split: The workload is divided into Prefill Workers (processing the prompt) and Decode Workers (generating tokens), allowing us to scale the compute-heavy "reading" phase independently from the memory-heavy "writing" phase .
        • +
        • Distributed execution across multiple GPU resources
        • +
        +
      4. +
      5. +

        vLLM: The Flexible Baseline

        +

        Suitable for: Rapid prototyping, testing new model architectures, or low-traffic internal tools where ease of deployment outweighs raw throughput.

        +

        While TensorRT-LLM is optimized for maximum speed, vLLM provides a robust and flexible baseline .

        +
          +
        • High throughput through dynamic batching and efficient memory utilization
        • +
        • Paged KV cache management for handling long contexts and concurrent requests
        • +
        • Strong support for open-source model ecosystems
        • +
        • Rapid Adoption: It allows us to onboard new model architectures immediately without waiting for a custom TensorRT build.
        • +
        • Benchmarking Insight: In our internal tests, vLLM provided a strong baseline but often lacked the specific max-token optimizations present in our custom TRT engines . We use it strategically for initial testing before committing to a full TensorRT optimization pipeline.
        • +
        +
      6. +
      +

      Conclusion

      +

      Large language model inference introduces a fundamentally new class of infrastructure challenges—where performance is governed not just by raw compute, but by memory efficiency, intelligent scheduling, runtime specialization, and lifecycle automation. Unlike traditional ML serving, LLM inference requires systems that understand token-level execution, manage rapidly growing context state, and continuously balance latency, throughput, and cost under highly dynamic workloads.

      +

      The LLM Inference Framework addresses these challenges by transforming inference into a fully automated, reproducible lifecycle—from model onboarding and compilation to deployment, optimization, and observability. By integrating automated quantization and engine compilation, intelligent runtime selection, cold-start mitigation strategies, and LLM-specific observability metrics such as Time-to-First-Token and Inter-Token Latency, the platform ensures both high performance and operational simplicity.

      +

      Equally important, the framework is designed with flexibility and future evolution in mind. Its runtime-agnostic architecture enables seamless adoption of emerging inference engines, hardware accelerators, and optimization techniques without requiring platform redesign. This ensures that teams can continuously leverage advancements in the rapidly evolving LLM ecosystem while maintaining consistent operational workflows.

      +

      Ultimately, the goal of the platform is to make production-scale LLM deployment as seamless and reliable as traditional software deployment—allowing teams to focus on building intelligent applications rather than managing infrastructure complexity. By combining lifecycle automation, runtime optimization, and deep observability, the LLM Inference Framework provides a scalable foundation for delivering fast, cost-efficient, and production-ready LLM experiences.

      +

      Future Explorations

      +

      While we have achieved significant milestones in latency and throughput, the landscape of GenAI is evolving rapidly. Our roadmap focuses on increasing flexibility, reducing costs, and enhancing reliability for enterprise-grade workloads. Here is what we are building next:

      +
        +
      • TPU Support: To diversify our hardware supply chain and further optimize cost-per-token, we are evaluating Google Cloud TPUs to bake it into our platform. By leveraging the JAX and PyTorch/XLA ecosystems, we aim to unlock the massive throughput potential of TPU v5e chips, particularly for our open-source Llama models. This will allow the hardware profiler to dynamically choose between NVIDIA GPUs and Google TPUs based on real-time availability and price-performance metrics.
      • +
      • Multi-LoRA Serving (Serverless Experience): Currently, deploying a fine-tuned model requires a dedicated GPU. We are building support for Multi-LoRA serving, which will allow us to serve hundreds of unique, fine-tuned adapters on top of a single frozen base model. This will drastically reduce costs for multi-tenant applications, enabling a "serverless" experience where specific fine-tunes are hot-swapped instantly per request.
      • +
      • Spot Instance Orchestration: To further optimize cloud costs, we are developing fault-tolerant mechanisms to run inference workloads on Spot Instances. By implementing aggressive checkpointing and seamless request draining, we aim to leverage cheaper, preemptible compute capacity without interrupting the user's streaming experience.
      • +
      • Semantic Caching Layer: We plan to move beyond standard Prefix Caching to implement Semantic Caching. By using a vector database to fetch responses for semantically similar queries (e.g., "How do I reset my password?" vs. "Password reset steps"), we can bypass the GPU entirely for repetitive queries, reducing latency to near-zero.
      • +
      • Context-Aware Autoscaling: Standard CPU/GPU utilization metrics are often insufficient signals for scaling LLMs. We are working on KV-cache pressure metrics for autoscaling. This ensures that we scale out before the memory fills up, preventing eviction-based slowdowns during traffic spikes.
      • +
      • Online Evaluation & Guardrails: We are integrating a lightweight "Trust Layer" into the proxy. This will allow for low-latency input/output filtering (Guardrails) and asynchronous "LLM-as-a-Judge" evaluation pipelines to monitor response quality in production, not just system health.
      • +

      Cracking the Code: Scaling Model Inference & Real-Time Embedding Search

      · 4 min read
      Aditya Kumar
      Lead Software Engineer @ Meesho
      Jaya Kumar
      Lead ML Engineer @ Meesho
      Adarsha Das
      Senior Architect @ Meesho

      BharatMLStack +By mid-2023, we had transformed our ML stack—building a real-time feature store, optimizing model retrieval, and fine-tuning ranking. But two critical gaps remained:

      +
        +
      • 🔹 Scaling model inference without hitting infrastructure roadblocks
      • +
      • 🔹 Moving embedding search from batch to real-time for candidate generation
      • +
      +

      Here’s how we tackled these last-mile challenges, broke free from infrastructure constraints, and built a cost-efficient, high-performance system.

      +

      Breaking Free from the Scalability Ceiling

      +

      The Model Serving Bottleneck—A Wake-Up Call

      +

      July 2023. With just months left for the Mega Blockbuster Sale (MBS), we noticed a serious issue—scaling our model-serving infrastructure was taking 10–15 minutes. In real-time ML, that’s an eternity. +In one of our war rooms, we ran a quick experiment:

      +
        +
      • 🚀 We deployed an XGBoost model on a self-hosted Triton Inference Server running on a 16-core machine.
      • +
      • 🚀 Fired requests and compared the outputs with our existing cloud-hosted setup.
      • +
      • 🚀 The results matched—perfectly.
      • +
      +

      That moment changed everything. We prepped a backup Triton setup on EKS, just in case our cloud provider couldn't allocate enough compute resources in time. Luckily, they did—but the seed was planted. +Then in October, just two weeks before MBS, we got an alarming response from our infrastructure team: +"Node availability may be an issue." +With no time to waste, we moved 30% of real-time ML traffic to our self-hosted Triton cluster. The results?

      +
        +
      • ✅ p99 latency dropped from 90–100ms to 30–40ms
      • +
      • ✅ Triton handled significantly higher throughput on fewer resources
      • +
      • ✅ No model changes were needed
      • +
      +

      MBS ran without a hitch, proving that self-hosted inference was the way forward.

      +

      Scaling Triton on GKE

      +

      This left us with two choices:

      +
        +
      • 1️⃣ Port models to a managed cloud inference service, investing time in learning a new deployment stack
      • +
      • 2️⃣ Scale our existing Triton setup on GKE, optimizing for cost and performance
      • +
      +

      We went with Option 2—and it slashed inference costs to 35% of what we previously paid, while giving us full control over scaling and optimizations.

      +

      Fixing the Cold Start Problem

      +

      As we onboarded more deep learning (DL) models, we hit a new bottleneck, new inference pods took 7–9 minutes to spin up.

      +

      After profiling, we found the culprits:

      +
        +
      • Triton’s base image—a massive 5GB
      • +
      • Model binaries—often 1GB+
      • +
      • Startup delay—mostly due to downloading and initializing these assets
      • +
      +

      To fix this, we built a lightweight Triton image, stripping unused components and shrinking the size to 900MB. This cut cold start times drastically, making auto-scaling faster and smoother.

      +

      Embedding Search: The Last Piece of the Puzzle

      +

      By mid-2023, most of our ML stack had gone real-time—except for Candidate Generation (CG), which still ran in batch mode. To truly power real-time recommendations, we needed an online embedding search system.

      +

      Choosing the Right Vector Database

      +

      We benchmarked three production-ready vector DBs across key parameters:

      +
        +
      • Milvus
      • +
      • Qdrant
      • +
      • Weaviate
      • +
      +

      After extensive POCs, Qdrant stood out for its:

      +
        +
      • ✅ Blazing-fast search latency on high-dimensional vectors
      • +
      • ✅ Efficient memory usage, crucial for in-memory workloads
      • +
      • ✅ Support for upserts and soft deletes, vital for Ads use cases
      • +
      • ✅ gRPC + REST APIs, making integration seamless
      • +
      • ✅ Powerful filtering, allowing fine-tuned retrieval (e.g., filtering Ads by category, active status, etc.)
      • +
      +

      At its core, Qdrant uses HNSW indexing, delivering both high recall and low-latency nearest-neighbor search—a perfect fit for our needs.

      +

      Embedding Freshness & Real-Time Updates

      +

      To ensure embeddings stayed up to date, we built a dual ingestion pipeline:

      +
        +
      • 📌 Daily Refresh: A bulk pipeline updated embeddings overnight
      • +
      • 📌 Real-Time Updates: Ads events triggered immediate upserts/deletes
      • +
      +

      This setup powered real-time "Similar Products" recommendations on the product page and became the foundation for Ads Candidate Generation, ensuring the right ads surfaced in milliseconds.

      +

      Skye

      +

      Final Takeaways: Scaling Smartly for Real-Time ML

      +
        +
      • 🚀 Self-hosted inference on Triton gave us lower cost, faster scaling, and better performance than managed services
      • +
      • 🚀 Building a custom Triton image reduced cold starts, improving responsiveness
      • +
      • 🚀 Qdrant-based embedding search enabled real-time personalization at scale
      • +
      • 🚀 Real-time updates for embeddings unlocked dynamic, up-to-date recommendations
      • +
      +

      By early 2024, Meesho’s ML stack had evolved into a fully real-time, scalable, and cost-efficient system, setting the foundation for even bigger leaps ahead.

      Building Meesho’s ML Platform: Lessons from the First-Gen System (Part 2)

      · 7 min read
      Bhawani Singh
      Architect @ Meesho
      Jigar Dave
      Lead Software Engineer @ Meesho
      Adarsha Das
      Senior Architect @ Meesho

      BharatMLStack +By late 2022, we had built something we were truly proud of—a real-time ML serving system with a DAG-based executor, a feature store, and an interaction store powering key ranking and personalization models. It was a major milestone, the culmination of months of effort from data scientists, ML engineers, and backend teams. Our system was live, and we were ready to push the boundaries of experimentation. +And it worked. Mostly. +But soon, cracks appeared. Every new model needed custom feature retrieval logic, DAGs became dense and unmanageable, and scaling turned into a constant firefight. Costs surged, and infra bottlenecks slowed experimentation. Our system worked, but it wasn’t built for scale. +This is the story of how we tackled these challenges—building Inferflow for seamless feature retrieval, optimizing real-time infra, and cutting costs while scaling to millions of QPS.

      +

      The Cost of Success

      +

      Every new Ranker model required its own feature set, often pulling from different entities. Each addition meant:

      +
        +
      • Adding new DAG nodes in IOP
      • +
      • Writing custom logic to fetch features from multiple sources (e.g., user, product, user × category)
      • +
      • Inferring intermediate features (e.g., extracting category from a product to fetch user × category data)
      • +
      • Optimizing I/O and dealing with the inevitable bugs
      • +
      +

      What began as clean DAGs soon turned into a tangled web of cross-dependent graphs. Every experimentation cycle meant new nodes, new dependencies, and slower iterations.

      +

      Scaling Pains (and Cassandra’s Limits)

      +

      At some point, we were hitting:

      +
        +
      • 250–300K reads/sec
      • +
      • 1M writes/sec (during lean hours)
      • +
      +

      All of this ran on Cassandra. While its distributed architecture had been proven in production, operating large-scale clusters came with considerable infrastructure overhead. Our proof-of-concept (POC) demonstrated throughput of around 100K ops/sec, but as we scaled further, the challenges grew. Ensuring node health, optimizing compaction, and maintaining storage balance became increasingly demanding. We also observed latency spikes under heavy load, alongside a sharp increase in total cost of ownership.

      +

      Interaction Store Woes

      +

      Our interaction store was another ticking time bomb:

      +
        +
      • 🚨 Clusters kept growing in size and cost
      • +
      • 🚨 Latency spikes became increasingly frequent
      • +
      • 🚨 The DMC proxy occasionally lost locality of nodes against shards, causing cross-node communication and degraded performance
      • +
      +

      Each time this happened, we had to manually rebalance shards just to restore stable latency, making operations unsustainable at scale.

      +

      Silver Linings

      +

      Despite the chaos, the system was live and delivering value:

      +
        +
      • Real-time infrastructure was in production
      • +
      • Costs dropped by 60–70% compared to offline personalization
      • +
      • New experiments rolled out faster and more successfully
      • +
      • User engagement metrics improved
      • +
      +

      It wasn’t perfect. It was far from easy. But it worked—and that counted for a lot.

      +

      Round Two: Solving the Top 2 Bottlenecks

      +

      With the first-gen system stretched to its limits, we stepped back. Conversations with data scientists and backend engineers revealed three recurring pain points:

      +
        +
      1. Coding feature retrieval logic for every new model was becoming unsustainable
      2. +
      3. ML scale was exploding—bringing rising infra costs with it
      4. +
      5. Real-time embedding search was the next big unlock
      6. +
      +

      We tackled them one by one—starting with the biggest pain point.

      +

      Problem 1: No-Code Feature Retrieval for Model Inference

      +

      We noticed a pattern: for personalized ranking, models needed features from:

      +
        +
      • ✅ Product
      • +
      • ✅ User
      • +
      • ✅ User × Category
      • +
      • ✅ Region, cohort, sub-category, etc.
      • +
      +

      A key insight emerged: Entities that contribute features for a model always map back to the context entities.

      +

      MP Dag

      +

      With this, we designed Inferflow, a graph-driven feature retrieval and model orchestration system:

      +
        +
      • 1️⃣ Inferflow takes a modelId and context IDs (e.g., userId, productIds)
      • +
      • 2️⃣ Loads a pre-defined feature retrieval graph from ZooKeeper
      • +
      • 3️⃣ Executes the graph to resolve entity relationships dynamically
      • +
      • 4️⃣ Outputs a 2D matrix of feature vectors
      • +
      +

      💡 The impact?

      +
        +
      • 🚀 No more custom feature retrieval code—just graph updates in config
      • +
      • 🚀 Feature consistency across experiments
      • +
      • 🚀 Faster iteration cycles for ranking, fraud detection, and beyond
      • +
      +

      Here’s a visual example that shows how this graph plays out during execution. We further extended the graph to call multiple models as needed: +MP matrix +We built Inferflow in GoLang, using gRPC and Proto3 serialization for efficiency.

      +

      Problem 2: Scaling Without Breaking the Bank

      +

      With more ML use cases coming online, we needed to cut costs without compromising performance. We focused on:

      +
        +
      • 🔹 Online Feature Store
      • +
      • 🔹 Interaction Store
      • +
      +

      Optimizing the Online Feature Store

      +

      Our costs were concentrated in:

      +
        +
      • 📌 Database (Cassandra)
      • +
      • 📌 Cache (Redis)
      • +
      • 📌 Running Pods (Java services)
      • +
      +

      1️⃣ Replacing Cassandra with ScyllaDB +As we hit the operational limits of large Cassandra clusters, we transitioned to ScyllaDB, which offered a seamless drop-in replacement without major code changes. The switch brought significant benefits:

      +
        +
      • Throughput: Matched or exceeded Cassandra's performance under identical workloads, even under high concurrency.
      • +
      • Latency: Achieved consistently lower P99 latencies due to ScyllaDB's shard-per-core architecture and better I/O utilization.
      • +
      • Cost Efficiency: Reduced infra footprint by ~70% through better CPU and memory efficiency, eliminating the need for over-provisioned nodes.
      • +
      +

      2️⃣ Finding the Right Cache +To reduce backend load and improve response times, we benchmarked multiple caching solutions—Memcached, KeyDB, and Dragonfly—under real production traffic patterns. Dragonfly stood out due to its robust architecture and operational simplicity:

      +
        +
      • Data Skew Handling: Efficiently managed extreme key hotness and uneven access patterns without performance degradation.
      • +
      • Throughput: Delivered consistently high throughput, even with large object sizes and concurrent access.
      • +
      • Ease of Adoption: Acted as a drop-in Redis replacement with full protocol compatibility—no changes needed in application code or client libraries.
      • +
      +

      3️⃣ Moving to GoLang for Cost-Efficient Serving +Java services were memory-heavy—so we rewrote core services in GoLang. The results?

      +

      ✅ Memory usage dropped by ~80% +✅ CPU utilization was significantly lower +✅ Faster, more efficient deployments

      +

      Optimizing the Interaction Store

      +

      We realized that we only need a user’s interaction data in Redis when they open the app. So, we implemented a tiered storage approach:

      +
        +
      • 📌 Cold Tier (ScyllaDB)—Stores click, order, wishlist events
      • +
      • 📌 Hot Tier (Redis)—Loads a user’s past interactions only when they open the app
      • +
      +

      Smart Offloading: We introduced an inactivity tracker to detect when a user session ends. At that point, Redis data was flushed back to Scylla, reducing unnecessary writes.

      +

      InteractionStore

      +

      Results

      +
        +
      • Online Feature Store hit 1M QPS for the first time during the 2023 Mega Blockbuster Sale—without breaking a sweat
      • +
      • Infra costs for Online Feature Store and Interaction Store dropped by ~60%
      • +
      +

      The Catch: Our ML Hosting Hit a Hard Limit

      +

      While planning for 2023 MBS, we ran into a critical scalability bottleneck:

      +
        +
      • ❌ Insufficient compute availability in our region for ML instances
      • +
      • ❌ Couldn’t provision enough nodes to handle real-time inference at scale
      • +
      +

      This forced us to rethink where and how we hosted our models. The existing setup was great for prototyping—but it wasn’t built to handle the bursty, high-QPS demands of real-world production workloads.

      +

      Conclusion: From Firefighting to Future-Proofing

      +

      What started as an ambitious experiment turned into a real-time ML infrastructure that powered millions of requests per second. We battled scaling pains, rethought feature retrieval with Inferflow, and rebuilt our infra stack for efficiency—driving down costs while improving experimentation velocity. +But new challenges emerged. Our infrastructure could now handle scale, but our ML model hosting setup hit a hard limit. With compute availability bottlenecks threatening real-time inference, we faced a critical decision: how do we make model serving as scalable and cost-efficient as the rest of our stack? That’s the next piece of the puzzle—and the story of Part 3.

      Building Meesho’s ML Platform: From Chaos to Cutting-Edge (Part 1)

      · 11 min read
      Adarsha Das
      Senior Architect @ Meesho
      Aditya Kumar
      Lead Software Engineer @ Meesho
      Bhawani Singh
      Architect @ Meesho
      Jigar Dave
      Lead Software Engineer @ Meesho

      BharatMLStack +It all started in early 2022, over a casual Friday evening catch-up. Like many great origin stories, this one began with friendly banter between a group of backend engineers and data scientists. As the conversations unfolded, so did the roasting—until one remark hit a little too close to home:

      "Why are we still crunching data for Monthly Active Users (MAU) when the next day it’s all about Daily Active Users (DAU)?"

      The laughter died down, and the question lingered. When we regrouped on Monday—clear-headed and slightly reflective—we decided to dig into the numbers. What they discovered was quite revealing: a large portion of compute resources wasn’t being put to good use. Much of the system’s effort was spent supporting users who weren’t actively engaging, and even for new users, the experience wasn’t optimized to make a meaningful impact.

      @@ -119,7 +574,7 @@

      feature_1_value,feature_2_value,feature_3_value;expiry_ts

      +
      feature_1_value,feature_2_value,feature_3_value;expiry_ts

      This format allowed:

      • Consistent writes and reads at the group level
      • @@ -182,7 +637,7 @@

        Why Redis?

        Storage Structure

        Each user’s interactions were stored using a composite key format, uniquely identifying the user and interaction type. This structure allowed efficient organization and quick retrieval of recent activity for recommendation generation:

        -
        userId_eventType → ZSET[...(pid, ts)...]
        +
        userId_eventType → ZSET[...(pid, ts)...]

        Within each ZSET:

      +

      This foundational work laid the path for a reliable and scalable real-time feature serving layer.

      \ No newline at end of file diff --git a/docs/blog/llm-inference-optimization-sub-sec-latency/index.html b/docs/blog/llm-inference-optimization-sub-sec-latency/index.html new file mode 100644 index 00000000..0ff7ad94 --- /dev/null +++ b/docs/blog/llm-inference-optimization-sub-sec-latency/index.html @@ -0,0 +1,80 @@ + + + + + +LLM Inference Optimization Techniques: Engineering Sub-Second Latency at Scale | BharatMLStack + + + + + + + + +

      LLM Inference Optimization Techniques: Engineering Sub-Second Latency at Scale

      · 5 min read
      Jaya Kumar
      Lead ML Engineer @ Meesho

      BharatMLStack +Raw execution of Large Language Models is inherently expensive and memory-intensive. To achieve sub-second latency and high throughput, we implement a multi-layered optimization strategy that targets the entire inference stack—from memory management to kernel execution.

      +

      1. Advanced Memory Management: Paged & Prefix KV Caching

      +

      The most significant bottleneck in LLM inference is not always compute, but memory bandwidth—specifically managing the Key-Value (KV) cache.

      +

      Paged KV caching

      +

      Standard caching suffers from fragmentation. We use Paged KV caching, which operates similarly to an operating system's virtual memory: the KV cache is divided into non-contiguous blocks. This lets us serve larger batch sizes without running out of memory.

      +

      KV cache quantization

      +

      To further maximize available memory, we implement KV cache quantization (e.g., FP8). By compressing stored attention keys and values from 16-bit to 8-bit, we nearly double the effective context window capacity of the GPU, allowing longer conversations or larger batches without materially degrading quality.

      +

      Prefix caching (the "voice bot" optimizer)

      +

      For use cases like GenAI voice bots where the system prompt (e.g., "You are a helpful assistant...") is static across thousands of requests, we enable prefix caching.

      +
        +
      • Impact: By reusing pre-computed KV states for common prefixes, we achieve a cache hit rate of ~90%. This reduces Time To First Token (TTFT) by skipping redundant computation of the system prompt.
      • +
      +

      2. Aggressive Quantization (INT4 AWQ & FP8)

      +

      Running models in their native 16-bit precision (BF16) restricts maximum batch size and throughput. We use quantization to shrink model weights without sacrificing accuracy.

      +

      INT4 AWQ (Activation-aware Weight Quantization)

      +

      For the Llama 3 family, we use AWQ to compress weights to 4 bits. This reduces model size by ~75%, allowing larger models to fit into L4 GPU memory and significantly improving token generation speed.

      +

      FP8 precision

      +

      For NVIDIA Hopper (H100) architectures, we are exploring FP8 quantization, leveraging native FP8 tensor cores to accelerate matrix multiplications while maintaining a higher dynamic range than integer quantization.

      +
        +
      • Verification: We validate quantized models by comparing dot-product similarity of embeddings against the FP16 baseline, consistently achieving >99% similarity.
      • +
      +

      3. Kernel Fusion & Custom Plugins

      +

      To minimize overhead from launching thousands of small GPU operations, we fuse them into monolithic kernels using NVIDIA TensorRT plugins.

      +
        +
      • Flash attention & FMHA: We enable Fused Multi-Head Attention (FMHA) combined with flash attention to reduce memory reads/writes.
      • +
      • GEMM plugins: We use specialized GEMM plugins to accelerate transformer linear layers.
      • +
      • Removing input padding: Instead of padding short sequences to match the longest, we remove input padding so the GPU processes only valid tokens.
      • +
      +

      4. Inflight (Continuous) Batching

      +

      Traditional static batching waits for all requests in a batch to finish before returning results—so one long response delays everyone else.

      +

      We implement inflight batching: as soon as one request completes, its slot is freed and filled by a new request from the queue. This keeps GPUs saturated and decouples latency of short queries from long ones.

      +

      5. Parallelism Strategies: Scaling Beyond One GPU

      +

      For large models (e.g., 70B+ parameters) that cannot fit into the VRAM of a single GPU, we use parallelism strategies.

      +
        +
      • Tensor parallelism (TP): Split weight matrices across multiple GPUs (e.g., 4× L4 or 8× A100). Each GPU computes a shard and outputs are reduced at every layer.
      • +
      • Pipeline parallelism (PP): Split model layers across GPUs to pipeline compute (e.g., while one GPU computes later layers for Request A, another starts early layers for Request B).
      • +
      +

      6. Speculative Decoding

      +

      To reduce inter-token latency (ITL), we explore speculative decoding.

      +
        +
      • Mechanism: A smaller, faster "draft" model speculatively generates a short token sequence (e.g., 5 tokens).
      • +
      • Verification: The larger target model verifies those tokens in one parallel forward pass. If correct, we effectively generate multiple tokens per large-model step; if not, we discard and regenerate. This is effective for predictable text, improving perceived generation speed.
      • +
      +

      Few Benchmarks

      +

      Below are a couple of representative use cases and performance numbers.

      +

      Search query rewriting

      +
        +
      • LLM: Fine-tuned llama-3.2-1B
      • +
      • Input & output token length: ~10–20
      • +
      • Response type: Non-streaming
      • +
      +
      Inference runtimeHardwareMax requests/secMax p99 latency
      TensorRT-LLM4 × L4 GPUs (multi-GPU)100095 ms
      TensorRT-LLM1 × A100 40 GB GPU100069 ms
      +

      Voice bot query

      +
        +
      • LLM: Llama-3.1-8B
      • +
      • Input token length: ~1900–2000
      • +
      • Output token length: ~200
      • +
      • Response type: Streaming
      • +
      +
      Inference runtimeConcurrencyp99 TTFT (ms)p99 ITL (ms)Token throughput (tokens/sec)Request throughput (req/sec)Hardware
      TensorRT-LLM136.2722.7845.660.23L4
      TensorRT-LLM249.8123.2189.370.45L4
      TensorRT-LLM455.3336.62153.390.78L4
      TensorRT-LLM866.539.11279.881.47L4
      TensorRT-LLM16131.830.39547.82.77L4
      TensorRT-LLM32277.2248.02925.74.78L4
      TensorRT-LLM64498.5271.621,164.406.2L4
      TensorRT-LLM128677.31120.371,445.187.69L4
      TensorRT-LLM2561,926.31216.881,600.818.52L4
      TensorRT-LLM121.179.24130.050.68A100
      TensorRT-LLM225.789.21264.51.35A100
      TensorRT-LLM428.5210.99437.692.27A100
      TensorRT-LLM834.412.61760.493.96A100
      TensorRT-LLM1668.0314.321,343.807.01A100
      TensorRT-LLM32185.9616.822,287.3011.92A100
      TensorRT-LLM64136.8721.173,625.2218.89A100
      TensorRT-LLM128463.7834.154,456.5123.24A100
      TensorRT-LLM256890.1259.185,188.2427.05A100
      +

      Conclusion

      +

      High-performance LLM inference is fundamentally a systems engineering problem: memory efficiency, kernel execution, batching strategy, and parallelism determine real-world latency and throughput. Techniques such as paged KV caching, aggressive quantization, kernel fusion, and inflight batching improve GPU utilization while reducing latency and memory pressure.

      +

      These optimizations enable the platform to deliver sub-second responses, sustain high concurrency, and efficiently serve both lightweight and long-context workloads. By continuously optimizing across the full inference stack, we keep LLM serving scalable, cost-efficient, and production-ready for real-time AI applications.

      + + \ No newline at end of file diff --git a/docs/blog/multi-engine-llm-inferencing-platform/index.html b/docs/blog/multi-engine-llm-inferencing-platform/index.html new file mode 100644 index 00000000..1401ca41 --- /dev/null +++ b/docs/blog/multi-engine-llm-inferencing-platform/index.html @@ -0,0 +1,205 @@ + + + + + +Designing a Production-Grade LLM Inference Platform: From Model Weights to Scalable GPU Serving | BharatMLStack + + + + + + + + +

      Designing a Production-Grade LLM Inference Platform: From Model Weights to Scalable GPU Serving

      · 14 min read
      Jaya Kumar
      Lead ML Engineer @ Meesho

      BharatMLStack +Serving large language models in production introduces new challenges across infrastructure, performance optimization, and operational lifecycle management. The LLM Inference Platform addresses these challenges by providing a unified system for deploying and managing open-source and fine-tuned LLMs at scale.

      +

      The platform implements a complete LLMOps lifecycle — from model registration and automated compilation to deployment, runtime optimization, and monitoring. Designed as a self-service environment, users can onboard models directly from open repositories such as Hugging Face or upload custom fine-tuned models, and deploy them using a single-click workflow with no manual infrastructure or configuration steps required.

      +

      In addition to fully automated deployment, the platform allows users to select and apply custom inference optimization techniques — such as quantization strategies, batching configurations, and runtime-specific performance enhancements — enabling teams to balance latency, throughput, and cost based on their use case. The goal is to reduce operational friction while enabling high-performance, production-grade LLM inference.

      +

      Why LLM Inference Is not just bigger ML model serving

      +

      Large language model (LLM) inference introduces a fundamentally different set of challenges compared to traditional machine learning inference. While classical ML models typically perform a single forward pass to produce a fixed prediction, LLMs operate as autoregressive systems, generating outputs token by token based on previously generated context. This difference dramatically changes how inference systems must be designed, optimized, and scaled.

      +

      Autoregressive Generation and Sequential Computation:

      +

      Unlike traditional models such as classifiers or recommenders — where inference cost is relatively constant — LLMs generate responses incrementally. Each new token depends on all previously generated tokens, making inference inherently sequential and dynamic. This means latency and compute requirements vary significantly depending on prompt length and output size, introducing complexity in scheduling and resource allocation. +Because tokens cannot be generated fully in parallel during decoding, GPUs may become underutilized without specialized batching and scheduling strategies. This has led to the development of dedicated LLM inference engines optimized for token-level execution.

      +

      Prefill and Decode Phases:

      +

      LLM inference typically consists of two distinct stages:

      +
        +
      • Prefill phase — the model processes the input prompt and builds internal representations. This stage is compute-heavy and highly parallelizable.
      • +
      • Decode phase — the model generates tokens sequentially, predicting one token at a time using previously generated context.
      • +
      +

      The decode stage often becomes memory-bound rather than compute-bound, which creates new performance bottlenecks compared to traditional ML workloads.

      +

      Context Management and KV Caching:

      +

      Another fundamental difference lies in how LLMs maintain context. Transformer-based models rely on attention mechanisms that require access to past token representations. To avoid recomputing these representations repeatedly, inference engines use key-value (KV) caching, which stores intermediate activations from previous tokens. +KV caching significantly improves performance by eliminating redundant computation, but it introduces new challenges:

      +
        +
      • Memory consumption grows with sequence length and batch size
      • +
      • GPU memory becomes a critical bottleneck
      • +
      • Efficient memory management becomes essential for scaling concurrent requests
      • +
      +

      This tradeoff between compute efficiency and memory usage is unique to LLM inference workloads.

      +

      Dynamic and Irregular Workloads:

      +

      Traditional ML inference typically operates on fixed-size inputs with predictable latency. In contrast, LLM requests vary widely in prompt length, output length, and runtime behavior. As a result:

      +
        +
      • Batch sizes must be dynamic rather than static
      • +
      • Requests may enter and leave batches asynchronously
      • +
      • Scheduling systems must continuously rebalance workloads to maximize GPU utilization
      • +
      +

      These characteristics require specialized serving architectures that differ significantly from standard ML serving pipelines.

      +

      Streaming and User Experience Constraints:

      +

      Another distinguishing factor is the expectation of real-time streaming responses. Instead of returning a single output, LLM systems often stream tokens to users as they are generated. +Because of these differences — sequential generation, growing memory requirements, dynamic workloads, and streaming constraints — LLM inference cannot be treated as a simple extension of existing ML serving systems. Production platforms must incorporate specialized runtime engines, advanced optimization techniques, and observability tailored specifically to LLM workloads.

      +

      LLMOps: High-Level Architecture

      +

      LLM Architecture

      +

      The LLM Inference Framework is designed as a fully automated, end-to-end system for deploying and operating open-source and fine-tuned large language models at scale. The architecture abstracts the complexity of model optimization, hardware selection, deployment, and runtime management into a unified workflow that enables users to move from raw model weights to production-ready inference endpoints with minimal manual intervention.

      +

      Our LLM Inference Framework is architected not just as a serving engine, but as a complete lifecycle management system. As illustrated in the high-level design below, the platform automates the journey of a model through seven distinct stages, ensuring reproducibility, performance, and scalability.

      +
        +
      1. +

        Onboarding & Registration (The Source of Truth)

        +

        The lifecycle begins with the Data Scientist or engineer.

        +
          +
        • Model Ingestion: Users onboard models—whether open-source (Hugging Face, NeMo) or internally fine-tuned—via the Truffle Box SDK/UI.
        • +
        • LLM + Prompt Registry: Unlike traditional systems that only track model weights, our registry is a unified control plane. It stores both the Model Artifacts and the Prompt Templates. This allows Data Scientists to register and version-control prompts (e.g., "customer_support_v2") independently of the application code.
        • +
        +
      2. +
      3. +

        The "Black Box" Build Engine

        +

        Once a model is registered, the Automated LLM Compiler + Quantizer Module kicks off a background job on ephemeral GPU resources.

        +
          +
        • Transformation: The raw model is converted into a TRT-LLM Checkpoint.
        • +
        • Quantization: The system automatically applies quantization algorithms (like INT4 AWQ or FP8) to reduce memory footprint.
        • +
        • Engine Building: Finally, it compiles a highly optimized TRT Engine specifically tuned for the target hardware.
        • +
        +
      4. +
      5. +

        Intelligent Profiling & Validation

        +

        Before deployment, the new engine passes through the Hardware & Inference Runtime Profiler.

        +
          +
        • Benchmarking: This module empirically tests the engine against various hardware configurations (L4 vs. A100) and runtimes (TRT-LLM vs. vLLM).
        • +
        • Optimization: It recommends the optimal configuration that meets latency SLAs (Time-To-First-Token) while minimizing cost.
        • +
        +
      6. +
      7. +

        Smart Artifact Generation & Distribution

        +

        To solve the Kubernetes "Cold Start" problem, the LLM Serving Artifacts Generation module packages the model using a bifurcated strategy:

        +
          +
        • Standard Models: Artifacts are uploaded to Cloud Storage (GCS) and downloaded by pods at startup.
        • +
        • Very Large Models: For massive models (>8GB) where network downloads are too slow, the system pre-caches the model onto Secondary Boot Disks. These disks are attached directly to new GPU nodes during autoscaling, eliminating download wait times.
        • +
        +
      8. +
      9. +

        Image Streaming & Deployment

        +

        Simultaneously, the inference runtime container images are pulled from the Artifact Registry.

        +
          +
        • Image Streaming: We utilize container image streaming to allow pods to start initializing while the massive Triton/Dynamo container layers are still downloading, further shaving seconds off the startup time. link
        • +
        +
      10. +
      11. +

        The Inference Runtime (Kubernetes)

        +

        The workload lands on Kubernetes with Autoscaling.

        +
          +
        • Dynamic Backends: Depending on the profile generated in Stage 3, the pod initializes either TensorRT-LLM (for throughput) or vLLM (for flexibility), or spins up a Dynamo worker for distributed inference.
        • +
        • Data Loading: The pod either downloads the model from Cloud Storage or mounts the pre-warmed Secondary Boot Disk ("Pull from Disk").
        • +
        +
      12. +
      13. +

        Client Interaction & Observability

        +

        Finally, the LLM Inference Client executes the request.

        +
          +
        • Prompt Injection: The client pulls the specific prompt template ID from the Registry, ensuring the exact versioned instructions are used.
        • +
        • Streaming Response: The request is sent via gRPC, and tokens are streamed back to the user in real-time.
        • +
        +
      14. +
      15. +

        Observability: Monitoring the Pulse of GenAI

        +

        In traditional microservices, success is measured by CPU utilization and request latency (p99). For Large Language Models, these metrics are insufficient. A user doesn't care if the GPU is at 80% utilization; they care about how fast the first word appears and how smoothly the rest of the sentence follows.

        +

        To capture the true user experience, our platform instrumentation focuses on three critical LLM-specific metrics:

        +
          +
        1. +

          Time to First Token (TTFT)

          +
            +
          • Definition: TTFT measures the time elapsed from the moment a request is received until the very first token is generated and streamed back to the user.
          • +
          • Why it matters: This represents the "Prefill Phase" latency—the time the model takes to process the input prompt and load weights. A high TTFT makes the application feel unresponsive or "hung."
          • +
          • Optimization: We closely monitor TTFT to ensure our Prefix Caching is effective (aiming for high cache hitrates), which drastically lowers this metric by skipping redundant prompt processing.
          • +
          +
        2. +
        3. +

          Inter-Token Latency (ITL)

          +
            +
          • Definition: ITL measures the average time interval between the generation of consecutive tokens during the "Decode Phase".
          • +
          • Why it matters: This defines the "perceived speed" of reading. Even if the first token is fast (low TTFT), high ITL makes the text generation look "jerky" or slow to the user.
          • +
          • Benchmarks: In our testing with Llama 3.1, we track p99 ITL to ensure it stays below human reading speeds to maintain a natural conversational flow.
          • +
          +
        4. +
        5. +

          Token Throughput vs. Request Throughput

          +
            +
          • We distinguish between two types of throughput to balance system efficiency with user load:
          • +
          • Token Throughput (tokens/sec): The total number of tokens generated across all concurrent requests. This measures the raw compute efficiency of the GPU and the effectiveness of batching.
          • +
          • Request Throughput (req/sec): The number of distinct user queries served per second. We use this to determine autoscaling thresholds, ensuring we scale out before the queue depth impacts ITL.
          • +
          +
        6. +
        7. +

          The Monitoring Stack

          +
            +
          • Real-time Dashboards: We utilize Grafana to visualize these streaming metrics in real-time, allowing on-call engineers to spot "slow generation" incidents that generic "500 error" alerts would miss.
          • +
          • Request Tracing: Since Triton Inference Server does not log request payloads by default, we integrate a Helix Client to asynchronously publish request logs to Log Tables. This allows us to trace a specific "slow" request back to its prompt to understand if a complex input caused the latency spike.
          • +
          +
        8. +
        +
      16. +
      +

      Supported Inference backends (TensorRT LLM, Dynamo & vLLM)

      +

      Tailored for the Use Case: We do not believe in a "one-size-fits-all" approach to inference. Different use cases—whether a real-time voice bot requiring ultra-lowsub-second latency or a massive reasoning task requiring huge context windows—demand different runtime characteristics. Our platform is designed to be runtime-agnostic, allowing us to automatically select and tailor the best engine based on the specific requirements of the application:

      +
        +
      1. +

        TensorRT-LLM: The High-Performance Standard

        +

        Suitable for: High-throughput production workloads where latency is critical (e.g., customer support chat, real-time voice bots).

        +

        TensorRT-LLM serves as our default backend for these scenarios. Our internal benchmarks on Llama 3.1 and 3.2 models demonstrated that a tuned TensorRT-LLM engine significantly outperforms standard runtimes, especially when utilizing INT4 AWQ and FP8 quantization .

        +

        Key optimizations we tailor for these high-load cases include:

        +
          +
        • Optimized execution via TensorRT engine compilation
        • +
        • Quantization-aware execution for reduced memory usage and improved throughput
        • +
        • Inflight Batching: Allowing requests to be processed continuously without waiting for the entire batch to finish, drastically improving GPU utilization .
        • +
        • Custom Plugins: Enabling specific NVIDIA plugins like the GEMM plugin and GPT Attention plugin to accelerate matrix multiplications and attention mechanisms .
        • +
        +
      2. +
      3. +

        Dynamo: Distributed Inference for Reasoning Models

        +

        Suitable for: Very large "reasoning" models (70B+) or scenarios requiring massive context windows where a single GPU's memory is insufficient.

        +

        For these memory-bound tasks, we utilize Dynamo, a low-latency distributed inference framework . Unlike monolithic servers, Dynamo disaggregates the inference process to scale resources horizontally:

        +
          +
        • KV Aware Routing: A specialized router directs requests to workers that already hold the relevant Key-Value (KV) cache, minimizing redundant computation .
        • +
        • Prefill vs. Decode Split: The workload is divided into Prefill Workers (processing the prompt) and Decode Workers (generating tokens), allowing us to scale the compute-heavy "reading" phase independently from the memory-heavy "writing" phase .
        • +
        • Distributed execution across multiple GPU resources
        • +
        +
      4. +
      5. +

        vLLM: The Flexible Baseline

        +

        Suitable for: Rapid prototyping, testing new model architectures, or low-traffic internal tools where ease of deployment outweighs raw throughput.

        +

        While TensorRT-LLM is optimized for maximum speed, vLLM provides a robust and flexible baseline .

        +
          +
        • High throughput through dynamic batching and efficient memory utilization
        • +
        • Paged KV cache management for handling long contexts and concurrent requests
        • +
        • Strong support for open-source model ecosystems
        • +
        • Rapid Adoption: It allows us to onboard new model architectures immediately without waiting for a custom TensorRT build.
        • +
        • Benchmarking Insight: In our internal tests, vLLM provided a strong baseline but often lacked the specific max-token optimizations present in our custom TRT engines . We use it strategically for initial testing before committing to a full TensorRT optimization pipeline.
        • +
        +
      6. +
      +

      Conclusion

      +

      Large language model inference introduces a fundamentally new class of infrastructure challenges—where performance is governed not just by raw compute, but by memory efficiency, intelligent scheduling, runtime specialization, and lifecycle automation. Unlike traditional ML serving, LLM inference requires systems that understand token-level execution, manage rapidly growing context state, and continuously balance latency, throughput, and cost under highly dynamic workloads.

      +

      The LLM Inference Framework addresses these challenges by transforming inference into a fully automated, reproducible lifecycle—from model onboarding and compilation to deployment, optimization, and observability. By integrating automated quantization and engine compilation, intelligent runtime selection, cold-start mitigation strategies, and LLM-specific observability metrics such as Time-to-First-Token and Inter-Token Latency, the platform ensures both high performance and operational simplicity.

      +

      Equally important, the framework is designed with flexibility and future evolution in mind. Its runtime-agnostic architecture enables seamless adoption of emerging inference engines, hardware accelerators, and optimization techniques without requiring platform redesign. This ensures that teams can continuously leverage advancements in the rapidly evolving LLM ecosystem while maintaining consistent operational workflows.

      +

      Ultimately, the goal of the platform is to make production-scale LLM deployment as seamless and reliable as traditional software deployment—allowing teams to focus on building intelligent applications rather than managing infrastructure complexity. By combining lifecycle automation, runtime optimization, and deep observability, the LLM Inference Framework provides a scalable foundation for delivering fast, cost-efficient, and production-ready LLM experiences.

      +

      Future Explorations

      +

      While we have achieved significant milestones in latency and throughput, the landscape of GenAI is evolving rapidly. Our roadmap focuses on increasing flexibility, reducing costs, and enhancing reliability for enterprise-grade workloads. Here is what we are building next:

      +
        +
      • TPU Support: To diversify our hardware supply chain and further optimize cost-per-token, we are evaluating Google Cloud TPUs to bake it into our platform. By leveraging the JAX and PyTorch/XLA ecosystems, we aim to unlock the massive throughput potential of TPU v5e chips, particularly for our open-source Llama models. This will allow the hardware profiler to dynamically choose between NVIDIA GPUs and Google TPUs based on real-time availability and price-performance metrics.
      • +
      • Multi-LoRA Serving (Serverless Experience): Currently, deploying a fine-tuned model requires a dedicated GPU. We are building support for Multi-LoRA serving, which will allow us to serve hundreds of unique, fine-tuned adapters on top of a single frozen base model. This will drastically reduce costs for multi-tenant applications, enabling a "serverless" experience where specific fine-tunes are hot-swapped instantly per request.
      • +
      • Spot Instance Orchestration: To further optimize cloud costs, we are developing fault-tolerant mechanisms to run inference workloads on Spot Instances. By implementing aggressive checkpointing and seamless request draining, we aim to leverage cheaper, preemptible compute capacity without interrupting the user's streaming experience.
      • +
      • Semantic Caching Layer: We plan to move beyond standard Prefix Caching to implement Semantic Caching. By using a vector database to fetch responses for semantically similar queries (e.g., "How do I reset my password?" vs. "Password reset steps"), we can bypass the GPU entirely for repetitive queries, reducing latency to near-zero.
      • +
      • Context-Aware Autoscaling: Standard CPU/GPU utilization metrics are often insufficient signals for scaling LLMs. We are working on KV-cache pressure metrics for autoscaling. This ensures that we scale out before the memory fills up, preventing eviction-based slowdowns during traffic spikes.
      • +
      • Online Evaluation & Guardrails: We are integrating a lightweight "Trust Layer" into the proxy. This will allow for low-latency input/output filtering (Guardrails) and asynchronous "LLM-as-a-Judge" evaluation pipelines to monitor response quality in production, not just system health.
      • +
      + + \ No newline at end of file diff --git a/docs/blog/post-one/index.html b/docs/blog/post-one/index.html deleted file mode 100644 index c202721c..00000000 --- a/docs/blog/post-one/index.html +++ /dev/null @@ -1,209 +0,0 @@ - - - - - -Building Meesho’s ML Platform: From Chaos to Cutting-Edge (Part 1) | BharatMLStack - - - - - - - - -

      Building Meesho’s ML Platform: From Chaos to Cutting-Edge (Part 1)

      · 11 min read
      Adarsha Das
      Senior Architect @ Meesho
      Aditya Kumar
      SDE-III @ Meesho
      Bhawani Singh
      SDE-IV @ Meesho
      Jigar Dave
      SDE-IV @ Meesho

      BharatMLStack

      -

      The Genesis: How a Friday Night Roast Sparked Meesho’s ML Platform

      -

      It all started in early 2022, over a casual Friday evening catch-up. Like many great origin stories, this one began with friendly banter between a group of backend engineers and data scientists. As the conversations unfolded, so did the roasting—until one remark hit a little too close to home:

      -

      "Why are we still crunching data for Monthly Active Users (MAU) when the next day it’s all about Daily Active Users (DAU)?"

      -

      The laughter died down, and the question lingered. When we regrouped on Monday—clear-headed and slightly reflective—we decided to dig into the numbers. What they discovered was quite revealing: a large portion of compute resources wasn’t being put to good use. -Much of the system’s effort was spent supporting users who weren’t actively engaging, and even for new users, the experience wasn’t optimized to make a meaningful impact.

      -

      At the same time, Meesho had just launched a company-wide initiative to reduce costs—and every team had to contribute. This realization sparked the journey that would eventually lead to the Meesho ML Platform, known today as BharatMLStack.

      -

      Alt Text

      -

      Before the ML Platform, our recommendation and ranking pipelines followed a batch processing approach:

      -
        -
      • Data Ingestion: The Data Platform team executed ETL jobs to ingest raw user data—including user profiles, interaction logs, and product impressions—into designated S3 buckets.
      • -
      • Layer 1: Embedding Generation: On the Data Science side, Spark jobs pulled data from multiple S3 sources, cleaned and preprocessed it, and applied matrix factorization to generate user and item embeddings. The processed data and embeddings were then stored back in S3 in a structured format.
      • -
      • Layer 2: Candidate Generation (CG): In this stage, Spark jobs leveraged embeddings and historical interaction data to generate candidate recommendations for users. These candidate lists were subsequently written to S3.
      • -
      • Layer 3: Ranking and Merging – A final round of processing ranked the generated candidates using ML models, combined different candidate lists, and stored the final ranked recommendations in a caching system.
      • -
      • Serving: A microservice retrieved ranked recommendations from an in-memory data store via exposed APIs, delivering personalized listings across key surfaces such as "For You" and Category Landing Pages (CLP).
      • -
      -

      This approach held up well—until Meesho started seeing a significant surge in traffic.

      -

      The Turning Point: From Batch to Real-Time

      -

      At this time, the team was iterating on new Ranker models, and real-time inference seemed like the next logical step. But Rankers needed real-time feature retrieval, which meant an online feature store had to be built first.

      -

      Exploring open-source options led to cost vs. performance trade-offs, but Meesho’s surging traffic meant that latency and stability were non-negotiable. After multiple debates and stakeholder discussions, a bold decision was made:

      -

      We would build our own feature store.

      -

      Meanwhile, efforts began to bring Candidate Generators (CGs) to real-time. The challenge? Storing and retrieving user interactions quickly enough to power real-time recommendations.

      -

      As the team dove deeper, a new roadblock emerged:
      -Our ML jobs were orchestrated using Airflow DAGs, giving data scientists flexibility in experimentation. But transitioning to real-time execution threatened this agility. Every change would now require backend engineering support, slowing down iteration cycles.

      -

      That’s when the idea struck:
      -We needed a framework for real-time DAG execution—one that preserved the same flexibility as Airflow but worked for streaming data.

      -

      This moment shaped the next phase of our journey.

      -

      First Generation Design

      -

      Alt Text

      -

      Laying the Groundwork: The First-Gen ML Platform

      -

      To solve these challenges, the team built three foundational components:

      -

      1. IOP Framework: A Real-Time DAG Executor

      -
        -
      • Reusable Nodes: Each DAG node (e.g., an invocation to a CG service, a ranker, or a filter) had to be implemented only once. After that, it could be reused across any workflow by referencing it in config.
      • -
      • Config-driven Dynamic Graphs: Execution graphs were defined as adjacency lists stored in ZooKeeper, allowing teams to modify the sequence or structure of operations without touching application code.
      • -
      • Plug-and-play CGs: The Candidate Generator interface was preserved, so a single CG node could call any CG service by passing cg_name in the request. This drastically reduced the code surface area and improved maintainability.
      • -
      • Production-Grade DAGs: DAGs were designed to execute in low-latency real-time environments, with support for parallel execution, retries, and branching.
      • -
      -More about IOP DAG -

      2. Online Feature Store - 0th Version

      -
        -
      • Used Cassandra and Redis for low-latency feature serving.
      • -
      • Maintained feature consistency using Feature Groups with TTL-based expiry.
      • -
      • A hybrid schema was used: feature keys stored in ZooKeeper, data stored in compact arrays.
      • -
      -

      3. Interaction Store - 0th Version

      -
        -
      • Captured real-time user interactions like clicks, orders, and add-to-cart events.
      • -
      • Stored event data in Redis ZSETs (sorted sets) to enable fast lookups for recommendation engines.
      • -
      • Provided an API to fetch a user's last k interactions or interactions within a time window.
      • -
      -

      With these components in place, real-time ML at Meesho became a reality.

      -

      This was just the beginning.

      -

      Building the Online Feature Store - 0th Version

      -

      Alt text

      -

      Choosing the Right Tech Stack

      -

      We spent considerable time evaluating various databases, caches, and communication protocols for our online feature store. After carefully weighing cost, latency, throughput, and operational stability, we settled on a combination of:

      -
        -
      • Cassandra and Redis for storage
      • -
      • gRPC + Proto3 as our communication layer
      • -
      -

      Streamlining the Data Flow

      -

      To keep things simple in the initial version:

      -
        -
      • Feature engineering jobs wrote raw outputs to an S3 bucket
      • -
      • A daily feature push job: -
          -
        • Read from S3
        • -
        • Grouped related features into Feature Groups (ensuring consistency)
        • -
        • Pushed them to Kafka
        • -
        -
      • -
      -

      For features requiring frequent updates:

      -
        -
      • Ad-hoc jobs computed features in higher frequency
      • -
      • These jobs pushed to both Kafka and S3 (S3 preserved historical data for future model training)
      • -
      -

      The Challenges: Data Format and Storage

      -

      One of the most critical design challenges was how to store feature data efficiently and consistently, especially in databases like Cassandra and Redis, which come with unique storage constraints.

      -

      We had to solve for three key requirements:

      -
        -
      • -

        Feature Consistency

        -

        When a feature group contains features like order_count_1h and click_count_1h, both must reflect the same time window. Inconsistent updates would lead to unreliable model predictions.

        -
      • -
      • -

        TTL Granularity

        -

        Each feature group required an expiry timestamp, so that all features within it expired together—preserving consistency during reads.

        -
      • -
      • -

        Extensibility Across Databases

        -

        We anticipated that infra needs would evolve. To future-proof our system, the data format was designed to be decoupled from DB-specific layouts, enabling portability to systems like ScyllaDB, DynamoDB, HBase, or BigTable.

        -
      • -
      -
      -

      Overcoming Technical Constraints

      -

      At the time, we were using Cassandra, which not only imposed a soft limit of 75 columns per row, but also exhibited significant performance degradation as the number of columns increased further, particularly in memory constrained machines. Wide rows caused high memory usage during reads, unpredictable latencies due to heavy deserialization overhead, and inefficiencies during compactions and repairs. This ruled out the naive "one column per feature" approach. We needed a format that was compact, minimized the number of columns, and remained efficient and portable across different storage systems.

      -

      The Solution: Schema Separation

      -

      We introduced the concept of Feature Groups—logical groupings of features that must remain consistent with one another. -To represent these groups efficiently, we adopted a layered storage approach:

      -
        -
      • Feature Labels (Keys) were stored in ZooKeeper, serving as the schema.
      • -
      • Feature Values were stored as a comma-separated string array in Cassandra or Redis.
      • -
      • Expiry Timestamp and Schema Version were appended using a semi-colon delimiter at the end of the string.
      • -
      -

      Example:

      -
      feature_1_value,feature_2_value,feature_3_value;expiry_ts
      -

      This format allowed:

      -
        -
      • Consistent writes and reads at the group level
      • -
      • Easy parsing of feature values using the schema lookup from ZooKeeper
      • -
      • Efficient storage with minimal DB column usage
      • -
      • Support for per-group TTLs and schema evolution
      • -
      -

      Tracking Changes in Feature Groups

      -

      Feature groups don’t stay static. As models evolve, features get added, renamed, or removed. But schema changes often go live before the data is ready—and stopping ingestion just to wait for everything to align isn't feasible.

      -

      Common Real-World Scenarios:

      -
        -
      • A new feature is added to the schema, but ingestion jobs still use the older schema version.
      • -
      • Ongoing writes don’t include the newly added feature, and stopping ingestion would break freshness for existing features.
      • -
      • During serving, models request a mix of old and new features, depending on rollout stages.
      • -
      -

      The Solution: Schema Versioning

      -

      We solved this with versioned feature group schemas, which unlocked several capabilities:

      -
        -
      • -

        Backward Compatibility

        -Older ingestion jobs can continue writing using older schema versions. During reads, the system uses the schema version embedded in the value to interpret the data correctly.
      • -
      • -

        Partial Availability Handling

        -During inference, if some features in the request aren’t available (due to rollout delays or missing data), the system serves default values, ensuring the inference call doesn’t fail.
      • -
      • -

        Safe Writes Without Pipeline Pauses

        -With schema versioning, we no longer had to stop ingestion pipelines for schema updates. Writes using previous versions can continue safely, and downstream consumers evolve independently. -This design gave us the flexibility to move fast without breaking things—preserving data quality, enabling experimentation, and ensuring reliability at scale.
      • -
      -

      Alt Text

      -

      Interaction Store - 0th Version

      -

      Alt Text

      -

      To power real-time Candidate Generators (CGs), we needed fast access to user behavior signals—like what a user recently clicked, ordered, or added to their cart. These interactions form the basis for many real-time recommendations, such as Similar Products, People Also Viewed, or Recently Ordered Again. -For the 0th version of the Interaction Store, we focused on a design that was simple, fast, and reliable — optimized for high-throughput ingestion and low-latency lookups.

      -

      Event Ingestion

      -

      We instrumented our backend services to emit key user interaction events to Kafka in real time. These included:

      -
        -
      • Click
      • -
      • Order
      • -
      • Add to Cart
      • -
      • Wishlist
      • -
      • Share
      • -
      -

      Each event carried essential metadata:

      -
        -
      • userId — uniquely identifies the user
      • -
      • productId — the item being interacted with
      • -
      • timestamp — the moment the interaction occurred
      • -
      -

      This decoupled the interaction logging from storage, allowing ingestion and consumption to scale independently.

      -

      Storage Design

      -

      To store these events, we built Kafka consumers that processed the incoming streams and wrote the data into Redis, using sorted sets (ZSETs) as the primary data structure.

      -

      Why Redis?

      -

      Redis gave us:

      -
        -
      • Low-latency reads and writes
      • -
      • Time-ordered data using ZSETs (via score = timestamp)
      • -
      • Native TTL support, if needed in later versions
      • -
      • In-memory performance —ideal for real-time CGs
      • -
      -

      Storage Structure

      -

      Each user’s interactions were stored using a composite key format, uniquely identifying the user and interaction type. This structure allowed efficient organization and quick retrieval of recent activity for recommendation generation:

      -
      userId_eventType → ZSET[...(pid, ts)...]
      -

      Within each ZSET:

      -
        -
      • The timestamp served as the score, maintaining temporal order
      • -
      • The productId (optionally with metadata) was the value
      • -
      -

      This allowed us to efficiently retrieve the interactions with HTTP-based API server with two query modes:

      -
        -
      • Fetch the last k interactions of a specific type for a given user with ZREVRANGE(userId_eventType, count)
      • -
      • Retrieve all interactions within a time range (e.g., last 24 hours) with ZREVRANGEBYSCORE(userId_eventType, timeRange)
      • -
      -

      Built-in Guardrails

      -

      Since Redis was the sole store, we implemented High Availability (HA) to prevent data loss. To optimize memory usage, we also enforced size limits per event type—only storing the last k interactions per user, with older entries getting truncated.

      -

      Conclusion: Laying the Foundation for Real-Time ML

      -

      In this first phase, we tackled the fundamentals—shifting from batch-based recommendations to a real-time Recommendation using ML platform that could keep up with Meesho’s growth.

      -

      With the IOP Framework, Online Feature Store, and Interaction Store, we built the core infrastructure to support real-time personalization at scale. These wins have already unlocked:

      -
        -
      • ✅ Faster, more dynamic recommendations for millions of users.
      • -
      • ✅ Better infrastructure efficiency, reducing wasted compute power.
      • -
      • ✅ A flexible, modular system that allows for further experimentation.
      • -
      -

      But this is just the beginning. While we've solved key challenges, certain roadblocks remain —from optimizing cost-performance trade-offs to seamlessly evolving schemas.

      -

      This foundational work laid the path for a reliable and scalable real-time feature serving layer.

      - - \ No newline at end of file diff --git a/docs/blog/rss.xml b/docs/blog/rss.xml index 2d52444d..0b795f1e 100644 --- a/docs/blog/rss.xml +++ b/docs/blog/rss.xml @@ -4,19 +4,635 @@ BharatMLStack Blog https://meesho.github.io/BharatMLStack/blog BharatMLStack Blog - Tue, 15 Nov 2022 00:00:00 GMT + Thu, 19 Feb 2026 00:00:00 GMT https://validator.w3.org/feed/docs/rss2.html https://github.com/jpmonette/feed en + + <![CDATA[Beyond Vector RAG: Building Agent Memory That Learns From Experience.]]> + https://meesho.github.io/BharatMLStack/blog/episodic-memory-for-agents + https://meesho.github.io/BharatMLStack/blog/episodic-memory-for-agents + Thu, 19 Feb 2026 00:00:00 GMT + + BharatMLStack +Agent memory has come a long way. Persistent context, vector retrieval, knowledge graphs — the building blocks are real and getting better fast.

      +

      But most of what we call "memory" today is still closer to search: chunk text, embed it, retrieve whatever looks similar at query time. That works well for recalling facts and preferences. It starts to break down when you need an agent to recall what happened last time, learn from a mistake, or avoid repeating a failed approach.

      +

      We are trying to experiment something different. An episodic memory system where a frozen LLM — same weights, no retraining — produces increasingly better decisions over time because the memory feeding it context is continuously evolving. +Then we tested it. The results were interesting.

      +

      The Gap Nobody Talks About

      +

      Here's a scenario every engineering team has encountered: AI agent hits a Redis connection pool exhaustion issue. It misdiagnoses it as a database problem. You correct it. Next week, a different service has the exact same failure pattern. The agent makes the exact same mistake.

      +

      Why? Because LLMs don't learn at inference time. Corrections adjust behavior within a conversation. Once the session ends, the lesson is gone. The model weights haven't changed. The next conversation starts from zero.

      +

      Current "memory" systems don't fully address this. They store facts — user preferences, document chunks, conversation summaries. But facts aren't experience. Knowing that "Redis connection pools can exhaust under load" is different from remembering "last time I saw 500 errors under load, I assumed it was the database, I was wrong, it was actually the connection pool, and here's the correction I received."

      +

      The first is a fact. The second is an episode. The difference matters.

      +

      What's Wrong With Vector RAG as Memory

      +

      We identified five structural gaps in how current agent frameworks handle memory:

      +

      No concept of time. Two events are either semantically similar or they're not. The system can't represent "this happened after that" without distorting similarity scores. An agent can't reason about sequence or causality.

      +

      No concept of situation. A production incident and a design review might use the same technical vocabulary. Flat vector search can't distinguish them. Your agent retrieves planning notes when it should be retrieving incident postmortems.

      +

      No outcome tracking. The system stores what happened but not whether it worked. A failed approach and a successful one are equally retrievable. The agent has no way to prefer strategies that worked over strategies that didn't.

      +

      Summaries destroy evidence. Summarization-based memory compresses experience but discards the reasoning chain. The agent loses the ability to explain how it arrived at a conclusion. The audit trail is gone.

      +

      No causal links. Each memory chunk is independent. There's no way to express that incident A caused decision B, which led to outcome C, which was corrected by approach D. Without this structure, the agent can't traverse chains of reasoning.

      +

      These gaps compound. As an agent accumulates more experience, flat vector memory gets noisier, more contradictory, and less useful. The system degrades precisely when it should be improving.

      +

      The Architecture: Episodic Memory

      +

      We are building a memory system modeled on how human episodic memory works — not as a metaphor, but as an engineering specification.

      +

      The system has four layers:

      +

      Layer 1: Immutable Timeline

      +

      Every piece of agent experience is recorded as an append-only timeline entry. Each entry carries a semantic embedding (what it means), a timestamp (when it happened), and a state label (what situation the agent was in — debugging, planning, code review, incident response). Entries are never modified, never deleted, never summarized. This is the source of truth.

      +

      Layer 2: Episode Segmentation

      +

      The system watches the timeline and detects when one coherent unit of experience ends and another begins — via state transitions, semantic shifts, temporal gaps, or explicit signals. Each episode is a reference into the timeline (not a copy) with a generated summary, an outcome (SUCCESS, FAILURE, PARTIAL, UNKNOWN), decisions made, assumptions held, and corrections received.

      +

      The outcome field is the most important thing that doesn't exist in any current memory system. Without it, you can't learn from mistakes.

      +

      Layer 3: Episodic Graph

      +

      Episodes are connected through typed, weighted links: CAUSED_BY, LED_TO, RETRY_OF, LEARNED_FROM, CONTINUATION, CONTRADICTED. Over time, this forms a directed graph that enables traversal by meaning and causality. You can follow the chain: "this incident caused that investigation, which led to a failed fix, which was corrected by this approach."

      +

      Layer 4: Generalized Facts

      +

      When multiple episodes exhibit consistent patterns, the system extracts reasoning heuristics: "When services fail immediately after deployment with no traffic change, investigate configuration errors before connection pool problems." Facts are versioned, never overwritten, and maintain links back to supporting and contradicting episodes. When contradicting evidence accumulates, confidence decreases. When confidence drops below a threshold, the fact is revised — but the old version is preserved.

      +

      The LLM sits above all four layers. At query time, the system assembles structured context — relevant episodes with outcomes, applicable facts with confidence scores, causal narratives — and passes it to the LLM for reasoning. The model reasons over structured memory. It doesn't store or manage memory.

      +

      The Reinforcement Loop

      +

      This is where it comes together:

      +
        +
      1. Agent reasons using retrieved episodes and facts
      2. +
      3. Outcome is detected (CI pass/fail, user correction, test result)
      4. +
      5. New episode is created with outcome tracking
      6. +
      7. Links are created between the retrieved episodes and the new episode
      8. +
      9. Facts are reinforced (if outcome aligned) or contradicted (if outcome conflicted)
      10. +
      11. If the decision was wrong and corrected, a LEARNED_FROM link is created
      12. +
      +

      The model weights never change. The memory structure evolves continuously. A frozen LLM produces better decisions over time because it receives better context from richer memory.

      +

      The Experiment

      +

      We built the full system in Python (~1,000 lines) and tested it head-to-head against a baseline flat-vector RAG agent across a 9-round synthetic debugging scenario. Both agents used the identical LLM (Claude Sonnet 4) for reasoning. The only variable was the memory system.

      +

      The scenario was designed to test five capabilities:

      +
      Round TypeWhat It TestsRounds
      LEARNCan the agent build experience from failures?1, 2, 4
      RED HERRINGCan the agent resist applying a pattern when it doesn't fit?3
      TESTCan the agent apply learned patterns to new services?5, 6
      SUBTLECan the agent generalize to different symptoms, same root cause?7
      CORRECTIONAfter being corrected, does the agent adapt?8, 9
      +

      Rounds 1-4 build experience: three connection pool failures across different services, plus one red herring (a deployment config error that looks like a connection pool issue). Rounds 5-7 test whether the agent applies the learned pattern to unfamiliar services and subtle symptom variations. Rounds 8-9 are the critical test: the agent is corrected after misdiagnosing a deployment-correlated error, then tested on a near-identical scenario to see if it adapts.

      +

      Results

      +

      Decision Accuracy

      +
      RoundTypeEpisodic AgentBaseline Agent
      1LEARN
      2LEARN
      3RED HERRING
      4LEARN
      5TEST
      6TEST
      7SUBTLE
      8CORRECTION
      9CORRECTION
      Total7/9 (78%)5/9 (56%)
      +

      The episodic agent won 7-5. A 40% relative improvement in decision accuracy using the exact same LLM.

      +

      Where the Gap Opened

      +

      The episodic agent's advantage concentrated in exactly the rounds designed to test memory quality:

      +

      Rounds 5-6 (pattern application): The episodic agent cited 4 past failure episodes with connection pool exhaustion as root cause, complete with correction annotations. It correctly identified pool exhaustion in new services. The baseline retrieved disconnected chunks and suggested checking timeout configurations — a pattern it picked up from the Round 3 red herring.

      +

      Round 7 (subtle symptoms — latency increase, no errors): Both agents had the same evidence available. The episodic agent's retrieval surfaced a diverse set of episodes (thanks to MMR diversity filtering) including the Redis pool exhaustion from Round 6, which primed it to recognize that latency without errors can still be pool contention. The baseline defaulted to "check recent config changes."

      +

      Round 9 (adaptation after correction): This is the result we're most proud of. Look at the episodic agent's reasoning:

      +
      +

      "Episode 1 directly parallels this situation — errors spiking immediately after a deployment (v2.4.1 then, v3.1.0 now) with no traffic change. In that case, the root cause was a database migration that dropped an index. The generalized fact confirms that deployment-related issues with immediate onset after version changes are more likely caused by configuration errors or missing dependencies than by connection pool problems."

      +
      +

      It cited a specific past episode by analogy, quoted a generalized fact, and explained why this situation matches the deployment pattern rather than the connection pool pattern. The baseline gave a vaguer assessment.

      +

      Retrieval Quality

      +

      This is where the structural difference is most visible:

      +
      MetricEpisodic AgentBaseline Agent
      Retrieved items with explicit outcome labels100%25%
      Correct pattern applications (Rounds 4-7)4/41/4
      False positives (Rounds 8-9)00
      +

      Every item the episodic agent retrieved carried a structured outcome label (SUCCESS or FAILURE) with correction details. Only 25% of the baseline's chunks contained any outcome information — and those were incidental text mentions, not structured labels.

      +

      The episodic agent correctly applied the connection pool pattern in all four rounds where it was the root cause, and correctly avoided it in both rounds where it wasn't. The baseline applied it correctly once.

      +

      What Didn't Work

      +

      Two things didn't work as anticipated:

      +

      Round 3 (red herring): Both agents failed. The symptoms looked like connection pool issues, but the root cause was a deployment config change. At this point, the episodic agent had only seen connection pool episodes — it had no counter-evidence for deployment-correlated errors. You can't distinguish patterns you've only seen one side of. After Round 8 introduced a correction, the agent successfully avoided this mistake in Round 9.

      +

      Fact quality variance. Some extracted facts were specific and actionable ("Deployment-related issues with immediate onset are more likely configuration errors"). Others were vague ("Initial symptom-based diagnosis often leads to misidentifying the root cause"). A production system needs a usefulness filter, not just a confidence score.

      +

      What This Means

      +

      The most important finding isn't the accuracy improvement. It's that the reinforcement loop closes without retraining.

      +

      In the POC, we observed:

      +
        +
      • Rounds 1-4: Agent encounters failures, episodes recorded with outcomes and corrections
      • +
      • After Round 4: Fact extracted — "Connection pool exhaustion is a common root cause under load"
      • +
      • Rounds 5-7: Agent applies the pattern with increasing confidence (fact support count grows)
      • +
      • Round 8: Agent encounters a deployment error, correctly identifies it as config, gets corrected
      • +
      • After Round 8: New fact — "Deployment-related issues with immediate onset are more likely configuration errors"
      • +
      • Round 9: Agent receives near-identical scenario, correctly avoids connection pool pattern, cites the Round 8 correction
      • +
      +

      The model didn't change. The memory evolved. That's the whole point.

      +

      How It Compares to Existing Solutions

      +

      Agent memory is a fast-moving space with several strong systems, each solving a different slice of the problem:

      +

      Mem0 excels at persistent personalization — extracting user preferences, managing session context, and reducing token costs through intelligent compression. It's the most production-ready memory layer available and integrates with nearly every agent framework. Its focus is on remembering about users and conversations rather than learning from task-level outcomes, which is a different problem than the one we're exploring here.

      +

      Zep/Graphiti is doing some of the most interesting work in temporal knowledge graphs. Their bi-temporal model — tracking both when an event occurred and when it was ingested — addresses a real structural gap in how agent memory handles changing facts over time. Their episode and entity subgraphs share some philosophical DNA with our approach. Where our work diverges is in outcome tracking and reinforcement: we're specifically focused on whether a decision worked, and using that signal to update memory structure.

      +

      Letta (formerly MemGPT) pioneered self-editing memory — giving the LLM tools to manage its own memory blocks. This is a powerful paradigm, and their recent work on "Context Repositories" and sleep-time compute suggests they're actively pushing toward agents that learn over time. Their team has been transparent that experiential learning is an unsolved problem, which is part of what motivated our exploration.

      +

      MemRL (Jan 2026 paper) is the closest to our work academically. It shares the core insight of decoupling stable LLM reasoning from plastic, evolving memory. Their approach uses reinforcement learning to assign utility Q-values to memories, which is elegant but requires training a value function. Our approach is purely structural — no training step, no Q-values, just graph evolution and LLM-based reasoning over outcomes.

      +

      The common thread: most existing systems focus on knowledge persistence — remembering facts, preferences, and conversation history across sessions. The problem we're exploring is experiential learning — tracking whether past decisions worked, forming causal chains between episodes, and extracting reasoning heuristics that improve over time. These are complementary capabilities that would be needed by an ideal production system.

      +

      Try It Yourself

      +

      The prototype is available in our experiments directory:

      +
      experiments/episodic-memory-prototype/
      ├── memory/ # Timeline, encoder, episodes, graph, facts, retriever, reinforcer
      ├── agent/ # Episodic memory agent
      ├── baseline/ # Flat vector RAG agent (comparison)
      ├── simulator/ # 9-round debugging scenario
      ├── eval/ # Head-to-head comparison + scoring
      └── tests/
      +

      To run the comparison:

      +
      cd experiments/episodic-memory-prototype
      python -m venv .venv && source .venv/bin/activate
      pip install -r requirements.txt
      export ANTHROPIC_API_KEY=sk-ant-...
      python -m eval.compare
      +

      Without an API key, it runs in heuristic mode (keyword-based decisions). With a key, both agents use Claude Sonnet for reasoning — that's where the quality gap becomes visible.

      +

      Conclusion

      +

      This is a 9-round synthetic scenario we designed. It demonstrates the poc architecture works end-to-end and shows where episodic memory provides qualitatively different reasoning. It is not a peer-reviewed benchmark and should not be interpreted as a statistically rigorous claim. We're publishing the prototype so others can reproduce and extend the evaluation. +If this sparks interest do trigger github discussion.

      +
      +

      The episodic memory prototype is available in BharatMLStack repo at /experiments/episodic-memory-prototype

      ]]>
      + ai-agents + memory + architecture + llm + episodic-memory +
      + + <![CDATA[LLM Inference Optimization Techniques: Engineering Sub-Second Latency at Scale]]> + https://meesho.github.io/BharatMLStack/blog/llm-inference-optimization-sub-sec-latency + https://meesho.github.io/BharatMLStack/blog/llm-inference-optimization-sub-sec-latency + Mon, 02 Jun 2025 00:00:00 GMT + + BharatMLStack +Raw execution of Large Language Models is inherently expensive and memory-intensive. To achieve sub-second latency and high throughput, we implement a multi-layered optimization strategy that targets the entire inference stack—from memory management to kernel execution.

      +

      1. Advanced Memory Management: Paged & Prefix KV Caching

      +

      The most significant bottleneck in LLM inference is not always compute, but memory bandwidth—specifically managing the Key-Value (KV) cache.

      +

      Paged KV caching

      +

      Standard caching suffers from fragmentation. We use Paged KV caching, which operates similarly to an operating system's virtual memory: the KV cache is divided into non-contiguous blocks. This lets us serve larger batch sizes without running out of memory.

      +

      KV cache quantization

      +

      To further maximize available memory, we implement KV cache quantization (e.g., FP8). By compressing stored attention keys and values from 16-bit to 8-bit, we nearly double the effective context window capacity of the GPU, allowing longer conversations or larger batches without materially degrading quality.

      +

      Prefix caching (the "voice bot" optimizer)

      +

      For use cases like GenAI voice bots where the system prompt (e.g., "You are a helpful assistant...") is static across thousands of requests, we enable prefix caching.

      +
        +
      • Impact: By reusing pre-computed KV states for common prefixes, we achieve a cache hit rate of ~90%. This reduces Time To First Token (TTFT) by skipping redundant computation of the system prompt.
      • +
      +

      2. Aggressive Quantization (INT4 AWQ & FP8)

      +

      Running models in their native 16-bit precision (BF16) restricts maximum batch size and throughput. We use quantization to shrink model weights without sacrificing accuracy.

      +

      INT4 AWQ (Activation-aware Weight Quantization)

      +

      For the Llama 3 family, we use AWQ to compress weights to 4 bits. This reduces model size by ~75%, allowing larger models to fit into L4 GPU memory and significantly improving token generation speed.

      +

      FP8 precision

      +

      For NVIDIA Hopper (H100) architectures, we are exploring FP8 quantization, leveraging native FP8 tensor cores to accelerate matrix multiplications while maintaining a higher dynamic range than integer quantization.

      +
        +
      • Verification: We validate quantized models by comparing dot-product similarity of embeddings against the FP16 baseline, consistently achieving >99% similarity.
      • +
      +

      3. Kernel Fusion & Custom Plugins

      +

      To minimize overhead from launching thousands of small GPU operations, we fuse them into monolithic kernels using NVIDIA TensorRT plugins.

      +
        +
      • Flash attention & FMHA: We enable Fused Multi-Head Attention (FMHA) combined with flash attention to reduce memory reads/writes.
      • +
      • GEMM plugins: We use specialized GEMM plugins to accelerate transformer linear layers.
      • +
      • Removing input padding: Instead of padding short sequences to match the longest, we remove input padding so the GPU processes only valid tokens.
      • +
      +

      4. Inflight (Continuous) Batching

      +

      Traditional static batching waits for all requests in a batch to finish before returning results—so one long response delays everyone else.

      +

      We implement inflight batching: as soon as one request completes, its slot is freed and filled by a new request from the queue. This keeps GPUs saturated and decouples latency of short queries from long ones.

      +

      5. Parallelism Strategies: Scaling Beyond One GPU

      +

      For large models (e.g., 70B+ parameters) that cannot fit into the VRAM of a single GPU, we use parallelism strategies.

      +
        +
      • Tensor parallelism (TP): Split weight matrices across multiple GPUs (e.g., 4× L4 or 8× A100). Each GPU computes a shard and outputs are reduced at every layer.
      • +
      • Pipeline parallelism (PP): Split model layers across GPUs to pipeline compute (e.g., while one GPU computes later layers for Request A, another starts early layers for Request B).
      • +
      +

      6. Speculative Decoding

      +

      To reduce inter-token latency (ITL), we explore speculative decoding.

      +
        +
      • Mechanism: A smaller, faster "draft" model speculatively generates a short token sequence (e.g., 5 tokens).
      • +
      • Verification: The larger target model verifies those tokens in one parallel forward pass. If correct, we effectively generate multiple tokens per large-model step; if not, we discard and regenerate. This is effective for predictable text, improving perceived generation speed.
      • +
      +

      Few Benchmarks

      +

      Below are a couple of representative use cases and performance numbers.

      +

      Search query rewriting

      +
        +
      • LLM: Fine-tuned llama-3.2-1B
      • +
      • Input & output token length: ~10–20
      • +
      • Response type: Non-streaming
      • +
      +
      Inference runtimeHardwareMax requests/secMax p99 latency
      TensorRT-LLM4 × L4 GPUs (multi-GPU)100095 ms
      TensorRT-LLM1 × A100 40 GB GPU100069 ms
      +

      Voice bot query

      +
        +
      • LLM: Llama-3.1-8B
      • +
      • Input token length: ~1900–2000
      • +
      • Output token length: ~200
      • +
      • Response type: Streaming
      • +
      +
      Inference runtimeConcurrencyp99 TTFT (ms)p99 ITL (ms)Token throughput (tokens/sec)Request throughput (req/sec)Hardware
      TensorRT-LLM136.2722.7845.660.23L4
      TensorRT-LLM249.8123.2189.370.45L4
      TensorRT-LLM455.3336.62153.390.78L4
      TensorRT-LLM866.539.11279.881.47L4
      TensorRT-LLM16131.830.39547.82.77L4
      TensorRT-LLM32277.2248.02925.74.78L4
      TensorRT-LLM64498.5271.621,164.406.2L4
      TensorRT-LLM128677.31120.371,445.187.69L4
      TensorRT-LLM2561,926.31216.881,600.818.52L4
      TensorRT-LLM121.179.24130.050.68A100
      TensorRT-LLM225.789.21264.51.35A100
      TensorRT-LLM428.5210.99437.692.27A100
      TensorRT-LLM834.412.61760.493.96A100
      TensorRT-LLM1668.0314.321,343.807.01A100
      TensorRT-LLM32185.9616.822,287.3011.92A100
      TensorRT-LLM64136.8721.173,625.2218.89A100
      TensorRT-LLM128463.7834.154,456.5123.24A100
      TensorRT-LLM256890.1259.185,188.2427.05A100
      +

      Conclusion

      +

      High-performance LLM inference is fundamentally a systems engineering problem: memory efficiency, kernel execution, batching strategy, and parallelism determine real-world latency and throughput. Techniques such as paged KV caching, aggressive quantization, kernel fusion, and inflight batching improve GPU utilization while reducing latency and memory pressure.

      +

      These optimizations enable the platform to deliver sub-second responses, sustain high concurrency, and efficiently serve both lightweight and long-context workloads. By continuously optimizing across the full inference stack, we keep LLM serving scalable, cost-efficient, and production-ready for real-time AI applications.

      ]]>
      + llm + vllm + tensorrt-llm + mlplatform + meesho + bharatmlstack +
      + + <![CDATA[Designing a Production-Grade LLM Inference Platform: From Model Weights to Scalable GPU Serving]]> + https://meesho.github.io/BharatMLStack/blog/multi-engine-llm-inferencing-platform + https://meesho.github.io/BharatMLStack/blog/multi-engine-llm-inferencing-platform + Sat, 29 Mar 2025 00:00:00 GMT + + BharatMLStack +Serving large language models in production introduces new challenges across infrastructure, performance optimization, and operational lifecycle management. The LLM Inference Platform addresses these challenges by providing a unified system for deploying and managing open-source and fine-tuned LLMs at scale.

      +

      The platform implements a complete LLMOps lifecycle — from model registration and automated compilation to deployment, runtime optimization, and monitoring. Designed as a self-service environment, users can onboard models directly from open repositories such as Hugging Face or upload custom fine-tuned models, and deploy them using a single-click workflow with no manual infrastructure or configuration steps required.

      +

      In addition to fully automated deployment, the platform allows users to select and apply custom inference optimization techniques — such as quantization strategies, batching configurations, and runtime-specific performance enhancements — enabling teams to balance latency, throughput, and cost based on their use case. The goal is to reduce operational friction while enabling high-performance, production-grade LLM inference.

      +

      Why LLM Inference Is not just bigger ML model serving

      +

      Large language model (LLM) inference introduces a fundamentally different set of challenges compared to traditional machine learning inference. While classical ML models typically perform a single forward pass to produce a fixed prediction, LLMs operate as autoregressive systems, generating outputs token by token based on previously generated context. This difference dramatically changes how inference systems must be designed, optimized, and scaled.

      +

      Autoregressive Generation and Sequential Computation:

      +

      Unlike traditional models such as classifiers or recommenders — where inference cost is relatively constant — LLMs generate responses incrementally. Each new token depends on all previously generated tokens, making inference inherently sequential and dynamic. This means latency and compute requirements vary significantly depending on prompt length and output size, introducing complexity in scheduling and resource allocation. +Because tokens cannot be generated fully in parallel during decoding, GPUs may become underutilized without specialized batching and scheduling strategies. This has led to the development of dedicated LLM inference engines optimized for token-level execution.

      +

      Prefill and Decode Phases:

      +

      LLM inference typically consists of two distinct stages:

      +
        +
      • Prefill phase — the model processes the input prompt and builds internal representations. This stage is compute-heavy and highly parallelizable.
      • +
      • Decode phase — the model generates tokens sequentially, predicting one token at a time using previously generated context.
      • +
      +

      The decode stage often becomes memory-bound rather than compute-bound, which creates new performance bottlenecks compared to traditional ML workloads.

      +

      Context Management and KV Caching:

      +

      Another fundamental difference lies in how LLMs maintain context. Transformer-based models rely on attention mechanisms that require access to past token representations. To avoid recomputing these representations repeatedly, inference engines use key-value (KV) caching, which stores intermediate activations from previous tokens. +KV caching significantly improves performance by eliminating redundant computation, but it introduces new challenges:

      +
        +
      • Memory consumption grows with sequence length and batch size
      • +
      • GPU memory becomes a critical bottleneck
      • +
      • Efficient memory management becomes essential for scaling concurrent requests
      • +
      +

      This tradeoff between compute efficiency and memory usage is unique to LLM inference workloads.

      +

      Dynamic and Irregular Workloads:

      +

      Traditional ML inference typically operates on fixed-size inputs with predictable latency. In contrast, LLM requests vary widely in prompt length, output length, and runtime behavior. As a result:

      +
        +
      • Batch sizes must be dynamic rather than static
      • +
      • Requests may enter and leave batches asynchronously
      • +
      • Scheduling systems must continuously rebalance workloads to maximize GPU utilization
      • +
      +

      These characteristics require specialized serving architectures that differ significantly from standard ML serving pipelines.

      +

      Streaming and User Experience Constraints:

      +

      Another distinguishing factor is the expectation of real-time streaming responses. Instead of returning a single output, LLM systems often stream tokens to users as they are generated. +Because of these differences — sequential generation, growing memory requirements, dynamic workloads, and streaming constraints — LLM inference cannot be treated as a simple extension of existing ML serving systems. Production platforms must incorporate specialized runtime engines, advanced optimization techniques, and observability tailored specifically to LLM workloads.

      +

      LLMOps: High-Level Architecture

      +

      LLM Architecture

      +

      The LLM Inference Framework is designed as a fully automated, end-to-end system for deploying and operating open-source and fine-tuned large language models at scale. The architecture abstracts the complexity of model optimization, hardware selection, deployment, and runtime management into a unified workflow that enables users to move from raw model weights to production-ready inference endpoints with minimal manual intervention.

      +

      Our LLM Inference Framework is architected not just as a serving engine, but as a complete lifecycle management system. As illustrated in the high-level design below, the platform automates the journey of a model through seven distinct stages, ensuring reproducibility, performance, and scalability.

      +
        +
      1. +

        Onboarding & Registration (The Source of Truth)

        +

        The lifecycle begins with the Data Scientist or engineer.

        +
          +
        • Model Ingestion: Users onboard models—whether open-source (Hugging Face, NeMo) or internally fine-tuned—via the Truffle Box SDK/UI.
        • +
        • LLM + Prompt Registry: Unlike traditional systems that only track model weights, our registry is a unified control plane. It stores both the Model Artifacts and the Prompt Templates. This allows Data Scientists to register and version-control prompts (e.g., "customer_support_v2") independently of the application code.
        • +
        +
      2. +
      3. +

        The "Black Box" Build Engine

        +

        Once a model is registered, the Automated LLM Compiler + Quantizer Module kicks off a background job on ephemeral GPU resources.

        +
          +
        • Transformation: The raw model is converted into a TRT-LLM Checkpoint.
        • +
        • Quantization: The system automatically applies quantization algorithms (like INT4 AWQ or FP8) to reduce memory footprint.
        • +
        • Engine Building: Finally, it compiles a highly optimized TRT Engine specifically tuned for the target hardware.
        • +
        +
      4. +
      5. +

        Intelligent Profiling & Validation

        +

        Before deployment, the new engine passes through the Hardware & Inference Runtime Profiler.

        +
          +
        • Benchmarking: This module empirically tests the engine against various hardware configurations (L4 vs. A100) and runtimes (TRT-LLM vs. vLLM).
        • +
        • Optimization: It recommends the optimal configuration that meets latency SLAs (Time-To-First-Token) while minimizing cost.
        • +
        +
      6. +
      7. +

        Smart Artifact Generation & Distribution

        +

        To solve the Kubernetes "Cold Start" problem, the LLM Serving Artifacts Generation module packages the model using a bifurcated strategy:

        +
          +
        • Standard Models: Artifacts are uploaded to Cloud Storage (GCS) and downloaded by pods at startup.
        • +
        • Very Large Models: For massive models (>8GB) where network downloads are too slow, the system pre-caches the model onto Secondary Boot Disks. These disks are attached directly to new GPU nodes during autoscaling, eliminating download wait times.
        • +
        +
      8. +
      9. +

        Image Streaming & Deployment

        +

        Simultaneously, the inference runtime container images are pulled from the Artifact Registry.

        +
          +
        • Image Streaming: We utilize container image streaming to allow pods to start initializing while the massive Triton/Dynamo container layers are still downloading, further shaving seconds off the startup time. link
        • +
        +
      10. +
      11. +

        The Inference Runtime (Kubernetes)

        +

        The workload lands on Kubernetes with Autoscaling.

        +
          +
        • Dynamic Backends: Depending on the profile generated in Stage 3, the pod initializes either TensorRT-LLM (for throughput) or vLLM (for flexibility), or spins up a Dynamo worker for distributed inference.
        • +
        • Data Loading: The pod either downloads the model from Cloud Storage or mounts the pre-warmed Secondary Boot Disk ("Pull from Disk").
        • +
        +
      12. +
      13. +

        Client Interaction & Observability

        +

        Finally, the LLM Inference Client executes the request.

        +
          +
        • Prompt Injection: The client pulls the specific prompt template ID from the Registry, ensuring the exact versioned instructions are used.
        • +
        • Streaming Response: The request is sent via gRPC, and tokens are streamed back to the user in real-time.
        • +
        +
      14. +
      15. +

        Observability: Monitoring the Pulse of GenAI

        +

        In traditional microservices, success is measured by CPU utilization and request latency (p99). For Large Language Models, these metrics are insufficient. A user doesn't care if the GPU is at 80% utilization; they care about how fast the first word appears and how smoothly the rest of the sentence follows.

        +

        To capture the true user experience, our platform instrumentation focuses on three critical LLM-specific metrics:

        +
          +
        1. +

          Time to First Token (TTFT)

          +
            +
          • Definition: TTFT measures the time elapsed from the moment a request is received until the very first token is generated and streamed back to the user.
          • +
          • Why it matters: This represents the "Prefill Phase" latency—the time the model takes to process the input prompt and load weights. A high TTFT makes the application feel unresponsive or "hung."
          • +
          • Optimization: We closely monitor TTFT to ensure our Prefix Caching is effective (aiming for high cache hitrates), which drastically lowers this metric by skipping redundant prompt processing.
          • +
          +
        2. +
        3. +

          Inter-Token Latency (ITL)

          +
            +
          • Definition: ITL measures the average time interval between the generation of consecutive tokens during the "Decode Phase".
          • +
          • Why it matters: This defines the "perceived speed" of reading. Even if the first token is fast (low TTFT), high ITL makes the text generation look "jerky" or slow to the user.
          • +
          • Benchmarks: In our testing with Llama 3.1, we track p99 ITL to ensure it stays below human reading speeds to maintain a natural conversational flow.
          • +
          +
        4. +
        5. +

          Token Throughput vs. Request Throughput

          +
            +
          • We distinguish between two types of throughput to balance system efficiency with user load:
          • +
          • Token Throughput (tokens/sec): The total number of tokens generated across all concurrent requests. This measures the raw compute efficiency of the GPU and the effectiveness of batching.
          • +
          • Request Throughput (req/sec): The number of distinct user queries served per second. We use this to determine autoscaling thresholds, ensuring we scale out before the queue depth impacts ITL.
          • +
          +
        6. +
        7. +

          The Monitoring Stack

          +
            +
          • Real-time Dashboards: We utilize Grafana to visualize these streaming metrics in real-time, allowing on-call engineers to spot "slow generation" incidents that generic "500 error" alerts would miss.
          • +
          • Request Tracing: Since Triton Inference Server does not log request payloads by default, we integrate a Helix Client to asynchronously publish request logs to Log Tables. This allows us to trace a specific "slow" request back to its prompt to understand if a complex input caused the latency spike.
          • +
          +
        8. +
        +
      16. +
      +

      Supported Inference backends (TensorRT LLM, Dynamo & vLLM)

      +

      Tailored for the Use Case: We do not believe in a "one-size-fits-all" approach to inference. Different use cases—whether a real-time voice bot requiring ultra-lowsub-second latency or a massive reasoning task requiring huge context windows—demand different runtime characteristics. Our platform is designed to be runtime-agnostic, allowing us to automatically select and tailor the best engine based on the specific requirements of the application:

      +
        +
      1. +

        TensorRT-LLM: The High-Performance Standard

        +

        Suitable for: High-throughput production workloads where latency is critical (e.g., customer support chat, real-time voice bots).

        +

        TensorRT-LLM serves as our default backend for these scenarios. Our internal benchmarks on Llama 3.1 and 3.2 models demonstrated that a tuned TensorRT-LLM engine significantly outperforms standard runtimes, especially when utilizing INT4 AWQ and FP8 quantization .

        +

        Key optimizations we tailor for these high-load cases include:

        +
          +
        • Optimized execution via TensorRT engine compilation
        • +
        • Quantization-aware execution for reduced memory usage and improved throughput
        • +
        • Inflight Batching: Allowing requests to be processed continuously without waiting for the entire batch to finish, drastically improving GPU utilization .
        • +
        • Custom Plugins: Enabling specific NVIDIA plugins like the GEMM plugin and GPT Attention plugin to accelerate matrix multiplications and attention mechanisms .
        • +
        +
      2. +
      3. +

        Dynamo: Distributed Inference for Reasoning Models

        +

        Suitable for: Very large "reasoning" models (70B+) or scenarios requiring massive context windows where a single GPU's memory is insufficient.

        +

        For these memory-bound tasks, we utilize Dynamo, a low-latency distributed inference framework . Unlike monolithic servers, Dynamo disaggregates the inference process to scale resources horizontally:

        +
          +
        • KV Aware Routing: A specialized router directs requests to workers that already hold the relevant Key-Value (KV) cache, minimizing redundant computation .
        • +
        • Prefill vs. Decode Split: The workload is divided into Prefill Workers (processing the prompt) and Decode Workers (generating tokens), allowing us to scale the compute-heavy "reading" phase independently from the memory-heavy "writing" phase .
        • +
        • Distributed execution across multiple GPU resources
        • +
        +
      4. +
      5. +

        vLLM: The Flexible Baseline

        +

        Suitable for: Rapid prototyping, testing new model architectures, or low-traffic internal tools where ease of deployment outweighs raw throughput.

        +

        While TensorRT-LLM is optimized for maximum speed, vLLM provides a robust and flexible baseline .

        +
          +
        • High throughput through dynamic batching and efficient memory utilization
        • +
        • Paged KV cache management for handling long contexts and concurrent requests
        • +
        • Strong support for open-source model ecosystems
        • +
        • Rapid Adoption: It allows us to onboard new model architectures immediately without waiting for a custom TensorRT build.
        • +
        • Benchmarking Insight: In our internal tests, vLLM provided a strong baseline but often lacked the specific max-token optimizations present in our custom TRT engines . We use it strategically for initial testing before committing to a full TensorRT optimization pipeline.
        • +
        +
      6. +
      +

      Conclusion

      +

      Large language model inference introduces a fundamentally new class of infrastructure challenges—where performance is governed not just by raw compute, but by memory efficiency, intelligent scheduling, runtime specialization, and lifecycle automation. Unlike traditional ML serving, LLM inference requires systems that understand token-level execution, manage rapidly growing context state, and continuously balance latency, throughput, and cost under highly dynamic workloads.

      +

      The LLM Inference Framework addresses these challenges by transforming inference into a fully automated, reproducible lifecycle—from model onboarding and compilation to deployment, optimization, and observability. By integrating automated quantization and engine compilation, intelligent runtime selection, cold-start mitigation strategies, and LLM-specific observability metrics such as Time-to-First-Token and Inter-Token Latency, the platform ensures both high performance and operational simplicity.

      +

      Equally important, the framework is designed with flexibility and future evolution in mind. Its runtime-agnostic architecture enables seamless adoption of emerging inference engines, hardware accelerators, and optimization techniques without requiring platform redesign. This ensures that teams can continuously leverage advancements in the rapidly evolving LLM ecosystem while maintaining consistent operational workflows.

      +

      Ultimately, the goal of the platform is to make production-scale LLM deployment as seamless and reliable as traditional software deployment—allowing teams to focus on building intelligent applications rather than managing infrastructure complexity. By combining lifecycle automation, runtime optimization, and deep observability, the LLM Inference Framework provides a scalable foundation for delivering fast, cost-efficient, and production-ready LLM experiences.

      +

      Future Explorations

      +

      While we have achieved significant milestones in latency and throughput, the landscape of GenAI is evolving rapidly. Our roadmap focuses on increasing flexibility, reducing costs, and enhancing reliability for enterprise-grade workloads. Here is what we are building next:

      +
        +
      • TPU Support: To diversify our hardware supply chain and further optimize cost-per-token, we are evaluating Google Cloud TPUs to bake it into our platform. By leveraging the JAX and PyTorch/XLA ecosystems, we aim to unlock the massive throughput potential of TPU v5e chips, particularly for our open-source Llama models. This will allow the hardware profiler to dynamically choose between NVIDIA GPUs and Google TPUs based on real-time availability and price-performance metrics.
      • +
      • Multi-LoRA Serving (Serverless Experience): Currently, deploying a fine-tuned model requires a dedicated GPU. We are building support for Multi-LoRA serving, which will allow us to serve hundreds of unique, fine-tuned adapters on top of a single frozen base model. This will drastically reduce costs for multi-tenant applications, enabling a "serverless" experience where specific fine-tunes are hot-swapped instantly per request.
      • +
      • Spot Instance Orchestration: To further optimize cloud costs, we are developing fault-tolerant mechanisms to run inference workloads on Spot Instances. By implementing aggressive checkpointing and seamless request draining, we aim to leverage cheaper, preemptible compute capacity without interrupting the user's streaming experience.
      • +
      • Semantic Caching Layer: We plan to move beyond standard Prefix Caching to implement Semantic Caching. By using a vector database to fetch responses for semantically similar queries (e.g., "How do I reset my password?" vs. "Password reset steps"), we can bypass the GPU entirely for repetitive queries, reducing latency to near-zero.
      • +
      • Context-Aware Autoscaling: Standard CPU/GPU utilization metrics are often insufficient signals for scaling LLMs. We are working on KV-cache pressure metrics for autoscaling. This ensures that we scale out before the memory fills up, preventing eviction-based slowdowns during traffic spikes.
      • +
      • Online Evaluation & Guardrails: We are integrating a lightweight "Trust Layer" into the proxy. This will allow for low-latency input/output filtering (Guardrails) and asynchronous "LLM-as-a-Judge" evaluation pipelines to monitor response quality in production, not just system health.
      • +
      ]]>
      + llm + vllm + tensorrt-llm + mlplatform + meesho + bharatmlstack +
      + + <![CDATA[Cracking the Code: Scaling Model Inference & Real-Time Embedding Search]]> + https://meesho.github.io/BharatMLStack/blog/scaling-model-inference-and-embedding-search + https://meesho.github.io/BharatMLStack/blog/scaling-model-inference-and-embedding-search + Tue, 21 May 2024 00:00:00 GMT + + BharatMLStack +By mid-2023, we had transformed our ML stack—building a real-time feature store, optimizing model retrieval, and fine-tuning ranking. But two critical gaps remained:

      +
        +
      • 🔹 Scaling model inference without hitting infrastructure roadblocks
      • +
      • 🔹 Moving embedding search from batch to real-time for candidate generation
      • +
      +

      Here’s how we tackled these last-mile challenges, broke free from infrastructure constraints, and built a cost-efficient, high-performance system.

      +

      Breaking Free from the Scalability Ceiling

      +

      The Model Serving Bottleneck—A Wake-Up Call

      +

      July 2023. With just months left for the Mega Blockbuster Sale (MBS), we noticed a serious issue—scaling our model-serving infrastructure was taking 10–15 minutes. In real-time ML, that’s an eternity. +In one of our war rooms, we ran a quick experiment:

      +
        +
      • 🚀 We deployed an XGBoost model on a self-hosted Triton Inference Server running on a 16-core machine.
      • +
      • 🚀 Fired requests and compared the outputs with our existing cloud-hosted setup.
      • +
      • 🚀 The results matched—perfectly.
      • +
      +

      That moment changed everything. We prepped a backup Triton setup on EKS, just in case our cloud provider couldn't allocate enough compute resources in time. Luckily, they did—but the seed was planted. +Then in October, just two weeks before MBS, we got an alarming response from our infrastructure team: +"Node availability may be an issue." +With no time to waste, we moved 30% of real-time ML traffic to our self-hosted Triton cluster. The results?

      +
        +
      • ✅ p99 latency dropped from 90–100ms to 30–40ms
      • +
      • ✅ Triton handled significantly higher throughput on fewer resources
      • +
      • ✅ No model changes were needed
      • +
      +

      MBS ran without a hitch, proving that self-hosted inference was the way forward.

      +

      Scaling Triton on GKE

      +

      This left us with two choices:

      +
        +
      • 1️⃣ Port models to a managed cloud inference service, investing time in learning a new deployment stack
      • +
      • 2️⃣ Scale our existing Triton setup on GKE, optimizing for cost and performance
      • +
      +

      We went with Option 2—and it slashed inference costs to 35% of what we previously paid, while giving us full control over scaling and optimizations.

      +

      Fixing the Cold Start Problem

      +

      As we onboarded more deep learning (DL) models, we hit a new bottleneck, new inference pods took 7–9 minutes to spin up.

      +

      After profiling, we found the culprits:

      +
        +
      • Triton’s base image—a massive 5GB
      • +
      • Model binaries—often 1GB+
      • +
      • Startup delay—mostly due to downloading and initializing these assets
      • +
      +

      To fix this, we built a lightweight Triton image, stripping unused components and shrinking the size to 900MB. This cut cold start times drastically, making auto-scaling faster and smoother.

      +

      Embedding Search: The Last Piece of the Puzzle

      +

      By mid-2023, most of our ML stack had gone real-time—except for Candidate Generation (CG), which still ran in batch mode. To truly power real-time recommendations, we needed an online embedding search system.

      +

      Choosing the Right Vector Database

      +

      We benchmarked three production-ready vector DBs across key parameters:

      +
        +
      • Milvus
      • +
      • Qdrant
      • +
      • Weaviate
      • +
      +

      After extensive POCs, Qdrant stood out for its:

      +
        +
      • ✅ Blazing-fast search latency on high-dimensional vectors
      • +
      • ✅ Efficient memory usage, crucial for in-memory workloads
      • +
      • ✅ Support for upserts and soft deletes, vital for Ads use cases
      • +
      • ✅ gRPC + REST APIs, making integration seamless
      • +
      • ✅ Powerful filtering, allowing fine-tuned retrieval (e.g., filtering Ads by category, active status, etc.)
      • +
      +

      At its core, Qdrant uses HNSW indexing, delivering both high recall and low-latency nearest-neighbor search—a perfect fit for our needs.

      +

      Embedding Freshness & Real-Time Updates

      +

      To ensure embeddings stayed up to date, we built a dual ingestion pipeline:

      +
        +
      • 📌 Daily Refresh: A bulk pipeline updated embeddings overnight
      • +
      • 📌 Real-Time Updates: Ads events triggered immediate upserts/deletes
      • +
      +

      This setup powered real-time "Similar Products" recommendations on the product page and became the foundation for Ads Candidate Generation, ensuring the right ads surfaced in milliseconds.

      +

      Skye

      +

      Final Takeaways: Scaling Smartly for Real-Time ML

      +
        +
      • 🚀 Self-hosted inference on Triton gave us lower cost, faster scaling, and better performance than managed services
      • +
      • 🚀 Building a custom Triton image reduced cold starts, improving responsiveness
      • +
      • 🚀 Qdrant-based embedding search enabled real-time personalization at scale
      • +
      • 🚀 Real-time updates for embeddings unlocked dynamic, up-to-date recommendations
      • +
      +

      By early 2024, Meesho’s ML stack had evolved into a fully real-time, scalable, and cost-efficient system, setting the foundation for even bigger leaps ahead.

      ]]>
      + model-inference + embedding-search + mlplatform + meesho + bharatmlstack +
      + + <![CDATA[Building Meesho’s ML Platform: Lessons from the First-Gen System (Part 2)]]> + https://meesho.github.io/BharatMLStack/blog/building-meeshos-mlplatform-lessons-from-first-gen + https://meesho.github.io/BharatMLStack/blog/building-meeshos-mlplatform-lessons-from-first-gen + Mon, 10 Apr 2023 00:00:00 GMT + + BharatMLStack +By late 2022, we had built something we were truly proud of—a real-time ML serving system with a DAG-based executor, a feature store, and an interaction store powering key ranking and personalization models. It was a major milestone, the culmination of months of effort from data scientists, ML engineers, and backend teams. Our system was live, and we were ready to push the boundaries of experimentation. +And it worked. Mostly. +But soon, cracks appeared. Every new model needed custom feature retrieval logic, DAGs became dense and unmanageable, and scaling turned into a constant firefight. Costs surged, and infra bottlenecks slowed experimentation. Our system worked, but it wasn’t built for scale. +This is the story of how we tackled these challenges—building Inferflow for seamless feature retrieval, optimizing real-time infra, and cutting costs while scaling to millions of QPS.

      +

      The Cost of Success

      +

      Every new Ranker model required its own feature set, often pulling from different entities. Each addition meant:

      +
        +
      • Adding new DAG nodes in IOP
      • +
      • Writing custom logic to fetch features from multiple sources (e.g., user, product, user × category)
      • +
      • Inferring intermediate features (e.g., extracting category from a product to fetch user × category data)
      • +
      • Optimizing I/O and dealing with the inevitable bugs
      • +
      +

      What began as clean DAGs soon turned into a tangled web of cross-dependent graphs. Every experimentation cycle meant new nodes, new dependencies, and slower iterations.

      +

      Scaling Pains (and Cassandra’s Limits)

      +

      At some point, we were hitting:

      +
        +
      • 250–300K reads/sec
      • +
      • 1M writes/sec (during lean hours)
      • +
      +

      All of this ran on Cassandra. While its distributed architecture had been proven in production, operating large-scale clusters came with considerable infrastructure overhead. Our proof-of-concept (POC) demonstrated throughput of around 100K ops/sec, but as we scaled further, the challenges grew. Ensuring node health, optimizing compaction, and maintaining storage balance became increasingly demanding. We also observed latency spikes under heavy load, alongside a sharp increase in total cost of ownership.

      +

      Interaction Store Woes

      +

      Our interaction store was another ticking time bomb:

      +
        +
      • 🚨 Clusters kept growing in size and cost
      • +
      • 🚨 Latency spikes became increasingly frequent
      • +
      • 🚨 The DMC proxy occasionally lost locality of nodes against shards, causing cross-node communication and degraded performance
      • +
      +

      Each time this happened, we had to manually rebalance shards just to restore stable latency, making operations unsustainable at scale.

      +

      Silver Linings

      +

      Despite the chaos, the system was live and delivering value:

      +
        +
      • Real-time infrastructure was in production
      • +
      • Costs dropped by 60–70% compared to offline personalization
      • +
      • New experiments rolled out faster and more successfully
      • +
      • User engagement metrics improved
      • +
      +

      It wasn’t perfect. It was far from easy. But it worked—and that counted for a lot.

      +

      Round Two: Solving the Top 2 Bottlenecks

      +

      With the first-gen system stretched to its limits, we stepped back. Conversations with data scientists and backend engineers revealed three recurring pain points:

      +
        +
      1. Coding feature retrieval logic for every new model was becoming unsustainable
      2. +
      3. ML scale was exploding—bringing rising infra costs with it
      4. +
      5. Real-time embedding search was the next big unlock
      6. +
      +

      We tackled them one by one—starting with the biggest pain point.

      +

      Problem 1: No-Code Feature Retrieval for Model Inference

      +

      We noticed a pattern: for personalized ranking, models needed features from:

      +
        +
      • ✅ Product
      • +
      • ✅ User
      • +
      • ✅ User × Category
      • +
      • ✅ Region, cohort, sub-category, etc.
      • +
      +

      A key insight emerged: Entities that contribute features for a model always map back to the context entities.

      +

      MP Dag

      +

      With this, we designed Inferflow, a graph-driven feature retrieval and model orchestration system:

      +
        +
      • 1️⃣ Inferflow takes a modelId and context IDs (e.g., userId, productIds)
      • +
      • 2️⃣ Loads a pre-defined feature retrieval graph from ZooKeeper
      • +
      • 3️⃣ Executes the graph to resolve entity relationships dynamically
      • +
      • 4️⃣ Outputs a 2D matrix of feature vectors
      • +
      +

      💡 The impact?

      +
        +
      • 🚀 No more custom feature retrieval code—just graph updates in config
      • +
      • 🚀 Feature consistency across experiments
      • +
      • 🚀 Faster iteration cycles for ranking, fraud detection, and beyond
      • +
      +

      Here’s a visual example that shows how this graph plays out during execution. We further extended the graph to call multiple models as needed: +MP matrix +We built Inferflow in GoLang, using gRPC and Proto3 serialization for efficiency.

      +

      Problem 2: Scaling Without Breaking the Bank

      +

      With more ML use cases coming online, we needed to cut costs without compromising performance. We focused on:

      +
        +
      • 🔹 Online Feature Store
      • +
      • 🔹 Interaction Store
      • +
      +

      Optimizing the Online Feature Store

      +

      Our costs were concentrated in:

      +
        +
      • 📌 Database (Cassandra)
      • +
      • 📌 Cache (Redis)
      • +
      • 📌 Running Pods (Java services)
      • +
      +

      1️⃣ Replacing Cassandra with ScyllaDB +As we hit the operational limits of large Cassandra clusters, we transitioned to ScyllaDB, which offered a seamless drop-in replacement without major code changes. The switch brought significant benefits:

      +
        +
      • Throughput: Matched or exceeded Cassandra's performance under identical workloads, even under high concurrency.
      • +
      • Latency: Achieved consistently lower P99 latencies due to ScyllaDB's shard-per-core architecture and better I/O utilization.
      • +
      • Cost Efficiency: Reduced infra footprint by ~70% through better CPU and memory efficiency, eliminating the need for over-provisioned nodes.
      • +
      +

      2️⃣ Finding the Right Cache +To reduce backend load and improve response times, we benchmarked multiple caching solutions—Memcached, KeyDB, and Dragonfly—under real production traffic patterns. Dragonfly stood out due to its robust architecture and operational simplicity:

      +
        +
      • Data Skew Handling: Efficiently managed extreme key hotness and uneven access patterns without performance degradation.
      • +
      • Throughput: Delivered consistently high throughput, even with large object sizes and concurrent access.
      • +
      • Ease of Adoption: Acted as a drop-in Redis replacement with full protocol compatibility—no changes needed in application code or client libraries.
      • +
      +

      3️⃣ Moving to GoLang for Cost-Efficient Serving +Java services were memory-heavy—so we rewrote core services in GoLang. The results?

      +

      ✅ Memory usage dropped by ~80% +✅ CPU utilization was significantly lower +✅ Faster, more efficient deployments

      +

      Optimizing the Interaction Store

      +

      We realized that we only need a user’s interaction data in Redis when they open the app. So, we implemented a tiered storage approach:

      +
        +
      • 📌 Cold Tier (ScyllaDB)—Stores click, order, wishlist events
      • +
      • 📌 Hot Tier (Redis)—Loads a user’s past interactions only when they open the app
      • +
      +

      Smart Offloading: We introduced an inactivity tracker to detect when a user session ends. At that point, Redis data was flushed back to Scylla, reducing unnecessary writes.

      +

      InteractionStore

      +

      Results

      +
        +
      • Online Feature Store hit 1M QPS for the first time during the 2023 Mega Blockbuster Sale—without breaking a sweat
      • +
      • Infra costs for Online Feature Store and Interaction Store dropped by ~60%
      • +
      +

      The Catch: Our ML Hosting Hit a Hard Limit

      +

      While planning for 2023 MBS, we ran into a critical scalability bottleneck:

      +
        +
      • ❌ Insufficient compute availability in our region for ML instances
      • +
      • ❌ Couldn’t provision enough nodes to handle real-time inference at scale
      • +
      +

      This forced us to rethink where and how we hosted our models. The existing setup was great for prototyping—but it wasn’t built to handle the bursty, high-QPS demands of real-world production workloads.

      +

      Conclusion: From Firefighting to Future-Proofing

      +

      What started as an ambitious experiment turned into a real-time ML infrastructure that powered millions of requests per second. We battled scaling pains, rethought feature retrieval with Inferflow, and rebuilt our infra stack for efficiency—driving down costs while improving experimentation velocity. +But new challenges emerged. Our infrastructure could now handle scale, but our ML model hosting setup hit a hard limit. With compute availability bottlenecks threatening real-time inference, we faced a critical decision: how do we make model serving as scalable and cost-efficient as the rest of our stack? That’s the next piece of the puzzle—and the story of Part 3.

      ]]>
      + inferflow + interaction-store + mlplatform + meesho + bharatmlstack +
      <![CDATA[Building Meesho’s ML Platform: From Chaos to Cutting-Edge (Part 1)]]> - https://meesho.github.io/BharatMLStack/blog/post-one - https://meesho.github.io/BharatMLStack/blog/post-one + https://meesho.github.io/BharatMLStack/blog/building-meeshos-mlplatform + https://meesho.github.io/BharatMLStack/blog/building-meeshos-mlplatform Tue, 15 Nov 2022 00:00:00 GMT - - BharatMLStack

      -

      The Genesis: How a Friday Night Roast Sparked Meesho’s ML Platform

      -

      It all started in early 2022, over a casual Friday evening catch-up. Like many great origin stories, this one began with friendly banter between a group of backend engineers and data scientists. As the conversations unfolded, so did the roasting—until one remark hit a little too close to home:

      + + BharatMLStack +It all started in early 2022, over a casual Friday evening catch-up. Like many great origin stories, this one began with friendly banter between a group of backend engineers and data scientists. As the conversations unfolded, so did the roasting—until one remark hit a little too close to home:

      "Why are we still crunching data for Monthly Active Users (MAU) when the next day it’s all about Daily Active Users (DAU)?"

      The laughter died down, and the question lingered. When we regrouped on Monday—clear-headed and slightly reflective—we decided to dig into the numbers. What they discovered was quite revealing: a large portion of compute resources wasn’t being put to good use. Much of the system’s effort was spent supporting users who weren’t actively engaging, and even for new users, the experience wasn’t optimized to make a meaningful impact.

      @@ -31,7 +647,7 @@ Much of the system’s effort was spent supporting users who weren’t actively
    • Serving: A microservice retrieved ranked recommendations from an in-memory data store via exposed APIs, delivering personalized listings across key surfaces such as "For You" and Category Landing Pages (CLP).

    This approach held up well—until Meesho started seeing a significant surge in traffic.

    -

    The Turning Point: From Batch to Real-Time

    +

    The Turning Point: From Batch to Real-Time

    At this time, the team was iterating on new Ranker models, and real-time inference seemed like the next logical step. But Rankers needed real-time feature retrieval, which meant an online feature store had to be built first.

    Exploring open-source options led to cost vs. performance trade-offs, but Meesho’s surging traffic meant that latency and stability were non-negotiable. After multiple debates and stakeholder discussions, a bold decision was made:

    We would build our own feature store.

    @@ -41,11 +657,11 @@ Much of the system’s effort was spent supporting users who weren’t actively

    That’s when the idea struck:
    We needed a framework for real-time DAG execution—one that preserved the same flexibility as Airflow but worked for streaming data.

    This moment shaped the next phase of our journey.

    -

    First Generation Design

    +

    First Generation Design

    Alt Text

    Laying the Groundwork: The First-Gen ML Platform

    To solve these challenges, the team built three foundational components:

    -

    1. IOP Framework: A Real-Time DAG Executor

    +

    1. IOP Framework: A Real-Time DAG Executor

    • Reusable Nodes: Each DAG node (e.g., an invocation to a CG service, a ranker, or a filter) had to be implemented only once. After that, it could be reused across any workflow by referencing it in config.
    • Config-driven Dynamic Graphs: Execution graphs were defined as adjacency lists stored in ZooKeeper, allowing teams to modify the sequence or structure of operations without touching application code.
    • @@ -53,13 +669,13 @@ Much of the system’s effort was spent supporting users who weren’t actively
    • Production-Grade DAGs: DAGs were designed to execute in low-latency real-time environments, with support for parallel execution, retries, and branching.
    More about IOP DAG -

    2. Online Feature Store - 0th Version

    +

    2. Online Feature Store - 0th Version

    • Used Cassandra and Redis for low-latency feature serving.
    • Maintained feature consistency using Feature Groups with TTL-based expiry.
    • A hybrid schema was used: feature keys stored in ZooKeeper, data stored in compact arrays.
    -

    3. Interaction Store - 0th Version

    +

    3. Interaction Store - 0th Version

    • Captured real-time user interactions like clicks, orders, and add-to-cart events.
    • Stored event data in Redis ZSETs (sorted sets) to enable fast lookups for recommendation engines.
    • @@ -67,15 +683,15 @@ Much of the system’s effort was spent supporting users who weren’t actively

    With these components in place, real-time ML at Meesho became a reality.

    This was just the beginning.

    -

    Building the Online Feature Store - 0th Version

    +

    Building the Online Feature Store - 0th Version

    Alt text

    -

    Choosing the Right Tech Stack

    +

    Choosing the Right Tech Stack

    We spent considerable time evaluating various databases, caches, and communication protocols for our online feature store. After carefully weighing cost, latency, throughput, and operational stability, we settled on a combination of:

    • Cassandra and Redis for storage
    • gRPC + Proto3 as our communication layer
    -

    Streamlining the Data Flow

    +

    Streamlining the Data Flow

    To keep things simple in the initial version:

    • Feature engineering jobs wrote raw outputs to an S3 bucket
    • @@ -92,27 +708,27 @@ Much of the system’s effort was spent supporting users who weren’t actively
    • Ad-hoc jobs computed features in higher frequency
    • These jobs pushed to both Kafka and S3 (S3 preserved historical data for future model training)
    -

    The Challenges: Data Format and Storage

    +

    The Challenges: Data Format and Storage

    One of the most critical design challenges was how to store feature data efficiently and consistently, especially in databases like Cassandra and Redis, which come with unique storage constraints.

    We had to solve for three key requirements:

    • -

      Feature Consistency

      +

      Feature Consistency

      When a feature group contains features like order_count_1h and click_count_1h, both must reflect the same time window. Inconsistent updates would lead to unreliable model predictions.

    • -

      TTL Granularity

      +

      TTL Granularity

      Each feature group required an expiry timestamp, so that all features within it expired together—preserving consistency during reads.

    • -

      Extensibility Across Databases

      +

      Extensibility Across Databases

      We anticipated that infra needs would evolve. To future-proof our system, the data format was designed to be decoupled from DB-specific layouts, enabling portability to systems like ScyllaDB, DynamoDB, HBase, or BigTable.


    -

    Overcoming Technical Constraints

    +

    Overcoming Technical Constraints

    At the time, we were using Cassandra, which not only imposed a soft limit of 75 columns per row, but also exhibited significant performance degradation as the number of columns increased further, particularly in memory constrained machines. Wide rows caused high memory usage during reads, unpredictable latencies due to heavy deserialization overhead, and inefficiencies during compactions and repairs. This ruled out the naive "one column per feature" approach. We needed a format that was compact, minimized the number of columns, and remained efficient and portable across different storage systems.

    -

    The Solution: Schema Separation

    +

    The Solution: Schema Separation

    We introduced the concept of Feature Groups—logical groupings of features that must remain consistent with one another. To represent these groups efficiently, we adopted a layered storage approach:

      @@ -121,7 +737,7 @@ To represent these groups efficiently, we adopted a layered storage approach:

      Expiry Timestamp and Schema Version were appended using a semi-colon delimiter at the end of the string.

    Example:

    -
    feature_1_value,feature_2_value,feature_3_value;expiry_ts
    +
    feature_1_value,feature_2_value,feature_3_value;expiry_ts

    This format allowed:

    • Consistent writes and reads at the group level
    • @@ -129,34 +745,34 @@ To represent these groups efficiently, we adopted a layered storage approach:

      Efficient storage with minimal DB column usage
    • Support for per-group TTLs and schema evolution
    -

    Tracking Changes in Feature Groups

    +

    Tracking Changes in Feature Groups

    Feature groups don’t stay static. As models evolve, features get added, renamed, or removed. But schema changes often go live before the data is ready—and stopping ingestion just to wait for everything to align isn't feasible.

    -

    Common Real-World Scenarios:

    +

    Common Real-World Scenarios:

    • A new feature is added to the schema, but ingestion jobs still use the older schema version.
    • Ongoing writes don’t include the newly added feature, and stopping ingestion would break freshness for existing features.
    • During serving, models request a mix of old and new features, depending on rollout stages.
    -

    The Solution: Schema Versioning

    +

    The Solution: Schema Versioning

    We solved this with versioned feature group schemas, which unlocked several capabilities:

    • -

      Backward Compatibility

      +

      Backward Compatibility

      Older ingestion jobs can continue writing using older schema versions. During reads, the system uses the schema version embedded in the value to interpret the data correctly.
    • -

      Partial Availability Handling

      +

      Partial Availability Handling

      During inference, if some features in the request aren’t available (due to rollout delays or missing data), the system serves default values, ensuring the inference call doesn’t fail.
    • -

      Safe Writes Without Pipeline Pauses

      +

      Safe Writes Without Pipeline Pauses

      With schema versioning, we no longer had to stop ingestion pipelines for schema updates. Writes using previous versions can continue safely, and downstream consumers evolve independently. This design gave us the flexibility to move fast without breaking things—preserving data quality, enabling experimentation, and ensuring reliability at scale.

    Alt Text

    -

    Interaction Store - 0th Version

    +

    Interaction Store - 0th Version

    Alt Text

    To power real-time Candidate Generators (CGs), we needed fast access to user behavior signals—like what a user recently clicked, ordered, or added to their cart. These interactions form the basis for many real-time recommendations, such as Similar Products, People Also Viewed, or Recently Ordered Again. For the 0th version of the Interaction Store, we focused on a design that was simple, fast, and reliable — optimized for high-throughput ingestion and low-latency lookups.

    -

    Event Ingestion

    +

    Event Ingestion

    We instrumented our backend services to emit key user interaction events to Kafka in real time. These included:

    • Click
    • @@ -172,9 +788,9 @@ For the 0th version of the Interaction Store, we focused on a d
    • timestamp — the moment the interaction occurred

    This decoupled the interaction logging from storage, allowing ingestion and consumption to scale independently.

    -

    Storage Design

    +

    Storage Design

    To store these events, we built Kafka consumers that processed the incoming streams and wrote the data into Redis, using sorted sets (ZSETs) as the primary data structure.

    -

    Why Redis?

    +

    Why Redis?

    Redis gave us:

    • Low-latency reads and writes
    • @@ -182,9 +798,9 @@ For the 0th version of the Interaction Store, we focused on a d
    • Native TTL support, if needed in later versions
    • In-memory performance —ideal for real-time CGs
    -

    Storage Structure

    +

    Storage Structure

    Each user’s interactions were stored using a composite key format, uniquely identifying the user and interaction type. This structure allowed efficient organization and quick retrieval of recent activity for recommendation generation:

    -
    userId_eventType → ZSET[...(pid, ts)...]
    +
    userId_eventType → ZSET[...(pid, ts)...]

    Within each ZSET:

    • The timestamp served as the score, maintaining temporal order
    • @@ -195,9 +811,9 @@ For the 0th version of the Interaction Store, we focused on a d
    • Fetch the last k interactions of a specific type for a given user with ZREVRANGE(userId_eventType, count)
    • Retrieve all interactions within a time range (e.g., last 24 hours) with ZREVRANGEBYSCORE(userId_eventType, timeRange)
    -

    Built-in Guardrails

    +

    Built-in Guardrails

    Since Redis was the sole store, we implemented High Availability (HA) to prevent data loss. To optimize memory usage, we also enforced size limits per event type—only storing the last k interactions per user, with older entries getting truncated.

    -

    Conclusion: Laying the Foundation for Real-Time ML

    +

    Conclusion: Laying the Foundation for Real-Time ML

    In this first phase, we tackled the fundamentals—shifting from batch-based recommendations to a real-time Recommendation using ML platform that could keep up with Meesho’s growth.

    With the IOP Framework, Online Feature Store, and Interaction Store, we built the core infrastructure to support real-time personalization at scale. These wins have already unlocked:

      diff --git a/docs/blog/scaling-model-inference-and-embedding-search/index.html b/docs/blog/scaling-model-inference-and-embedding-search/index.html new file mode 100644 index 00000000..a5da21ff --- /dev/null +++ b/docs/blog/scaling-model-inference-and-embedding-search/index.html @@ -0,0 +1,92 @@ + + + + + +Cracking the Code: Scaling Model Inference & Real-Time Embedding Search | BharatMLStack + + + + + + + + +

      Cracking the Code: Scaling Model Inference & Real-Time Embedding Search

      · 4 min read
      Aditya Kumar
      Lead Software Engineer @ Meesho
      Jaya Kumar
      Lead ML Engineer @ Meesho
      Adarsha Das
      Senior Architect @ Meesho

      BharatMLStack +By mid-2023, we had transformed our ML stack—building a real-time feature store, optimizing model retrieval, and fine-tuning ranking. But two critical gaps remained:

      +
        +
      • 🔹 Scaling model inference without hitting infrastructure roadblocks
      • +
      • 🔹 Moving embedding search from batch to real-time for candidate generation
      • +
      +

      Here’s how we tackled these last-mile challenges, broke free from infrastructure constraints, and built a cost-efficient, high-performance system.

      +

      Breaking Free from the Scalability Ceiling

      +

      The Model Serving Bottleneck—A Wake-Up Call

      +

      July 2023. With just months left for the Mega Blockbuster Sale (MBS), we noticed a serious issue—scaling our model-serving infrastructure was taking 10–15 minutes. In real-time ML, that’s an eternity. +In one of our war rooms, we ran a quick experiment:

      +
        +
      • 🚀 We deployed an XGBoost model on a self-hosted Triton Inference Server running on a 16-core machine.
      • +
      • 🚀 Fired requests and compared the outputs with our existing cloud-hosted setup.
      • +
      • 🚀 The results matched—perfectly.
      • +
      +

      That moment changed everything. We prepped a backup Triton setup on EKS, just in case our cloud provider couldn't allocate enough compute resources in time. Luckily, they did—but the seed was planted. +Then in October, just two weeks before MBS, we got an alarming response from our infrastructure team: +"Node availability may be an issue." +With no time to waste, we moved 30% of real-time ML traffic to our self-hosted Triton cluster. The results?

      +
        +
      • ✅ p99 latency dropped from 90–100ms to 30–40ms
      • +
      • ✅ Triton handled significantly higher throughput on fewer resources
      • +
      • ✅ No model changes were needed
      • +
      +

      MBS ran without a hitch, proving that self-hosted inference was the way forward.

      +

      Scaling Triton on GKE

      +

      This left us with two choices:

      +
        +
      • 1️⃣ Port models to a managed cloud inference service, investing time in learning a new deployment stack
      • +
      • 2️⃣ Scale our existing Triton setup on GKE, optimizing for cost and performance
      • +
      +

      We went with Option 2—and it slashed inference costs to 35% of what we previously paid, while giving us full control over scaling and optimizations.

      +

      Fixing the Cold Start Problem

      +

      As we onboarded more deep learning (DL) models, we hit a new bottleneck, new inference pods took 7–9 minutes to spin up.

      +

      After profiling, we found the culprits:

      +
        +
      • Triton’s base image—a massive 5GB
      • +
      • Model binaries—often 1GB+
      • +
      • Startup delay—mostly due to downloading and initializing these assets
      • +
      +

      To fix this, we built a lightweight Triton image, stripping unused components and shrinking the size to 900MB. This cut cold start times drastically, making auto-scaling faster and smoother.

      +

      Embedding Search: The Last Piece of the Puzzle

      +

      By mid-2023, most of our ML stack had gone real-time—except for Candidate Generation (CG), which still ran in batch mode. To truly power real-time recommendations, we needed an online embedding search system.

      +

      Choosing the Right Vector Database

      +

      We benchmarked three production-ready vector DBs across key parameters:

      +
        +
      • Milvus
      • +
      • Qdrant
      • +
      • Weaviate
      • +
      +

      After extensive POCs, Qdrant stood out for its:

      +
        +
      • ✅ Blazing-fast search latency on high-dimensional vectors
      • +
      • ✅ Efficient memory usage, crucial for in-memory workloads
      • +
      • ✅ Support for upserts and soft deletes, vital for Ads use cases
      • +
      • ✅ gRPC + REST APIs, making integration seamless
      • +
      • ✅ Powerful filtering, allowing fine-tuned retrieval (e.g., filtering Ads by category, active status, etc.)
      • +
      +

      At its core, Qdrant uses HNSW indexing, delivering both high recall and low-latency nearest-neighbor search—a perfect fit for our needs.

      +

      Embedding Freshness & Real-Time Updates

      +

      To ensure embeddings stayed up to date, we built a dual ingestion pipeline:

      +
        +
      • 📌 Daily Refresh: A bulk pipeline updated embeddings overnight
      • +
      • 📌 Real-Time Updates: Ads events triggered immediate upserts/deletes
      • +
      +

      This setup powered real-time "Similar Products" recommendations on the product page and became the foundation for Ads Candidate Generation, ensuring the right ads surfaced in milliseconds.

      +

      Skye

      +

      Final Takeaways: Scaling Smartly for Real-Time ML

      +
        +
      • 🚀 Self-hosted inference on Triton gave us lower cost, faster scaling, and better performance than managed services
      • +
      • 🚀 Building a custom Triton image reduced cold starts, improving responsiveness
      • +
      • 🚀 Qdrant-based embedding search enabled real-time personalization at scale
      • +
      • 🚀 Real-time updates for embeddings unlocked dynamic, up-to-date recommendations
      • +
      +

      By early 2024, Meesho’s ML stack had evolved into a fully real-time, scalable, and cost-efficient system, setting the foundation for even bigger leaps ahead.

      + + \ No newline at end of file diff --git a/docs/blog/tags/ai-agents/index.html b/docs/blog/tags/ai-agents/index.html new file mode 100644 index 00000000..c1dd3fa2 --- /dev/null +++ b/docs/blog/tags/ai-agents/index.html @@ -0,0 +1,21 @@ + + + + + +One post tagged with "ai-agents" | BharatMLStack + + + + + + + + +

      One post tagged with "ai-agents"

      View All Tags

      Beyond Vector RAG: Building Agent Memory That Learns From Experience.

      · 12 min read
      Adarsha Das
      Senior Architect @ Meesho

      BharatMLStack +Agent memory has come a long way. Persistent context, vector retrieval, knowledge graphs — the building blocks are real and getting better fast.

      +

      But most of what we call "memory" today is still closer to search: chunk text, embed it, retrieve whatever looks similar at query time. That works well for recalling facts and preferences. It starts to break down when you need an agent to recall what happened last time, learn from a mistake, or avoid repeating a failed approach.

      +

      We are trying to experiment something different. An episodic memory system where a frozen LLM — same weights, no retraining — produces increasingly better decisions over time because the memory feeding it context is continuously evolving. +Then we tested it. The results were interesting.

      + + \ No newline at end of file diff --git a/docs/blog/tags/architecture/index.html b/docs/blog/tags/architecture/index.html new file mode 100644 index 00000000..817be7a7 --- /dev/null +++ b/docs/blog/tags/architecture/index.html @@ -0,0 +1,21 @@ + + + + + +One post tagged with "architecture" | BharatMLStack + + + + + + + + +

      One post tagged with "architecture"

      View All Tags

      Beyond Vector RAG: Building Agent Memory That Learns From Experience.

      · 12 min read
      Adarsha Das
      Senior Architect @ Meesho

      BharatMLStack +Agent memory has come a long way. Persistent context, vector retrieval, knowledge graphs — the building blocks are real and getting better fast.

      +

      But most of what we call "memory" today is still closer to search: chunk text, embed it, retrieve whatever looks similar at query time. That works well for recalling facts and preferences. It starts to break down when you need an agent to recall what happened last time, learn from a mistake, or avoid repeating a failed approach.

      +

      We are trying to experiment something different. An episodic memory system where a frozen LLM — same weights, no retraining — produces increasingly better decisions over time because the memory feeding it context is continuously evolving. +Then we tested it. The results were interesting.

      + + \ No newline at end of file diff --git a/docs/blog/tags/bharatmlstack/index.html b/docs/blog/tags/bharatmlstack/index.html new file mode 100644 index 00000000..867a1596 --- /dev/null +++ b/docs/blog/tags/bharatmlstack/index.html @@ -0,0 +1,469 @@ + + + + + +4 posts tagged with "bharatmlstack" | BharatMLStack + + + + + + + + +

      4 posts tagged with "bharatmlstack"

      View All Tags

      LLM Inference Optimization Techniques: Engineering Sub-Second Latency at Scale

      · 5 min read
      Jaya Kumar
      Lead ML Engineer @ Meesho

      BharatMLStack +Raw execution of Large Language Models is inherently expensive and memory-intensive. To achieve sub-second latency and high throughput, we implement a multi-layered optimization strategy that targets the entire inference stack—from memory management to kernel execution.

      +

      1. Advanced Memory Management: Paged & Prefix KV Caching

      +

      The most significant bottleneck in LLM inference is not always compute, but memory bandwidth—specifically managing the Key-Value (KV) cache.

      +

      Paged KV caching

      +

      Standard caching suffers from fragmentation. We use Paged KV caching, which operates similarly to an operating system's virtual memory: the KV cache is divided into non-contiguous blocks. This lets us serve larger batch sizes without running out of memory.

      +

      KV cache quantization

      +

      To further maximize available memory, we implement KV cache quantization (e.g., FP8). By compressing stored attention keys and values from 16-bit to 8-bit, we nearly double the effective context window capacity of the GPU, allowing longer conversations or larger batches without materially degrading quality.

      +

      Prefix caching (the "voice bot" optimizer)

      +

      For use cases like GenAI voice bots where the system prompt (e.g., "You are a helpful assistant...") is static across thousands of requests, we enable prefix caching.

      +
        +
      • Impact: By reusing pre-computed KV states for common prefixes, we achieve a cache hit rate of ~90%. This reduces Time To First Token (TTFT) by skipping redundant computation of the system prompt.
      • +
      +

      2. Aggressive Quantization (INT4 AWQ & FP8)

      +

      Running models in their native 16-bit precision (BF16) restricts maximum batch size and throughput. We use quantization to shrink model weights without sacrificing accuracy.

      +

      INT4 AWQ (Activation-aware Weight Quantization)

      +

      For the Llama 3 family, we use AWQ to compress weights to 4 bits. This reduces model size by ~75%, allowing larger models to fit into L4 GPU memory and significantly improving token generation speed.

      +

      FP8 precision

      +

      For NVIDIA Hopper (H100) architectures, we are exploring FP8 quantization, leveraging native FP8 tensor cores to accelerate matrix multiplications while maintaining a higher dynamic range than integer quantization.

      +
        +
      • Verification: We validate quantized models by comparing dot-product similarity of embeddings against the FP16 baseline, consistently achieving >99% similarity.
      • +
      +

      3. Kernel Fusion & Custom Plugins

      +

      To minimize overhead from launching thousands of small GPU operations, we fuse them into monolithic kernels using NVIDIA TensorRT plugins.

      +
        +
      • Flash attention & FMHA: We enable Fused Multi-Head Attention (FMHA) combined with flash attention to reduce memory reads/writes.
      • +
      • GEMM plugins: We use specialized GEMM plugins to accelerate transformer linear layers.
      • +
      • Removing input padding: Instead of padding short sequences to match the longest, we remove input padding so the GPU processes only valid tokens.
      • +
      +

      4. Inflight (Continuous) Batching

      +

      Traditional static batching waits for all requests in a batch to finish before returning results—so one long response delays everyone else.

      +

      We implement inflight batching: as soon as one request completes, its slot is freed and filled by a new request from the queue. This keeps GPUs saturated and decouples latency of short queries from long ones.

      +

      5. Parallelism Strategies: Scaling Beyond One GPU

      +

      For large models (e.g., 70B+ parameters) that cannot fit into the VRAM of a single GPU, we use parallelism strategies.

      +
        +
      • Tensor parallelism (TP): Split weight matrices across multiple GPUs (e.g., 4× L4 or 8× A100). Each GPU computes a shard and outputs are reduced at every layer.
      • +
      • Pipeline parallelism (PP): Split model layers across GPUs to pipeline compute (e.g., while one GPU computes later layers for Request A, another starts early layers for Request B).
      • +
      +

      6. Speculative Decoding

      +

      To reduce inter-token latency (ITL), we explore speculative decoding.

      +
        +
      • Mechanism: A smaller, faster "draft" model speculatively generates a short token sequence (e.g., 5 tokens).
      • +
      • Verification: The larger target model verifies those tokens in one parallel forward pass. If correct, we effectively generate multiple tokens per large-model step; if not, we discard and regenerate. This is effective for predictable text, improving perceived generation speed.
      • +
      +

      Few Benchmarks

      +

      Below are a couple of representative use cases and performance numbers.

      +

      Search query rewriting

      +
        +
      • LLM: Fine-tuned llama-3.2-1B
      • +
      • Input & output token length: ~10–20
      • +
      • Response type: Non-streaming
      • +
      +
      Inference runtimeHardwareMax requests/secMax p99 latency
      TensorRT-LLM4 × L4 GPUs (multi-GPU)100095 ms
      TensorRT-LLM1 × A100 40 GB GPU100069 ms
      +

      Voice bot query

      +
        +
      • LLM: Llama-3.1-8B
      • +
      • Input token length: ~1900–2000
      • +
      • Output token length: ~200
      • +
      • Response type: Streaming
      • +
      +
      Inference runtimeConcurrencyp99 TTFT (ms)p99 ITL (ms)Token throughput (tokens/sec)Request throughput (req/sec)Hardware
      TensorRT-LLM136.2722.7845.660.23L4
      TensorRT-LLM249.8123.2189.370.45L4
      TensorRT-LLM455.3336.62153.390.78L4
      TensorRT-LLM866.539.11279.881.47L4
      TensorRT-LLM16131.830.39547.82.77L4
      TensorRT-LLM32277.2248.02925.74.78L4
      TensorRT-LLM64498.5271.621,164.406.2L4
      TensorRT-LLM128677.31120.371,445.187.69L4
      TensorRT-LLM2561,926.31216.881,600.818.52L4
      TensorRT-LLM121.179.24130.050.68A100
      TensorRT-LLM225.789.21264.51.35A100
      TensorRT-LLM428.5210.99437.692.27A100
      TensorRT-LLM834.412.61760.493.96A100
      TensorRT-LLM1668.0314.321,343.807.01A100
      TensorRT-LLM32185.9616.822,287.3011.92A100
      TensorRT-LLM64136.8721.173,625.2218.89A100
      TensorRT-LLM128463.7834.154,456.5123.24A100
      TensorRT-LLM256890.1259.185,188.2427.05A100
      +

      Conclusion

      +

      High-performance LLM inference is fundamentally a systems engineering problem: memory efficiency, kernel execution, batching strategy, and parallelism determine real-world latency and throughput. Techniques such as paged KV caching, aggressive quantization, kernel fusion, and inflight batching improve GPU utilization while reducing latency and memory pressure.

      +

      These optimizations enable the platform to deliver sub-second responses, sustain high concurrency, and efficiently serve both lightweight and long-context workloads. By continuously optimizing across the full inference stack, we keep LLM serving scalable, cost-efficient, and production-ready for real-time AI applications.

      Designing a Production-Grade LLM Inference Platform: From Model Weights to Scalable GPU Serving

      · 14 min read
      Jaya Kumar
      Lead ML Engineer @ Meesho

      BharatMLStack +Serving large language models in production introduces new challenges across infrastructure, performance optimization, and operational lifecycle management. The LLM Inference Platform addresses these challenges by providing a unified system for deploying and managing open-source and fine-tuned LLMs at scale.

      +

      The platform implements a complete LLMOps lifecycle — from model registration and automated compilation to deployment, runtime optimization, and monitoring. Designed as a self-service environment, users can onboard models directly from open repositories such as Hugging Face or upload custom fine-tuned models, and deploy them using a single-click workflow with no manual infrastructure or configuration steps required.

      +

      In addition to fully automated deployment, the platform allows users to select and apply custom inference optimization techniques — such as quantization strategies, batching configurations, and runtime-specific performance enhancements — enabling teams to balance latency, throughput, and cost based on their use case. The goal is to reduce operational friction while enabling high-performance, production-grade LLM inference.

      +

      Why LLM Inference Is not just bigger ML model serving

      +

      Large language model (LLM) inference introduces a fundamentally different set of challenges compared to traditional machine learning inference. While classical ML models typically perform a single forward pass to produce a fixed prediction, LLMs operate as autoregressive systems, generating outputs token by token based on previously generated context. This difference dramatically changes how inference systems must be designed, optimized, and scaled.

      +

      Autoregressive Generation and Sequential Computation:

      +

      Unlike traditional models such as classifiers or recommenders — where inference cost is relatively constant — LLMs generate responses incrementally. Each new token depends on all previously generated tokens, making inference inherently sequential and dynamic. This means latency and compute requirements vary significantly depending on prompt length and output size, introducing complexity in scheduling and resource allocation. +Because tokens cannot be generated fully in parallel during decoding, GPUs may become underutilized without specialized batching and scheduling strategies. This has led to the development of dedicated LLM inference engines optimized for token-level execution.

      +

      Prefill and Decode Phases:

      +

      LLM inference typically consists of two distinct stages:

      +
        +
      • Prefill phase — the model processes the input prompt and builds internal representations. This stage is compute-heavy and highly parallelizable.
      • +
      • Decode phase — the model generates tokens sequentially, predicting one token at a time using previously generated context.
      • +
      +

      The decode stage often becomes memory-bound rather than compute-bound, which creates new performance bottlenecks compared to traditional ML workloads.

      +

      Context Management and KV Caching:

      +

      Another fundamental difference lies in how LLMs maintain context. Transformer-based models rely on attention mechanisms that require access to past token representations. To avoid recomputing these representations repeatedly, inference engines use key-value (KV) caching, which stores intermediate activations from previous tokens. +KV caching significantly improves performance by eliminating redundant computation, but it introduces new challenges:

      +
        +
      • Memory consumption grows with sequence length and batch size
      • +
      • GPU memory becomes a critical bottleneck
      • +
      • Efficient memory management becomes essential for scaling concurrent requests
      • +
      +

      This tradeoff between compute efficiency and memory usage is unique to LLM inference workloads.

      +

      Dynamic and Irregular Workloads:

      +

      Traditional ML inference typically operates on fixed-size inputs with predictable latency. In contrast, LLM requests vary widely in prompt length, output length, and runtime behavior. As a result:

      +
        +
      • Batch sizes must be dynamic rather than static
      • +
      • Requests may enter and leave batches asynchronously
      • +
      • Scheduling systems must continuously rebalance workloads to maximize GPU utilization
      • +
      +

      These characteristics require specialized serving architectures that differ significantly from standard ML serving pipelines.

      +

      Streaming and User Experience Constraints:

      +

      Another distinguishing factor is the expectation of real-time streaming responses. Instead of returning a single output, LLM systems often stream tokens to users as they are generated. +Because of these differences — sequential generation, growing memory requirements, dynamic workloads, and streaming constraints — LLM inference cannot be treated as a simple extension of existing ML serving systems. Production platforms must incorporate specialized runtime engines, advanced optimization techniques, and observability tailored specifically to LLM workloads.

      +

      LLMOps: High-Level Architecture

      +

      LLM Architecture

      +

      The LLM Inference Framework is designed as a fully automated, end-to-end system for deploying and operating open-source and fine-tuned large language models at scale. The architecture abstracts the complexity of model optimization, hardware selection, deployment, and runtime management into a unified workflow that enables users to move from raw model weights to production-ready inference endpoints with minimal manual intervention.

      +

      Our LLM Inference Framework is architected not just as a serving engine, but as a complete lifecycle management system. As illustrated in the high-level design below, the platform automates the journey of a model through seven distinct stages, ensuring reproducibility, performance, and scalability.

      +
        +
      1. +

        Onboarding & Registration (The Source of Truth)

        +

        The lifecycle begins with the Data Scientist or engineer.

        +
          +
        • Model Ingestion: Users onboard models—whether open-source (Hugging Face, NeMo) or internally fine-tuned—via the Truffle Box SDK/UI.
        • +
        • LLM + Prompt Registry: Unlike traditional systems that only track model weights, our registry is a unified control plane. It stores both the Model Artifacts and the Prompt Templates. This allows Data Scientists to register and version-control prompts (e.g., "customer_support_v2") independently of the application code.
        • +
        +
      2. +
      3. +

        The "Black Box" Build Engine

        +

        Once a model is registered, the Automated LLM Compiler + Quantizer Module kicks off a background job on ephemeral GPU resources.

        +
          +
        • Transformation: The raw model is converted into a TRT-LLM Checkpoint.
        • +
        • Quantization: The system automatically applies quantization algorithms (like INT4 AWQ or FP8) to reduce memory footprint.
        • +
        • Engine Building: Finally, it compiles a highly optimized TRT Engine specifically tuned for the target hardware.
        • +
        +
      4. +
      5. +

        Intelligent Profiling & Validation

        +

        Before deployment, the new engine passes through the Hardware & Inference Runtime Profiler.

        +
          +
        • Benchmarking: This module empirically tests the engine against various hardware configurations (L4 vs. A100) and runtimes (TRT-LLM vs. vLLM).
        • +
        • Optimization: It recommends the optimal configuration that meets latency SLAs (Time-To-First-Token) while minimizing cost.
        • +
        +
      6. +
      7. +

        Smart Artifact Generation & Distribution

        +

        To solve the Kubernetes "Cold Start" problem, the LLM Serving Artifacts Generation module packages the model using a bifurcated strategy:

        +
          +
        • Standard Models: Artifacts are uploaded to Cloud Storage (GCS) and downloaded by pods at startup.
        • +
        • Very Large Models: For massive models (>8GB) where network downloads are too slow, the system pre-caches the model onto Secondary Boot Disks. These disks are attached directly to new GPU nodes during autoscaling, eliminating download wait times.
        • +
        +
      8. +
      9. +

        Image Streaming & Deployment

        +

        Simultaneously, the inference runtime container images are pulled from the Artifact Registry.

        +
          +
        • Image Streaming: We utilize container image streaming to allow pods to start initializing while the massive Triton/Dynamo container layers are still downloading, further shaving seconds off the startup time. link
        • +
        +
      10. +
      11. +

        The Inference Runtime (Kubernetes)

        +

        The workload lands on Kubernetes with Autoscaling.

        +
          +
        • Dynamic Backends: Depending on the profile generated in Stage 3, the pod initializes either TensorRT-LLM (for throughput) or vLLM (for flexibility), or spins up a Dynamo worker for distributed inference.
        • +
        • Data Loading: The pod either downloads the model from Cloud Storage or mounts the pre-warmed Secondary Boot Disk ("Pull from Disk").
        • +
        +
      12. +
      13. +

        Client Interaction & Observability

        +

        Finally, the LLM Inference Client executes the request.

        +
          +
        • Prompt Injection: The client pulls the specific prompt template ID from the Registry, ensuring the exact versioned instructions are used.
        • +
        • Streaming Response: The request is sent via gRPC, and tokens are streamed back to the user in real-time.
        • +
        +
      14. +
      15. +

        Observability: Monitoring the Pulse of GenAI

        +

        In traditional microservices, success is measured by CPU utilization and request latency (p99). For Large Language Models, these metrics are insufficient. A user doesn't care if the GPU is at 80% utilization; they care about how fast the first word appears and how smoothly the rest of the sentence follows.

        +

        To capture the true user experience, our platform instrumentation focuses on three critical LLM-specific metrics:

        +
          +
        1. +

          Time to First Token (TTFT)

          +
            +
          • Definition: TTFT measures the time elapsed from the moment a request is received until the very first token is generated and streamed back to the user.
          • +
          • Why it matters: This represents the "Prefill Phase" latency—the time the model takes to process the input prompt and load weights. A high TTFT makes the application feel unresponsive or "hung."
          • +
          • Optimization: We closely monitor TTFT to ensure our Prefix Caching is effective (aiming for high cache hitrates), which drastically lowers this metric by skipping redundant prompt processing.
          • +
          +
        2. +
        3. +

          Inter-Token Latency (ITL)

          +
            +
          • Definition: ITL measures the average time interval between the generation of consecutive tokens during the "Decode Phase".
          • +
          • Why it matters: This defines the "perceived speed" of reading. Even if the first token is fast (low TTFT), high ITL makes the text generation look "jerky" or slow to the user.
          • +
          • Benchmarks: In our testing with Llama 3.1, we track p99 ITL to ensure it stays below human reading speeds to maintain a natural conversational flow.
          • +
          +
        4. +
        5. +

          Token Throughput vs. Request Throughput

          +
            +
          • We distinguish between two types of throughput to balance system efficiency with user load:
          • +
          • Token Throughput (tokens/sec): The total number of tokens generated across all concurrent requests. This measures the raw compute efficiency of the GPU and the effectiveness of batching.
          • +
          • Request Throughput (req/sec): The number of distinct user queries served per second. We use this to determine autoscaling thresholds, ensuring we scale out before the queue depth impacts ITL.
          • +
          +
        6. +
        7. +

          The Monitoring Stack

          +
            +
          • Real-time Dashboards: We utilize Grafana to visualize these streaming metrics in real-time, allowing on-call engineers to spot "slow generation" incidents that generic "500 error" alerts would miss.
          • +
          • Request Tracing: Since Triton Inference Server does not log request payloads by default, we integrate a Helix Client to asynchronously publish request logs to Log Tables. This allows us to trace a specific "slow" request back to its prompt to understand if a complex input caused the latency spike.
          • +
          +
        8. +
        +
      16. +
      +

      Supported Inference backends (TensorRT LLM, Dynamo & vLLM)

      +

      Tailored for the Use Case: We do not believe in a "one-size-fits-all" approach to inference. Different use cases—whether a real-time voice bot requiring ultra-lowsub-second latency or a massive reasoning task requiring huge context windows—demand different runtime characteristics. Our platform is designed to be runtime-agnostic, allowing us to automatically select and tailor the best engine based on the specific requirements of the application:

      +
        +
      1. +

        TensorRT-LLM: The High-Performance Standard

        +

        Suitable for: High-throughput production workloads where latency is critical (e.g., customer support chat, real-time voice bots).

        +

        TensorRT-LLM serves as our default backend for these scenarios. Our internal benchmarks on Llama 3.1 and 3.2 models demonstrated that a tuned TensorRT-LLM engine significantly outperforms standard runtimes, especially when utilizing INT4 AWQ and FP8 quantization .

        +

        Key optimizations we tailor for these high-load cases include:

        +
          +
        • Optimized execution via TensorRT engine compilation
        • +
        • Quantization-aware execution for reduced memory usage and improved throughput
        • +
        • Inflight Batching: Allowing requests to be processed continuously without waiting for the entire batch to finish, drastically improving GPU utilization .
        • +
        • Custom Plugins: Enabling specific NVIDIA plugins like the GEMM plugin and GPT Attention plugin to accelerate matrix multiplications and attention mechanisms .
        • +
        +
      2. +
      3. +

        Dynamo: Distributed Inference for Reasoning Models

        +

        Suitable for: Very large "reasoning" models (70B+) or scenarios requiring massive context windows where a single GPU's memory is insufficient.

        +

        For these memory-bound tasks, we utilize Dynamo, a low-latency distributed inference framework . Unlike monolithic servers, Dynamo disaggregates the inference process to scale resources horizontally:

        +
          +
        • KV Aware Routing: A specialized router directs requests to workers that already hold the relevant Key-Value (KV) cache, minimizing redundant computation .
        • +
        • Prefill vs. Decode Split: The workload is divided into Prefill Workers (processing the prompt) and Decode Workers (generating tokens), allowing us to scale the compute-heavy "reading" phase independently from the memory-heavy "writing" phase .
        • +
        • Distributed execution across multiple GPU resources
        • +
        +
      4. +
      5. +

        vLLM: The Flexible Baseline

        +

        Suitable for: Rapid prototyping, testing new model architectures, or low-traffic internal tools where ease of deployment outweighs raw throughput.

        +

        While TensorRT-LLM is optimized for maximum speed, vLLM provides a robust and flexible baseline .

        +
          +
        • High throughput through dynamic batching and efficient memory utilization
        • +
        • Paged KV cache management for handling long contexts and concurrent requests
        • +
        • Strong support for open-source model ecosystems
        • +
        • Rapid Adoption: It allows us to onboard new model architectures immediately without waiting for a custom TensorRT build.
        • +
        • Benchmarking Insight: In our internal tests, vLLM provided a strong baseline but often lacked the specific max-token optimizations present in our custom TRT engines . We use it strategically for initial testing before committing to a full TensorRT optimization pipeline.
        • +
        +
      6. +
      +

      Conclusion

      +

      Large language model inference introduces a fundamentally new class of infrastructure challenges—where performance is governed not just by raw compute, but by memory efficiency, intelligent scheduling, runtime specialization, and lifecycle automation. Unlike traditional ML serving, LLM inference requires systems that understand token-level execution, manage rapidly growing context state, and continuously balance latency, throughput, and cost under highly dynamic workloads.

      +

      The LLM Inference Framework addresses these challenges by transforming inference into a fully automated, reproducible lifecycle—from model onboarding and compilation to deployment, optimization, and observability. By integrating automated quantization and engine compilation, intelligent runtime selection, cold-start mitigation strategies, and LLM-specific observability metrics such as Time-to-First-Token and Inter-Token Latency, the platform ensures both high performance and operational simplicity.

      +

      Equally important, the framework is designed with flexibility and future evolution in mind. Its runtime-agnostic architecture enables seamless adoption of emerging inference engines, hardware accelerators, and optimization techniques without requiring platform redesign. This ensures that teams can continuously leverage advancements in the rapidly evolving LLM ecosystem while maintaining consistent operational workflows.

      +

      Ultimately, the goal of the platform is to make production-scale LLM deployment as seamless and reliable as traditional software deployment—allowing teams to focus on building intelligent applications rather than managing infrastructure complexity. By combining lifecycle automation, runtime optimization, and deep observability, the LLM Inference Framework provides a scalable foundation for delivering fast, cost-efficient, and production-ready LLM experiences.

      +

      Future Explorations

      +

      While we have achieved significant milestones in latency and throughput, the landscape of GenAI is evolving rapidly. Our roadmap focuses on increasing flexibility, reducing costs, and enhancing reliability for enterprise-grade workloads. Here is what we are building next:

      +
        +
      • TPU Support: To diversify our hardware supply chain and further optimize cost-per-token, we are evaluating Google Cloud TPUs to bake it into our platform. By leveraging the JAX and PyTorch/XLA ecosystems, we aim to unlock the massive throughput potential of TPU v5e chips, particularly for our open-source Llama models. This will allow the hardware profiler to dynamically choose between NVIDIA GPUs and Google TPUs based on real-time availability and price-performance metrics.
      • +
      • Multi-LoRA Serving (Serverless Experience): Currently, deploying a fine-tuned model requires a dedicated GPU. We are building support for Multi-LoRA serving, which will allow us to serve hundreds of unique, fine-tuned adapters on top of a single frozen base model. This will drastically reduce costs for multi-tenant applications, enabling a "serverless" experience where specific fine-tunes are hot-swapped instantly per request.
      • +
      • Spot Instance Orchestration: To further optimize cloud costs, we are developing fault-tolerant mechanisms to run inference workloads on Spot Instances. By implementing aggressive checkpointing and seamless request draining, we aim to leverage cheaper, preemptible compute capacity without interrupting the user's streaming experience.
      • +
      • Semantic Caching Layer: We plan to move beyond standard Prefix Caching to implement Semantic Caching. By using a vector database to fetch responses for semantically similar queries (e.g., "How do I reset my password?" vs. "Password reset steps"), we can bypass the GPU entirely for repetitive queries, reducing latency to near-zero.
      • +
      • Context-Aware Autoscaling: Standard CPU/GPU utilization metrics are often insufficient signals for scaling LLMs. We are working on KV-cache pressure metrics for autoscaling. This ensures that we scale out before the memory fills up, preventing eviction-based slowdowns during traffic spikes.
      • +
      • Online Evaluation & Guardrails: We are integrating a lightweight "Trust Layer" into the proxy. This will allow for low-latency input/output filtering (Guardrails) and asynchronous "LLM-as-a-Judge" evaluation pipelines to monitor response quality in production, not just system health.
      • +

      Cracking the Code: Scaling Model Inference & Real-Time Embedding Search

      · 4 min read
      Aditya Kumar
      Lead Software Engineer @ Meesho
      Jaya Kumar
      Lead ML Engineer @ Meesho
      Adarsha Das
      Senior Architect @ Meesho

      BharatMLStack +By mid-2023, we had transformed our ML stack—building a real-time feature store, optimizing model retrieval, and fine-tuning ranking. But two critical gaps remained:

      +
        +
      • 🔹 Scaling model inference without hitting infrastructure roadblocks
      • +
      • 🔹 Moving embedding search from batch to real-time for candidate generation
      • +
      +

      Here’s how we tackled these last-mile challenges, broke free from infrastructure constraints, and built a cost-efficient, high-performance system.

      +

      Breaking Free from the Scalability Ceiling

      +

      The Model Serving Bottleneck—A Wake-Up Call

      +

      July 2023. With just months left for the Mega Blockbuster Sale (MBS), we noticed a serious issue—scaling our model-serving infrastructure was taking 10–15 minutes. In real-time ML, that’s an eternity. +In one of our war rooms, we ran a quick experiment:

      +
        +
      • 🚀 We deployed an XGBoost model on a self-hosted Triton Inference Server running on a 16-core machine.
      • +
      • 🚀 Fired requests and compared the outputs with our existing cloud-hosted setup.
      • +
      • 🚀 The results matched—perfectly.
      • +
      +

      That moment changed everything. We prepped a backup Triton setup on EKS, just in case our cloud provider couldn't allocate enough compute resources in time. Luckily, they did—but the seed was planted. +Then in October, just two weeks before MBS, we got an alarming response from our infrastructure team: +"Node availability may be an issue." +With no time to waste, we moved 30% of real-time ML traffic to our self-hosted Triton cluster. The results?

      +
        +
      • ✅ p99 latency dropped from 90–100ms to 30–40ms
      • +
      • ✅ Triton handled significantly higher throughput on fewer resources
      • +
      • ✅ No model changes were needed
      • +
      +

      MBS ran without a hitch, proving that self-hosted inference was the way forward.

      +

      Scaling Triton on GKE

      +

      This left us with two choices:

      +
        +
      • 1️⃣ Port models to a managed cloud inference service, investing time in learning a new deployment stack
      • +
      • 2️⃣ Scale our existing Triton setup on GKE, optimizing for cost and performance
      • +
      +

      We went with Option 2—and it slashed inference costs to 35% of what we previously paid, while giving us full control over scaling and optimizations.

      +

      Fixing the Cold Start Problem

      +

      As we onboarded more deep learning (DL) models, we hit a new bottleneck, new inference pods took 7–9 minutes to spin up.

      +

      After profiling, we found the culprits:

      +
        +
      • Triton’s base image—a massive 5GB
      • +
      • Model binaries—often 1GB+
      • +
      • Startup delay—mostly due to downloading and initializing these assets
      • +
      +

      To fix this, we built a lightweight Triton image, stripping unused components and shrinking the size to 900MB. This cut cold start times drastically, making auto-scaling faster and smoother.

      +

      Embedding Search: The Last Piece of the Puzzle

      +

      By mid-2023, most of our ML stack had gone real-time—except for Candidate Generation (CG), which still ran in batch mode. To truly power real-time recommendations, we needed an online embedding search system.

      +

      Choosing the Right Vector Database

      +

      We benchmarked three production-ready vector DBs across key parameters:

      +
        +
      • Milvus
      • +
      • Qdrant
      • +
      • Weaviate
      • +
      +

      After extensive POCs, Qdrant stood out for its:

      +
        +
      • ✅ Blazing-fast search latency on high-dimensional vectors
      • +
      • ✅ Efficient memory usage, crucial for in-memory workloads
      • +
      • ✅ Support for upserts and soft deletes, vital for Ads use cases
      • +
      • ✅ gRPC + REST APIs, making integration seamless
      • +
      • ✅ Powerful filtering, allowing fine-tuned retrieval (e.g., filtering Ads by category, active status, etc.)
      • +
      +

      At its core, Qdrant uses HNSW indexing, delivering both high recall and low-latency nearest-neighbor search—a perfect fit for our needs.

      +

      Embedding Freshness & Real-Time Updates

      +

      To ensure embeddings stayed up to date, we built a dual ingestion pipeline:

      +
        +
      • 📌 Daily Refresh: A bulk pipeline updated embeddings overnight
      • +
      • 📌 Real-Time Updates: Ads events triggered immediate upserts/deletes
      • +
      +

      This setup powered real-time "Similar Products" recommendations on the product page and became the foundation for Ads Candidate Generation, ensuring the right ads surfaced in milliseconds.

      +

      Skye

      +

      Final Takeaways: Scaling Smartly for Real-Time ML

      +
        +
      • 🚀 Self-hosted inference on Triton gave us lower cost, faster scaling, and better performance than managed services
      • +
      • 🚀 Building a custom Triton image reduced cold starts, improving responsiveness
      • +
      • 🚀 Qdrant-based embedding search enabled real-time personalization at scale
      • +
      • 🚀 Real-time updates for embeddings unlocked dynamic, up-to-date recommendations
      • +
      +

      By early 2024, Meesho’s ML stack had evolved into a fully real-time, scalable, and cost-efficient system, setting the foundation for even bigger leaps ahead.

      Building Meesho’s ML Platform: Lessons from the First-Gen System (Part 2)

      · 7 min read
      Bhawani Singh
      Architect @ Meesho
      Jigar Dave
      Lead Software Engineer @ Meesho
      Adarsha Das
      Senior Architect @ Meesho

      BharatMLStack +By late 2022, we had built something we were truly proud of—a real-time ML serving system with a DAG-based executor, a feature store, and an interaction store powering key ranking and personalization models. It was a major milestone, the culmination of months of effort from data scientists, ML engineers, and backend teams. Our system was live, and we were ready to push the boundaries of experimentation. +And it worked. Mostly. +But soon, cracks appeared. Every new model needed custom feature retrieval logic, DAGs became dense and unmanageable, and scaling turned into a constant firefight. Costs surged, and infra bottlenecks slowed experimentation. Our system worked, but it wasn’t built for scale. +This is the story of how we tackled these challenges—building Inferflow for seamless feature retrieval, optimizing real-time infra, and cutting costs while scaling to millions of QPS.

      +

      The Cost of Success

      +

      Every new Ranker model required its own feature set, often pulling from different entities. Each addition meant:

      +
        +
      • Adding new DAG nodes in IOP
      • +
      • Writing custom logic to fetch features from multiple sources (e.g., user, product, user × category)
      • +
      • Inferring intermediate features (e.g., extracting category from a product to fetch user × category data)
      • +
      • Optimizing I/O and dealing with the inevitable bugs
      • +
      +

      What began as clean DAGs soon turned into a tangled web of cross-dependent graphs. Every experimentation cycle meant new nodes, new dependencies, and slower iterations.

      +

      Scaling Pains (and Cassandra’s Limits)

      +

      At some point, we were hitting:

      +
        +
      • 250–300K reads/sec
      • +
      • 1M writes/sec (during lean hours)
      • +
      +

      All of this ran on Cassandra. While its distributed architecture had been proven in production, operating large-scale clusters came with considerable infrastructure overhead. Our proof-of-concept (POC) demonstrated throughput of around 100K ops/sec, but as we scaled further, the challenges grew. Ensuring node health, optimizing compaction, and maintaining storage balance became increasingly demanding. We also observed latency spikes under heavy load, alongside a sharp increase in total cost of ownership.

      +

      Interaction Store Woes

      +

      Our interaction store was another ticking time bomb:

      +
        +
      • 🚨 Clusters kept growing in size and cost
      • +
      • 🚨 Latency spikes became increasingly frequent
      • +
      • 🚨 The DMC proxy occasionally lost locality of nodes against shards, causing cross-node communication and degraded performance
      • +
      +

      Each time this happened, we had to manually rebalance shards just to restore stable latency, making operations unsustainable at scale.

      +

      Silver Linings

      +

      Despite the chaos, the system was live and delivering value:

      +
        +
      • Real-time infrastructure was in production
      • +
      • Costs dropped by 60–70% compared to offline personalization
      • +
      • New experiments rolled out faster and more successfully
      • +
      • User engagement metrics improved
      • +
      +

      It wasn’t perfect. It was far from easy. But it worked—and that counted for a lot.

      +

      Round Two: Solving the Top 2 Bottlenecks

      +

      With the first-gen system stretched to its limits, we stepped back. Conversations with data scientists and backend engineers revealed three recurring pain points:

      +
        +
      1. Coding feature retrieval logic for every new model was becoming unsustainable
      2. +
      3. ML scale was exploding—bringing rising infra costs with it
      4. +
      5. Real-time embedding search was the next big unlock
      6. +
      +

      We tackled them one by one—starting with the biggest pain point.

      +

      Problem 1: No-Code Feature Retrieval for Model Inference

      +

      We noticed a pattern: for personalized ranking, models needed features from:

      +
        +
      • ✅ Product
      • +
      • ✅ User
      • +
      • ✅ User × Category
      • +
      • ✅ Region, cohort, sub-category, etc.
      • +
      +

      A key insight emerged: Entities that contribute features for a model always map back to the context entities.

      +

      MP Dag

      +

      With this, we designed Inferflow, a graph-driven feature retrieval and model orchestration system:

      +
        +
      • 1️⃣ Inferflow takes a modelId and context IDs (e.g., userId, productIds)
      • +
      • 2️⃣ Loads a pre-defined feature retrieval graph from ZooKeeper
      • +
      • 3️⃣ Executes the graph to resolve entity relationships dynamically
      • +
      • 4️⃣ Outputs a 2D matrix of feature vectors
      • +
      +

      💡 The impact?

      +
        +
      • 🚀 No more custom feature retrieval code—just graph updates in config
      • +
      • 🚀 Feature consistency across experiments
      • +
      • 🚀 Faster iteration cycles for ranking, fraud detection, and beyond
      • +
      +

      Here’s a visual example that shows how this graph plays out during execution. We further extended the graph to call multiple models as needed: +MP matrix +We built Inferflow in GoLang, using gRPC and Proto3 serialization for efficiency.

      +

      Problem 2: Scaling Without Breaking the Bank

      +

      With more ML use cases coming online, we needed to cut costs without compromising performance. We focused on:

      +
        +
      • 🔹 Online Feature Store
      • +
      • 🔹 Interaction Store
      • +
      +

      Optimizing the Online Feature Store

      +

      Our costs were concentrated in:

      +
        +
      • 📌 Database (Cassandra)
      • +
      • 📌 Cache (Redis)
      • +
      • 📌 Running Pods (Java services)
      • +
      +

      1️⃣ Replacing Cassandra with ScyllaDB +As we hit the operational limits of large Cassandra clusters, we transitioned to ScyllaDB, which offered a seamless drop-in replacement without major code changes. The switch brought significant benefits:

      +
        +
      • Throughput: Matched or exceeded Cassandra's performance under identical workloads, even under high concurrency.
      • +
      • Latency: Achieved consistently lower P99 latencies due to ScyllaDB's shard-per-core architecture and better I/O utilization.
      • +
      • Cost Efficiency: Reduced infra footprint by ~70% through better CPU and memory efficiency, eliminating the need for over-provisioned nodes.
      • +
      +

      2️⃣ Finding the Right Cache +To reduce backend load and improve response times, we benchmarked multiple caching solutions—Memcached, KeyDB, and Dragonfly—under real production traffic patterns. Dragonfly stood out due to its robust architecture and operational simplicity:

      +
        +
      • Data Skew Handling: Efficiently managed extreme key hotness and uneven access patterns without performance degradation.
      • +
      • Throughput: Delivered consistently high throughput, even with large object sizes and concurrent access.
      • +
      • Ease of Adoption: Acted as a drop-in Redis replacement with full protocol compatibility—no changes needed in application code or client libraries.
      • +
      +

      3️⃣ Moving to GoLang for Cost-Efficient Serving +Java services were memory-heavy—so we rewrote core services in GoLang. The results?

      +

      ✅ Memory usage dropped by ~80% +✅ CPU utilization was significantly lower +✅ Faster, more efficient deployments

      +

      Optimizing the Interaction Store

      +

      We realized that we only need a user’s interaction data in Redis when they open the app. So, we implemented a tiered storage approach:

      +
        +
      • 📌 Cold Tier (ScyllaDB)—Stores click, order, wishlist events
      • +
      • 📌 Hot Tier (Redis)—Loads a user’s past interactions only when they open the app
      • +
      +

      Smart Offloading: We introduced an inactivity tracker to detect when a user session ends. At that point, Redis data was flushed back to Scylla, reducing unnecessary writes.

      +

      InteractionStore

      +

      Results

      +
        +
      • Online Feature Store hit 1M QPS for the first time during the 2023 Mega Blockbuster Sale—without breaking a sweat
      • +
      • Infra costs for Online Feature Store and Interaction Store dropped by ~60%
      • +
      +

      The Catch: Our ML Hosting Hit a Hard Limit

      +

      While planning for 2023 MBS, we ran into a critical scalability bottleneck:

      +
        +
      • ❌ Insufficient compute availability in our region for ML instances
      • +
      • ❌ Couldn’t provision enough nodes to handle real-time inference at scale
      • +
      +

      This forced us to rethink where and how we hosted our models. The existing setup was great for prototyping—but it wasn’t built to handle the bursty, high-QPS demands of real-world production workloads.

      +

      Conclusion: From Firefighting to Future-Proofing

      +

      What started as an ambitious experiment turned into a real-time ML infrastructure that powered millions of requests per second. We battled scaling pains, rethought feature retrieval with Inferflow, and rebuilt our infra stack for efficiency—driving down costs while improving experimentation velocity. +But new challenges emerged. Our infrastructure could now handle scale, but our ML model hosting setup hit a hard limit. With compute availability bottlenecks threatening real-time inference, we faced a critical decision: how do we make model serving as scalable and cost-efficient as the rest of our stack? That’s the next piece of the puzzle—and the story of Part 3.

      + + \ No newline at end of file diff --git a/docs/blog/tags/embedding-search/index.html b/docs/blog/tags/embedding-search/index.html new file mode 100644 index 00000000..de433cf7 --- /dev/null +++ b/docs/blog/tags/embedding-search/index.html @@ -0,0 +1,92 @@ + + + + + +One post tagged with "embedding-search" | BharatMLStack + + + + + + + + +

      One post tagged with "embedding-search"

      View All Tags

      Cracking the Code: Scaling Model Inference & Real-Time Embedding Search

      · 4 min read
      Aditya Kumar
      Lead Software Engineer @ Meesho
      Jaya Kumar
      Lead ML Engineer @ Meesho
      Adarsha Das
      Senior Architect @ Meesho

      BharatMLStack +By mid-2023, we had transformed our ML stack—building a real-time feature store, optimizing model retrieval, and fine-tuning ranking. But two critical gaps remained:

      +
        +
      • 🔹 Scaling model inference without hitting infrastructure roadblocks
      • +
      • 🔹 Moving embedding search from batch to real-time for candidate generation
      • +
      +

      Here’s how we tackled these last-mile challenges, broke free from infrastructure constraints, and built a cost-efficient, high-performance system.

      +

      Breaking Free from the Scalability Ceiling

      +

      The Model Serving Bottleneck—A Wake-Up Call

      +

      July 2023. With just months left for the Mega Blockbuster Sale (MBS), we noticed a serious issue—scaling our model-serving infrastructure was taking 10–15 minutes. In real-time ML, that’s an eternity. +In one of our war rooms, we ran a quick experiment:

      +
        +
      • 🚀 We deployed an XGBoost model on a self-hosted Triton Inference Server running on a 16-core machine.
      • +
      • 🚀 Fired requests and compared the outputs with our existing cloud-hosted setup.
      • +
      • 🚀 The results matched—perfectly.
      • +
      +

      That moment changed everything. We prepped a backup Triton setup on EKS, just in case our cloud provider couldn't allocate enough compute resources in time. Luckily, they did—but the seed was planted. +Then in October, just two weeks before MBS, we got an alarming response from our infrastructure team: +"Node availability may be an issue." +With no time to waste, we moved 30% of real-time ML traffic to our self-hosted Triton cluster. The results?

      +
        +
      • ✅ p99 latency dropped from 90–100ms to 30–40ms
      • +
      • ✅ Triton handled significantly higher throughput on fewer resources
      • +
      • ✅ No model changes were needed
      • +
      +

      MBS ran without a hitch, proving that self-hosted inference was the way forward.

      +

      Scaling Triton on GKE

      +

      This left us with two choices:

      +
        +
      • 1️⃣ Port models to a managed cloud inference service, investing time in learning a new deployment stack
      • +
      • 2️⃣ Scale our existing Triton setup on GKE, optimizing for cost and performance
      • +
      +

      We went with Option 2—and it slashed inference costs to 35% of what we previously paid, while giving us full control over scaling and optimizations.

      +

      Fixing the Cold Start Problem

      +

      As we onboarded more deep learning (DL) models, we hit a new bottleneck, new inference pods took 7–9 minutes to spin up.

      +

      After profiling, we found the culprits:

      +
        +
      • Triton’s base image—a massive 5GB
      • +
      • Model binaries—often 1GB+
      • +
      • Startup delay—mostly due to downloading and initializing these assets
      • +
      +

      To fix this, we built a lightweight Triton image, stripping unused components and shrinking the size to 900MB. This cut cold start times drastically, making auto-scaling faster and smoother.

      +

      Embedding Search: The Last Piece of the Puzzle

      +

      By mid-2023, most of our ML stack had gone real-time—except for Candidate Generation (CG), which still ran in batch mode. To truly power real-time recommendations, we needed an online embedding search system.

      +

      Choosing the Right Vector Database

      +

      We benchmarked three production-ready vector DBs across key parameters:

      +
        +
      • Milvus
      • +
      • Qdrant
      • +
      • Weaviate
      • +
      +

      After extensive POCs, Qdrant stood out for its:

      +
        +
      • ✅ Blazing-fast search latency on high-dimensional vectors
      • +
      • ✅ Efficient memory usage, crucial for in-memory workloads
      • +
      • ✅ Support for upserts and soft deletes, vital for Ads use cases
      • +
      • ✅ gRPC + REST APIs, making integration seamless
      • +
      • ✅ Powerful filtering, allowing fine-tuned retrieval (e.g., filtering Ads by category, active status, etc.)
      • +
      +

      At its core, Qdrant uses HNSW indexing, delivering both high recall and low-latency nearest-neighbor search—a perfect fit for our needs.

      +

      Embedding Freshness & Real-Time Updates

      +

      To ensure embeddings stayed up to date, we built a dual ingestion pipeline:

      +
        +
      • 📌 Daily Refresh: A bulk pipeline updated embeddings overnight
      • +
      • 📌 Real-Time Updates: Ads events triggered immediate upserts/deletes
      • +
      +

      This setup powered real-time "Similar Products" recommendations on the product page and became the foundation for Ads Candidate Generation, ensuring the right ads surfaced in milliseconds.

      +

      Skye

      +

      Final Takeaways: Scaling Smartly for Real-Time ML

      +
        +
      • 🚀 Self-hosted inference on Triton gave us lower cost, faster scaling, and better performance than managed services
      • +
      • 🚀 Building a custom Triton image reduced cold starts, improving responsiveness
      • +
      • 🚀 Qdrant-based embedding search enabled real-time personalization at scale
      • +
      • 🚀 Real-time updates for embeddings unlocked dynamic, up-to-date recommendations
      • +
      +

      By early 2024, Meesho’s ML stack had evolved into a fully real-time, scalable, and cost-efficient system, setting the foundation for even bigger leaps ahead.

      + + \ No newline at end of file diff --git a/docs/blog/tags/episodic-memory/index.html b/docs/blog/tags/episodic-memory/index.html new file mode 100644 index 00000000..8dd34da7 --- /dev/null +++ b/docs/blog/tags/episodic-memory/index.html @@ -0,0 +1,21 @@ + + + + + +One post tagged with "episodic-memory" | BharatMLStack + + + + + + + + +

      One post tagged with "episodic-memory"

      View All Tags

      Beyond Vector RAG: Building Agent Memory That Learns From Experience.

      · 12 min read
      Adarsha Das
      Senior Architect @ Meesho

      BharatMLStack +Agent memory has come a long way. Persistent context, vector retrieval, knowledge graphs — the building blocks are real and getting better fast.

      +

      But most of what we call "memory" today is still closer to search: chunk text, embed it, retrieve whatever looks similar at query time. That works well for recalling facts and preferences. It starts to break down when you need an agent to recall what happened last time, learn from a mistake, or avoid repeating a failed approach.

      +

      We are trying to experiment something different. An episodic memory system where a frozen LLM — same weights, no retraining — produces increasingly better decisions over time because the memory feeding it context is continuously evolving. +Then we tested it. The results were interesting.

      + + \ No newline at end of file diff --git a/docs/blog/tags/index.html b/docs/blog/tags/index.html index 01854bc7..076ffac2 100644 --- a/docs/blog/tags/index.html +++ b/docs/blog/tags/index.html @@ -4,14 +4,14 @@ Tags | BharatMLStack - - - + + + - + \ No newline at end of file diff --git a/docs/blog/tags/inferflow/index.html b/docs/blog/tags/inferflow/index.html new file mode 100644 index 00000000..f7cfe9b8 --- /dev/null +++ b/docs/blog/tags/inferflow/index.html @@ -0,0 +1,143 @@ + + + + + +One post tagged with "inferflow" | BharatMLStack + + + + + + + + +

      One post tagged with "inferflow"

      View All Tags

      Building Meesho’s ML Platform: Lessons from the First-Gen System (Part 2)

      · 7 min read
      Bhawani Singh
      Architect @ Meesho
      Jigar Dave
      Lead Software Engineer @ Meesho
      Adarsha Das
      Senior Architect @ Meesho

      BharatMLStack +By late 2022, we had built something we were truly proud of—a real-time ML serving system with a DAG-based executor, a feature store, and an interaction store powering key ranking and personalization models. It was a major milestone, the culmination of months of effort from data scientists, ML engineers, and backend teams. Our system was live, and we were ready to push the boundaries of experimentation. +And it worked. Mostly. +But soon, cracks appeared. Every new model needed custom feature retrieval logic, DAGs became dense and unmanageable, and scaling turned into a constant firefight. Costs surged, and infra bottlenecks slowed experimentation. Our system worked, but it wasn’t built for scale. +This is the story of how we tackled these challenges—building Inferflow for seamless feature retrieval, optimizing real-time infra, and cutting costs while scaling to millions of QPS.

      +

      The Cost of Success

      +

      Every new Ranker model required its own feature set, often pulling from different entities. Each addition meant:

      +
        +
      • Adding new DAG nodes in IOP
      • +
      • Writing custom logic to fetch features from multiple sources (e.g., user, product, user × category)
      • +
      • Inferring intermediate features (e.g., extracting category from a product to fetch user × category data)
      • +
      • Optimizing I/O and dealing with the inevitable bugs
      • +
      +

      What began as clean DAGs soon turned into a tangled web of cross-dependent graphs. Every experimentation cycle meant new nodes, new dependencies, and slower iterations.

      +

      Scaling Pains (and Cassandra’s Limits)

      +

      At some point, we were hitting:

      +
        +
      • 250–300K reads/sec
      • +
      • 1M writes/sec (during lean hours)
      • +
      +

      All of this ran on Cassandra. While its distributed architecture had been proven in production, operating large-scale clusters came with considerable infrastructure overhead. Our proof-of-concept (POC) demonstrated throughput of around 100K ops/sec, but as we scaled further, the challenges grew. Ensuring node health, optimizing compaction, and maintaining storage balance became increasingly demanding. We also observed latency spikes under heavy load, alongside a sharp increase in total cost of ownership.

      +

      Interaction Store Woes

      +

      Our interaction store was another ticking time bomb:

      +
        +
      • 🚨 Clusters kept growing in size and cost
      • +
      • 🚨 Latency spikes became increasingly frequent
      • +
      • 🚨 The DMC proxy occasionally lost locality of nodes against shards, causing cross-node communication and degraded performance
      • +
      +

      Each time this happened, we had to manually rebalance shards just to restore stable latency, making operations unsustainable at scale.

      +

      Silver Linings

      +

      Despite the chaos, the system was live and delivering value:

      +
        +
      • Real-time infrastructure was in production
      • +
      • Costs dropped by 60–70% compared to offline personalization
      • +
      • New experiments rolled out faster and more successfully
      • +
      • User engagement metrics improved
      • +
      +

      It wasn’t perfect. It was far from easy. But it worked—and that counted for a lot.

      +

      Round Two: Solving the Top 2 Bottlenecks

      +

      With the first-gen system stretched to its limits, we stepped back. Conversations with data scientists and backend engineers revealed three recurring pain points:

      +
        +
      1. Coding feature retrieval logic for every new model was becoming unsustainable
      2. +
      3. ML scale was exploding—bringing rising infra costs with it
      4. +
      5. Real-time embedding search was the next big unlock
      6. +
      +

      We tackled them one by one—starting with the biggest pain point.

      +

      Problem 1: No-Code Feature Retrieval for Model Inference

      +

      We noticed a pattern: for personalized ranking, models needed features from:

      +
        +
      • ✅ Product
      • +
      • ✅ User
      • +
      • ✅ User × Category
      • +
      • ✅ Region, cohort, sub-category, etc.
      • +
      +

      A key insight emerged: Entities that contribute features for a model always map back to the context entities.

      +

      MP Dag

      +

      With this, we designed Inferflow, a graph-driven feature retrieval and model orchestration system:

      +
        +
      • 1️⃣ Inferflow takes a modelId and context IDs (e.g., userId, productIds)
      • +
      • 2️⃣ Loads a pre-defined feature retrieval graph from ZooKeeper
      • +
      • 3️⃣ Executes the graph to resolve entity relationships dynamically
      • +
      • 4️⃣ Outputs a 2D matrix of feature vectors
      • +
      +

      💡 The impact?

      +
        +
      • 🚀 No more custom feature retrieval code—just graph updates in config
      • +
      • 🚀 Feature consistency across experiments
      • +
      • 🚀 Faster iteration cycles for ranking, fraud detection, and beyond
      • +
      +

      Here’s a visual example that shows how this graph plays out during execution. We further extended the graph to call multiple models as needed: +MP matrix +We built Inferflow in GoLang, using gRPC and Proto3 serialization for efficiency.

      +

      Problem 2: Scaling Without Breaking the Bank

      +

      With more ML use cases coming online, we needed to cut costs without compromising performance. We focused on:

      +
        +
      • 🔹 Online Feature Store
      • +
      • 🔹 Interaction Store
      • +
      +

      Optimizing the Online Feature Store

      +

      Our costs were concentrated in:

      +
        +
      • 📌 Database (Cassandra)
      • +
      • 📌 Cache (Redis)
      • +
      • 📌 Running Pods (Java services)
      • +
      +

      1️⃣ Replacing Cassandra with ScyllaDB +As we hit the operational limits of large Cassandra clusters, we transitioned to ScyllaDB, which offered a seamless drop-in replacement without major code changes. The switch brought significant benefits:

      +
        +
      • Throughput: Matched or exceeded Cassandra's performance under identical workloads, even under high concurrency.
      • +
      • Latency: Achieved consistently lower P99 latencies due to ScyllaDB's shard-per-core architecture and better I/O utilization.
      • +
      • Cost Efficiency: Reduced infra footprint by ~70% through better CPU and memory efficiency, eliminating the need for over-provisioned nodes.
      • +
      +

      2️⃣ Finding the Right Cache +To reduce backend load and improve response times, we benchmarked multiple caching solutions—Memcached, KeyDB, and Dragonfly—under real production traffic patterns. Dragonfly stood out due to its robust architecture and operational simplicity:

      +
        +
      • Data Skew Handling: Efficiently managed extreme key hotness and uneven access patterns without performance degradation.
      • +
      • Throughput: Delivered consistently high throughput, even with large object sizes and concurrent access.
      • +
      • Ease of Adoption: Acted as a drop-in Redis replacement with full protocol compatibility—no changes needed in application code or client libraries.
      • +
      +

      3️⃣ Moving to GoLang for Cost-Efficient Serving +Java services were memory-heavy—so we rewrote core services in GoLang. The results?

      +

      ✅ Memory usage dropped by ~80% +✅ CPU utilization was significantly lower +✅ Faster, more efficient deployments

      +

      Optimizing the Interaction Store

      +

      We realized that we only need a user’s interaction data in Redis when they open the app. So, we implemented a tiered storage approach:

      +
        +
      • 📌 Cold Tier (ScyllaDB)—Stores click, order, wishlist events
      • +
      • 📌 Hot Tier (Redis)—Loads a user’s past interactions only when they open the app
      • +
      +

      Smart Offloading: We introduced an inactivity tracker to detect when a user session ends. At that point, Redis data was flushed back to Scylla, reducing unnecessary writes.

      +

      InteractionStore

      +

      Results

      +
        +
      • Online Feature Store hit 1M QPS for the first time during the 2023 Mega Blockbuster Sale—without breaking a sweat
      • +
      • Infra costs for Online Feature Store and Interaction Store dropped by ~60%
      • +
      +

      The Catch: Our ML Hosting Hit a Hard Limit

      +

      While planning for 2023 MBS, we ran into a critical scalability bottleneck:

      +
        +
      • ❌ Insufficient compute availability in our region for ML instances
      • +
      • ❌ Couldn’t provision enough nodes to handle real-time inference at scale
      • +
      +

      This forced us to rethink where and how we hosted our models. The existing setup was great for prototyping—but it wasn’t built to handle the bursty, high-QPS demands of real-world production workloads.

      +

      Conclusion: From Firefighting to Future-Proofing

      +

      What started as an ambitious experiment turned into a real-time ML infrastructure that powered millions of requests per second. We battled scaling pains, rethought feature retrieval with Inferflow, and rebuilt our infra stack for efficiency—driving down costs while improving experimentation velocity. +But new challenges emerged. Our infrastructure could now handle scale, but our ML model hosting setup hit a hard limit. With compute availability bottlenecks threatening real-time inference, we faced a critical decision: how do we make model serving as scalable and cost-efficient as the rest of our stack? That’s the next piece of the puzzle—and the story of Part 3.

      + + \ No newline at end of file diff --git a/docs/blog/tags/interaction-store/index.html b/docs/blog/tags/interaction-store/index.html index 363b22a3..58804adf 100644 --- a/docs/blog/tags/interaction-store/index.html +++ b/docs/blog/tags/interaction-store/index.html @@ -3,18 +3,143 @@ -One post tagged with "interaction-store" | BharatMLStack - - - +2 posts tagged with "interaction-store" | BharatMLStack + + + -

      One post tagged with "interaction-store"

      View All Tags

      Building Meesho’s ML Platform: From Chaos to Cutting-Edge (Part 1)

      · 11 min read
      Adarsha Das
      Senior Architect @ Meesho
      Aditya Kumar
      SDE-III @ Meesho
      Bhawani Singh
      SDE-IV @ Meesho
      Jigar Dave
      SDE-IV @ Meesho

      BharatMLStack

      -

      The Genesis: How a Friday Night Roast Sparked Meesho’s ML Platform

      -

      It all started in early 2022, over a casual Friday evening catch-up. Like many great origin stories, this one began with friendly banter between a group of backend engineers and data scientists. As the conversations unfolded, so did the roasting—until one remark hit a little too close to home:

      +

      2 posts tagged with "interaction-store"

      View All Tags

      Building Meesho’s ML Platform: Lessons from the First-Gen System (Part 2)

      · 7 min read
      Bhawani Singh
      Architect @ Meesho
      Jigar Dave
      Lead Software Engineer @ Meesho
      Adarsha Das
      Senior Architect @ Meesho

      BharatMLStack +By late 2022, we had built something we were truly proud of—a real-time ML serving system with a DAG-based executor, a feature store, and an interaction store powering key ranking and personalization models. It was a major milestone, the culmination of months of effort from data scientists, ML engineers, and backend teams. Our system was live, and we were ready to push the boundaries of experimentation. +And it worked. Mostly. +But soon, cracks appeared. Every new model needed custom feature retrieval logic, DAGs became dense and unmanageable, and scaling turned into a constant firefight. Costs surged, and infra bottlenecks slowed experimentation. Our system worked, but it wasn’t built for scale. +This is the story of how we tackled these challenges—building Inferflow for seamless feature retrieval, optimizing real-time infra, and cutting costs while scaling to millions of QPS.

      +

      The Cost of Success

      +

      Every new Ranker model required its own feature set, often pulling from different entities. Each addition meant:

      +
        +
      • Adding new DAG nodes in IOP
      • +
      • Writing custom logic to fetch features from multiple sources (e.g., user, product, user × category)
      • +
      • Inferring intermediate features (e.g., extracting category from a product to fetch user × category data)
      • +
      • Optimizing I/O and dealing with the inevitable bugs
      • +
      +

      What began as clean DAGs soon turned into a tangled web of cross-dependent graphs. Every experimentation cycle meant new nodes, new dependencies, and slower iterations.

      +

      Scaling Pains (and Cassandra’s Limits)

      +

      At some point, we were hitting:

      +
        +
      • 250–300K reads/sec
      • +
      • 1M writes/sec (during lean hours)
      • +
      +

      All of this ran on Cassandra. While its distributed architecture had been proven in production, operating large-scale clusters came with considerable infrastructure overhead. Our proof-of-concept (POC) demonstrated throughput of around 100K ops/sec, but as we scaled further, the challenges grew. Ensuring node health, optimizing compaction, and maintaining storage balance became increasingly demanding. We also observed latency spikes under heavy load, alongside a sharp increase in total cost of ownership.

      +

      Interaction Store Woes

      +

      Our interaction store was another ticking time bomb:

      +
        +
      • 🚨 Clusters kept growing in size and cost
      • +
      • 🚨 Latency spikes became increasingly frequent
      • +
      • 🚨 The DMC proxy occasionally lost locality of nodes against shards, causing cross-node communication and degraded performance
      • +
      +

      Each time this happened, we had to manually rebalance shards just to restore stable latency, making operations unsustainable at scale.

      +

      Silver Linings

      +

      Despite the chaos, the system was live and delivering value:

      +
        +
      • Real-time infrastructure was in production
      • +
      • Costs dropped by 60–70% compared to offline personalization
      • +
      • New experiments rolled out faster and more successfully
      • +
      • User engagement metrics improved
      • +
      +

      It wasn’t perfect. It was far from easy. But it worked—and that counted for a lot.

      +

      Round Two: Solving the Top 2 Bottlenecks

      +

      With the first-gen system stretched to its limits, we stepped back. Conversations with data scientists and backend engineers revealed three recurring pain points:

      +
        +
      1. Coding feature retrieval logic for every new model was becoming unsustainable
      2. +
      3. ML scale was exploding—bringing rising infra costs with it
      4. +
      5. Real-time embedding search was the next big unlock
      6. +
      +

      We tackled them one by one—starting with the biggest pain point.

      +

      Problem 1: No-Code Feature Retrieval for Model Inference

      +

      We noticed a pattern: for personalized ranking, models needed features from:

      +
        +
      • ✅ Product
      • +
      • ✅ User
      • +
      • ✅ User × Category
      • +
      • ✅ Region, cohort, sub-category, etc.
      • +
      +

      A key insight emerged: Entities that contribute features for a model always map back to the context entities.

      +

      MP Dag

      +

      With this, we designed Inferflow, a graph-driven feature retrieval and model orchestration system:

      +
        +
      • 1️⃣ Inferflow takes a modelId and context IDs (e.g., userId, productIds)
      • +
      • 2️⃣ Loads a pre-defined feature retrieval graph from ZooKeeper
      • +
      • 3️⃣ Executes the graph to resolve entity relationships dynamically
      • +
      • 4️⃣ Outputs a 2D matrix of feature vectors
      • +
      +

      💡 The impact?

      +
        +
      • 🚀 No more custom feature retrieval code—just graph updates in config
      • +
      • 🚀 Feature consistency across experiments
      • +
      • 🚀 Faster iteration cycles for ranking, fraud detection, and beyond
      • +
      +

      Here’s a visual example that shows how this graph plays out during execution. We further extended the graph to call multiple models as needed: +MP matrix +We built Inferflow in GoLang, using gRPC and Proto3 serialization for efficiency.

      +

      Problem 2: Scaling Without Breaking the Bank

      +

      With more ML use cases coming online, we needed to cut costs without compromising performance. We focused on:

      +
        +
      • 🔹 Online Feature Store
      • +
      • 🔹 Interaction Store
      • +
      +

      Optimizing the Online Feature Store

      +

      Our costs were concentrated in:

      +
        +
      • 📌 Database (Cassandra)
      • +
      • 📌 Cache (Redis)
      • +
      • 📌 Running Pods (Java services)
      • +
      +

      1️⃣ Replacing Cassandra with ScyllaDB +As we hit the operational limits of large Cassandra clusters, we transitioned to ScyllaDB, which offered a seamless drop-in replacement without major code changes. The switch brought significant benefits:

      +
        +
      • Throughput: Matched or exceeded Cassandra's performance under identical workloads, even under high concurrency.
      • +
      • Latency: Achieved consistently lower P99 latencies due to ScyllaDB's shard-per-core architecture and better I/O utilization.
      • +
      • Cost Efficiency: Reduced infra footprint by ~70% through better CPU and memory efficiency, eliminating the need for over-provisioned nodes.
      • +
      +

      2️⃣ Finding the Right Cache +To reduce backend load and improve response times, we benchmarked multiple caching solutions—Memcached, KeyDB, and Dragonfly—under real production traffic patterns. Dragonfly stood out due to its robust architecture and operational simplicity:

      +
        +
      • Data Skew Handling: Efficiently managed extreme key hotness and uneven access patterns without performance degradation.
      • +
      • Throughput: Delivered consistently high throughput, even with large object sizes and concurrent access.
      • +
      • Ease of Adoption: Acted as a drop-in Redis replacement with full protocol compatibility—no changes needed in application code or client libraries.
      • +
      +

      3️⃣ Moving to GoLang for Cost-Efficient Serving +Java services were memory-heavy—so we rewrote core services in GoLang. The results?

      +

      ✅ Memory usage dropped by ~80% +✅ CPU utilization was significantly lower +✅ Faster, more efficient deployments

      +

      Optimizing the Interaction Store

      +

      We realized that we only need a user’s interaction data in Redis when they open the app. So, we implemented a tiered storage approach:

      +
        +
      • 📌 Cold Tier (ScyllaDB)—Stores click, order, wishlist events
      • +
      • 📌 Hot Tier (Redis)—Loads a user’s past interactions only when they open the app
      • +
      +

      Smart Offloading: We introduced an inactivity tracker to detect when a user session ends. At that point, Redis data was flushed back to Scylla, reducing unnecessary writes.

      +

      InteractionStore

      +

      Results

      +
        +
      • Online Feature Store hit 1M QPS for the first time during the 2023 Mega Blockbuster Sale—without breaking a sweat
      • +
      • Infra costs for Online Feature Store and Interaction Store dropped by ~60%
      • +
      +

      The Catch: Our ML Hosting Hit a Hard Limit

      +

      While planning for 2023 MBS, we ran into a critical scalability bottleneck:

      +
        +
      • ❌ Insufficient compute availability in our region for ML instances
      • +
      • ❌ Couldn’t provision enough nodes to handle real-time inference at scale
      • +
      +

      This forced us to rethink where and how we hosted our models. The existing setup was great for prototyping—but it wasn’t built to handle the bursty, high-QPS demands of real-world production workloads.

      +

      Conclusion: From Firefighting to Future-Proofing

      +

      What started as an ambitious experiment turned into a real-time ML infrastructure that powered millions of requests per second. We battled scaling pains, rethought feature retrieval with Inferflow, and rebuilt our infra stack for efficiency—driving down costs while improving experimentation velocity. +But new challenges emerged. Our infrastructure could now handle scale, but our ML model hosting setup hit a hard limit. With compute availability bottlenecks threatening real-time inference, we faced a critical decision: how do we make model serving as scalable and cost-efficient as the rest of our stack? That’s the next piece of the puzzle—and the story of Part 3.

      Building Meesho’s ML Platform: From Chaos to Cutting-Edge (Part 1)

      · 11 min read
      Adarsha Das
      Senior Architect @ Meesho
      Aditya Kumar
      Lead Software Engineer @ Meesho
      Bhawani Singh
      Architect @ Meesho
      Jigar Dave
      Lead Software Engineer @ Meesho

      BharatMLStack +It all started in early 2022, over a casual Friday evening catch-up. Like many great origin stories, this one began with friendly banter between a group of backend engineers and data scientists. As the conversations unfolded, so did the roasting—until one remark hit a little too close to home:

      "Why are we still crunching data for Monthly Active Users (MAU) when the next day it’s all about Daily Active Users (DAU)?"

      The laughter died down, and the question lingered. When we regrouped on Monday—clear-headed and slightly reflective—we decided to dig into the numbers. What they discovered was quite revealing: a large portion of compute resources wasn’t being put to good use. Much of the system’s effort was spent supporting users who weren’t actively engaging, and even for new users, the experience wasn’t optimized to make a meaningful impact.

      @@ -119,7 +244,7 @@

      feature_1_value,feature_2_value,feature_3_value;expiry_ts

      +
      feature_1_value,feature_2_value,feature_3_value;expiry_ts

      This format allowed:

      • Consistent writes and reads at the group level
      • @@ -182,7 +307,7 @@

        Why Redis?

        Storage Structure

        Each user’s interactions were stored using a composite key format, uniquely identifying the user and interaction type. This structure allowed efficient organization and quick retrieval of recent activity for recommendation generation:

        -
        userId_eventType → ZSET[...(pid, ts)...]
        +
        userId_eventType → ZSET[...(pid, ts)...]

        Within each ZSET:

      +

      This foundational work laid the path for a reliable and scalable real-time feature serving layer.

      \ No newline at end of file diff --git a/docs/blog/tags/llm/index.html b/docs/blog/tags/llm/index.html new file mode 100644 index 00000000..81eeea46 --- /dev/null +++ b/docs/blog/tags/llm/index.html @@ -0,0 +1,272 @@ + + + + + +3 posts tagged with "llm" | BharatMLStack + + + + + + + + +

      3 posts tagged with "llm"

      View All Tags

      Beyond Vector RAG: Building Agent Memory That Learns From Experience.

      · 12 min read
      Adarsha Das
      Senior Architect @ Meesho

      BharatMLStack +Agent memory has come a long way. Persistent context, vector retrieval, knowledge graphs — the building blocks are real and getting better fast.

      +

      But most of what we call "memory" today is still closer to search: chunk text, embed it, retrieve whatever looks similar at query time. That works well for recalling facts and preferences. It starts to break down when you need an agent to recall what happened last time, learn from a mistake, or avoid repeating a failed approach.

      +

      We are trying to experiment something different. An episodic memory system where a frozen LLM — same weights, no retraining — produces increasingly better decisions over time because the memory feeding it context is continuously evolving. +Then we tested it. The results were interesting.

      LLM Inference Optimization Techniques: Engineering Sub-Second Latency at Scale

      · 5 min read
      Jaya Kumar
      Lead ML Engineer @ Meesho

      BharatMLStack +Raw execution of Large Language Models is inherently expensive and memory-intensive. To achieve sub-second latency and high throughput, we implement a multi-layered optimization strategy that targets the entire inference stack—from memory management to kernel execution.

      +

      1. Advanced Memory Management: Paged & Prefix KV Caching

      +

      The most significant bottleneck in LLM inference is not always compute, but memory bandwidth—specifically managing the Key-Value (KV) cache.

      +

      Paged KV caching

      +

      Standard caching suffers from fragmentation. We use Paged KV caching, which operates similarly to an operating system's virtual memory: the KV cache is divided into non-contiguous blocks. This lets us serve larger batch sizes without running out of memory.

      +

      KV cache quantization

      +

      To further maximize available memory, we implement KV cache quantization (e.g., FP8). By compressing stored attention keys and values from 16-bit to 8-bit, we nearly double the effective context window capacity of the GPU, allowing longer conversations or larger batches without materially degrading quality.

      +

      Prefix caching (the "voice bot" optimizer)

      +

      For use cases like GenAI voice bots where the system prompt (e.g., "You are a helpful assistant...") is static across thousands of requests, we enable prefix caching.

      +
        +
      • Impact: By reusing pre-computed KV states for common prefixes, we achieve a cache hit rate of ~90%. This reduces Time To First Token (TTFT) by skipping redundant computation of the system prompt.
      • +
      +

      2. Aggressive Quantization (INT4 AWQ & FP8)

      +

      Running models in their native 16-bit precision (BF16) restricts maximum batch size and throughput. We use quantization to shrink model weights without sacrificing accuracy.

      +

      INT4 AWQ (Activation-aware Weight Quantization)

      +

      For the Llama 3 family, we use AWQ to compress weights to 4 bits. This reduces model size by ~75%, allowing larger models to fit into L4 GPU memory and significantly improving token generation speed.

      +

      FP8 precision

      +

      For NVIDIA Hopper (H100) architectures, we are exploring FP8 quantization, leveraging native FP8 tensor cores to accelerate matrix multiplications while maintaining a higher dynamic range than integer quantization.

      +
        +
      • Verification: We validate quantized models by comparing dot-product similarity of embeddings against the FP16 baseline, consistently achieving >99% similarity.
      • +
      +

      3. Kernel Fusion & Custom Plugins

      +

      To minimize overhead from launching thousands of small GPU operations, we fuse them into monolithic kernels using NVIDIA TensorRT plugins.

      +
        +
      • Flash attention & FMHA: We enable Fused Multi-Head Attention (FMHA) combined with flash attention to reduce memory reads/writes.
      • +
      • GEMM plugins: We use specialized GEMM plugins to accelerate transformer linear layers.
      • +
      • Removing input padding: Instead of padding short sequences to match the longest, we remove input padding so the GPU processes only valid tokens.
      • +
      +

      4. Inflight (Continuous) Batching

      +

      Traditional static batching waits for all requests in a batch to finish before returning results—so one long response delays everyone else.

      +

      We implement inflight batching: as soon as one request completes, its slot is freed and filled by a new request from the queue. This keeps GPUs saturated and decouples latency of short queries from long ones.

      +

      5. Parallelism Strategies: Scaling Beyond One GPU

      +

      For large models (e.g., 70B+ parameters) that cannot fit into the VRAM of a single GPU, we use parallelism strategies.

      +
        +
      • Tensor parallelism (TP): Split weight matrices across multiple GPUs (e.g., 4× L4 or 8× A100). Each GPU computes a shard and outputs are reduced at every layer.
      • +
      • Pipeline parallelism (PP): Split model layers across GPUs to pipeline compute (e.g., while one GPU computes later layers for Request A, another starts early layers for Request B).
      • +
      +

      6. Speculative Decoding

      +

      To reduce inter-token latency (ITL), we explore speculative decoding.

      +
        +
      • Mechanism: A smaller, faster "draft" model speculatively generates a short token sequence (e.g., 5 tokens).
      • +
      • Verification: The larger target model verifies those tokens in one parallel forward pass. If correct, we effectively generate multiple tokens per large-model step; if not, we discard and regenerate. This is effective for predictable text, improving perceived generation speed.
      • +
      +

      Few Benchmarks

      +

      Below are a couple of representative use cases and performance numbers.

      +

      Search query rewriting

      +
        +
      • LLM: Fine-tuned llama-3.2-1B
      • +
      • Input & output token length: ~10–20
      • +
      • Response type: Non-streaming
      • +
      +
      Inference runtimeHardwareMax requests/secMax p99 latency
      TensorRT-LLM4 × L4 GPUs (multi-GPU)100095 ms
      TensorRT-LLM1 × A100 40 GB GPU100069 ms
      +

      Voice bot query

      +
        +
      • LLM: Llama-3.1-8B
      • +
      • Input token length: ~1900–2000
      • +
      • Output token length: ~200
      • +
      • Response type: Streaming
      • +
      +
      Inference runtimeConcurrencyp99 TTFT (ms)p99 ITL (ms)Token throughput (tokens/sec)Request throughput (req/sec)Hardware
      TensorRT-LLM136.2722.7845.660.23L4
      TensorRT-LLM249.8123.2189.370.45L4
      TensorRT-LLM455.3336.62153.390.78L4
      TensorRT-LLM866.539.11279.881.47L4
      TensorRT-LLM16131.830.39547.82.77L4
      TensorRT-LLM32277.2248.02925.74.78L4
      TensorRT-LLM64498.5271.621,164.406.2L4
      TensorRT-LLM128677.31120.371,445.187.69L4
      TensorRT-LLM2561,926.31216.881,600.818.52L4
      TensorRT-LLM121.179.24130.050.68A100
      TensorRT-LLM225.789.21264.51.35A100
      TensorRT-LLM428.5210.99437.692.27A100
      TensorRT-LLM834.412.61760.493.96A100
      TensorRT-LLM1668.0314.321,343.807.01A100
      TensorRT-LLM32185.9616.822,287.3011.92A100
      TensorRT-LLM64136.8721.173,625.2218.89A100
      TensorRT-LLM128463.7834.154,456.5123.24A100
      TensorRT-LLM256890.1259.185,188.2427.05A100
      +

      Conclusion

      +

      High-performance LLM inference is fundamentally a systems engineering problem: memory efficiency, kernel execution, batching strategy, and parallelism determine real-world latency and throughput. Techniques such as paged KV caching, aggressive quantization, kernel fusion, and inflight batching improve GPU utilization while reducing latency and memory pressure.

      +

      These optimizations enable the platform to deliver sub-second responses, sustain high concurrency, and efficiently serve both lightweight and long-context workloads. By continuously optimizing across the full inference stack, we keep LLM serving scalable, cost-efficient, and production-ready for real-time AI applications.

      Designing a Production-Grade LLM Inference Platform: From Model Weights to Scalable GPU Serving

      · 14 min read
      Jaya Kumar
      Lead ML Engineer @ Meesho

      BharatMLStack +Serving large language models in production introduces new challenges across infrastructure, performance optimization, and operational lifecycle management. The LLM Inference Platform addresses these challenges by providing a unified system for deploying and managing open-source and fine-tuned LLMs at scale.

      +

      The platform implements a complete LLMOps lifecycle — from model registration and automated compilation to deployment, runtime optimization, and monitoring. Designed as a self-service environment, users can onboard models directly from open repositories such as Hugging Face or upload custom fine-tuned models, and deploy them using a single-click workflow with no manual infrastructure or configuration steps required.

      +

      In addition to fully automated deployment, the platform allows users to select and apply custom inference optimization techniques — such as quantization strategies, batching configurations, and runtime-specific performance enhancements — enabling teams to balance latency, throughput, and cost based on their use case. The goal is to reduce operational friction while enabling high-performance, production-grade LLM inference.

      +

      Why LLM Inference Is not just bigger ML model serving

      +

      Large language model (LLM) inference introduces a fundamentally different set of challenges compared to traditional machine learning inference. While classical ML models typically perform a single forward pass to produce a fixed prediction, LLMs operate as autoregressive systems, generating outputs token by token based on previously generated context. This difference dramatically changes how inference systems must be designed, optimized, and scaled.

      +

      Autoregressive Generation and Sequential Computation:

      +

      Unlike traditional models such as classifiers or recommenders — where inference cost is relatively constant — LLMs generate responses incrementally. Each new token depends on all previously generated tokens, making inference inherently sequential and dynamic. This means latency and compute requirements vary significantly depending on prompt length and output size, introducing complexity in scheduling and resource allocation. +Because tokens cannot be generated fully in parallel during decoding, GPUs may become underutilized without specialized batching and scheduling strategies. This has led to the development of dedicated LLM inference engines optimized for token-level execution.

      +

      Prefill and Decode Phases:

      +

      LLM inference typically consists of two distinct stages:

      +
        +
      • Prefill phase — the model processes the input prompt and builds internal representations. This stage is compute-heavy and highly parallelizable.
      • +
      • Decode phase — the model generates tokens sequentially, predicting one token at a time using previously generated context.
      • +
      +

      The decode stage often becomes memory-bound rather than compute-bound, which creates new performance bottlenecks compared to traditional ML workloads.

      +

      Context Management and KV Caching:

      +

      Another fundamental difference lies in how LLMs maintain context. Transformer-based models rely on attention mechanisms that require access to past token representations. To avoid recomputing these representations repeatedly, inference engines use key-value (KV) caching, which stores intermediate activations from previous tokens. +KV caching significantly improves performance by eliminating redundant computation, but it introduces new challenges:

      +
        +
      • Memory consumption grows with sequence length and batch size
      • +
      • GPU memory becomes a critical bottleneck
      • +
      • Efficient memory management becomes essential for scaling concurrent requests
      • +
      +

      This tradeoff between compute efficiency and memory usage is unique to LLM inference workloads.

      +

      Dynamic and Irregular Workloads:

      +

      Traditional ML inference typically operates on fixed-size inputs with predictable latency. In contrast, LLM requests vary widely in prompt length, output length, and runtime behavior. As a result:

      +
        +
      • Batch sizes must be dynamic rather than static
      • +
      • Requests may enter and leave batches asynchronously
      • +
      • Scheduling systems must continuously rebalance workloads to maximize GPU utilization
      • +
      +

      These characteristics require specialized serving architectures that differ significantly from standard ML serving pipelines.

      +

      Streaming and User Experience Constraints:

      +

      Another distinguishing factor is the expectation of real-time streaming responses. Instead of returning a single output, LLM systems often stream tokens to users as they are generated. +Because of these differences — sequential generation, growing memory requirements, dynamic workloads, and streaming constraints — LLM inference cannot be treated as a simple extension of existing ML serving systems. Production platforms must incorporate specialized runtime engines, advanced optimization techniques, and observability tailored specifically to LLM workloads.

      +

      LLMOps: High-Level Architecture

      +

      LLM Architecture

      +

      The LLM Inference Framework is designed as a fully automated, end-to-end system for deploying and operating open-source and fine-tuned large language models at scale. The architecture abstracts the complexity of model optimization, hardware selection, deployment, and runtime management into a unified workflow that enables users to move from raw model weights to production-ready inference endpoints with minimal manual intervention.

      +

      Our LLM Inference Framework is architected not just as a serving engine, but as a complete lifecycle management system. As illustrated in the high-level design below, the platform automates the journey of a model through seven distinct stages, ensuring reproducibility, performance, and scalability.

      +
        +
      1. +

        Onboarding & Registration (The Source of Truth)

        +

        The lifecycle begins with the Data Scientist or engineer.

        +
          +
        • Model Ingestion: Users onboard models—whether open-source (Hugging Face, NeMo) or internally fine-tuned—via the Truffle Box SDK/UI.
        • +
        • LLM + Prompt Registry: Unlike traditional systems that only track model weights, our registry is a unified control plane. It stores both the Model Artifacts and the Prompt Templates. This allows Data Scientists to register and version-control prompts (e.g., "customer_support_v2") independently of the application code.
        • +
        +
      2. +
      3. +

        The "Black Box" Build Engine

        +

        Once a model is registered, the Automated LLM Compiler + Quantizer Module kicks off a background job on ephemeral GPU resources.

        +
          +
        • Transformation: The raw model is converted into a TRT-LLM Checkpoint.
        • +
        • Quantization: The system automatically applies quantization algorithms (like INT4 AWQ or FP8) to reduce memory footprint.
        • +
        • Engine Building: Finally, it compiles a highly optimized TRT Engine specifically tuned for the target hardware.
        • +
        +
      4. +
      5. +

        Intelligent Profiling & Validation

        +

        Before deployment, the new engine passes through the Hardware & Inference Runtime Profiler.

        +
          +
        • Benchmarking: This module empirically tests the engine against various hardware configurations (L4 vs. A100) and runtimes (TRT-LLM vs. vLLM).
        • +
        • Optimization: It recommends the optimal configuration that meets latency SLAs (Time-To-First-Token) while minimizing cost.
        • +
        +
      6. +
      7. +

        Smart Artifact Generation & Distribution

        +

        To solve the Kubernetes "Cold Start" problem, the LLM Serving Artifacts Generation module packages the model using a bifurcated strategy:

        +
          +
        • Standard Models: Artifacts are uploaded to Cloud Storage (GCS) and downloaded by pods at startup.
        • +
        • Very Large Models: For massive models (>8GB) where network downloads are too slow, the system pre-caches the model onto Secondary Boot Disks. These disks are attached directly to new GPU nodes during autoscaling, eliminating download wait times.
        • +
        +
      8. +
      9. +

        Image Streaming & Deployment

        +

        Simultaneously, the inference runtime container images are pulled from the Artifact Registry.

        +
          +
        • Image Streaming: We utilize container image streaming to allow pods to start initializing while the massive Triton/Dynamo container layers are still downloading, further shaving seconds off the startup time. link
        • +
        +
      10. +
      11. +

        The Inference Runtime (Kubernetes)

        +

        The workload lands on Kubernetes with Autoscaling.

        +
          +
        • Dynamic Backends: Depending on the profile generated in Stage 3, the pod initializes either TensorRT-LLM (for throughput) or vLLM (for flexibility), or spins up a Dynamo worker for distributed inference.
        • +
        • Data Loading: The pod either downloads the model from Cloud Storage or mounts the pre-warmed Secondary Boot Disk ("Pull from Disk").
        • +
        +
      12. +
      13. +

        Client Interaction & Observability

        +

        Finally, the LLM Inference Client executes the request.

        +
          +
        • Prompt Injection: The client pulls the specific prompt template ID from the Registry, ensuring the exact versioned instructions are used.
        • +
        • Streaming Response: The request is sent via gRPC, and tokens are streamed back to the user in real-time.
        • +
        +
      14. +
      15. +

        Observability: Monitoring the Pulse of GenAI

        +

        In traditional microservices, success is measured by CPU utilization and request latency (p99). For Large Language Models, these metrics are insufficient. A user doesn't care if the GPU is at 80% utilization; they care about how fast the first word appears and how smoothly the rest of the sentence follows.

        +

        To capture the true user experience, our platform instrumentation focuses on three critical LLM-specific metrics:

        +
          +
        1. +

          Time to First Token (TTFT)

          +
            +
          • Definition: TTFT measures the time elapsed from the moment a request is received until the very first token is generated and streamed back to the user.
          • +
          • Why it matters: This represents the "Prefill Phase" latency—the time the model takes to process the input prompt and load weights. A high TTFT makes the application feel unresponsive or "hung."
          • +
          • Optimization: We closely monitor TTFT to ensure our Prefix Caching is effective (aiming for high cache hitrates), which drastically lowers this metric by skipping redundant prompt processing.
          • +
          +
        2. +
        3. +

          Inter-Token Latency (ITL)

          +
            +
          • Definition: ITL measures the average time interval between the generation of consecutive tokens during the "Decode Phase".
          • +
          • Why it matters: This defines the "perceived speed" of reading. Even if the first token is fast (low TTFT), high ITL makes the text generation look "jerky" or slow to the user.
          • +
          • Benchmarks: In our testing with Llama 3.1, we track p99 ITL to ensure it stays below human reading speeds to maintain a natural conversational flow.
          • +
          +
        4. +
        5. +

          Token Throughput vs. Request Throughput

          +
            +
          • We distinguish between two types of throughput to balance system efficiency with user load:
          • +
          • Token Throughput (tokens/sec): The total number of tokens generated across all concurrent requests. This measures the raw compute efficiency of the GPU and the effectiveness of batching.
          • +
          • Request Throughput (req/sec): The number of distinct user queries served per second. We use this to determine autoscaling thresholds, ensuring we scale out before the queue depth impacts ITL.
          • +
          +
        6. +
        7. +

          The Monitoring Stack

          +
            +
          • Real-time Dashboards: We utilize Grafana to visualize these streaming metrics in real-time, allowing on-call engineers to spot "slow generation" incidents that generic "500 error" alerts would miss.
          • +
          • Request Tracing: Since Triton Inference Server does not log request payloads by default, we integrate a Helix Client to asynchronously publish request logs to Log Tables. This allows us to trace a specific "slow" request back to its prompt to understand if a complex input caused the latency spike.
          • +
          +
        8. +
        +
      16. +
      +

      Supported Inference backends (TensorRT LLM, Dynamo & vLLM)

      +

      Tailored for the Use Case: We do not believe in a "one-size-fits-all" approach to inference. Different use cases—whether a real-time voice bot requiring ultra-lowsub-second latency or a massive reasoning task requiring huge context windows—demand different runtime characteristics. Our platform is designed to be runtime-agnostic, allowing us to automatically select and tailor the best engine based on the specific requirements of the application:

      +
        +
      1. +

        TensorRT-LLM: The High-Performance Standard

        +

        Suitable for: High-throughput production workloads where latency is critical (e.g., customer support chat, real-time voice bots).

        +

        TensorRT-LLM serves as our default backend for these scenarios. Our internal benchmarks on Llama 3.1 and 3.2 models demonstrated that a tuned TensorRT-LLM engine significantly outperforms standard runtimes, especially when utilizing INT4 AWQ and FP8 quantization .

        +

        Key optimizations we tailor for these high-load cases include:

        +
          +
        • Optimized execution via TensorRT engine compilation
        • +
        • Quantization-aware execution for reduced memory usage and improved throughput
        • +
        • Inflight Batching: Allowing requests to be processed continuously without waiting for the entire batch to finish, drastically improving GPU utilization .
        • +
        • Custom Plugins: Enabling specific NVIDIA plugins like the GEMM plugin and GPT Attention plugin to accelerate matrix multiplications and attention mechanisms .
        • +
        +
      2. +
      3. +

        Dynamo: Distributed Inference for Reasoning Models

        +

        Suitable for: Very large "reasoning" models (70B+) or scenarios requiring massive context windows where a single GPU's memory is insufficient.

        +

        For these memory-bound tasks, we utilize Dynamo, a low-latency distributed inference framework . Unlike monolithic servers, Dynamo disaggregates the inference process to scale resources horizontally:

        +
          +
        • KV Aware Routing: A specialized router directs requests to workers that already hold the relevant Key-Value (KV) cache, minimizing redundant computation .
        • +
        • Prefill vs. Decode Split: The workload is divided into Prefill Workers (processing the prompt) and Decode Workers (generating tokens), allowing us to scale the compute-heavy "reading" phase independently from the memory-heavy "writing" phase .
        • +
        • Distributed execution across multiple GPU resources
        • +
        +
      4. +
      5. +

        vLLM: The Flexible Baseline

        +

        Suitable for: Rapid prototyping, testing new model architectures, or low-traffic internal tools where ease of deployment outweighs raw throughput.

        +

        While TensorRT-LLM is optimized for maximum speed, vLLM provides a robust and flexible baseline .

        +
          +
        • High throughput through dynamic batching and efficient memory utilization
        • +
        • Paged KV cache management for handling long contexts and concurrent requests
        • +
        • Strong support for open-source model ecosystems
        • +
        • Rapid Adoption: It allows us to onboard new model architectures immediately without waiting for a custom TensorRT build.
        • +
        • Benchmarking Insight: In our internal tests, vLLM provided a strong baseline but often lacked the specific max-token optimizations present in our custom TRT engines . We use it strategically for initial testing before committing to a full TensorRT optimization pipeline.
        • +
        +
      6. +
      +

      Conclusion

      +

      Large language model inference introduces a fundamentally new class of infrastructure challenges—where performance is governed not just by raw compute, but by memory efficiency, intelligent scheduling, runtime specialization, and lifecycle automation. Unlike traditional ML serving, LLM inference requires systems that understand token-level execution, manage rapidly growing context state, and continuously balance latency, throughput, and cost under highly dynamic workloads.

      +

      The LLM Inference Framework addresses these challenges by transforming inference into a fully automated, reproducible lifecycle—from model onboarding and compilation to deployment, optimization, and observability. By integrating automated quantization and engine compilation, intelligent runtime selection, cold-start mitigation strategies, and LLM-specific observability metrics such as Time-to-First-Token and Inter-Token Latency, the platform ensures both high performance and operational simplicity.

      +

      Equally important, the framework is designed with flexibility and future evolution in mind. Its runtime-agnostic architecture enables seamless adoption of emerging inference engines, hardware accelerators, and optimization techniques without requiring platform redesign. This ensures that teams can continuously leverage advancements in the rapidly evolving LLM ecosystem while maintaining consistent operational workflows.

      +

      Ultimately, the goal of the platform is to make production-scale LLM deployment as seamless and reliable as traditional software deployment—allowing teams to focus on building intelligent applications rather than managing infrastructure complexity. By combining lifecycle automation, runtime optimization, and deep observability, the LLM Inference Framework provides a scalable foundation for delivering fast, cost-efficient, and production-ready LLM experiences.

      +

      Future Explorations

      +

      While we have achieved significant milestones in latency and throughput, the landscape of GenAI is evolving rapidly. Our roadmap focuses on increasing flexibility, reducing costs, and enhancing reliability for enterprise-grade workloads. Here is what we are building next:

      +
        +
      • TPU Support: To diversify our hardware supply chain and further optimize cost-per-token, we are evaluating Google Cloud TPUs to bake it into our platform. By leveraging the JAX and PyTorch/XLA ecosystems, we aim to unlock the massive throughput potential of TPU v5e chips, particularly for our open-source Llama models. This will allow the hardware profiler to dynamically choose between NVIDIA GPUs and Google TPUs based on real-time availability and price-performance metrics.
      • +
      • Multi-LoRA Serving (Serverless Experience): Currently, deploying a fine-tuned model requires a dedicated GPU. We are building support for Multi-LoRA serving, which will allow us to serve hundreds of unique, fine-tuned adapters on top of a single frozen base model. This will drastically reduce costs for multi-tenant applications, enabling a "serverless" experience where specific fine-tunes are hot-swapped instantly per request.
      • +
      • Spot Instance Orchestration: To further optimize cloud costs, we are developing fault-tolerant mechanisms to run inference workloads on Spot Instances. By implementing aggressive checkpointing and seamless request draining, we aim to leverage cheaper, preemptible compute capacity without interrupting the user's streaming experience.
      • +
      • Semantic Caching Layer: We plan to move beyond standard Prefix Caching to implement Semantic Caching. By using a vector database to fetch responses for semantically similar queries (e.g., "How do I reset my password?" vs. "Password reset steps"), we can bypass the GPU entirely for repetitive queries, reducing latency to near-zero.
      • +
      • Context-Aware Autoscaling: Standard CPU/GPU utilization metrics are often insufficient signals for scaling LLMs. We are working on KV-cache pressure metrics for autoscaling. This ensures that we scale out before the memory fills up, preventing eviction-based slowdowns during traffic spikes.
      • +
      • Online Evaluation & Guardrails: We are integrating a lightweight "Trust Layer" into the proxy. This will allow for low-latency input/output filtering (Guardrails) and asynchronous "LLM-as-a-Judge" evaluation pipelines to monitor response quality in production, not just system health.
      • +
      + + \ No newline at end of file diff --git a/docs/blog/tags/meesho/index.html b/docs/blog/tags/meesho/index.html index f74846f7..a6e3d5f9 100644 --- a/docs/blog/tags/meesho/index.html +++ b/docs/blog/tags/meesho/index.html @@ -3,18 +3,469 @@ -One post tagged with "meesho" | BharatMLStack - - - +5 posts tagged with "meesho" | BharatMLStack + + + -

      One post tagged with "meesho"

      View All Tags

      Building Meesho’s ML Platform: From Chaos to Cutting-Edge (Part 1)

      · 11 min read
      Adarsha Das
      Senior Architect @ Meesho
      Aditya Kumar
      SDE-III @ Meesho
      Bhawani Singh
      SDE-IV @ Meesho
      Jigar Dave
      SDE-IV @ Meesho

      BharatMLStack

      -

      The Genesis: How a Friday Night Roast Sparked Meesho’s ML Platform

      -

      It all started in early 2022, over a casual Friday evening catch-up. Like many great origin stories, this one began with friendly banter between a group of backend engineers and data scientists. As the conversations unfolded, so did the roasting—until one remark hit a little too close to home:

      +

      5 posts tagged with "meesho"

      View All Tags

      LLM Inference Optimization Techniques: Engineering Sub-Second Latency at Scale

      · 5 min read
      Jaya Kumar
      Lead ML Engineer @ Meesho

      BharatMLStack +Raw execution of Large Language Models is inherently expensive and memory-intensive. To achieve sub-second latency and high throughput, we implement a multi-layered optimization strategy that targets the entire inference stack—from memory management to kernel execution.

      +

      1. Advanced Memory Management: Paged & Prefix KV Caching

      +

      The most significant bottleneck in LLM inference is not always compute, but memory bandwidth—specifically managing the Key-Value (KV) cache.

      +

      Paged KV caching

      +

      Standard caching suffers from fragmentation. We use Paged KV caching, which operates similarly to an operating system's virtual memory: the KV cache is divided into non-contiguous blocks. This lets us serve larger batch sizes without running out of memory.

      +

      KV cache quantization

      +

      To further maximize available memory, we implement KV cache quantization (e.g., FP8). By compressing stored attention keys and values from 16-bit to 8-bit, we nearly double the effective context window capacity of the GPU, allowing longer conversations or larger batches without materially degrading quality.

      +

      Prefix caching (the "voice bot" optimizer)

      +

      For use cases like GenAI voice bots where the system prompt (e.g., "You are a helpful assistant...") is static across thousands of requests, we enable prefix caching.

      +
        +
      • Impact: By reusing pre-computed KV states for common prefixes, we achieve a cache hit rate of ~90%. This reduces Time To First Token (TTFT) by skipping redundant computation of the system prompt.
      • +
      +

      2. Aggressive Quantization (INT4 AWQ & FP8)

      +

      Running models in their native 16-bit precision (BF16) restricts maximum batch size and throughput. We use quantization to shrink model weights without sacrificing accuracy.

      +

      INT4 AWQ (Activation-aware Weight Quantization)

      +

      For the Llama 3 family, we use AWQ to compress weights to 4 bits. This reduces model size by ~75%, allowing larger models to fit into L4 GPU memory and significantly improving token generation speed.

      +

      FP8 precision

      +

      For NVIDIA Hopper (H100) architectures, we are exploring FP8 quantization, leveraging native FP8 tensor cores to accelerate matrix multiplications while maintaining a higher dynamic range than integer quantization.

      +
        +
      • Verification: We validate quantized models by comparing dot-product similarity of embeddings against the FP16 baseline, consistently achieving >99% similarity.
      • +
      +

      3. Kernel Fusion & Custom Plugins

      +

      To minimize overhead from launching thousands of small GPU operations, we fuse them into monolithic kernels using NVIDIA TensorRT plugins.

      +
        +
      • Flash attention & FMHA: We enable Fused Multi-Head Attention (FMHA) combined with flash attention to reduce memory reads/writes.
      • +
      • GEMM plugins: We use specialized GEMM plugins to accelerate transformer linear layers.
      • +
      • Removing input padding: Instead of padding short sequences to match the longest, we remove input padding so the GPU processes only valid tokens.
      • +
      +

      4. Inflight (Continuous) Batching

      +

      Traditional static batching waits for all requests in a batch to finish before returning results—so one long response delays everyone else.

      +

      We implement inflight batching: as soon as one request completes, its slot is freed and filled by a new request from the queue. This keeps GPUs saturated and decouples latency of short queries from long ones.

      +

      5. Parallelism Strategies: Scaling Beyond One GPU

      +

      For large models (e.g., 70B+ parameters) that cannot fit into the VRAM of a single GPU, we use parallelism strategies.

      +
        +
      • Tensor parallelism (TP): Split weight matrices across multiple GPUs (e.g., 4× L4 or 8× A100). Each GPU computes a shard and outputs are reduced at every layer.
      • +
      • Pipeline parallelism (PP): Split model layers across GPUs to pipeline compute (e.g., while one GPU computes later layers for Request A, another starts early layers for Request B).
      • +
      +

      6. Speculative Decoding

      +

      To reduce inter-token latency (ITL), we explore speculative decoding.

      +
        +
      • Mechanism: A smaller, faster "draft" model speculatively generates a short token sequence (e.g., 5 tokens).
      • +
      • Verification: The larger target model verifies those tokens in one parallel forward pass. If correct, we effectively generate multiple tokens per large-model step; if not, we discard and regenerate. This is effective for predictable text, improving perceived generation speed.
      • +
      +

      Few Benchmarks

      +

      Below are a couple of representative use cases and performance numbers.

      +

      Search query rewriting

      +
        +
      • LLM: Fine-tuned llama-3.2-1B
      • +
      • Input & output token length: ~10–20
      • +
      • Response type: Non-streaming
      • +
      +
      Inference runtimeHardwareMax requests/secMax p99 latency
      TensorRT-LLM4 × L4 GPUs (multi-GPU)100095 ms
      TensorRT-LLM1 × A100 40 GB GPU100069 ms
      +

      Voice bot query

      +
        +
      • LLM: Llama-3.1-8B
      • +
      • Input token length: ~1900–2000
      • +
      • Output token length: ~200
      • +
      • Response type: Streaming
      • +
      +
      Inference runtimeConcurrencyp99 TTFT (ms)p99 ITL (ms)Token throughput (tokens/sec)Request throughput (req/sec)Hardware
      TensorRT-LLM136.2722.7845.660.23L4
      TensorRT-LLM249.8123.2189.370.45L4
      TensorRT-LLM455.3336.62153.390.78L4
      TensorRT-LLM866.539.11279.881.47L4
      TensorRT-LLM16131.830.39547.82.77L4
      TensorRT-LLM32277.2248.02925.74.78L4
      TensorRT-LLM64498.5271.621,164.406.2L4
      TensorRT-LLM128677.31120.371,445.187.69L4
      TensorRT-LLM2561,926.31216.881,600.818.52L4
      TensorRT-LLM121.179.24130.050.68A100
      TensorRT-LLM225.789.21264.51.35A100
      TensorRT-LLM428.5210.99437.692.27A100
      TensorRT-LLM834.412.61760.493.96A100
      TensorRT-LLM1668.0314.321,343.807.01A100
      TensorRT-LLM32185.9616.822,287.3011.92A100
      TensorRT-LLM64136.8721.173,625.2218.89A100
      TensorRT-LLM128463.7834.154,456.5123.24A100
      TensorRT-LLM256890.1259.185,188.2427.05A100
      +

      Conclusion

      +

      High-performance LLM inference is fundamentally a systems engineering problem: memory efficiency, kernel execution, batching strategy, and parallelism determine real-world latency and throughput. Techniques such as paged KV caching, aggressive quantization, kernel fusion, and inflight batching improve GPU utilization while reducing latency and memory pressure.

      +

      These optimizations enable the platform to deliver sub-second responses, sustain high concurrency, and efficiently serve both lightweight and long-context workloads. By continuously optimizing across the full inference stack, we keep LLM serving scalable, cost-efficient, and production-ready for real-time AI applications.

      Designing a Production-Grade LLM Inference Platform: From Model Weights to Scalable GPU Serving

      · 14 min read
      Jaya Kumar
      Lead ML Engineer @ Meesho

      BharatMLStack +Serving large language models in production introduces new challenges across infrastructure, performance optimization, and operational lifecycle management. The LLM Inference Platform addresses these challenges by providing a unified system for deploying and managing open-source and fine-tuned LLMs at scale.

      +

      The platform implements a complete LLMOps lifecycle — from model registration and automated compilation to deployment, runtime optimization, and monitoring. Designed as a self-service environment, users can onboard models directly from open repositories such as Hugging Face or upload custom fine-tuned models, and deploy them using a single-click workflow with no manual infrastructure or configuration steps required.

      +

      In addition to fully automated deployment, the platform allows users to select and apply custom inference optimization techniques — such as quantization strategies, batching configurations, and runtime-specific performance enhancements — enabling teams to balance latency, throughput, and cost based on their use case. The goal is to reduce operational friction while enabling high-performance, production-grade LLM inference.

      +

      Why LLM Inference Is not just bigger ML model serving

      +

      Large language model (LLM) inference introduces a fundamentally different set of challenges compared to traditional machine learning inference. While classical ML models typically perform a single forward pass to produce a fixed prediction, LLMs operate as autoregressive systems, generating outputs token by token based on previously generated context. This difference dramatically changes how inference systems must be designed, optimized, and scaled.

      +

      Autoregressive Generation and Sequential Computation:

      +

      Unlike traditional models such as classifiers or recommenders — where inference cost is relatively constant — LLMs generate responses incrementally. Each new token depends on all previously generated tokens, making inference inherently sequential and dynamic. This means latency and compute requirements vary significantly depending on prompt length and output size, introducing complexity in scheduling and resource allocation. +Because tokens cannot be generated fully in parallel during decoding, GPUs may become underutilized without specialized batching and scheduling strategies. This has led to the development of dedicated LLM inference engines optimized for token-level execution.

      +

      Prefill and Decode Phases:

      +

      LLM inference typically consists of two distinct stages:

      +
        +
      • Prefill phase — the model processes the input prompt and builds internal representations. This stage is compute-heavy and highly parallelizable.
      • +
      • Decode phase — the model generates tokens sequentially, predicting one token at a time using previously generated context.
      • +
      +

      The decode stage often becomes memory-bound rather than compute-bound, which creates new performance bottlenecks compared to traditional ML workloads.

      +

      Context Management and KV Caching:

      +

      Another fundamental difference lies in how LLMs maintain context. Transformer-based models rely on attention mechanisms that require access to past token representations. To avoid recomputing these representations repeatedly, inference engines use key-value (KV) caching, which stores intermediate activations from previous tokens. +KV caching significantly improves performance by eliminating redundant computation, but it introduces new challenges:

      +
        +
      • Memory consumption grows with sequence length and batch size
      • +
      • GPU memory becomes a critical bottleneck
      • +
      • Efficient memory management becomes essential for scaling concurrent requests
      • +
      +

      This tradeoff between compute efficiency and memory usage is unique to LLM inference workloads.

      +

      Dynamic and Irregular Workloads:

      +

      Traditional ML inference typically operates on fixed-size inputs with predictable latency. In contrast, LLM requests vary widely in prompt length, output length, and runtime behavior. As a result:

      +
        +
      • Batch sizes must be dynamic rather than static
      • +
      • Requests may enter and leave batches asynchronously
      • +
      • Scheduling systems must continuously rebalance workloads to maximize GPU utilization
      • +
      +

      These characteristics require specialized serving architectures that differ significantly from standard ML serving pipelines.

      +

      Streaming and User Experience Constraints:

      +

      Another distinguishing factor is the expectation of real-time streaming responses. Instead of returning a single output, LLM systems often stream tokens to users as they are generated. +Because of these differences — sequential generation, growing memory requirements, dynamic workloads, and streaming constraints — LLM inference cannot be treated as a simple extension of existing ML serving systems. Production platforms must incorporate specialized runtime engines, advanced optimization techniques, and observability tailored specifically to LLM workloads.

      +

      LLMOps: High-Level Architecture

      +

      LLM Architecture

      +

      The LLM Inference Framework is designed as a fully automated, end-to-end system for deploying and operating open-source and fine-tuned large language models at scale. The architecture abstracts the complexity of model optimization, hardware selection, deployment, and runtime management into a unified workflow that enables users to move from raw model weights to production-ready inference endpoints with minimal manual intervention.

      +

      Our LLM Inference Framework is architected not just as a serving engine, but as a complete lifecycle management system. As illustrated in the high-level design below, the platform automates the journey of a model through seven distinct stages, ensuring reproducibility, performance, and scalability.

      +
        +
      1. +

        Onboarding & Registration (The Source of Truth)

        +

        The lifecycle begins with the Data Scientist or engineer.

        +
          +
        • Model Ingestion: Users onboard models—whether open-source (Hugging Face, NeMo) or internally fine-tuned—via the Truffle Box SDK/UI.
        • +
        • LLM + Prompt Registry: Unlike traditional systems that only track model weights, our registry is a unified control plane. It stores both the Model Artifacts and the Prompt Templates. This allows Data Scientists to register and version-control prompts (e.g., "customer_support_v2") independently of the application code.
        • +
        +
      2. +
      3. +

        The "Black Box" Build Engine

        +

        Once a model is registered, the Automated LLM Compiler + Quantizer Module kicks off a background job on ephemeral GPU resources.

        +
          +
        • Transformation: The raw model is converted into a TRT-LLM Checkpoint.
        • +
        • Quantization: The system automatically applies quantization algorithms (like INT4 AWQ or FP8) to reduce memory footprint.
        • +
        • Engine Building: Finally, it compiles a highly optimized TRT Engine specifically tuned for the target hardware.
        • +
        +
      4. +
      5. +

        Intelligent Profiling & Validation

        +

        Before deployment, the new engine passes through the Hardware & Inference Runtime Profiler.

        +
          +
        • Benchmarking: This module empirically tests the engine against various hardware configurations (L4 vs. A100) and runtimes (TRT-LLM vs. vLLM).
        • +
        • Optimization: It recommends the optimal configuration that meets latency SLAs (Time-To-First-Token) while minimizing cost.
        • +
        +
      6. +
      7. +

        Smart Artifact Generation & Distribution

        +

        To solve the Kubernetes "Cold Start" problem, the LLM Serving Artifacts Generation module packages the model using a bifurcated strategy:

        +
          +
        • Standard Models: Artifacts are uploaded to Cloud Storage (GCS) and downloaded by pods at startup.
        • +
        • Very Large Models: For massive models (>8GB) where network downloads are too slow, the system pre-caches the model onto Secondary Boot Disks. These disks are attached directly to new GPU nodes during autoscaling, eliminating download wait times.
        • +
        +
      8. +
      9. +

        Image Streaming & Deployment

        +

        Simultaneously, the inference runtime container images are pulled from the Artifact Registry.

        +
          +
        • Image Streaming: We utilize container image streaming to allow pods to start initializing while the massive Triton/Dynamo container layers are still downloading, further shaving seconds off the startup time. link
        • +
        +
      10. +
      11. +

        The Inference Runtime (Kubernetes)

        +

        The workload lands on Kubernetes with Autoscaling.

        +
          +
        • Dynamic Backends: Depending on the profile generated in Stage 3, the pod initializes either TensorRT-LLM (for throughput) or vLLM (for flexibility), or spins up a Dynamo worker for distributed inference.
        • +
        • Data Loading: The pod either downloads the model from Cloud Storage or mounts the pre-warmed Secondary Boot Disk ("Pull from Disk").
        • +
        +
      12. +
      13. +

        Client Interaction & Observability

        +

        Finally, the LLM Inference Client executes the request.

        +
          +
        • Prompt Injection: The client pulls the specific prompt template ID from the Registry, ensuring the exact versioned instructions are used.
        • +
        • Streaming Response: The request is sent via gRPC, and tokens are streamed back to the user in real-time.
        • +
        +
      14. +
      15. +

        Observability: Monitoring the Pulse of GenAI

        +

        In traditional microservices, success is measured by CPU utilization and request latency (p99). For Large Language Models, these metrics are insufficient. A user doesn't care if the GPU is at 80% utilization; they care about how fast the first word appears and how smoothly the rest of the sentence follows.

        +

        To capture the true user experience, our platform instrumentation focuses on three critical LLM-specific metrics:

        +
          +
        1. +

          Time to First Token (TTFT)

          +
            +
          • Definition: TTFT measures the time elapsed from the moment a request is received until the very first token is generated and streamed back to the user.
          • +
          • Why it matters: This represents the "Prefill Phase" latency—the time the model takes to process the input prompt and load weights. A high TTFT makes the application feel unresponsive or "hung."
          • +
          • Optimization: We closely monitor TTFT to ensure our Prefix Caching is effective (aiming for high cache hitrates), which drastically lowers this metric by skipping redundant prompt processing.
          • +
          +
        2. +
        3. +

          Inter-Token Latency (ITL)

          +
            +
          • Definition: ITL measures the average time interval between the generation of consecutive tokens during the "Decode Phase".
          • +
          • Why it matters: This defines the "perceived speed" of reading. Even if the first token is fast (low TTFT), high ITL makes the text generation look "jerky" or slow to the user.
          • +
          • Benchmarks: In our testing with Llama 3.1, we track p99 ITL to ensure it stays below human reading speeds to maintain a natural conversational flow.
          • +
          +
        4. +
        5. +

          Token Throughput vs. Request Throughput

          +
            +
          • We distinguish between two types of throughput to balance system efficiency with user load:
          • +
          • Token Throughput (tokens/sec): The total number of tokens generated across all concurrent requests. This measures the raw compute efficiency of the GPU and the effectiveness of batching.
          • +
          • Request Throughput (req/sec): The number of distinct user queries served per second. We use this to determine autoscaling thresholds, ensuring we scale out before the queue depth impacts ITL.
          • +
          +
        6. +
        7. +

          The Monitoring Stack

          +
            +
          • Real-time Dashboards: We utilize Grafana to visualize these streaming metrics in real-time, allowing on-call engineers to spot "slow generation" incidents that generic "500 error" alerts would miss.
          • +
          • Request Tracing: Since Triton Inference Server does not log request payloads by default, we integrate a Helix Client to asynchronously publish request logs to Log Tables. This allows us to trace a specific "slow" request back to its prompt to understand if a complex input caused the latency spike.
          • +
          +
        8. +
        +
      16. +
      +

      Supported Inference backends (TensorRT LLM, Dynamo & vLLM)

      +

      Tailored for the Use Case: We do not believe in a "one-size-fits-all" approach to inference. Different use cases—whether a real-time voice bot requiring ultra-lowsub-second latency or a massive reasoning task requiring huge context windows—demand different runtime characteristics. Our platform is designed to be runtime-agnostic, allowing us to automatically select and tailor the best engine based on the specific requirements of the application:

      +
        +
      1. +

        TensorRT-LLM: The High-Performance Standard

        +

        Suitable for: High-throughput production workloads where latency is critical (e.g., customer support chat, real-time voice bots).

        +

        TensorRT-LLM serves as our default backend for these scenarios. Our internal benchmarks on Llama 3.1 and 3.2 models demonstrated that a tuned TensorRT-LLM engine significantly outperforms standard runtimes, especially when utilizing INT4 AWQ and FP8 quantization .

        +

        Key optimizations we tailor for these high-load cases include:

        +
          +
        • Optimized execution via TensorRT engine compilation
        • +
        • Quantization-aware execution for reduced memory usage and improved throughput
        • +
        • Inflight Batching: Allowing requests to be processed continuously without waiting for the entire batch to finish, drastically improving GPU utilization .
        • +
        • Custom Plugins: Enabling specific NVIDIA plugins like the GEMM plugin and GPT Attention plugin to accelerate matrix multiplications and attention mechanisms .
        • +
        +
      2. +
      3. +

        Dynamo: Distributed Inference for Reasoning Models

        +

        Suitable for: Very large "reasoning" models (70B+) or scenarios requiring massive context windows where a single GPU's memory is insufficient.

        +

        For these memory-bound tasks, we utilize Dynamo, a low-latency distributed inference framework . Unlike monolithic servers, Dynamo disaggregates the inference process to scale resources horizontally:

        +
          +
        • KV Aware Routing: A specialized router directs requests to workers that already hold the relevant Key-Value (KV) cache, minimizing redundant computation .
        • +
        • Prefill vs. Decode Split: The workload is divided into Prefill Workers (processing the prompt) and Decode Workers (generating tokens), allowing us to scale the compute-heavy "reading" phase independently from the memory-heavy "writing" phase .
        • +
        • Distributed execution across multiple GPU resources
        • +
        +
      4. +
      5. +

        vLLM: The Flexible Baseline

        +

        Suitable for: Rapid prototyping, testing new model architectures, or low-traffic internal tools where ease of deployment outweighs raw throughput.

        +

        While TensorRT-LLM is optimized for maximum speed, vLLM provides a robust and flexible baseline .

        +
          +
        • High throughput through dynamic batching and efficient memory utilization
        • +
        • Paged KV cache management for handling long contexts and concurrent requests
        • +
        • Strong support for open-source model ecosystems
        • +
        • Rapid Adoption: It allows us to onboard new model architectures immediately without waiting for a custom TensorRT build.
        • +
        • Benchmarking Insight: In our internal tests, vLLM provided a strong baseline but often lacked the specific max-token optimizations present in our custom TRT engines . We use it strategically for initial testing before committing to a full TensorRT optimization pipeline.
        • +
        +
      6. +
      +

      Conclusion

      +

      Large language model inference introduces a fundamentally new class of infrastructure challenges—where performance is governed not just by raw compute, but by memory efficiency, intelligent scheduling, runtime specialization, and lifecycle automation. Unlike traditional ML serving, LLM inference requires systems that understand token-level execution, manage rapidly growing context state, and continuously balance latency, throughput, and cost under highly dynamic workloads.

      +

      The LLM Inference Framework addresses these challenges by transforming inference into a fully automated, reproducible lifecycle—from model onboarding and compilation to deployment, optimization, and observability. By integrating automated quantization and engine compilation, intelligent runtime selection, cold-start mitigation strategies, and LLM-specific observability metrics such as Time-to-First-Token and Inter-Token Latency, the platform ensures both high performance and operational simplicity.

      +

      Equally important, the framework is designed with flexibility and future evolution in mind. Its runtime-agnostic architecture enables seamless adoption of emerging inference engines, hardware accelerators, and optimization techniques without requiring platform redesign. This ensures that teams can continuously leverage advancements in the rapidly evolving LLM ecosystem while maintaining consistent operational workflows.

      +

      Ultimately, the goal of the platform is to make production-scale LLM deployment as seamless and reliable as traditional software deployment—allowing teams to focus on building intelligent applications rather than managing infrastructure complexity. By combining lifecycle automation, runtime optimization, and deep observability, the LLM Inference Framework provides a scalable foundation for delivering fast, cost-efficient, and production-ready LLM experiences.

      +

      Future Explorations

      +

      While we have achieved significant milestones in latency and throughput, the landscape of GenAI is evolving rapidly. Our roadmap focuses on increasing flexibility, reducing costs, and enhancing reliability for enterprise-grade workloads. Here is what we are building next:

      +
        +
      • TPU Support: To diversify our hardware supply chain and further optimize cost-per-token, we are evaluating Google Cloud TPUs to bake it into our platform. By leveraging the JAX and PyTorch/XLA ecosystems, we aim to unlock the massive throughput potential of TPU v5e chips, particularly for our open-source Llama models. This will allow the hardware profiler to dynamically choose between NVIDIA GPUs and Google TPUs based on real-time availability and price-performance metrics.
      • +
      • Multi-LoRA Serving (Serverless Experience): Currently, deploying a fine-tuned model requires a dedicated GPU. We are building support for Multi-LoRA serving, which will allow us to serve hundreds of unique, fine-tuned adapters on top of a single frozen base model. This will drastically reduce costs for multi-tenant applications, enabling a "serverless" experience where specific fine-tunes are hot-swapped instantly per request.
      • +
      • Spot Instance Orchestration: To further optimize cloud costs, we are developing fault-tolerant mechanisms to run inference workloads on Spot Instances. By implementing aggressive checkpointing and seamless request draining, we aim to leverage cheaper, preemptible compute capacity without interrupting the user's streaming experience.
      • +
      • Semantic Caching Layer: We plan to move beyond standard Prefix Caching to implement Semantic Caching. By using a vector database to fetch responses for semantically similar queries (e.g., "How do I reset my password?" vs. "Password reset steps"), we can bypass the GPU entirely for repetitive queries, reducing latency to near-zero.
      • +
      • Context-Aware Autoscaling: Standard CPU/GPU utilization metrics are often insufficient signals for scaling LLMs. We are working on KV-cache pressure metrics for autoscaling. This ensures that we scale out before the memory fills up, preventing eviction-based slowdowns during traffic spikes.
      • +
      • Online Evaluation & Guardrails: We are integrating a lightweight "Trust Layer" into the proxy. This will allow for low-latency input/output filtering (Guardrails) and asynchronous "LLM-as-a-Judge" evaluation pipelines to monitor response quality in production, not just system health.
      • +

      Cracking the Code: Scaling Model Inference & Real-Time Embedding Search

      · 4 min read
      Aditya Kumar
      Lead Software Engineer @ Meesho
      Jaya Kumar
      Lead ML Engineer @ Meesho
      Adarsha Das
      Senior Architect @ Meesho

      BharatMLStack +By mid-2023, we had transformed our ML stack—building a real-time feature store, optimizing model retrieval, and fine-tuning ranking. But two critical gaps remained:

      +
        +
      • 🔹 Scaling model inference without hitting infrastructure roadblocks
      • +
      • 🔹 Moving embedding search from batch to real-time for candidate generation
      • +
      +

      Here’s how we tackled these last-mile challenges, broke free from infrastructure constraints, and built a cost-efficient, high-performance system.

      +

      Breaking Free from the Scalability Ceiling

      +

      The Model Serving Bottleneck—A Wake-Up Call

      +

      July 2023. With just months left for the Mega Blockbuster Sale (MBS), we noticed a serious issue—scaling our model-serving infrastructure was taking 10–15 minutes. In real-time ML, that’s an eternity. +In one of our war rooms, we ran a quick experiment:

      +
        +
      • 🚀 We deployed an XGBoost model on a self-hosted Triton Inference Server running on a 16-core machine.
      • +
      • 🚀 Fired requests and compared the outputs with our existing cloud-hosted setup.
      • +
      • 🚀 The results matched—perfectly.
      • +
      +

      That moment changed everything. We prepped a backup Triton setup on EKS, just in case our cloud provider couldn't allocate enough compute resources in time. Luckily, they did—but the seed was planted. +Then in October, just two weeks before MBS, we got an alarming response from our infrastructure team: +"Node availability may be an issue." +With no time to waste, we moved 30% of real-time ML traffic to our self-hosted Triton cluster. The results?

      +
        +
      • ✅ p99 latency dropped from 90–100ms to 30–40ms
      • +
      • ✅ Triton handled significantly higher throughput on fewer resources
      • +
      • ✅ No model changes were needed
      • +
      +

      MBS ran without a hitch, proving that self-hosted inference was the way forward.

      +

      Scaling Triton on GKE

      +

      This left us with two choices:

      +
        +
      • 1️⃣ Port models to a managed cloud inference service, investing time in learning a new deployment stack
      • +
      • 2️⃣ Scale our existing Triton setup on GKE, optimizing for cost and performance
      • +
      +

      We went with Option 2—and it slashed inference costs to 35% of what we previously paid, while giving us full control over scaling and optimizations.

      +

      Fixing the Cold Start Problem

      +

      As we onboarded more deep learning (DL) models, we hit a new bottleneck, new inference pods took 7–9 minutes to spin up.

      +

      After profiling, we found the culprits:

      +
        +
      • Triton’s base image—a massive 5GB
      • +
      • Model binaries—often 1GB+
      • +
      • Startup delay—mostly due to downloading and initializing these assets
      • +
      +

      To fix this, we built a lightweight Triton image, stripping unused components and shrinking the size to 900MB. This cut cold start times drastically, making auto-scaling faster and smoother.

      +

      Embedding Search: The Last Piece of the Puzzle

      +

      By mid-2023, most of our ML stack had gone real-time—except for Candidate Generation (CG), which still ran in batch mode. To truly power real-time recommendations, we needed an online embedding search system.

      +

      Choosing the Right Vector Database

      +

      We benchmarked three production-ready vector DBs across key parameters:

      +
        +
      • Milvus
      • +
      • Qdrant
      • +
      • Weaviate
      • +
      +

      After extensive POCs, Qdrant stood out for its:

      +
        +
      • ✅ Blazing-fast search latency on high-dimensional vectors
      • +
      • ✅ Efficient memory usage, crucial for in-memory workloads
      • +
      • ✅ Support for upserts and soft deletes, vital for Ads use cases
      • +
      • ✅ gRPC + REST APIs, making integration seamless
      • +
      • ✅ Powerful filtering, allowing fine-tuned retrieval (e.g., filtering Ads by category, active status, etc.)
      • +
      +

      At its core, Qdrant uses HNSW indexing, delivering both high recall and low-latency nearest-neighbor search—a perfect fit for our needs.

      +

      Embedding Freshness & Real-Time Updates

      +

      To ensure embeddings stayed up to date, we built a dual ingestion pipeline:

      +
        +
      • 📌 Daily Refresh: A bulk pipeline updated embeddings overnight
      • +
      • 📌 Real-Time Updates: Ads events triggered immediate upserts/deletes
      • +
      +

      This setup powered real-time "Similar Products" recommendations on the product page and became the foundation for Ads Candidate Generation, ensuring the right ads surfaced in milliseconds.

      +

      Skye

      +

      Final Takeaways: Scaling Smartly for Real-Time ML

      +
        +
      • 🚀 Self-hosted inference on Triton gave us lower cost, faster scaling, and better performance than managed services
      • +
      • 🚀 Building a custom Triton image reduced cold starts, improving responsiveness
      • +
      • 🚀 Qdrant-based embedding search enabled real-time personalization at scale
      • +
      • 🚀 Real-time updates for embeddings unlocked dynamic, up-to-date recommendations
      • +
      +

      By early 2024, Meesho’s ML stack had evolved into a fully real-time, scalable, and cost-efficient system, setting the foundation for even bigger leaps ahead.

      Building Meesho’s ML Platform: Lessons from the First-Gen System (Part 2)

      · 7 min read
      Bhawani Singh
      Architect @ Meesho
      Jigar Dave
      Lead Software Engineer @ Meesho
      Adarsha Das
      Senior Architect @ Meesho

      BharatMLStack +By late 2022, we had built something we were truly proud of—a real-time ML serving system with a DAG-based executor, a feature store, and an interaction store powering key ranking and personalization models. It was a major milestone, the culmination of months of effort from data scientists, ML engineers, and backend teams. Our system was live, and we were ready to push the boundaries of experimentation. +And it worked. Mostly. +But soon, cracks appeared. Every new model needed custom feature retrieval logic, DAGs became dense and unmanageable, and scaling turned into a constant firefight. Costs surged, and infra bottlenecks slowed experimentation. Our system worked, but it wasn’t built for scale. +This is the story of how we tackled these challenges—building Inferflow for seamless feature retrieval, optimizing real-time infra, and cutting costs while scaling to millions of QPS.

      +

      The Cost of Success

      +

      Every new Ranker model required its own feature set, often pulling from different entities. Each addition meant:

      +
        +
      • Adding new DAG nodes in IOP
      • +
      • Writing custom logic to fetch features from multiple sources (e.g., user, product, user × category)
      • +
      • Inferring intermediate features (e.g., extracting category from a product to fetch user × category data)
      • +
      • Optimizing I/O and dealing with the inevitable bugs
      • +
      +

      What began as clean DAGs soon turned into a tangled web of cross-dependent graphs. Every experimentation cycle meant new nodes, new dependencies, and slower iterations.

      +

      Scaling Pains (and Cassandra’s Limits)

      +

      At some point, we were hitting:

      +
        +
      • 250–300K reads/sec
      • +
      • 1M writes/sec (during lean hours)
      • +
      +

      All of this ran on Cassandra. While its distributed architecture had been proven in production, operating large-scale clusters came with considerable infrastructure overhead. Our proof-of-concept (POC) demonstrated throughput of around 100K ops/sec, but as we scaled further, the challenges grew. Ensuring node health, optimizing compaction, and maintaining storage balance became increasingly demanding. We also observed latency spikes under heavy load, alongside a sharp increase in total cost of ownership.

      +

      Interaction Store Woes

      +

      Our interaction store was another ticking time bomb:

      +
        +
      • 🚨 Clusters kept growing in size and cost
      • +
      • 🚨 Latency spikes became increasingly frequent
      • +
      • 🚨 The DMC proxy occasionally lost locality of nodes against shards, causing cross-node communication and degraded performance
      • +
      +

      Each time this happened, we had to manually rebalance shards just to restore stable latency, making operations unsustainable at scale.

      +

      Silver Linings

      +

      Despite the chaos, the system was live and delivering value:

      +
        +
      • Real-time infrastructure was in production
      • +
      • Costs dropped by 60–70% compared to offline personalization
      • +
      • New experiments rolled out faster and more successfully
      • +
      • User engagement metrics improved
      • +
      +

      It wasn’t perfect. It was far from easy. But it worked—and that counted for a lot.

      +

      Round Two: Solving the Top 2 Bottlenecks

      +

      With the first-gen system stretched to its limits, we stepped back. Conversations with data scientists and backend engineers revealed three recurring pain points:

      +
        +
      1. Coding feature retrieval logic for every new model was becoming unsustainable
      2. +
      3. ML scale was exploding—bringing rising infra costs with it
      4. +
      5. Real-time embedding search was the next big unlock
      6. +
      +

      We tackled them one by one—starting with the biggest pain point.

      +

      Problem 1: No-Code Feature Retrieval for Model Inference

      +

      We noticed a pattern: for personalized ranking, models needed features from:

      +
        +
      • ✅ Product
      • +
      • ✅ User
      • +
      • ✅ User × Category
      • +
      • ✅ Region, cohort, sub-category, etc.
      • +
      +

      A key insight emerged: Entities that contribute features for a model always map back to the context entities.

      +

      MP Dag

      +

      With this, we designed Inferflow, a graph-driven feature retrieval and model orchestration system:

      +
        +
      • 1️⃣ Inferflow takes a modelId and context IDs (e.g., userId, productIds)
      • +
      • 2️⃣ Loads a pre-defined feature retrieval graph from ZooKeeper
      • +
      • 3️⃣ Executes the graph to resolve entity relationships dynamically
      • +
      • 4️⃣ Outputs a 2D matrix of feature vectors
      • +
      +

      💡 The impact?

      +
        +
      • 🚀 No more custom feature retrieval code—just graph updates in config
      • +
      • 🚀 Feature consistency across experiments
      • +
      • 🚀 Faster iteration cycles for ranking, fraud detection, and beyond
      • +
      +

      Here’s a visual example that shows how this graph plays out during execution. We further extended the graph to call multiple models as needed: +MP matrix +We built Inferflow in GoLang, using gRPC and Proto3 serialization for efficiency.

      +

      Problem 2: Scaling Without Breaking the Bank

      +

      With more ML use cases coming online, we needed to cut costs without compromising performance. We focused on:

      +
        +
      • 🔹 Online Feature Store
      • +
      • 🔹 Interaction Store
      • +
      +

      Optimizing the Online Feature Store

      +

      Our costs were concentrated in:

      +
        +
      • 📌 Database (Cassandra)
      • +
      • 📌 Cache (Redis)
      • +
      • 📌 Running Pods (Java services)
      • +
      +

      1️⃣ Replacing Cassandra with ScyllaDB +As we hit the operational limits of large Cassandra clusters, we transitioned to ScyllaDB, which offered a seamless drop-in replacement without major code changes. The switch brought significant benefits:

      +
        +
      • Throughput: Matched or exceeded Cassandra's performance under identical workloads, even under high concurrency.
      • +
      • Latency: Achieved consistently lower P99 latencies due to ScyllaDB's shard-per-core architecture and better I/O utilization.
      • +
      • Cost Efficiency: Reduced infra footprint by ~70% through better CPU and memory efficiency, eliminating the need for over-provisioned nodes.
      • +
      +

      2️⃣ Finding the Right Cache +To reduce backend load and improve response times, we benchmarked multiple caching solutions—Memcached, KeyDB, and Dragonfly—under real production traffic patterns. Dragonfly stood out due to its robust architecture and operational simplicity:

      +
        +
      • Data Skew Handling: Efficiently managed extreme key hotness and uneven access patterns without performance degradation.
      • +
      • Throughput: Delivered consistently high throughput, even with large object sizes and concurrent access.
      • +
      • Ease of Adoption: Acted as a drop-in Redis replacement with full protocol compatibility—no changes needed in application code or client libraries.
      • +
      +

      3️⃣ Moving to GoLang for Cost-Efficient Serving +Java services were memory-heavy—so we rewrote core services in GoLang. The results?

      +

      ✅ Memory usage dropped by ~80% +✅ CPU utilization was significantly lower +✅ Faster, more efficient deployments

      +

      Optimizing the Interaction Store

      +

      We realized that we only need a user’s interaction data in Redis when they open the app. So, we implemented a tiered storage approach:

      +
        +
      • 📌 Cold Tier (ScyllaDB)—Stores click, order, wishlist events
      • +
      • 📌 Hot Tier (Redis)—Loads a user’s past interactions only when they open the app
      • +
      +

      Smart Offloading: We introduced an inactivity tracker to detect when a user session ends. At that point, Redis data was flushed back to Scylla, reducing unnecessary writes.

      +

      InteractionStore

      +

      Results

      +
        +
      • Online Feature Store hit 1M QPS for the first time during the 2023 Mega Blockbuster Sale—without breaking a sweat
      • +
      • Infra costs for Online Feature Store and Interaction Store dropped by ~60%
      • +
      +

      The Catch: Our ML Hosting Hit a Hard Limit

      +

      While planning for 2023 MBS, we ran into a critical scalability bottleneck:

      +
        +
      • ❌ Insufficient compute availability in our region for ML instances
      • +
      • ❌ Couldn’t provision enough nodes to handle real-time inference at scale
      • +
      +

      This forced us to rethink where and how we hosted our models. The existing setup was great for prototyping—but it wasn’t built to handle the bursty, high-QPS demands of real-world production workloads.

      +

      Conclusion: From Firefighting to Future-Proofing

      +

      What started as an ambitious experiment turned into a real-time ML infrastructure that powered millions of requests per second. We battled scaling pains, rethought feature retrieval with Inferflow, and rebuilt our infra stack for efficiency—driving down costs while improving experimentation velocity. +But new challenges emerged. Our infrastructure could now handle scale, but our ML model hosting setup hit a hard limit. With compute availability bottlenecks threatening real-time inference, we faced a critical decision: how do we make model serving as scalable and cost-efficient as the rest of our stack? That’s the next piece of the puzzle—and the story of Part 3.

      Building Meesho’s ML Platform: From Chaos to Cutting-Edge (Part 1)

      · 11 min read
      Adarsha Das
      Senior Architect @ Meesho
      Aditya Kumar
      Lead Software Engineer @ Meesho
      Bhawani Singh
      Architect @ Meesho
      Jigar Dave
      Lead Software Engineer @ Meesho

      BharatMLStack +It all started in early 2022, over a casual Friday evening catch-up. Like many great origin stories, this one began with friendly banter between a group of backend engineers and data scientists. As the conversations unfolded, so did the roasting—until one remark hit a little too close to home:

      "Why are we still crunching data for Monthly Active Users (MAU) when the next day it’s all about Daily Active Users (DAU)?"

      The laughter died down, and the question lingered. When we regrouped on Monday—clear-headed and slightly reflective—we decided to dig into the numbers. What they discovered was quite revealing: a large portion of compute resources wasn’t being put to good use. Much of the system’s effort was spent supporting users who weren’t actively engaging, and even for new users, the experience wasn’t optimized to make a meaningful impact.

      @@ -119,7 +570,7 @@

      feature_1_value,feature_2_value,feature_3_value;expiry_ts

      +
      feature_1_value,feature_2_value,feature_3_value;expiry_ts

      This format allowed:

      • Consistent writes and reads at the group level
      • @@ -182,7 +633,7 @@

        Why Redis?

        Storage Structure

        Each user’s interactions were stored using a composite key format, uniquely identifying the user and interaction type. This structure allowed efficient organization and quick retrieval of recent activity for recommendation generation:

        -
        userId_eventType → ZSET[...(pid, ts)...]
        +
        userId_eventType → ZSET[...(pid, ts)...]

        Within each ZSET:

      +

      This foundational work laid the path for a reliable and scalable real-time feature serving layer.

      \ No newline at end of file diff --git a/docs/blog/tags/memory/index.html b/docs/blog/tags/memory/index.html new file mode 100644 index 00000000..df8215d6 --- /dev/null +++ b/docs/blog/tags/memory/index.html @@ -0,0 +1,21 @@ + + + + + +One post tagged with "memory" | BharatMLStack + + + + + + + + +

      One post tagged with "memory"

      View All Tags

      Beyond Vector RAG: Building Agent Memory That Learns From Experience.

      · 12 min read
      Adarsha Das
      Senior Architect @ Meesho

      BharatMLStack +Agent memory has come a long way. Persistent context, vector retrieval, knowledge graphs — the building blocks are real and getting better fast.

      +

      But most of what we call "memory" today is still closer to search: chunk text, embed it, retrieve whatever looks similar at query time. That works well for recalling facts and preferences. It starts to break down when you need an agent to recall what happened last time, learn from a mistake, or avoid repeating a failed approach.

      +

      We are trying to experiment something different. An episodic memory system where a frozen LLM — same weights, no retraining — produces increasingly better decisions over time because the memory feeding it context is continuously evolving. +Then we tested it. The results were interesting.

      + + \ No newline at end of file diff --git a/docs/blog/tags/mlplatform/index.html b/docs/blog/tags/mlplatform/index.html index ec91f7f9..55ef3368 100644 --- a/docs/blog/tags/mlplatform/index.html +++ b/docs/blog/tags/mlplatform/index.html @@ -3,18 +3,469 @@ -One post tagged with "mlplatform" | BharatMLStack - - - +5 posts tagged with "mlplatform" | BharatMLStack + + + -

      One post tagged with "mlplatform"

      View All Tags

      Building Meesho’s ML Platform: From Chaos to Cutting-Edge (Part 1)

      · 11 min read
      Adarsha Das
      Senior Architect @ Meesho
      Aditya Kumar
      SDE-III @ Meesho
      Bhawani Singh
      SDE-IV @ Meesho
      Jigar Dave
      SDE-IV @ Meesho

      BharatMLStack

      -

      The Genesis: How a Friday Night Roast Sparked Meesho’s ML Platform

      -

      It all started in early 2022, over a casual Friday evening catch-up. Like many great origin stories, this one began with friendly banter between a group of backend engineers and data scientists. As the conversations unfolded, so did the roasting—until one remark hit a little too close to home:

      +

      5 posts tagged with "mlplatform"

      View All Tags

      LLM Inference Optimization Techniques: Engineering Sub-Second Latency at Scale

      · 5 min read
      Jaya Kumar
      Lead ML Engineer @ Meesho

      BharatMLStack +Raw execution of Large Language Models is inherently expensive and memory-intensive. To achieve sub-second latency and high throughput, we implement a multi-layered optimization strategy that targets the entire inference stack—from memory management to kernel execution.

      +

      1. Advanced Memory Management: Paged & Prefix KV Caching

      +

      The most significant bottleneck in LLM inference is not always compute, but memory bandwidth—specifically managing the Key-Value (KV) cache.

      +

      Paged KV caching

      +

      Standard caching suffers from fragmentation. We use Paged KV caching, which operates similarly to an operating system's virtual memory: the KV cache is divided into non-contiguous blocks. This lets us serve larger batch sizes without running out of memory.

      +

      KV cache quantization

      +

      To further maximize available memory, we implement KV cache quantization (e.g., FP8). By compressing stored attention keys and values from 16-bit to 8-bit, we nearly double the effective context window capacity of the GPU, allowing longer conversations or larger batches without materially degrading quality.

      +

      Prefix caching (the "voice bot" optimizer)

      +

      For use cases like GenAI voice bots where the system prompt (e.g., "You are a helpful assistant...") is static across thousands of requests, we enable prefix caching.

      +
        +
      • Impact: By reusing pre-computed KV states for common prefixes, we achieve a cache hit rate of ~90%. This reduces Time To First Token (TTFT) by skipping redundant computation of the system prompt.
      • +
      +

      2. Aggressive Quantization (INT4 AWQ & FP8)

      +

      Running models in their native 16-bit precision (BF16) restricts maximum batch size and throughput. We use quantization to shrink model weights without sacrificing accuracy.

      +

      INT4 AWQ (Activation-aware Weight Quantization)

      +

      For the Llama 3 family, we use AWQ to compress weights to 4 bits. This reduces model size by ~75%, allowing larger models to fit into L4 GPU memory and significantly improving token generation speed.

      +

      FP8 precision

      +

      For NVIDIA Hopper (H100) architectures, we are exploring FP8 quantization, leveraging native FP8 tensor cores to accelerate matrix multiplications while maintaining a higher dynamic range than integer quantization.

      +
        +
      • Verification: We validate quantized models by comparing dot-product similarity of embeddings against the FP16 baseline, consistently achieving >99% similarity.
      • +
      +

      3. Kernel Fusion & Custom Plugins

      +

      To minimize overhead from launching thousands of small GPU operations, we fuse them into monolithic kernels using NVIDIA TensorRT plugins.

      +
        +
      • Flash attention & FMHA: We enable Fused Multi-Head Attention (FMHA) combined with flash attention to reduce memory reads/writes.
      • +
      • GEMM plugins: We use specialized GEMM plugins to accelerate transformer linear layers.
      • +
      • Removing input padding: Instead of padding short sequences to match the longest, we remove input padding so the GPU processes only valid tokens.
      • +
      +

      4. Inflight (Continuous) Batching

      +

      Traditional static batching waits for all requests in a batch to finish before returning results—so one long response delays everyone else.

      +

      We implement inflight batching: as soon as one request completes, its slot is freed and filled by a new request from the queue. This keeps GPUs saturated and decouples latency of short queries from long ones.

      +

      5. Parallelism Strategies: Scaling Beyond One GPU

      +

      For large models (e.g., 70B+ parameters) that cannot fit into the VRAM of a single GPU, we use parallelism strategies.

      +
        +
      • Tensor parallelism (TP): Split weight matrices across multiple GPUs (e.g., 4× L4 or 8× A100). Each GPU computes a shard and outputs are reduced at every layer.
      • +
      • Pipeline parallelism (PP): Split model layers across GPUs to pipeline compute (e.g., while one GPU computes later layers for Request A, another starts early layers for Request B).
      • +
      +

      6. Speculative Decoding

      +

      To reduce inter-token latency (ITL), we explore speculative decoding.

      +
        +
      • Mechanism: A smaller, faster "draft" model speculatively generates a short token sequence (e.g., 5 tokens).
      • +
      • Verification: The larger target model verifies those tokens in one parallel forward pass. If correct, we effectively generate multiple tokens per large-model step; if not, we discard and regenerate. This is effective for predictable text, improving perceived generation speed.
      • +
      +

      Few Benchmarks

      +

      Below are a couple of representative use cases and performance numbers.

      +

      Search query rewriting

      +
        +
      • LLM: Fine-tuned llama-3.2-1B
      • +
      • Input & output token length: ~10–20
      • +
      • Response type: Non-streaming
      • +
      +
      Inference runtimeHardwareMax requests/secMax p99 latency
      TensorRT-LLM4 × L4 GPUs (multi-GPU)100095 ms
      TensorRT-LLM1 × A100 40 GB GPU100069 ms
      +

      Voice bot query

      +
        +
      • LLM: Llama-3.1-8B
      • +
      • Input token length: ~1900–2000
      • +
      • Output token length: ~200
      • +
      • Response type: Streaming
      • +
      +
      Inference runtimeConcurrencyp99 TTFT (ms)p99 ITL (ms)Token throughput (tokens/sec)Request throughput (req/sec)Hardware
      TensorRT-LLM136.2722.7845.660.23L4
      TensorRT-LLM249.8123.2189.370.45L4
      TensorRT-LLM455.3336.62153.390.78L4
      TensorRT-LLM866.539.11279.881.47L4
      TensorRT-LLM16131.830.39547.82.77L4
      TensorRT-LLM32277.2248.02925.74.78L4
      TensorRT-LLM64498.5271.621,164.406.2L4
      TensorRT-LLM128677.31120.371,445.187.69L4
      TensorRT-LLM2561,926.31216.881,600.818.52L4
      TensorRT-LLM121.179.24130.050.68A100
      TensorRT-LLM225.789.21264.51.35A100
      TensorRT-LLM428.5210.99437.692.27A100
      TensorRT-LLM834.412.61760.493.96A100
      TensorRT-LLM1668.0314.321,343.807.01A100
      TensorRT-LLM32185.9616.822,287.3011.92A100
      TensorRT-LLM64136.8721.173,625.2218.89A100
      TensorRT-LLM128463.7834.154,456.5123.24A100
      TensorRT-LLM256890.1259.185,188.2427.05A100
      +

      Conclusion

      +

      High-performance LLM inference is fundamentally a systems engineering problem: memory efficiency, kernel execution, batching strategy, and parallelism determine real-world latency and throughput. Techniques such as paged KV caching, aggressive quantization, kernel fusion, and inflight batching improve GPU utilization while reducing latency and memory pressure.

      +

      These optimizations enable the platform to deliver sub-second responses, sustain high concurrency, and efficiently serve both lightweight and long-context workloads. By continuously optimizing across the full inference stack, we keep LLM serving scalable, cost-efficient, and production-ready for real-time AI applications.

      Designing a Production-Grade LLM Inference Platform: From Model Weights to Scalable GPU Serving

      · 14 min read
      Jaya Kumar
      Lead ML Engineer @ Meesho

      BharatMLStack +Serving large language models in production introduces new challenges across infrastructure, performance optimization, and operational lifecycle management. The LLM Inference Platform addresses these challenges by providing a unified system for deploying and managing open-source and fine-tuned LLMs at scale.

      +

      The platform implements a complete LLMOps lifecycle — from model registration and automated compilation to deployment, runtime optimization, and monitoring. Designed as a self-service environment, users can onboard models directly from open repositories such as Hugging Face or upload custom fine-tuned models, and deploy them using a single-click workflow with no manual infrastructure or configuration steps required.

      +

      In addition to fully automated deployment, the platform allows users to select and apply custom inference optimization techniques — such as quantization strategies, batching configurations, and runtime-specific performance enhancements — enabling teams to balance latency, throughput, and cost based on their use case. The goal is to reduce operational friction while enabling high-performance, production-grade LLM inference.

      +

      Why LLM Inference Is not just bigger ML model serving

      +

      Large language model (LLM) inference introduces a fundamentally different set of challenges compared to traditional machine learning inference. While classical ML models typically perform a single forward pass to produce a fixed prediction, LLMs operate as autoregressive systems, generating outputs token by token based on previously generated context. This difference dramatically changes how inference systems must be designed, optimized, and scaled.

      +

      Autoregressive Generation and Sequential Computation:

      +

      Unlike traditional models such as classifiers or recommenders — where inference cost is relatively constant — LLMs generate responses incrementally. Each new token depends on all previously generated tokens, making inference inherently sequential and dynamic. This means latency and compute requirements vary significantly depending on prompt length and output size, introducing complexity in scheduling and resource allocation. +Because tokens cannot be generated fully in parallel during decoding, GPUs may become underutilized without specialized batching and scheduling strategies. This has led to the development of dedicated LLM inference engines optimized for token-level execution.

      +

      Prefill and Decode Phases:

      +

      LLM inference typically consists of two distinct stages:

      +
        +
      • Prefill phase — the model processes the input prompt and builds internal representations. This stage is compute-heavy and highly parallelizable.
      • +
      • Decode phase — the model generates tokens sequentially, predicting one token at a time using previously generated context.
      • +
      +

      The decode stage often becomes memory-bound rather than compute-bound, which creates new performance bottlenecks compared to traditional ML workloads.

      +

      Context Management and KV Caching:

      +

      Another fundamental difference lies in how LLMs maintain context. Transformer-based models rely on attention mechanisms that require access to past token representations. To avoid recomputing these representations repeatedly, inference engines use key-value (KV) caching, which stores intermediate activations from previous tokens. +KV caching significantly improves performance by eliminating redundant computation, but it introduces new challenges:

      +
        +
      • Memory consumption grows with sequence length and batch size
      • +
      • GPU memory becomes a critical bottleneck
      • +
      • Efficient memory management becomes essential for scaling concurrent requests
      • +
      +

      This tradeoff between compute efficiency and memory usage is unique to LLM inference workloads.

      +

      Dynamic and Irregular Workloads:

      +

      Traditional ML inference typically operates on fixed-size inputs with predictable latency. In contrast, LLM requests vary widely in prompt length, output length, and runtime behavior. As a result:

      +
        +
      • Batch sizes must be dynamic rather than static
      • +
      • Requests may enter and leave batches asynchronously
      • +
      • Scheduling systems must continuously rebalance workloads to maximize GPU utilization
      • +
      +

      These characteristics require specialized serving architectures that differ significantly from standard ML serving pipelines.

      +

      Streaming and User Experience Constraints:

      +

      Another distinguishing factor is the expectation of real-time streaming responses. Instead of returning a single output, LLM systems often stream tokens to users as they are generated. +Because of these differences — sequential generation, growing memory requirements, dynamic workloads, and streaming constraints — LLM inference cannot be treated as a simple extension of existing ML serving systems. Production platforms must incorporate specialized runtime engines, advanced optimization techniques, and observability tailored specifically to LLM workloads.

      +

      LLMOps: High-Level Architecture

      +

      LLM Architecture

      +

      The LLM Inference Framework is designed as a fully automated, end-to-end system for deploying and operating open-source and fine-tuned large language models at scale. The architecture abstracts the complexity of model optimization, hardware selection, deployment, and runtime management into a unified workflow that enables users to move from raw model weights to production-ready inference endpoints with minimal manual intervention.

      +

      Our LLM Inference Framework is architected not just as a serving engine, but as a complete lifecycle management system. As illustrated in the high-level design below, the platform automates the journey of a model through seven distinct stages, ensuring reproducibility, performance, and scalability.

      +
        +
      1. +

        Onboarding & Registration (The Source of Truth)

        +

        The lifecycle begins with the Data Scientist or engineer.

        +
          +
        • Model Ingestion: Users onboard models—whether open-source (Hugging Face, NeMo) or internally fine-tuned—via the Truffle Box SDK/UI.
        • +
        • LLM + Prompt Registry: Unlike traditional systems that only track model weights, our registry is a unified control plane. It stores both the Model Artifacts and the Prompt Templates. This allows Data Scientists to register and version-control prompts (e.g., "customer_support_v2") independently of the application code.
        • +
        +
      2. +
      3. +

        The "Black Box" Build Engine

        +

        Once a model is registered, the Automated LLM Compiler + Quantizer Module kicks off a background job on ephemeral GPU resources.

        +
          +
        • Transformation: The raw model is converted into a TRT-LLM Checkpoint.
        • +
        • Quantization: The system automatically applies quantization algorithms (like INT4 AWQ or FP8) to reduce memory footprint.
        • +
        • Engine Building: Finally, it compiles a highly optimized TRT Engine specifically tuned for the target hardware.
        • +
        +
      4. +
      5. +

        Intelligent Profiling & Validation

        +

        Before deployment, the new engine passes through the Hardware & Inference Runtime Profiler.

        +
          +
        • Benchmarking: This module empirically tests the engine against various hardware configurations (L4 vs. A100) and runtimes (TRT-LLM vs. vLLM).
        • +
        • Optimization: It recommends the optimal configuration that meets latency SLAs (Time-To-First-Token) while minimizing cost.
        • +
        +
      6. +
      7. +

        Smart Artifact Generation & Distribution

        +

        To solve the Kubernetes "Cold Start" problem, the LLM Serving Artifacts Generation module packages the model using a bifurcated strategy:

        +
          +
        • Standard Models: Artifacts are uploaded to Cloud Storage (GCS) and downloaded by pods at startup.
        • +
        • Very Large Models: For massive models (>8GB) where network downloads are too slow, the system pre-caches the model onto Secondary Boot Disks. These disks are attached directly to new GPU nodes during autoscaling, eliminating download wait times.
        • +
        +
      8. +
      9. +

        Image Streaming & Deployment

        +

        Simultaneously, the inference runtime container images are pulled from the Artifact Registry.

        +
          +
        • Image Streaming: We utilize container image streaming to allow pods to start initializing while the massive Triton/Dynamo container layers are still downloading, further shaving seconds off the startup time. link
        • +
        +
      10. +
      11. +

        The Inference Runtime (Kubernetes)

        +

        The workload lands on Kubernetes with Autoscaling.

        +
          +
        • Dynamic Backends: Depending on the profile generated in Stage 3, the pod initializes either TensorRT-LLM (for throughput) or vLLM (for flexibility), or spins up a Dynamo worker for distributed inference.
        • +
        • Data Loading: The pod either downloads the model from Cloud Storage or mounts the pre-warmed Secondary Boot Disk ("Pull from Disk").
        • +
        +
      12. +
      13. +

        Client Interaction & Observability

        +

        Finally, the LLM Inference Client executes the request.

        +
          +
        • Prompt Injection: The client pulls the specific prompt template ID from the Registry, ensuring the exact versioned instructions are used.
        • +
        • Streaming Response: The request is sent via gRPC, and tokens are streamed back to the user in real-time.
        • +
        +
      14. +
      15. +

        Observability: Monitoring the Pulse of GenAI

        +

        In traditional microservices, success is measured by CPU utilization and request latency (p99). For Large Language Models, these metrics are insufficient. A user doesn't care if the GPU is at 80% utilization; they care about how fast the first word appears and how smoothly the rest of the sentence follows.

        +

        To capture the true user experience, our platform instrumentation focuses on three critical LLM-specific metrics:

        +
          +
        1. +

          Time to First Token (TTFT)

          +
            +
          • Definition: TTFT measures the time elapsed from the moment a request is received until the very first token is generated and streamed back to the user.
          • +
          • Why it matters: This represents the "Prefill Phase" latency—the time the model takes to process the input prompt and load weights. A high TTFT makes the application feel unresponsive or "hung."
          • +
          • Optimization: We closely monitor TTFT to ensure our Prefix Caching is effective (aiming for high cache hitrates), which drastically lowers this metric by skipping redundant prompt processing.
          • +
          +
        2. +
        3. +

          Inter-Token Latency (ITL)

          +
            +
          • Definition: ITL measures the average time interval between the generation of consecutive tokens during the "Decode Phase".
          • +
          • Why it matters: This defines the "perceived speed" of reading. Even if the first token is fast (low TTFT), high ITL makes the text generation look "jerky" or slow to the user.
          • +
          • Benchmarks: In our testing with Llama 3.1, we track p99 ITL to ensure it stays below human reading speeds to maintain a natural conversational flow.
          • +
          +
        4. +
        5. +

          Token Throughput vs. Request Throughput

          +
            +
          • We distinguish between two types of throughput to balance system efficiency with user load:
          • +
          • Token Throughput (tokens/sec): The total number of tokens generated across all concurrent requests. This measures the raw compute efficiency of the GPU and the effectiveness of batching.
          • +
          • Request Throughput (req/sec): The number of distinct user queries served per second. We use this to determine autoscaling thresholds, ensuring we scale out before the queue depth impacts ITL.
          • +
          +
        6. +
        7. +

          The Monitoring Stack

          +
            +
          • Real-time Dashboards: We utilize Grafana to visualize these streaming metrics in real-time, allowing on-call engineers to spot "slow generation" incidents that generic "500 error" alerts would miss.
          • +
          • Request Tracing: Since Triton Inference Server does not log request payloads by default, we integrate a Helix Client to asynchronously publish request logs to Log Tables. This allows us to trace a specific "slow" request back to its prompt to understand if a complex input caused the latency spike.
          • +
          +
        8. +
        +
      16. +
      +

      Supported Inference backends (TensorRT LLM, Dynamo & vLLM)

      +

      Tailored for the Use Case: We do not believe in a "one-size-fits-all" approach to inference. Different use cases—whether a real-time voice bot requiring ultra-lowsub-second latency or a massive reasoning task requiring huge context windows—demand different runtime characteristics. Our platform is designed to be runtime-agnostic, allowing us to automatically select and tailor the best engine based on the specific requirements of the application:

      +
        +
      1. +

        TensorRT-LLM: The High-Performance Standard

        +

        Suitable for: High-throughput production workloads where latency is critical (e.g., customer support chat, real-time voice bots).

        +

        TensorRT-LLM serves as our default backend for these scenarios. Our internal benchmarks on Llama 3.1 and 3.2 models demonstrated that a tuned TensorRT-LLM engine significantly outperforms standard runtimes, especially when utilizing INT4 AWQ and FP8 quantization .

        +

        Key optimizations we tailor for these high-load cases include:

        +
          +
        • Optimized execution via TensorRT engine compilation
        • +
        • Quantization-aware execution for reduced memory usage and improved throughput
        • +
        • Inflight Batching: Allowing requests to be processed continuously without waiting for the entire batch to finish, drastically improving GPU utilization .
        • +
        • Custom Plugins: Enabling specific NVIDIA plugins like the GEMM plugin and GPT Attention plugin to accelerate matrix multiplications and attention mechanisms .
        • +
        +
      2. +
      3. +

        Dynamo: Distributed Inference for Reasoning Models

        +

        Suitable for: Very large "reasoning" models (70B+) or scenarios requiring massive context windows where a single GPU's memory is insufficient.

        +

        For these memory-bound tasks, we utilize Dynamo, a low-latency distributed inference framework . Unlike monolithic servers, Dynamo disaggregates the inference process to scale resources horizontally:

        +
          +
        • KV Aware Routing: A specialized router directs requests to workers that already hold the relevant Key-Value (KV) cache, minimizing redundant computation .
        • +
        • Prefill vs. Decode Split: The workload is divided into Prefill Workers (processing the prompt) and Decode Workers (generating tokens), allowing us to scale the compute-heavy "reading" phase independently from the memory-heavy "writing" phase .
        • +
        • Distributed execution across multiple GPU resources
        • +
        +
      4. +
      5. +

        vLLM: The Flexible Baseline

        +

        Suitable for: Rapid prototyping, testing new model architectures, or low-traffic internal tools where ease of deployment outweighs raw throughput.

        +

        While TensorRT-LLM is optimized for maximum speed, vLLM provides a robust and flexible baseline .

        +
          +
        • High throughput through dynamic batching and efficient memory utilization
        • +
        • Paged KV cache management for handling long contexts and concurrent requests
        • +
        • Strong support for open-source model ecosystems
        • +
        • Rapid Adoption: It allows us to onboard new model architectures immediately without waiting for a custom TensorRT build.
        • +
        • Benchmarking Insight: In our internal tests, vLLM provided a strong baseline but often lacked the specific max-token optimizations present in our custom TRT engines . We use it strategically for initial testing before committing to a full TensorRT optimization pipeline.
        • +
        +
      6. +
      +

      Conclusion

      +

      Large language model inference introduces a fundamentally new class of infrastructure challenges—where performance is governed not just by raw compute, but by memory efficiency, intelligent scheduling, runtime specialization, and lifecycle automation. Unlike traditional ML serving, LLM inference requires systems that understand token-level execution, manage rapidly growing context state, and continuously balance latency, throughput, and cost under highly dynamic workloads.

      +

      The LLM Inference Framework addresses these challenges by transforming inference into a fully automated, reproducible lifecycle—from model onboarding and compilation to deployment, optimization, and observability. By integrating automated quantization and engine compilation, intelligent runtime selection, cold-start mitigation strategies, and LLM-specific observability metrics such as Time-to-First-Token and Inter-Token Latency, the platform ensures both high performance and operational simplicity.

      +

      Equally important, the framework is designed with flexibility and future evolution in mind. Its runtime-agnostic architecture enables seamless adoption of emerging inference engines, hardware accelerators, and optimization techniques without requiring platform redesign. This ensures that teams can continuously leverage advancements in the rapidly evolving LLM ecosystem while maintaining consistent operational workflows.

      +

      Ultimately, the goal of the platform is to make production-scale LLM deployment as seamless and reliable as traditional software deployment—allowing teams to focus on building intelligent applications rather than managing infrastructure complexity. By combining lifecycle automation, runtime optimization, and deep observability, the LLM Inference Framework provides a scalable foundation for delivering fast, cost-efficient, and production-ready LLM experiences.

      +

      Future Explorations

      +

      While we have achieved significant milestones in latency and throughput, the landscape of GenAI is evolving rapidly. Our roadmap focuses on increasing flexibility, reducing costs, and enhancing reliability for enterprise-grade workloads. Here is what we are building next:

      +
        +
      • TPU Support: To diversify our hardware supply chain and further optimize cost-per-token, we are evaluating Google Cloud TPUs to bake it into our platform. By leveraging the JAX and PyTorch/XLA ecosystems, we aim to unlock the massive throughput potential of TPU v5e chips, particularly for our open-source Llama models. This will allow the hardware profiler to dynamically choose between NVIDIA GPUs and Google TPUs based on real-time availability and price-performance metrics.
      • +
      • Multi-LoRA Serving (Serverless Experience): Currently, deploying a fine-tuned model requires a dedicated GPU. We are building support for Multi-LoRA serving, which will allow us to serve hundreds of unique, fine-tuned adapters on top of a single frozen base model. This will drastically reduce costs for multi-tenant applications, enabling a "serverless" experience where specific fine-tunes are hot-swapped instantly per request.
      • +
      • Spot Instance Orchestration: To further optimize cloud costs, we are developing fault-tolerant mechanisms to run inference workloads on Spot Instances. By implementing aggressive checkpointing and seamless request draining, we aim to leverage cheaper, preemptible compute capacity without interrupting the user's streaming experience.
      • +
      • Semantic Caching Layer: We plan to move beyond standard Prefix Caching to implement Semantic Caching. By using a vector database to fetch responses for semantically similar queries (e.g., "How do I reset my password?" vs. "Password reset steps"), we can bypass the GPU entirely for repetitive queries, reducing latency to near-zero.
      • +
      • Context-Aware Autoscaling: Standard CPU/GPU utilization metrics are often insufficient signals for scaling LLMs. We are working on KV-cache pressure metrics for autoscaling. This ensures that we scale out before the memory fills up, preventing eviction-based slowdowns during traffic spikes.
      • +
      • Online Evaluation & Guardrails: We are integrating a lightweight "Trust Layer" into the proxy. This will allow for low-latency input/output filtering (Guardrails) and asynchronous "LLM-as-a-Judge" evaluation pipelines to monitor response quality in production, not just system health.
      • +

      Cracking the Code: Scaling Model Inference & Real-Time Embedding Search

      · 4 min read
      Aditya Kumar
      Lead Software Engineer @ Meesho
      Jaya Kumar
      Lead ML Engineer @ Meesho
      Adarsha Das
      Senior Architect @ Meesho

      BharatMLStack +By mid-2023, we had transformed our ML stack—building a real-time feature store, optimizing model retrieval, and fine-tuning ranking. But two critical gaps remained:

      +
        +
      • 🔹 Scaling model inference without hitting infrastructure roadblocks
      • +
      • 🔹 Moving embedding search from batch to real-time for candidate generation
      • +
      +

      Here’s how we tackled these last-mile challenges, broke free from infrastructure constraints, and built a cost-efficient, high-performance system.

      +

      Breaking Free from the Scalability Ceiling

      +

      The Model Serving Bottleneck—A Wake-Up Call

      +

      July 2023. With just months left for the Mega Blockbuster Sale (MBS), we noticed a serious issue—scaling our model-serving infrastructure was taking 10–15 minutes. In real-time ML, that’s an eternity. +In one of our war rooms, we ran a quick experiment:

      +
        +
      • 🚀 We deployed an XGBoost model on a self-hosted Triton Inference Server running on a 16-core machine.
      • +
      • 🚀 Fired requests and compared the outputs with our existing cloud-hosted setup.
      • +
      • 🚀 The results matched—perfectly.
      • +
      +

      That moment changed everything. We prepped a backup Triton setup on EKS, just in case our cloud provider couldn't allocate enough compute resources in time. Luckily, they did—but the seed was planted. +Then in October, just two weeks before MBS, we got an alarming response from our infrastructure team: +"Node availability may be an issue." +With no time to waste, we moved 30% of real-time ML traffic to our self-hosted Triton cluster. The results?

      +
        +
      • ✅ p99 latency dropped from 90–100ms to 30–40ms
      • +
      • ✅ Triton handled significantly higher throughput on fewer resources
      • +
      • ✅ No model changes were needed
      • +
      +

      MBS ran without a hitch, proving that self-hosted inference was the way forward.

      +

      Scaling Triton on GKE

      +

      This left us with two choices:

      +
        +
      • 1️⃣ Port models to a managed cloud inference service, investing time in learning a new deployment stack
      • +
      • 2️⃣ Scale our existing Triton setup on GKE, optimizing for cost and performance
      • +
      +

      We went with Option 2—and it slashed inference costs to 35% of what we previously paid, while giving us full control over scaling and optimizations.

      +

      Fixing the Cold Start Problem

      +

      As we onboarded more deep learning (DL) models, we hit a new bottleneck, new inference pods took 7–9 minutes to spin up.

      +

      After profiling, we found the culprits:

      +
        +
      • Triton’s base image—a massive 5GB
      • +
      • Model binaries—often 1GB+
      • +
      • Startup delay—mostly due to downloading and initializing these assets
      • +
      +

      To fix this, we built a lightweight Triton image, stripping unused components and shrinking the size to 900MB. This cut cold start times drastically, making auto-scaling faster and smoother.

      +

      Embedding Search: The Last Piece of the Puzzle

      +

      By mid-2023, most of our ML stack had gone real-time—except for Candidate Generation (CG), which still ran in batch mode. To truly power real-time recommendations, we needed an online embedding search system.

      +

      Choosing the Right Vector Database

      +

      We benchmarked three production-ready vector DBs across key parameters:

      +
        +
      • Milvus
      • +
      • Qdrant
      • +
      • Weaviate
      • +
      +

      After extensive POCs, Qdrant stood out for its:

      +
        +
      • ✅ Blazing-fast search latency on high-dimensional vectors
      • +
      • ✅ Efficient memory usage, crucial for in-memory workloads
      • +
      • ✅ Support for upserts and soft deletes, vital for Ads use cases
      • +
      • ✅ gRPC + REST APIs, making integration seamless
      • +
      • ✅ Powerful filtering, allowing fine-tuned retrieval (e.g., filtering Ads by category, active status, etc.)
      • +
      +

      At its core, Qdrant uses HNSW indexing, delivering both high recall and low-latency nearest-neighbor search—a perfect fit for our needs.

      +

      Embedding Freshness & Real-Time Updates

      +

      To ensure embeddings stayed up to date, we built a dual ingestion pipeline:

      +
        +
      • 📌 Daily Refresh: A bulk pipeline updated embeddings overnight
      • +
      • 📌 Real-Time Updates: Ads events triggered immediate upserts/deletes
      • +
      +

      This setup powered real-time "Similar Products" recommendations on the product page and became the foundation for Ads Candidate Generation, ensuring the right ads surfaced in milliseconds.

      +

      Skye

      +

      Final Takeaways: Scaling Smartly for Real-Time ML

      +
        +
      • 🚀 Self-hosted inference on Triton gave us lower cost, faster scaling, and better performance than managed services
      • +
      • 🚀 Building a custom Triton image reduced cold starts, improving responsiveness
      • +
      • 🚀 Qdrant-based embedding search enabled real-time personalization at scale
      • +
      • 🚀 Real-time updates for embeddings unlocked dynamic, up-to-date recommendations
      • +
      +

      By early 2024, Meesho’s ML stack had evolved into a fully real-time, scalable, and cost-efficient system, setting the foundation for even bigger leaps ahead.

      Building Meesho’s ML Platform: Lessons from the First-Gen System (Part 2)

      · 7 min read
      Bhawani Singh
      Architect @ Meesho
      Jigar Dave
      Lead Software Engineer @ Meesho
      Adarsha Das
      Senior Architect @ Meesho

      BharatMLStack +By late 2022, we had built something we were truly proud of—a real-time ML serving system with a DAG-based executor, a feature store, and an interaction store powering key ranking and personalization models. It was a major milestone, the culmination of months of effort from data scientists, ML engineers, and backend teams. Our system was live, and we were ready to push the boundaries of experimentation. +And it worked. Mostly. +But soon, cracks appeared. Every new model needed custom feature retrieval logic, DAGs became dense and unmanageable, and scaling turned into a constant firefight. Costs surged, and infra bottlenecks slowed experimentation. Our system worked, but it wasn’t built for scale. +This is the story of how we tackled these challenges—building Inferflow for seamless feature retrieval, optimizing real-time infra, and cutting costs while scaling to millions of QPS.

      +

      The Cost of Success

      +

      Every new Ranker model required its own feature set, often pulling from different entities. Each addition meant:

      +
        +
      • Adding new DAG nodes in IOP
      • +
      • Writing custom logic to fetch features from multiple sources (e.g., user, product, user × category)
      • +
      • Inferring intermediate features (e.g., extracting category from a product to fetch user × category data)
      • +
      • Optimizing I/O and dealing with the inevitable bugs
      • +
      +

      What began as clean DAGs soon turned into a tangled web of cross-dependent graphs. Every experimentation cycle meant new nodes, new dependencies, and slower iterations.

      +

      Scaling Pains (and Cassandra’s Limits)

      +

      At some point, we were hitting:

      +
        +
      • 250–300K reads/sec
      • +
      • 1M writes/sec (during lean hours)
      • +
      +

      All of this ran on Cassandra. While its distributed architecture had been proven in production, operating large-scale clusters came with considerable infrastructure overhead. Our proof-of-concept (POC) demonstrated throughput of around 100K ops/sec, but as we scaled further, the challenges grew. Ensuring node health, optimizing compaction, and maintaining storage balance became increasingly demanding. We also observed latency spikes under heavy load, alongside a sharp increase in total cost of ownership.

      +

      Interaction Store Woes

      +

      Our interaction store was another ticking time bomb:

      +
        +
      • 🚨 Clusters kept growing in size and cost
      • +
      • 🚨 Latency spikes became increasingly frequent
      • +
      • 🚨 The DMC proxy occasionally lost locality of nodes against shards, causing cross-node communication and degraded performance
      • +
      +

      Each time this happened, we had to manually rebalance shards just to restore stable latency, making operations unsustainable at scale.

      +

      Silver Linings

      +

      Despite the chaos, the system was live and delivering value:

      +
        +
      • Real-time infrastructure was in production
      • +
      • Costs dropped by 60–70% compared to offline personalization
      • +
      • New experiments rolled out faster and more successfully
      • +
      • User engagement metrics improved
      • +
      +

      It wasn’t perfect. It was far from easy. But it worked—and that counted for a lot.

      +

      Round Two: Solving the Top 2 Bottlenecks

      +

      With the first-gen system stretched to its limits, we stepped back. Conversations with data scientists and backend engineers revealed three recurring pain points:

      +
        +
      1. Coding feature retrieval logic for every new model was becoming unsustainable
      2. +
      3. ML scale was exploding—bringing rising infra costs with it
      4. +
      5. Real-time embedding search was the next big unlock
      6. +
      +

      We tackled them one by one—starting with the biggest pain point.

      +

      Problem 1: No-Code Feature Retrieval for Model Inference

      +

      We noticed a pattern: for personalized ranking, models needed features from:

      +
        +
      • ✅ Product
      • +
      • ✅ User
      • +
      • ✅ User × Category
      • +
      • ✅ Region, cohort, sub-category, etc.
      • +
      +

      A key insight emerged: Entities that contribute features for a model always map back to the context entities.

      +

      MP Dag

      +

      With this, we designed Inferflow, a graph-driven feature retrieval and model orchestration system:

      +
        +
      • 1️⃣ Inferflow takes a modelId and context IDs (e.g., userId, productIds)
      • +
      • 2️⃣ Loads a pre-defined feature retrieval graph from ZooKeeper
      • +
      • 3️⃣ Executes the graph to resolve entity relationships dynamically
      • +
      • 4️⃣ Outputs a 2D matrix of feature vectors
      • +
      +

      💡 The impact?

      +
        +
      • 🚀 No more custom feature retrieval code—just graph updates in config
      • +
      • 🚀 Feature consistency across experiments
      • +
      • 🚀 Faster iteration cycles for ranking, fraud detection, and beyond
      • +
      +

      Here’s a visual example that shows how this graph plays out during execution. We further extended the graph to call multiple models as needed: +MP matrix +We built Inferflow in GoLang, using gRPC and Proto3 serialization for efficiency.

      +

      Problem 2: Scaling Without Breaking the Bank

      +

      With more ML use cases coming online, we needed to cut costs without compromising performance. We focused on:

      +
        +
      • 🔹 Online Feature Store
      • +
      • 🔹 Interaction Store
      • +
      +

      Optimizing the Online Feature Store

      +

      Our costs were concentrated in:

      +
        +
      • 📌 Database (Cassandra)
      • +
      • 📌 Cache (Redis)
      • +
      • 📌 Running Pods (Java services)
      • +
      +

      1️⃣ Replacing Cassandra with ScyllaDB +As we hit the operational limits of large Cassandra clusters, we transitioned to ScyllaDB, which offered a seamless drop-in replacement without major code changes. The switch brought significant benefits:

      +
        +
      • Throughput: Matched or exceeded Cassandra's performance under identical workloads, even under high concurrency.
      • +
      • Latency: Achieved consistently lower P99 latencies due to ScyllaDB's shard-per-core architecture and better I/O utilization.
      • +
      • Cost Efficiency: Reduced infra footprint by ~70% through better CPU and memory efficiency, eliminating the need for over-provisioned nodes.
      • +
      +

      2️⃣ Finding the Right Cache +To reduce backend load and improve response times, we benchmarked multiple caching solutions—Memcached, KeyDB, and Dragonfly—under real production traffic patterns. Dragonfly stood out due to its robust architecture and operational simplicity:

      +
        +
      • Data Skew Handling: Efficiently managed extreme key hotness and uneven access patterns without performance degradation.
      • +
      • Throughput: Delivered consistently high throughput, even with large object sizes and concurrent access.
      • +
      • Ease of Adoption: Acted as a drop-in Redis replacement with full protocol compatibility—no changes needed in application code or client libraries.
      • +
      +

      3️⃣ Moving to GoLang for Cost-Efficient Serving +Java services were memory-heavy—so we rewrote core services in GoLang. The results?

      +

      ✅ Memory usage dropped by ~80% +✅ CPU utilization was significantly lower +✅ Faster, more efficient deployments

      +

      Optimizing the Interaction Store

      +

      We realized that we only need a user’s interaction data in Redis when they open the app. So, we implemented a tiered storage approach:

      +
        +
      • 📌 Cold Tier (ScyllaDB)—Stores click, order, wishlist events
      • +
      • 📌 Hot Tier (Redis)—Loads a user’s past interactions only when they open the app
      • +
      +

      Smart Offloading: We introduced an inactivity tracker to detect when a user session ends. At that point, Redis data was flushed back to Scylla, reducing unnecessary writes.

      +

      InteractionStore

      +

      Results

      +
        +
      • Online Feature Store hit 1M QPS for the first time during the 2023 Mega Blockbuster Sale—without breaking a sweat
      • +
      • Infra costs for Online Feature Store and Interaction Store dropped by ~60%
      • +
      +

      The Catch: Our ML Hosting Hit a Hard Limit

      +

      While planning for 2023 MBS, we ran into a critical scalability bottleneck:

      +
        +
      • ❌ Insufficient compute availability in our region for ML instances
      • +
      • ❌ Couldn’t provision enough nodes to handle real-time inference at scale
      • +
      +

      This forced us to rethink where and how we hosted our models. The existing setup was great for prototyping—but it wasn’t built to handle the bursty, high-QPS demands of real-world production workloads.

      +

      Conclusion: From Firefighting to Future-Proofing

      +

      What started as an ambitious experiment turned into a real-time ML infrastructure that powered millions of requests per second. We battled scaling pains, rethought feature retrieval with Inferflow, and rebuilt our infra stack for efficiency—driving down costs while improving experimentation velocity. +But new challenges emerged. Our infrastructure could now handle scale, but our ML model hosting setup hit a hard limit. With compute availability bottlenecks threatening real-time inference, we faced a critical decision: how do we make model serving as scalable and cost-efficient as the rest of our stack? That’s the next piece of the puzzle—and the story of Part 3.

      Building Meesho’s ML Platform: From Chaos to Cutting-Edge (Part 1)

      · 11 min read
      Adarsha Das
      Senior Architect @ Meesho
      Aditya Kumar
      Lead Software Engineer @ Meesho
      Bhawani Singh
      Architect @ Meesho
      Jigar Dave
      Lead Software Engineer @ Meesho

      BharatMLStack +It all started in early 2022, over a casual Friday evening catch-up. Like many great origin stories, this one began with friendly banter between a group of backend engineers and data scientists. As the conversations unfolded, so did the roasting—until one remark hit a little too close to home:

      "Why are we still crunching data for Monthly Active Users (MAU) when the next day it’s all about Daily Active Users (DAU)?"

      The laughter died down, and the question lingered. When we regrouped on Monday—clear-headed and slightly reflective—we decided to dig into the numbers. What they discovered was quite revealing: a large portion of compute resources wasn’t being put to good use. Much of the system’s effort was spent supporting users who weren’t actively engaging, and even for new users, the experience wasn’t optimized to make a meaningful impact.

      @@ -119,7 +570,7 @@

      feature_1_value,feature_2_value,feature_3_value;expiry_ts

      +
      feature_1_value,feature_2_value,feature_3_value;expiry_ts

      This format allowed:

      • Consistent writes and reads at the group level
      • @@ -182,7 +633,7 @@

        Why Redis?

        Storage Structure

        Each user’s interactions were stored using a composite key format, uniquely identifying the user and interaction type. This structure allowed efficient organization and quick retrieval of recent activity for recommendation generation:

        -
        userId_eventType → ZSET[...(pid, ts)...]
        +
        userId_eventType → ZSET[...(pid, ts)...]

        Within each ZSET:

      +

      This foundational work laid the path for a reliable and scalable real-time feature serving layer.

      \ No newline at end of file diff --git a/docs/blog/tags/model-inference/index.html b/docs/blog/tags/model-inference/index.html new file mode 100644 index 00000000..ebe156ec --- /dev/null +++ b/docs/blog/tags/model-inference/index.html @@ -0,0 +1,92 @@ + + + + + +One post tagged with "model-inference" | BharatMLStack + + + + + + + + +

      One post tagged with "model-inference"

      View All Tags

      Cracking the Code: Scaling Model Inference & Real-Time Embedding Search

      · 4 min read
      Aditya Kumar
      Lead Software Engineer @ Meesho
      Jaya Kumar
      Lead ML Engineer @ Meesho
      Adarsha Das
      Senior Architect @ Meesho

      BharatMLStack +By mid-2023, we had transformed our ML stack—building a real-time feature store, optimizing model retrieval, and fine-tuning ranking. But two critical gaps remained:

      +
        +
      • 🔹 Scaling model inference without hitting infrastructure roadblocks
      • +
      • 🔹 Moving embedding search from batch to real-time for candidate generation
      • +
      +

      Here’s how we tackled these last-mile challenges, broke free from infrastructure constraints, and built a cost-efficient, high-performance system.

      +

      Breaking Free from the Scalability Ceiling

      +

      The Model Serving Bottleneck—A Wake-Up Call

      +

      July 2023. With just months left for the Mega Blockbuster Sale (MBS), we noticed a serious issue—scaling our model-serving infrastructure was taking 10–15 minutes. In real-time ML, that’s an eternity. +In one of our war rooms, we ran a quick experiment:

      +
        +
      • 🚀 We deployed an XGBoost model on a self-hosted Triton Inference Server running on a 16-core machine.
      • +
      • 🚀 Fired requests and compared the outputs with our existing cloud-hosted setup.
      • +
      • 🚀 The results matched—perfectly.
      • +
      +

      That moment changed everything. We prepped a backup Triton setup on EKS, just in case our cloud provider couldn't allocate enough compute resources in time. Luckily, they did—but the seed was planted. +Then in October, just two weeks before MBS, we got an alarming response from our infrastructure team: +"Node availability may be an issue." +With no time to waste, we moved 30% of real-time ML traffic to our self-hosted Triton cluster. The results?

      +
        +
      • ✅ p99 latency dropped from 90–100ms to 30–40ms
      • +
      • ✅ Triton handled significantly higher throughput on fewer resources
      • +
      • ✅ No model changes were needed
      • +
      +

      MBS ran without a hitch, proving that self-hosted inference was the way forward.

      +

      Scaling Triton on GKE

      +

      This left us with two choices:

      +
        +
      • 1️⃣ Port models to a managed cloud inference service, investing time in learning a new deployment stack
      • +
      • 2️⃣ Scale our existing Triton setup on GKE, optimizing for cost and performance
      • +
      +

      We went with Option 2—and it slashed inference costs to 35% of what we previously paid, while giving us full control over scaling and optimizations.

      +

      Fixing the Cold Start Problem

      +

      As we onboarded more deep learning (DL) models, we hit a new bottleneck, new inference pods took 7–9 minutes to spin up.

      +

      After profiling, we found the culprits:

      +
        +
      • Triton’s base image—a massive 5GB
      • +
      • Model binaries—often 1GB+
      • +
      • Startup delay—mostly due to downloading and initializing these assets
      • +
      +

      To fix this, we built a lightweight Triton image, stripping unused components and shrinking the size to 900MB. This cut cold start times drastically, making auto-scaling faster and smoother.

      +

      Embedding Search: The Last Piece of the Puzzle

      +

      By mid-2023, most of our ML stack had gone real-time—except for Candidate Generation (CG), which still ran in batch mode. To truly power real-time recommendations, we needed an online embedding search system.

      +

      Choosing the Right Vector Database

      +

      We benchmarked three production-ready vector DBs across key parameters:

      +
        +
      • Milvus
      • +
      • Qdrant
      • +
      • Weaviate
      • +
      +

      After extensive POCs, Qdrant stood out for its:

      +
        +
      • ✅ Blazing-fast search latency on high-dimensional vectors
      • +
      • ✅ Efficient memory usage, crucial for in-memory workloads
      • +
      • ✅ Support for upserts and soft deletes, vital for Ads use cases
      • +
      • ✅ gRPC + REST APIs, making integration seamless
      • +
      • ✅ Powerful filtering, allowing fine-tuned retrieval (e.g., filtering Ads by category, active status, etc.)
      • +
      +

      At its core, Qdrant uses HNSW indexing, delivering both high recall and low-latency nearest-neighbor search—a perfect fit for our needs.

      +

      Embedding Freshness & Real-Time Updates

      +

      To ensure embeddings stayed up to date, we built a dual ingestion pipeline:

      +
        +
      • 📌 Daily Refresh: A bulk pipeline updated embeddings overnight
      • +
      • 📌 Real-Time Updates: Ads events triggered immediate upserts/deletes
      • +
      +

      This setup powered real-time "Similar Products" recommendations on the product page and became the foundation for Ads Candidate Generation, ensuring the right ads surfaced in milliseconds.

      +

      Skye

      +

      Final Takeaways: Scaling Smartly for Real-Time ML

      +
        +
      • 🚀 Self-hosted inference on Triton gave us lower cost, faster scaling, and better performance than managed services
      • +
      • 🚀 Building a custom Triton image reduced cold starts, improving responsiveness
      • +
      • 🚀 Qdrant-based embedding search enabled real-time personalization at scale
      • +
      • 🚀 Real-time updates for embeddings unlocked dynamic, up-to-date recommendations
      • +
      +

      By early 2024, Meesho’s ML stack had evolved into a fully real-time, scalable, and cost-efficient system, setting the foundation for even bigger leaps ahead.

      + + \ No newline at end of file diff --git a/docs/blog/tags/online-feature-store/index.html b/docs/blog/tags/online-feature-store/index.html index 6ae561ea..4174fd89 100644 --- a/docs/blog/tags/online-feature-store/index.html +++ b/docs/blog/tags/online-feature-store/index.html @@ -4,17 +4,16 @@ One post tagged with "online-feature-store" | BharatMLStack - - - + + + -

      One post tagged with "online-feature-store"

      View All Tags

      Building Meesho’s ML Platform: From Chaos to Cutting-Edge (Part 1)

      · 11 min read
      Adarsha Das
      Senior Architect @ Meesho
      Aditya Kumar
      SDE-III @ Meesho
      Bhawani Singh
      SDE-IV @ Meesho
      Jigar Dave
      SDE-IV @ Meesho

      BharatMLStack

      -

      The Genesis: How a Friday Night Roast Sparked Meesho’s ML Platform

      -

      It all started in early 2022, over a casual Friday evening catch-up. Like many great origin stories, this one began with friendly banter between a group of backend engineers and data scientists. As the conversations unfolded, so did the roasting—until one remark hit a little too close to home:

      +

      One post tagged with "online-feature-store"

      View All Tags

      Building Meesho’s ML Platform: From Chaos to Cutting-Edge (Part 1)

      · 11 min read
      Adarsha Das
      Senior Architect @ Meesho
      Aditya Kumar
      Lead Software Engineer @ Meesho
      Bhawani Singh
      Architect @ Meesho
      Jigar Dave
      Lead Software Engineer @ Meesho

      BharatMLStack +It all started in early 2022, over a casual Friday evening catch-up. Like many great origin stories, this one began with friendly banter between a group of backend engineers and data scientists. As the conversations unfolded, so did the roasting—until one remark hit a little too close to home:

      "Why are we still crunching data for Monthly Active Users (MAU) when the next day it’s all about Daily Active Users (DAU)?"

      The laughter died down, and the question lingered. When we regrouped on Monday—clear-headed and slightly reflective—we decided to dig into the numbers. What they discovered was quite revealing: a large portion of compute resources wasn’t being put to good use. Much of the system’s effort was spent supporting users who weren’t actively engaging, and even for new users, the experience wasn’t optimized to make a meaningful impact.

      @@ -119,7 +118,7 @@

      feature_1_value,feature_2_value,feature_3_value;expiry_ts

      +
      feature_1_value,feature_2_value,feature_3_value;expiry_ts

      This format allowed:

      • Consistent writes and reads at the group level
      • @@ -182,7 +181,7 @@

        Why Redis?

        Storage Structure

        Each user’s interactions were stored using a composite key format, uniquely identifying the user and interaction type. This structure allowed efficient organization and quick retrieval of recent activity for recommendation generation:

        -
        userId_eventType → ZSET[...(pid, ts)...]
        +
        userId_eventType → ZSET[...(pid, ts)...]

        Within each ZSET:

      +

      This foundational work laid the path for a reliable and scalable real-time feature serving layer.

      \ No newline at end of file diff --git a/docs/blog/tags/tensorrt-llm/index.html b/docs/blog/tags/tensorrt-llm/index.html new file mode 100644 index 00000000..40061f44 --- /dev/null +++ b/docs/blog/tags/tensorrt-llm/index.html @@ -0,0 +1,268 @@ + + + + + +2 posts tagged with "tensorrt-llm" | BharatMLStack + + + + + + + + +

      2 posts tagged with "tensorrt-llm"

      View All Tags

      LLM Inference Optimization Techniques: Engineering Sub-Second Latency at Scale

      · 5 min read
      Jaya Kumar
      Lead ML Engineer @ Meesho

      BharatMLStack +Raw execution of Large Language Models is inherently expensive and memory-intensive. To achieve sub-second latency and high throughput, we implement a multi-layered optimization strategy that targets the entire inference stack—from memory management to kernel execution.

      +

      1. Advanced Memory Management: Paged & Prefix KV Caching

      +

      The most significant bottleneck in LLM inference is not always compute, but memory bandwidth—specifically managing the Key-Value (KV) cache.

      +

      Paged KV caching

      +

      Standard caching suffers from fragmentation. We use Paged KV caching, which operates similarly to an operating system's virtual memory: the KV cache is divided into non-contiguous blocks. This lets us serve larger batch sizes without running out of memory.

      +

      KV cache quantization

      +

      To further maximize available memory, we implement KV cache quantization (e.g., FP8). By compressing stored attention keys and values from 16-bit to 8-bit, we nearly double the effective context window capacity of the GPU, allowing longer conversations or larger batches without materially degrading quality.

      +

      Prefix caching (the "voice bot" optimizer)

      +

      For use cases like GenAI voice bots where the system prompt (e.g., "You are a helpful assistant...") is static across thousands of requests, we enable prefix caching.

      +
        +
      • Impact: By reusing pre-computed KV states for common prefixes, we achieve a cache hit rate of ~90%. This reduces Time To First Token (TTFT) by skipping redundant computation of the system prompt.
      • +
      +

      2. Aggressive Quantization (INT4 AWQ & FP8)

      +

      Running models in their native 16-bit precision (BF16) restricts maximum batch size and throughput. We use quantization to shrink model weights without sacrificing accuracy.

      +

      INT4 AWQ (Activation-aware Weight Quantization)

      +

      For the Llama 3 family, we use AWQ to compress weights to 4 bits. This reduces model size by ~75%, allowing larger models to fit into L4 GPU memory and significantly improving token generation speed.

      +

      FP8 precision

      +

      For NVIDIA Hopper (H100) architectures, we are exploring FP8 quantization, leveraging native FP8 tensor cores to accelerate matrix multiplications while maintaining a higher dynamic range than integer quantization.

      +
        +
      • Verification: We validate quantized models by comparing dot-product similarity of embeddings against the FP16 baseline, consistently achieving >99% similarity.
      • +
      +

      3. Kernel Fusion & Custom Plugins

      +

      To minimize overhead from launching thousands of small GPU operations, we fuse them into monolithic kernels using NVIDIA TensorRT plugins.

      +
        +
      • Flash attention & FMHA: We enable Fused Multi-Head Attention (FMHA) combined with flash attention to reduce memory reads/writes.
      • +
      • GEMM plugins: We use specialized GEMM plugins to accelerate transformer linear layers.
      • +
      • Removing input padding: Instead of padding short sequences to match the longest, we remove input padding so the GPU processes only valid tokens.
      • +
      +

      4. Inflight (Continuous) Batching

      +

      Traditional static batching waits for all requests in a batch to finish before returning results—so one long response delays everyone else.

      +

      We implement inflight batching: as soon as one request completes, its slot is freed and filled by a new request from the queue. This keeps GPUs saturated and decouples latency of short queries from long ones.

      +

      5. Parallelism Strategies: Scaling Beyond One GPU

      +

      For large models (e.g., 70B+ parameters) that cannot fit into the VRAM of a single GPU, we use parallelism strategies.

      +
        +
      • Tensor parallelism (TP): Split weight matrices across multiple GPUs (e.g., 4× L4 or 8× A100). Each GPU computes a shard and outputs are reduced at every layer.
      • +
      • Pipeline parallelism (PP): Split model layers across GPUs to pipeline compute (e.g., while one GPU computes later layers for Request A, another starts early layers for Request B).
      • +
      +

      6. Speculative Decoding

      +

      To reduce inter-token latency (ITL), we explore speculative decoding.

      +
        +
      • Mechanism: A smaller, faster "draft" model speculatively generates a short token sequence (e.g., 5 tokens).
      • +
      • Verification: The larger target model verifies those tokens in one parallel forward pass. If correct, we effectively generate multiple tokens per large-model step; if not, we discard and regenerate. This is effective for predictable text, improving perceived generation speed.
      • +
      +

      Few Benchmarks

      +

      Below are a couple of representative use cases and performance numbers.

      +

      Search query rewriting

      +
        +
      • LLM: Fine-tuned llama-3.2-1B
      • +
      • Input & output token length: ~10–20
      • +
      • Response type: Non-streaming
      • +
      +
      Inference runtimeHardwareMax requests/secMax p99 latency
      TensorRT-LLM4 × L4 GPUs (multi-GPU)100095 ms
      TensorRT-LLM1 × A100 40 GB GPU100069 ms
      +

      Voice bot query

      +
        +
      • LLM: Llama-3.1-8B
      • +
      • Input token length: ~1900–2000
      • +
      • Output token length: ~200
      • +
      • Response type: Streaming
      • +
      +
      Inference runtimeConcurrencyp99 TTFT (ms)p99 ITL (ms)Token throughput (tokens/sec)Request throughput (req/sec)Hardware
      TensorRT-LLM136.2722.7845.660.23L4
      TensorRT-LLM249.8123.2189.370.45L4
      TensorRT-LLM455.3336.62153.390.78L4
      TensorRT-LLM866.539.11279.881.47L4
      TensorRT-LLM16131.830.39547.82.77L4
      TensorRT-LLM32277.2248.02925.74.78L4
      TensorRT-LLM64498.5271.621,164.406.2L4
      TensorRT-LLM128677.31120.371,445.187.69L4
      TensorRT-LLM2561,926.31216.881,600.818.52L4
      TensorRT-LLM121.179.24130.050.68A100
      TensorRT-LLM225.789.21264.51.35A100
      TensorRT-LLM428.5210.99437.692.27A100
      TensorRT-LLM834.412.61760.493.96A100
      TensorRT-LLM1668.0314.321,343.807.01A100
      TensorRT-LLM32185.9616.822,287.3011.92A100
      TensorRT-LLM64136.8721.173,625.2218.89A100
      TensorRT-LLM128463.7834.154,456.5123.24A100
      TensorRT-LLM256890.1259.185,188.2427.05A100
      +

      Conclusion

      +

      High-performance LLM inference is fundamentally a systems engineering problem: memory efficiency, kernel execution, batching strategy, and parallelism determine real-world latency and throughput. Techniques such as paged KV caching, aggressive quantization, kernel fusion, and inflight batching improve GPU utilization while reducing latency and memory pressure.

      +

      These optimizations enable the platform to deliver sub-second responses, sustain high concurrency, and efficiently serve both lightweight and long-context workloads. By continuously optimizing across the full inference stack, we keep LLM serving scalable, cost-efficient, and production-ready for real-time AI applications.

      Designing a Production-Grade LLM Inference Platform: From Model Weights to Scalable GPU Serving

      · 14 min read
      Jaya Kumar
      Lead ML Engineer @ Meesho

      BharatMLStack +Serving large language models in production introduces new challenges across infrastructure, performance optimization, and operational lifecycle management. The LLM Inference Platform addresses these challenges by providing a unified system for deploying and managing open-source and fine-tuned LLMs at scale.

      +

      The platform implements a complete LLMOps lifecycle — from model registration and automated compilation to deployment, runtime optimization, and monitoring. Designed as a self-service environment, users can onboard models directly from open repositories such as Hugging Face or upload custom fine-tuned models, and deploy them using a single-click workflow with no manual infrastructure or configuration steps required.

      +

      In addition to fully automated deployment, the platform allows users to select and apply custom inference optimization techniques — such as quantization strategies, batching configurations, and runtime-specific performance enhancements — enabling teams to balance latency, throughput, and cost based on their use case. The goal is to reduce operational friction while enabling high-performance, production-grade LLM inference.

      +

      Why LLM Inference Is not just bigger ML model serving

      +

      Large language model (LLM) inference introduces a fundamentally different set of challenges compared to traditional machine learning inference. While classical ML models typically perform a single forward pass to produce a fixed prediction, LLMs operate as autoregressive systems, generating outputs token by token based on previously generated context. This difference dramatically changes how inference systems must be designed, optimized, and scaled.

      +

      Autoregressive Generation and Sequential Computation:

      +

      Unlike traditional models such as classifiers or recommenders — where inference cost is relatively constant — LLMs generate responses incrementally. Each new token depends on all previously generated tokens, making inference inherently sequential and dynamic. This means latency and compute requirements vary significantly depending on prompt length and output size, introducing complexity in scheduling and resource allocation. +Because tokens cannot be generated fully in parallel during decoding, GPUs may become underutilized without specialized batching and scheduling strategies. This has led to the development of dedicated LLM inference engines optimized for token-level execution.

      +

      Prefill and Decode Phases:

      +

      LLM inference typically consists of two distinct stages:

      +
        +
      • Prefill phase — the model processes the input prompt and builds internal representations. This stage is compute-heavy and highly parallelizable.
      • +
      • Decode phase — the model generates tokens sequentially, predicting one token at a time using previously generated context.
      • +
      +

      The decode stage often becomes memory-bound rather than compute-bound, which creates new performance bottlenecks compared to traditional ML workloads.

      +

      Context Management and KV Caching:

      +

      Another fundamental difference lies in how LLMs maintain context. Transformer-based models rely on attention mechanisms that require access to past token representations. To avoid recomputing these representations repeatedly, inference engines use key-value (KV) caching, which stores intermediate activations from previous tokens. +KV caching significantly improves performance by eliminating redundant computation, but it introduces new challenges:

      +
        +
      • Memory consumption grows with sequence length and batch size
      • +
      • GPU memory becomes a critical bottleneck
      • +
      • Efficient memory management becomes essential for scaling concurrent requests
      • +
      +

      This tradeoff between compute efficiency and memory usage is unique to LLM inference workloads.

      +

      Dynamic and Irregular Workloads:

      +

      Traditional ML inference typically operates on fixed-size inputs with predictable latency. In contrast, LLM requests vary widely in prompt length, output length, and runtime behavior. As a result:

      +
        +
      • Batch sizes must be dynamic rather than static
      • +
      • Requests may enter and leave batches asynchronously
      • +
      • Scheduling systems must continuously rebalance workloads to maximize GPU utilization
      • +
      +

      These characteristics require specialized serving architectures that differ significantly from standard ML serving pipelines.

      +

      Streaming and User Experience Constraints:

      +

      Another distinguishing factor is the expectation of real-time streaming responses. Instead of returning a single output, LLM systems often stream tokens to users as they are generated. +Because of these differences — sequential generation, growing memory requirements, dynamic workloads, and streaming constraints — LLM inference cannot be treated as a simple extension of existing ML serving systems. Production platforms must incorporate specialized runtime engines, advanced optimization techniques, and observability tailored specifically to LLM workloads.

      +

      LLMOps: High-Level Architecture

      +

      LLM Architecture

      +

      The LLM Inference Framework is designed as a fully automated, end-to-end system for deploying and operating open-source and fine-tuned large language models at scale. The architecture abstracts the complexity of model optimization, hardware selection, deployment, and runtime management into a unified workflow that enables users to move from raw model weights to production-ready inference endpoints with minimal manual intervention.

      +

      Our LLM Inference Framework is architected not just as a serving engine, but as a complete lifecycle management system. As illustrated in the high-level design below, the platform automates the journey of a model through seven distinct stages, ensuring reproducibility, performance, and scalability.

      +
        +
      1. +

        Onboarding & Registration (The Source of Truth)

        +

        The lifecycle begins with the Data Scientist or engineer.

        +
          +
        • Model Ingestion: Users onboard models—whether open-source (Hugging Face, NeMo) or internally fine-tuned—via the Truffle Box SDK/UI.
        • +
        • LLM + Prompt Registry: Unlike traditional systems that only track model weights, our registry is a unified control plane. It stores both the Model Artifacts and the Prompt Templates. This allows Data Scientists to register and version-control prompts (e.g., "customer_support_v2") independently of the application code.
        • +
        +
      2. +
      3. +

        The "Black Box" Build Engine

        +

        Once a model is registered, the Automated LLM Compiler + Quantizer Module kicks off a background job on ephemeral GPU resources.

        +
          +
        • Transformation: The raw model is converted into a TRT-LLM Checkpoint.
        • +
        • Quantization: The system automatically applies quantization algorithms (like INT4 AWQ or FP8) to reduce memory footprint.
        • +
        • Engine Building: Finally, it compiles a highly optimized TRT Engine specifically tuned for the target hardware.
        • +
        +
      4. +
      5. +

        Intelligent Profiling & Validation

        +

        Before deployment, the new engine passes through the Hardware & Inference Runtime Profiler.

        +
          +
        • Benchmarking: This module empirically tests the engine against various hardware configurations (L4 vs. A100) and runtimes (TRT-LLM vs. vLLM).
        • +
        • Optimization: It recommends the optimal configuration that meets latency SLAs (Time-To-First-Token) while minimizing cost.
        • +
        +
      6. +
      7. +

        Smart Artifact Generation & Distribution

        +

        To solve the Kubernetes "Cold Start" problem, the LLM Serving Artifacts Generation module packages the model using a bifurcated strategy:

        +
          +
        • Standard Models: Artifacts are uploaded to Cloud Storage (GCS) and downloaded by pods at startup.
        • +
        • Very Large Models: For massive models (>8GB) where network downloads are too slow, the system pre-caches the model onto Secondary Boot Disks. These disks are attached directly to new GPU nodes during autoscaling, eliminating download wait times.
        • +
        +
      8. +
      9. +

        Image Streaming & Deployment

        +

        Simultaneously, the inference runtime container images are pulled from the Artifact Registry.

        +
          +
        • Image Streaming: We utilize container image streaming to allow pods to start initializing while the massive Triton/Dynamo container layers are still downloading, further shaving seconds off the startup time. link
        • +
        +
      10. +
      11. +

        The Inference Runtime (Kubernetes)

        +

        The workload lands on Kubernetes with Autoscaling.

        +
          +
        • Dynamic Backends: Depending on the profile generated in Stage 3, the pod initializes either TensorRT-LLM (for throughput) or vLLM (for flexibility), or spins up a Dynamo worker for distributed inference.
        • +
        • Data Loading: The pod either downloads the model from Cloud Storage or mounts the pre-warmed Secondary Boot Disk ("Pull from Disk").
        • +
        +
      12. +
      13. +

        Client Interaction & Observability

        +

        Finally, the LLM Inference Client executes the request.

        +
          +
        • Prompt Injection: The client pulls the specific prompt template ID from the Registry, ensuring the exact versioned instructions are used.
        • +
        • Streaming Response: The request is sent via gRPC, and tokens are streamed back to the user in real-time.
        • +
        +
      14. +
      15. +

        Observability: Monitoring the Pulse of GenAI

        +

        In traditional microservices, success is measured by CPU utilization and request latency (p99). For Large Language Models, these metrics are insufficient. A user doesn't care if the GPU is at 80% utilization; they care about how fast the first word appears and how smoothly the rest of the sentence follows.

        +

        To capture the true user experience, our platform instrumentation focuses on three critical LLM-specific metrics:

        +
          +
        1. +

          Time to First Token (TTFT)

          +
            +
          • Definition: TTFT measures the time elapsed from the moment a request is received until the very first token is generated and streamed back to the user.
          • +
          • Why it matters: This represents the "Prefill Phase" latency—the time the model takes to process the input prompt and load weights. A high TTFT makes the application feel unresponsive or "hung."
          • +
          • Optimization: We closely monitor TTFT to ensure our Prefix Caching is effective (aiming for high cache hitrates), which drastically lowers this metric by skipping redundant prompt processing.
          • +
          +
        2. +
        3. +

          Inter-Token Latency (ITL)

          +
            +
          • Definition: ITL measures the average time interval between the generation of consecutive tokens during the "Decode Phase".
          • +
          • Why it matters: This defines the "perceived speed" of reading. Even if the first token is fast (low TTFT), high ITL makes the text generation look "jerky" or slow to the user.
          • +
          • Benchmarks: In our testing with Llama 3.1, we track p99 ITL to ensure it stays below human reading speeds to maintain a natural conversational flow.
          • +
          +
        4. +
        5. +

          Token Throughput vs. Request Throughput

          +
            +
          • We distinguish between two types of throughput to balance system efficiency with user load:
          • +
          • Token Throughput (tokens/sec): The total number of tokens generated across all concurrent requests. This measures the raw compute efficiency of the GPU and the effectiveness of batching.
          • +
          • Request Throughput (req/sec): The number of distinct user queries served per second. We use this to determine autoscaling thresholds, ensuring we scale out before the queue depth impacts ITL.
          • +
          +
        6. +
        7. +

          The Monitoring Stack

          +
            +
          • Real-time Dashboards: We utilize Grafana to visualize these streaming metrics in real-time, allowing on-call engineers to spot "slow generation" incidents that generic "500 error" alerts would miss.
          • +
          • Request Tracing: Since Triton Inference Server does not log request payloads by default, we integrate a Helix Client to asynchronously publish request logs to Log Tables. This allows us to trace a specific "slow" request back to its prompt to understand if a complex input caused the latency spike.
          • +
          +
        8. +
        +
      16. +
      +

      Supported Inference backends (TensorRT LLM, Dynamo & vLLM)

      +

      Tailored for the Use Case: We do not believe in a "one-size-fits-all" approach to inference. Different use cases—whether a real-time voice bot requiring ultra-lowsub-second latency or a massive reasoning task requiring huge context windows—demand different runtime characteristics. Our platform is designed to be runtime-agnostic, allowing us to automatically select and tailor the best engine based on the specific requirements of the application:

      +
        +
      1. +

        TensorRT-LLM: The High-Performance Standard

        +

        Suitable for: High-throughput production workloads where latency is critical (e.g., customer support chat, real-time voice bots).

        +

        TensorRT-LLM serves as our default backend for these scenarios. Our internal benchmarks on Llama 3.1 and 3.2 models demonstrated that a tuned TensorRT-LLM engine significantly outperforms standard runtimes, especially when utilizing INT4 AWQ and FP8 quantization .

        +

        Key optimizations we tailor for these high-load cases include:

        +
          +
        • Optimized execution via TensorRT engine compilation
        • +
        • Quantization-aware execution for reduced memory usage and improved throughput
        • +
        • Inflight Batching: Allowing requests to be processed continuously without waiting for the entire batch to finish, drastically improving GPU utilization .
        • +
        • Custom Plugins: Enabling specific NVIDIA plugins like the GEMM plugin and GPT Attention plugin to accelerate matrix multiplications and attention mechanisms .
        • +
        +
      2. +
      3. +

        Dynamo: Distributed Inference for Reasoning Models

        +

        Suitable for: Very large "reasoning" models (70B+) or scenarios requiring massive context windows where a single GPU's memory is insufficient.

        +

        For these memory-bound tasks, we utilize Dynamo, a low-latency distributed inference framework . Unlike monolithic servers, Dynamo disaggregates the inference process to scale resources horizontally:

        +
          +
        • KV Aware Routing: A specialized router directs requests to workers that already hold the relevant Key-Value (KV) cache, minimizing redundant computation .
        • +
        • Prefill vs. Decode Split: The workload is divided into Prefill Workers (processing the prompt) and Decode Workers (generating tokens), allowing us to scale the compute-heavy "reading" phase independently from the memory-heavy "writing" phase .
        • +
        • Distributed execution across multiple GPU resources
        • +
        +
      4. +
      5. +

        vLLM: The Flexible Baseline

        +

        Suitable for: Rapid prototyping, testing new model architectures, or low-traffic internal tools where ease of deployment outweighs raw throughput.

        +

        While TensorRT-LLM is optimized for maximum speed, vLLM provides a robust and flexible baseline .

        +
          +
        • High throughput through dynamic batching and efficient memory utilization
        • +
        • Paged KV cache management for handling long contexts and concurrent requests
        • +
        • Strong support for open-source model ecosystems
        • +
        • Rapid Adoption: It allows us to onboard new model architectures immediately without waiting for a custom TensorRT build.
        • +
        • Benchmarking Insight: In our internal tests, vLLM provided a strong baseline but often lacked the specific max-token optimizations present in our custom TRT engines . We use it strategically for initial testing before committing to a full TensorRT optimization pipeline.
        • +
        +
      6. +
      +

      Conclusion

      +

      Large language model inference introduces a fundamentally new class of infrastructure challenges—where performance is governed not just by raw compute, but by memory efficiency, intelligent scheduling, runtime specialization, and lifecycle automation. Unlike traditional ML serving, LLM inference requires systems that understand token-level execution, manage rapidly growing context state, and continuously balance latency, throughput, and cost under highly dynamic workloads.

      +

      The LLM Inference Framework addresses these challenges by transforming inference into a fully automated, reproducible lifecycle—from model onboarding and compilation to deployment, optimization, and observability. By integrating automated quantization and engine compilation, intelligent runtime selection, cold-start mitigation strategies, and LLM-specific observability metrics such as Time-to-First-Token and Inter-Token Latency, the platform ensures both high performance and operational simplicity.

      +

      Equally important, the framework is designed with flexibility and future evolution in mind. Its runtime-agnostic architecture enables seamless adoption of emerging inference engines, hardware accelerators, and optimization techniques without requiring platform redesign. This ensures that teams can continuously leverage advancements in the rapidly evolving LLM ecosystem while maintaining consistent operational workflows.

      +

      Ultimately, the goal of the platform is to make production-scale LLM deployment as seamless and reliable as traditional software deployment—allowing teams to focus on building intelligent applications rather than managing infrastructure complexity. By combining lifecycle automation, runtime optimization, and deep observability, the LLM Inference Framework provides a scalable foundation for delivering fast, cost-efficient, and production-ready LLM experiences.

      +

      Future Explorations

      +

      While we have achieved significant milestones in latency and throughput, the landscape of GenAI is evolving rapidly. Our roadmap focuses on increasing flexibility, reducing costs, and enhancing reliability for enterprise-grade workloads. Here is what we are building next:

      +
        +
      • TPU Support: To diversify our hardware supply chain and further optimize cost-per-token, we are evaluating Google Cloud TPUs to bake it into our platform. By leveraging the JAX and PyTorch/XLA ecosystems, we aim to unlock the massive throughput potential of TPU v5e chips, particularly for our open-source Llama models. This will allow the hardware profiler to dynamically choose between NVIDIA GPUs and Google TPUs based on real-time availability and price-performance metrics.
      • +
      • Multi-LoRA Serving (Serverless Experience): Currently, deploying a fine-tuned model requires a dedicated GPU. We are building support for Multi-LoRA serving, which will allow us to serve hundreds of unique, fine-tuned adapters on top of a single frozen base model. This will drastically reduce costs for multi-tenant applications, enabling a "serverless" experience where specific fine-tunes are hot-swapped instantly per request.
      • +
      • Spot Instance Orchestration: To further optimize cloud costs, we are developing fault-tolerant mechanisms to run inference workloads on Spot Instances. By implementing aggressive checkpointing and seamless request draining, we aim to leverage cheaper, preemptible compute capacity without interrupting the user's streaming experience.
      • +
      • Semantic Caching Layer: We plan to move beyond standard Prefix Caching to implement Semantic Caching. By using a vector database to fetch responses for semantically similar queries (e.g., "How do I reset my password?" vs. "Password reset steps"), we can bypass the GPU entirely for repetitive queries, reducing latency to near-zero.
      • +
      • Context-Aware Autoscaling: Standard CPU/GPU utilization metrics are often insufficient signals for scaling LLMs. We are working on KV-cache pressure metrics for autoscaling. This ensures that we scale out before the memory fills up, preventing eviction-based slowdowns during traffic spikes.
      • +
      • Online Evaluation & Guardrails: We are integrating a lightweight "Trust Layer" into the proxy. This will allow for low-latency input/output filtering (Guardrails) and asynchronous "LLM-as-a-Judge" evaluation pipelines to monitor response quality in production, not just system health.
      • +
      + + \ No newline at end of file diff --git a/docs/blog/tags/vllm/index.html b/docs/blog/tags/vllm/index.html new file mode 100644 index 00000000..e0924c19 --- /dev/null +++ b/docs/blog/tags/vllm/index.html @@ -0,0 +1,268 @@ + + + + + +2 posts tagged with "vllm" | BharatMLStack + + + + + + + + +

      2 posts tagged with "vllm"

      View All Tags

      LLM Inference Optimization Techniques: Engineering Sub-Second Latency at Scale

      · 5 min read
      Jaya Kumar
      Lead ML Engineer @ Meesho

      BharatMLStack +Raw execution of Large Language Models is inherently expensive and memory-intensive. To achieve sub-second latency and high throughput, we implement a multi-layered optimization strategy that targets the entire inference stack—from memory management to kernel execution.

      +

      1. Advanced Memory Management: Paged & Prefix KV Caching

      +

      The most significant bottleneck in LLM inference is not always compute, but memory bandwidth—specifically managing the Key-Value (KV) cache.

      +

      Paged KV caching

      +

      Standard caching suffers from fragmentation. We use Paged KV caching, which operates similarly to an operating system's virtual memory: the KV cache is divided into non-contiguous blocks. This lets us serve larger batch sizes without running out of memory.

      +

      KV cache quantization

      +

      To further maximize available memory, we implement KV cache quantization (e.g., FP8). By compressing stored attention keys and values from 16-bit to 8-bit, we nearly double the effective context window capacity of the GPU, allowing longer conversations or larger batches without materially degrading quality.

      +

      Prefix caching (the "voice bot" optimizer)

      +

      For use cases like GenAI voice bots where the system prompt (e.g., "You are a helpful assistant...") is static across thousands of requests, we enable prefix caching.

      +
        +
      • Impact: By reusing pre-computed KV states for common prefixes, we achieve a cache hit rate of ~90%. This reduces Time To First Token (TTFT) by skipping redundant computation of the system prompt.
      • +
      +

      2. Aggressive Quantization (INT4 AWQ & FP8)

      +

      Running models in their native 16-bit precision (BF16) restricts maximum batch size and throughput. We use quantization to shrink model weights without sacrificing accuracy.

      +

      INT4 AWQ (Activation-aware Weight Quantization)

      +

      For the Llama 3 family, we use AWQ to compress weights to 4 bits. This reduces model size by ~75%, allowing larger models to fit into L4 GPU memory and significantly improving token generation speed.

      +

      FP8 precision

      +

      For NVIDIA Hopper (H100) architectures, we are exploring FP8 quantization, leveraging native FP8 tensor cores to accelerate matrix multiplications while maintaining a higher dynamic range than integer quantization.

      +
        +
      • Verification: We validate quantized models by comparing dot-product similarity of embeddings against the FP16 baseline, consistently achieving >99% similarity.
      • +
      +

      3. Kernel Fusion & Custom Plugins

      +

      To minimize overhead from launching thousands of small GPU operations, we fuse them into monolithic kernels using NVIDIA TensorRT plugins.

      +
        +
      • Flash attention & FMHA: We enable Fused Multi-Head Attention (FMHA) combined with flash attention to reduce memory reads/writes.
      • +
      • GEMM plugins: We use specialized GEMM plugins to accelerate transformer linear layers.
      • +
      • Removing input padding: Instead of padding short sequences to match the longest, we remove input padding so the GPU processes only valid tokens.
      • +
      +

      4. Inflight (Continuous) Batching

      +

      Traditional static batching waits for all requests in a batch to finish before returning results—so one long response delays everyone else.

      +

      We implement inflight batching: as soon as one request completes, its slot is freed and filled by a new request from the queue. This keeps GPUs saturated and decouples latency of short queries from long ones.

      +

      5. Parallelism Strategies: Scaling Beyond One GPU

      +

      For large models (e.g., 70B+ parameters) that cannot fit into the VRAM of a single GPU, we use parallelism strategies.

      +
        +
      • Tensor parallelism (TP): Split weight matrices across multiple GPUs (e.g., 4× L4 or 8× A100). Each GPU computes a shard and outputs are reduced at every layer.
      • +
      • Pipeline parallelism (PP): Split model layers across GPUs to pipeline compute (e.g., while one GPU computes later layers for Request A, another starts early layers for Request B).
      • +
      +

      6. Speculative Decoding

      +

      To reduce inter-token latency (ITL), we explore speculative decoding.

      +
        +
      • Mechanism: A smaller, faster "draft" model speculatively generates a short token sequence (e.g., 5 tokens).
      • +
      • Verification: The larger target model verifies those tokens in one parallel forward pass. If correct, we effectively generate multiple tokens per large-model step; if not, we discard and regenerate. This is effective for predictable text, improving perceived generation speed.
      • +
      +

      Few Benchmarks

      +

      Below are a couple of representative use cases and performance numbers.

      +

      Search query rewriting

      +
        +
      • LLM: Fine-tuned llama-3.2-1B
      • +
      • Input & output token length: ~10–20
      • +
      • Response type: Non-streaming
      • +
      +
      Inference runtimeHardwareMax requests/secMax p99 latency
      TensorRT-LLM4 × L4 GPUs (multi-GPU)100095 ms
      TensorRT-LLM1 × A100 40 GB GPU100069 ms
      +

      Voice bot query

      +
        +
      • LLM: Llama-3.1-8B
      • +
      • Input token length: ~1900–2000
      • +
      • Output token length: ~200
      • +
      • Response type: Streaming
      • +
      +
      Inference runtimeConcurrencyp99 TTFT (ms)p99 ITL (ms)Token throughput (tokens/sec)Request throughput (req/sec)Hardware
      TensorRT-LLM136.2722.7845.660.23L4
      TensorRT-LLM249.8123.2189.370.45L4
      TensorRT-LLM455.3336.62153.390.78L4
      TensorRT-LLM866.539.11279.881.47L4
      TensorRT-LLM16131.830.39547.82.77L4
      TensorRT-LLM32277.2248.02925.74.78L4
      TensorRT-LLM64498.5271.621,164.406.2L4
      TensorRT-LLM128677.31120.371,445.187.69L4
      TensorRT-LLM2561,926.31216.881,600.818.52L4
      TensorRT-LLM121.179.24130.050.68A100
      TensorRT-LLM225.789.21264.51.35A100
      TensorRT-LLM428.5210.99437.692.27A100
      TensorRT-LLM834.412.61760.493.96A100
      TensorRT-LLM1668.0314.321,343.807.01A100
      TensorRT-LLM32185.9616.822,287.3011.92A100
      TensorRT-LLM64136.8721.173,625.2218.89A100
      TensorRT-LLM128463.7834.154,456.5123.24A100
      TensorRT-LLM256890.1259.185,188.2427.05A100
      +

      Conclusion

      +

      High-performance LLM inference is fundamentally a systems engineering problem: memory efficiency, kernel execution, batching strategy, and parallelism determine real-world latency and throughput. Techniques such as paged KV caching, aggressive quantization, kernel fusion, and inflight batching improve GPU utilization while reducing latency and memory pressure.

      +

      These optimizations enable the platform to deliver sub-second responses, sustain high concurrency, and efficiently serve both lightweight and long-context workloads. By continuously optimizing across the full inference stack, we keep LLM serving scalable, cost-efficient, and production-ready for real-time AI applications.

      Designing a Production-Grade LLM Inference Platform: From Model Weights to Scalable GPU Serving

      · 14 min read
      Jaya Kumar
      Lead ML Engineer @ Meesho

      BharatMLStack +Serving large language models in production introduces new challenges across infrastructure, performance optimization, and operational lifecycle management. The LLM Inference Platform addresses these challenges by providing a unified system for deploying and managing open-source and fine-tuned LLMs at scale.

      +

      The platform implements a complete LLMOps lifecycle — from model registration and automated compilation to deployment, runtime optimization, and monitoring. Designed as a self-service environment, users can onboard models directly from open repositories such as Hugging Face or upload custom fine-tuned models, and deploy them using a single-click workflow with no manual infrastructure or configuration steps required.

      +

      In addition to fully automated deployment, the platform allows users to select and apply custom inference optimization techniques — such as quantization strategies, batching configurations, and runtime-specific performance enhancements — enabling teams to balance latency, throughput, and cost based on their use case. The goal is to reduce operational friction while enabling high-performance, production-grade LLM inference.

      +

      Why LLM Inference Is not just bigger ML model serving

      +

      Large language model (LLM) inference introduces a fundamentally different set of challenges compared to traditional machine learning inference. While classical ML models typically perform a single forward pass to produce a fixed prediction, LLMs operate as autoregressive systems, generating outputs token by token based on previously generated context. This difference dramatically changes how inference systems must be designed, optimized, and scaled.

      +

      Autoregressive Generation and Sequential Computation:

      +

      Unlike traditional models such as classifiers or recommenders — where inference cost is relatively constant — LLMs generate responses incrementally. Each new token depends on all previously generated tokens, making inference inherently sequential and dynamic. This means latency and compute requirements vary significantly depending on prompt length and output size, introducing complexity in scheduling and resource allocation. +Because tokens cannot be generated fully in parallel during decoding, GPUs may become underutilized without specialized batching and scheduling strategies. This has led to the development of dedicated LLM inference engines optimized for token-level execution.

      +

      Prefill and Decode Phases:

      +

      LLM inference typically consists of two distinct stages:

      +
        +
      • Prefill phase — the model processes the input prompt and builds internal representations. This stage is compute-heavy and highly parallelizable.
      • +
      • Decode phase — the model generates tokens sequentially, predicting one token at a time using previously generated context.
      • +
      +

      The decode stage often becomes memory-bound rather than compute-bound, which creates new performance bottlenecks compared to traditional ML workloads.

      +

      Context Management and KV Caching:

      +

      Another fundamental difference lies in how LLMs maintain context. Transformer-based models rely on attention mechanisms that require access to past token representations. To avoid recomputing these representations repeatedly, inference engines use key-value (KV) caching, which stores intermediate activations from previous tokens. +KV caching significantly improves performance by eliminating redundant computation, but it introduces new challenges:

      +
        +
      • Memory consumption grows with sequence length and batch size
      • +
      • GPU memory becomes a critical bottleneck
      • +
      • Efficient memory management becomes essential for scaling concurrent requests
      • +
      +

      This tradeoff between compute efficiency and memory usage is unique to LLM inference workloads.

      +

      Dynamic and Irregular Workloads:

      +

      Traditional ML inference typically operates on fixed-size inputs with predictable latency. In contrast, LLM requests vary widely in prompt length, output length, and runtime behavior. As a result:

      +
        +
      • Batch sizes must be dynamic rather than static
      • +
      • Requests may enter and leave batches asynchronously
      • +
      • Scheduling systems must continuously rebalance workloads to maximize GPU utilization
      • +
      +

      These characteristics require specialized serving architectures that differ significantly from standard ML serving pipelines.

      +

      Streaming and User Experience Constraints:

      +

      Another distinguishing factor is the expectation of real-time streaming responses. Instead of returning a single output, LLM systems often stream tokens to users as they are generated. +Because of these differences — sequential generation, growing memory requirements, dynamic workloads, and streaming constraints — LLM inference cannot be treated as a simple extension of existing ML serving systems. Production platforms must incorporate specialized runtime engines, advanced optimization techniques, and observability tailored specifically to LLM workloads.

      +

      LLMOps: High-Level Architecture

      +

      LLM Architecture

      +

      The LLM Inference Framework is designed as a fully automated, end-to-end system for deploying and operating open-source and fine-tuned large language models at scale. The architecture abstracts the complexity of model optimization, hardware selection, deployment, and runtime management into a unified workflow that enables users to move from raw model weights to production-ready inference endpoints with minimal manual intervention.

      +

      Our LLM Inference Framework is architected not just as a serving engine, but as a complete lifecycle management system. As illustrated in the high-level design below, the platform automates the journey of a model through seven distinct stages, ensuring reproducibility, performance, and scalability.

      +
        +
      1. +

        Onboarding & Registration (The Source of Truth)

        +

        The lifecycle begins with the Data Scientist or engineer.

        +
          +
        • Model Ingestion: Users onboard models—whether open-source (Hugging Face, NeMo) or internally fine-tuned—via the Truffle Box SDK/UI.
        • +
        • LLM + Prompt Registry: Unlike traditional systems that only track model weights, our registry is a unified control plane. It stores both the Model Artifacts and the Prompt Templates. This allows Data Scientists to register and version-control prompts (e.g., "customer_support_v2") independently of the application code.
        • +
        +
      2. +
      3. +

        The "Black Box" Build Engine

        +

        Once a model is registered, the Automated LLM Compiler + Quantizer Module kicks off a background job on ephemeral GPU resources.

        +
          +
        • Transformation: The raw model is converted into a TRT-LLM Checkpoint.
        • +
        • Quantization: The system automatically applies quantization algorithms (like INT4 AWQ or FP8) to reduce memory footprint.
        • +
        • Engine Building: Finally, it compiles a highly optimized TRT Engine specifically tuned for the target hardware.
        • +
        +
      4. +
      5. +

        Intelligent Profiling & Validation

        +

        Before deployment, the new engine passes through the Hardware & Inference Runtime Profiler.

        +
          +
        • Benchmarking: This module empirically tests the engine against various hardware configurations (L4 vs. A100) and runtimes (TRT-LLM vs. vLLM).
        • +
        • Optimization: It recommends the optimal configuration that meets latency SLAs (Time-To-First-Token) while minimizing cost.
        • +
        +
      6. +
      7. +

        Smart Artifact Generation & Distribution

        +

        To solve the Kubernetes "Cold Start" problem, the LLM Serving Artifacts Generation module packages the model using a bifurcated strategy:

        +
          +
        • Standard Models: Artifacts are uploaded to Cloud Storage (GCS) and downloaded by pods at startup.
        • +
        • Very Large Models: For massive models (>8GB) where network downloads are too slow, the system pre-caches the model onto Secondary Boot Disks. These disks are attached directly to new GPU nodes during autoscaling, eliminating download wait times.
        • +
        +
      8. +
      9. +

        Image Streaming & Deployment

        +

        Simultaneously, the inference runtime container images are pulled from the Artifact Registry.

        +
          +
        • Image Streaming: We utilize container image streaming to allow pods to start initializing while the massive Triton/Dynamo container layers are still downloading, further shaving seconds off the startup time. link
        • +
        +
      10. +
      11. +

        The Inference Runtime (Kubernetes)

        +

        The workload lands on Kubernetes with Autoscaling.

        +
          +
        • Dynamic Backends: Depending on the profile generated in Stage 3, the pod initializes either TensorRT-LLM (for throughput) or vLLM (for flexibility), or spins up a Dynamo worker for distributed inference.
        • +
        • Data Loading: The pod either downloads the model from Cloud Storage or mounts the pre-warmed Secondary Boot Disk ("Pull from Disk").
        • +
        +
      12. +
      13. +

        Client Interaction & Observability

        +

        Finally, the LLM Inference Client executes the request.

        +
          +
        • Prompt Injection: The client pulls the specific prompt template ID from the Registry, ensuring the exact versioned instructions are used.
        • +
        • Streaming Response: The request is sent via gRPC, and tokens are streamed back to the user in real-time.
        • +
        +
      14. +
      15. +

        Observability: Monitoring the Pulse of GenAI

        +

        In traditional microservices, success is measured by CPU utilization and request latency (p99). For Large Language Models, these metrics are insufficient. A user doesn't care if the GPU is at 80% utilization; they care about how fast the first word appears and how smoothly the rest of the sentence follows.

        +

        To capture the true user experience, our platform instrumentation focuses on three critical LLM-specific metrics:

        +
          +
        1. +

          Time to First Token (TTFT)

          +
            +
          • Definition: TTFT measures the time elapsed from the moment a request is received until the very first token is generated and streamed back to the user.
          • +
          • Why it matters: This represents the "Prefill Phase" latency—the time the model takes to process the input prompt and load weights. A high TTFT makes the application feel unresponsive or "hung."
          • +
          • Optimization: We closely monitor TTFT to ensure our Prefix Caching is effective (aiming for high cache hitrates), which drastically lowers this metric by skipping redundant prompt processing.
          • +
          +
        2. +
        3. +

          Inter-Token Latency (ITL)

          +
            +
          • Definition: ITL measures the average time interval between the generation of consecutive tokens during the "Decode Phase".
          • +
          • Why it matters: This defines the "perceived speed" of reading. Even if the first token is fast (low TTFT), high ITL makes the text generation look "jerky" or slow to the user.
          • +
          • Benchmarks: In our testing with Llama 3.1, we track p99 ITL to ensure it stays below human reading speeds to maintain a natural conversational flow.
          • +
          +
        4. +
        5. +

          Token Throughput vs. Request Throughput

          +
            +
          • We distinguish between two types of throughput to balance system efficiency with user load:
          • +
          • Token Throughput (tokens/sec): The total number of tokens generated across all concurrent requests. This measures the raw compute efficiency of the GPU and the effectiveness of batching.
          • +
          • Request Throughput (req/sec): The number of distinct user queries served per second. We use this to determine autoscaling thresholds, ensuring we scale out before the queue depth impacts ITL.
          • +
          +
        6. +
        7. +

          The Monitoring Stack

          +
            +
          • Real-time Dashboards: We utilize Grafana to visualize these streaming metrics in real-time, allowing on-call engineers to spot "slow generation" incidents that generic "500 error" alerts would miss.
          • +
          • Request Tracing: Since Triton Inference Server does not log request payloads by default, we integrate a Helix Client to asynchronously publish request logs to Log Tables. This allows us to trace a specific "slow" request back to its prompt to understand if a complex input caused the latency spike.
          • +
          +
        8. +
        +
      16. +
      +

      Supported Inference backends (TensorRT LLM, Dynamo & vLLM)

      +

      Tailored for the Use Case: We do not believe in a "one-size-fits-all" approach to inference. Different use cases—whether a real-time voice bot requiring ultra-lowsub-second latency or a massive reasoning task requiring huge context windows—demand different runtime characteristics. Our platform is designed to be runtime-agnostic, allowing us to automatically select and tailor the best engine based on the specific requirements of the application:

      +
        +
      1. +

        TensorRT-LLM: The High-Performance Standard

        +

        Suitable for: High-throughput production workloads where latency is critical (e.g., customer support chat, real-time voice bots).

        +

        TensorRT-LLM serves as our default backend for these scenarios. Our internal benchmarks on Llama 3.1 and 3.2 models demonstrated that a tuned TensorRT-LLM engine significantly outperforms standard runtimes, especially when utilizing INT4 AWQ and FP8 quantization .

        +

        Key optimizations we tailor for these high-load cases include:

        +
          +
        • Optimized execution via TensorRT engine compilation
        • +
        • Quantization-aware execution for reduced memory usage and improved throughput
        • +
        • Inflight Batching: Allowing requests to be processed continuously without waiting for the entire batch to finish, drastically improving GPU utilization .
        • +
        • Custom Plugins: Enabling specific NVIDIA plugins like the GEMM plugin and GPT Attention plugin to accelerate matrix multiplications and attention mechanisms .
        • +
        +
      2. +
      3. +

        Dynamo: Distributed Inference for Reasoning Models

        +

        Suitable for: Very large "reasoning" models (70B+) or scenarios requiring massive context windows where a single GPU's memory is insufficient.

        +

        For these memory-bound tasks, we utilize Dynamo, a low-latency distributed inference framework . Unlike monolithic servers, Dynamo disaggregates the inference process to scale resources horizontally:

        +
          +
        • KV Aware Routing: A specialized router directs requests to workers that already hold the relevant Key-Value (KV) cache, minimizing redundant computation .
        • +
        • Prefill vs. Decode Split: The workload is divided into Prefill Workers (processing the prompt) and Decode Workers (generating tokens), allowing us to scale the compute-heavy "reading" phase independently from the memory-heavy "writing" phase .
        • +
        • Distributed execution across multiple GPU resources
        • +
        +
      4. +
      5. +

        vLLM: The Flexible Baseline

        +

        Suitable for: Rapid prototyping, testing new model architectures, or low-traffic internal tools where ease of deployment outweighs raw throughput.

        +

        While TensorRT-LLM is optimized for maximum speed, vLLM provides a robust and flexible baseline .

        +
          +
        • High throughput through dynamic batching and efficient memory utilization
        • +
        • Paged KV cache management for handling long contexts and concurrent requests
        • +
        • Strong support for open-source model ecosystems
        • +
        • Rapid Adoption: It allows us to onboard new model architectures immediately without waiting for a custom TensorRT build.
        • +
        • Benchmarking Insight: In our internal tests, vLLM provided a strong baseline but often lacked the specific max-token optimizations present in our custom TRT engines . We use it strategically for initial testing before committing to a full TensorRT optimization pipeline.
        • +
        +
      6. +
      +

      Conclusion

      +

      Large language model inference introduces a fundamentally new class of infrastructure challenges—where performance is governed not just by raw compute, but by memory efficiency, intelligent scheduling, runtime specialization, and lifecycle automation. Unlike traditional ML serving, LLM inference requires systems that understand token-level execution, manage rapidly growing context state, and continuously balance latency, throughput, and cost under highly dynamic workloads.

      +

      The LLM Inference Framework addresses these challenges by transforming inference into a fully automated, reproducible lifecycle—from model onboarding and compilation to deployment, optimization, and observability. By integrating automated quantization and engine compilation, intelligent runtime selection, cold-start mitigation strategies, and LLM-specific observability metrics such as Time-to-First-Token and Inter-Token Latency, the platform ensures both high performance and operational simplicity.

      +

      Equally important, the framework is designed with flexibility and future evolution in mind. Its runtime-agnostic architecture enables seamless adoption of emerging inference engines, hardware accelerators, and optimization techniques without requiring platform redesign. This ensures that teams can continuously leverage advancements in the rapidly evolving LLM ecosystem while maintaining consistent operational workflows.

      +

      Ultimately, the goal of the platform is to make production-scale LLM deployment as seamless and reliable as traditional software deployment—allowing teams to focus on building intelligent applications rather than managing infrastructure complexity. By combining lifecycle automation, runtime optimization, and deep observability, the LLM Inference Framework provides a scalable foundation for delivering fast, cost-efficient, and production-ready LLM experiences.

      +

      Future Explorations

      +

      While we have achieved significant milestones in latency and throughput, the landscape of GenAI is evolving rapidly. Our roadmap focuses on increasing flexibility, reducing costs, and enhancing reliability for enterprise-grade workloads. Here is what we are building next:

      +
        +
      • TPU Support: To diversify our hardware supply chain and further optimize cost-per-token, we are evaluating Google Cloud TPUs to bake it into our platform. By leveraging the JAX and PyTorch/XLA ecosystems, we aim to unlock the massive throughput potential of TPU v5e chips, particularly for our open-source Llama models. This will allow the hardware profiler to dynamically choose between NVIDIA GPUs and Google TPUs based on real-time availability and price-performance metrics.
      • +
      • Multi-LoRA Serving (Serverless Experience): Currently, deploying a fine-tuned model requires a dedicated GPU. We are building support for Multi-LoRA serving, which will allow us to serve hundreds of unique, fine-tuned adapters on top of a single frozen base model. This will drastically reduce costs for multi-tenant applications, enabling a "serverless" experience where specific fine-tunes are hot-swapped instantly per request.
      • +
      • Spot Instance Orchestration: To further optimize cloud costs, we are developing fault-tolerant mechanisms to run inference workloads on Spot Instances. By implementing aggressive checkpointing and seamless request draining, we aim to leverage cheaper, preemptible compute capacity without interrupting the user's streaming experience.
      • +
      • Semantic Caching Layer: We plan to move beyond standard Prefix Caching to implement Semantic Caching. By using a vector database to fetch responses for semantically similar queries (e.g., "How do I reset my password?" vs. "Password reset steps"), we can bypass the GPU entirely for repetitive queries, reducing latency to near-zero.
      • +
      • Context-Aware Autoscaling: Standard CPU/GPU utilization metrics are often insufficient signals for scaling LLMs. We are working on KV-cache pressure metrics for autoscaling. This ensures that we scale out before the memory fills up, preventing eviction-based slowdowns during traffic spikes.
      • +
      • Online Evaluation & Guardrails: We are integrating a lightweight "Trust Layer" into the proxy. This will allow for low-latency input/output filtering (Guardrails) and asynchronous "LLM-as-a-Judge" evaluation pipelines to monitor response quality in production, not just system health.
      • +
      + + \ No newline at end of file diff --git a/docs/category/go-sdk/index.html b/docs/category/go-sdk/index.html index 8e58e52a..7fc9cba0 100644 --- a/docs/category/go-sdk/index.html +++ b/docs/category/go-sdk/index.html @@ -4,14 +4,14 @@ Go SDK | BharatMLStack - - - + + + -

      Go SDK

      Go SDK for BharatML Stack. Provides Go client libraries and packages for interacting with the online feature store, including gRPC clients and protocol buffer definitions.

      +

      Go SDK

      Go SDK for BharatML Stack. Provides Go client libraries and packages for interacting with the online feature store, including gRPC clients and protocol buffer definitions.

      \ No newline at end of file diff --git a/docs/category/inferflow/index.html b/docs/category/inferflow/index.html new file mode 100644 index 00000000..40a09723 --- /dev/null +++ b/docs/category/inferflow/index.html @@ -0,0 +1,17 @@ + + + + + +Inferflow | BharatMLStack + + + + + + + + +

      Inferflow

      Inferflow is a graph-driven feature retrieval and model inference orchestration engine. It dynamically resolves entity relationships via configurable DAGs, retrieves features from the Online Feature Store, and orchestrates model scoring — all without custom code.

      + + \ No newline at end of file diff --git a/docs/category/numerix/index.html b/docs/category/numerix/index.html new file mode 100644 index 00000000..f913409f --- /dev/null +++ b/docs/category/numerix/index.html @@ -0,0 +1,17 @@ + + + + + +Numerix | BharatMLStack + + + + + + + + +

      Numerix

      Numerix is a mathematical compute engine for BharatML Stack. It is used to perform mathematical operations on matrices and vectors.

      + + \ No newline at end of file diff --git a/docs/category/online-feature-store/index.html b/docs/category/online-feature-store/index.html index 8b52edc4..ee8747e9 100644 --- a/docs/category/online-feature-store/index.html +++ b/docs/category/online-feature-store/index.html @@ -4,14 +4,14 @@ Online Feature Store | BharatMLStack - - - + + + -

      Online Feature Store

      Online-feature-store is a high-performance, scalable, and production-grade feature store built for modern machine learning systems. It supports both real-time and batch workflows, with a strong emphasis on developer experience, system observability, and low-latency feature retrieval.

      +

      Online Feature Store

      Online-feature-store is a high-performance, scalable, and production-grade feature store built for modern machine learning systems. It supports both real-time and batch workflows, with a strong emphasis on developer experience, system observability, and low-latency feature retrieval.

      \ No newline at end of file diff --git a/docs/category/predator/index.html b/docs/category/predator/index.html new file mode 100644 index 00000000..de730787 --- /dev/null +++ b/docs/category/predator/index.html @@ -0,0 +1,17 @@ + + + + + +Predator | BharatMLStack + + + + + + + + +

      Predator

      Predator is a scalable, high-performance model inference service built as a wrapper around NVIDIA Triton Inference Server, designed to serve ML models with low latency in Kubernetes, with OnFS and Interflow integration.

      + + \ No newline at end of file diff --git a/docs/category/python-sdk/index.html b/docs/category/python-sdk/index.html index 4d4ac5de..83f5e379 100644 --- a/docs/category/python-sdk/index.html +++ b/docs/category/python-sdk/index.html @@ -4,14 +4,14 @@ Python SDK | BharatMLStack - - - + + + -

      Python SDK

      Python SDK for BharatML Stack. Provides Python client libraries and utilities for interacting with the online feature store, including gRPC clients, Spark integration, and common utilities.

      +

      Python SDK

      Python SDK for BharatML Stack. Provides Python client libraries and utilities for interacting with the online feature store, including gRPC clients, Spark integration, and common utilities.

      \ No newline at end of file diff --git a/docs/category/quick-start/index.html b/docs/category/quick-start/index.html index 9e6dc2d1..6f09dac7 100644 --- a/docs/category/quick-start/index.html +++ b/docs/category/quick-start/index.html @@ -4,14 +4,14 @@ Quick Start | BharatMLStack - - - + + + -

      Quick Start

      Quick Start guide for BharatML Stack. Get up and running quickly with step-by-step instructions, sample data, and Docker Compose setup for local development and testing.

      +

      Quick Start

      Quick Start guide for BharatML Stack. Get up and running quickly with step-by-step instructions, sample data, and Docker Compose setup for local development and testing.

      \ No newline at end of file diff --git a/docs/category/sdks/index.html b/docs/category/sdks/index.html index f6c91986..6a6d8bc6 100644 --- a/docs/category/sdks/index.html +++ b/docs/category/sdks/index.html @@ -4,14 +4,14 @@ SDKs | BharatMLStack - - - + + + -

      SDKs

      Software Development Kits (SDKs) for BharatML Stack. Includes client libraries for Go and Python to interact with the online feature store and other platform components.

      +

      SDKs

      Software Development Kits (SDKs) for BharatML Stack. Includes client libraries for Go and Python to interact with the online feature store and other platform components.

      \ No newline at end of file diff --git a/docs/category/skye/index.html b/docs/category/skye/index.html new file mode 100644 index 00000000..60187e30 --- /dev/null +++ b/docs/category/skye/index.html @@ -0,0 +1,17 @@ + + + + + +Skye | BharatMLStack + + + + + + + + +

      Skye

      Skye is a high-performance vector similarity search platform that enables fast semantic retrieval by representing data as vectors and querying nearest matches in high-dimensional space. It supports pluggable vector databases, tenant-level index isolation, intelligent caching, and centralized cluster management.

      + + \ No newline at end of file diff --git a/docs/category/trufflebox-ui/index.html b/docs/category/trufflebox-ui/index.html index be9543fc..2eddcd14 100644 --- a/docs/category/trufflebox-ui/index.html +++ b/docs/category/trufflebox-ui/index.html @@ -4,14 +4,14 @@ Trufflebox UI | BharatMLStack - - - + + + -

      Trufflebox UI

      Trufflebox UI is a modern, feature rich UI framework for supporting MLOps. It supports Feature catalog, management, user managemnet and other adminops

      +

      Trufflebox UI

      Trufflebox UI is a modern, feature rich UI framework for supporting MLOps. It supports Feature catalog, management, user managemnet and other adminops

      \ No newline at end of file diff --git a/docs/category/v100/index.html b/docs/category/v100/index.html deleted file mode 100644 index f3681e85..00000000 --- a/docs/category/v100/index.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - -v1.0.0 | BharatMLStack - - - - - - - - -

      v1.0.0

      Python SDK v1.0.0 documentation for BharatML Stack. Contains API reference, usage guides, and examples for the Python client libraries including gRPC feature client, Spark feature push client, and common utilities.

      - - \ No newline at end of file diff --git a/docs/img/bharatml-stack-logo.jpg b/docs/img/bharatml-stack-logo.jpg new file mode 100644 index 00000000..46ecdfa5 Binary files /dev/null and b/docs/img/bharatml-stack-logo.jpg differ diff --git a/docs/img/skye-rt-consumer-flow.png b/docs/img/skye-rt-consumer-flow.png new file mode 100644 index 00000000..11e40769 Binary files /dev/null and b/docs/img/skye-rt-consumer-flow.png differ diff --git a/docs/img/skye-system-overview.png b/docs/img/skye-system-overview.png new file mode 100644 index 00000000..2f992dbf Binary files /dev/null and b/docs/img/skye-system-overview.png differ diff --git a/docs/img/v1.0.0-inferflow-arch.png b/docs/img/v1.0.0-inferflow-arch.png new file mode 100644 index 00000000..acad4888 Binary files /dev/null and b/docs/img/v1.0.0-inferflow-arch.png differ diff --git a/docs/img/v1.0.0-inferflow-dag-matrix.png b/docs/img/v1.0.0-inferflow-dag-matrix.png new file mode 100644 index 00000000..2345d69f Binary files /dev/null and b/docs/img/v1.0.0-inferflow-dag-matrix.png differ diff --git a/docs/img/v1.0.0-predator-hld.png b/docs/img/v1.0.0-predator-hld.png new file mode 100644 index 00000000..3e8a21ad Binary files /dev/null and b/docs/img/v1.0.0-predator-hld.png differ diff --git a/docs/index.html b/docs/index.html index ed910758..e4a27af4 100644 --- a/docs/index.html +++ b/docs/index.html @@ -3,15 +3,20 @@ -BharatMLStack - Open Source ML Infrastructure | BharatMLStack - - - +BharatMLStack - Open Source ML Infrastructure | BharatMLStack + + + -
      BharatMLStack Logo

      Welcome to BharatMLStack

      Open source, end-to-end ML infrastructure stack built for scale, speed, and simplicity.

      Sub-10msP99 Latency
      1M+ RPSTested Capacity
      Multi-DBSupport

      Online Feature Store

      High-performance, production-ready feature serving for real-time ML inference

      🚀

      High-Performance Feature Store

      Sub-10ms P99 latency and 1M+ RPS capacity. Built for real-time ML inference with custom PSDB serialization format that outperforms Protocol Buffers and Apache Arrow.

      Production-Ready ML Infrastructure

      Multi-database backends (Scylla, Dragonfly, Redis), comprehensive monitoring, and enterprise-grade features. Deploy with confidence using battle-tested components.

      🛠️

      Developer-First Experience

      Multi-language SDKs (Go, Python), gRPC APIs, and extensive documentation. From data scientists, ML engineers to backend engineers, everyone gets tools they love.

      Built for India's Scale

      BharatMLStack is a comprehensive, production-ready machine learning infrastructure platform designed to democratize ML capabilities across India and beyond. Our mission is to provide a robust, scalable, and accessible ML stack that empowers organizations to build, deploy, and manage machine learning solutions at massive scale.

      Explore Online Feature Store →

      🏆 Key Achievements

      • ✅ Sub-10ms P99 latency for real-time inference
      • ✅ 1M+ RPS tested with 100 IDs per request
      • ✅ PSDB format outperforms Proto3 & Arrow
      • ✅ Multi-database: Scylla, Dragonfly, Redis
      • ✅ Production-ready with comprehensive monitoring

      Trufflebox UI

      Modern, feature-rich UI framework for comprehensive MLOps management

      📋

      Feature Catalog & Management

      Comprehensive feature catalog with metadata management, versioning, and governance. Organize and discover features across your ML platform with ease.

      👥

      User Management & Admin Ops

      Role-based access control, user authentication, and administrative operations. Secure your ML platform with enterprise-grade user management capabilities.

      🎨

      Modern UI Framework

      Intuitive, responsive web interface built with modern web technologies. Streamline MLOps workflows with beautiful and functional user experiences.

      Modern MLOps Management

      Trufflebox UI provides a comprehensive, modern web interface for managing your entire ML infrastructure. Built with cutting-edge web technologies, it delivers an intuitive experience for feature management, user administration, and operational oversight. Streamline your MLOps workflows with enterprise-grade UI components.

      Explore Trufflebox UI →

      🎨 UI Features

      • ✅ Comprehensive feature catalog & discovery
      • ✅ Role-based access control & user management
      • ✅ Job, Store, Admin Ops management
      • ✅ Approval flow for everything
      • ✅ Responsive design for desktop & mobile

      SDKs

      Developer-friendly client libraries and APIs for seamless platform integration

      🌐

      Multi-Language Support

      Native SDKs for Go and Python with idiomatic APIs. Choose the language that fits your team's expertise and existing infrastructure.

      🔗

      gRPC & REST APIs

      High-performance gRPC clients and REST APIs for seamless integration. Built-in support for streaming, batching, and async operations.

      Spark Integration

      Native Apache Spark integration for batch feature processing and ingestion. Scale your feature engineering workflows with distributed computing power.

      Developer-First Integration

      Our SDKs are designed with developers in mind, providing idiomatic APIs for Go and Python that feel natural in your existing codebase. Whether you're building microservices, data pipelines, or ML applications, our SDKs provide the tools you need for seamless integration with BharatMLStack's powerful infrastructure.

      Explore SDKs →

      🛠️ Developer Tools

      • ✅ Native Go & Python SDKs with type safety
      • ✅ High-performance gRPC
      • ✅ Apache Spark integration for publishing features
      +
      Open-source, scalable stack for enterprise ML

      Build production ML pipelines faster

      Open source, end-to-end ML infrastructure stack built for scale, speed, and simplicity. Integrate, deploy, and manage robust ML workflows with full reliability and control.

      Adopted by data teams building at scale

      BharatML Stack Logo

      Why BharatMLStack

      The Real Barriers to Scaling Machine Learning

      ML teams spend more time fighting infrastructure than building intelligence. BharatMLStack removes those barriers.

      🧠

      Focus on building intelligence, not infrastructure

      • Does every model deployment require a full-stack integration effort?
      • Do engineers have to rebuild feature retrieval, endpoint integrations, and logging for each new model?
      • Does changing a simple expression like 0.2×s₁ + 0.8×s₂ to 0.3×s₁ + 0.7×s₂ really need code reviews and redeployments?
      • Why does deploying intelligence require the devops team to provision infra?

      Machine learning teams should be iterating on models, not systems. Yet today, infrastructure complexity turns simple improvements into weeks of engineering effort, slowing experimentation and innovation.

      💰

      Built for scale without exponential cost growth

      • Do your infrastructure costs scale faster than your ML impact?
      • Are you recomputing the same features, reloading the same data, and moving the same bytes across systems repeatedly?
      • Are expensive GPUs and compute sitting underutilized while workloads wait on data or inefficient pipelines?
      • Why does scaling ML often mean scaling cost linearly—or worse?

      A modern ML platform should eliminate redundant computation, reuse features intelligently, and optimize data access across memory, NVMe, and object storage. Compute should be pooled, scheduled efficiently, and fully utilized—ensuring that scale drives impact, not runaway infrastructure costs.

      🌍

      Freedom to deploy anywhere, without lock-in

      • Are your models tied to a single cloud, making migration costly and complex?
      • Does adopting managed services today limit your ability to optimize cost or move infrastructure tomorrow?
      • Can you deploy the same ML stack across public cloud, private cloud, or sovereign environments without redesigning everything?
      • Why should infrastructure choices dictate the future of your ML systems?

      A modern ML platform should be built on open standards and cloud-neutral abstractions, allowing you to deploy anywhere—public cloud, private infrastructure, or sovereign environments. This ensures complete control over your data, freedom from vendor lock-in, and the ability to optimize for cost, performance, and compliance without architectural constraints.

      Platform Components

      BharatMLStack Components

      Purpose-built components for every stage of the ML lifecycle, from feature serving to model deployment.

      Online Feature Store

      BharatMLStack Online Feature Store delivers sub-10ms, high-throughput access to machine learning features for real-time inference. It seamlessly ingests batch and streaming data, validates schemas, and persists compact, versioned feature groups optimized for low latency and efficiency. With scalable storage backends, gRPC APIs, and binary-optimized formats, it ensures consistent, reliable feature serving across ML pipelines.

      Learn more →
      🔀

      Inferflow

      Inferflow is BharatMLStack's intelligent inference gateway that dynamically retrieves and assembles features required by ML models using a graph-based configuration called Inferpipes. It automatically resolves entity relationships, fetches features from the Online Feature Store, and constructs feature vectors without custom code.

      Learn more →
      🔍

      Skye

      Skye enables fast similarity retrieval by representing data as vectors and querying nearest matches in high-dimensional space. It supports pluggable vector databases, ensuring flexibility across infrastructure. The system provides tenant-level index isolation while allowing single embedding ingestion even when shared across tenants, reducing redundancy.

      Learn more →
      🧮

      Numerix

      Numerix is a high-performance compute engine designed for ultra-fast element-wise matrix operations. Built in Rust and accelerated using SIMD, it delivers exceptional efficiency and predictable performance. Optimized for real-time inference workloads, it achieves strict sub-5ms p99 latency on matrices up to 1000×10.

      Learn more →
      🚀

      Predator

      Predator streamlines infrastructure and model lifecycle management. It enables the creation of deployables with specific Triton Server versions and supports seamless model rollouts. Leveraging Helm charts and Argo CD, Predator automates Kubernetes-based deployments while integrating with KEDA for auto-scaling and performance tuning.

      Learn more →

      Proven at scale

      Scaling Numbers

      Daily Orders

      0.0M+

      Daily orders processed via ML pipelines

      QPS on FS

      0.0M

      QPS on Feature Store with batch size of 100 id lookups

      QPS Inference

      0M+

      QPS on Model Inference

      QPS Embedding

      0K

      QPS Embedding Search

      See it in action

      Demo Videos

      Watch short demos of each BharatMLStack component in action.

      Feature Store

      Learn how to onboard and manage features using the self-serve UI for the Online Feature Store.

      Embedding Platform

      Walkthrough of onboarding and managing embedding models via the Skye self-serve UI.

      Numerix

      Step-by-step guide to configuring and running matrix operations through the Numerix self-serve UI.

      Predator

      How to deploy and manage ML models on Kubernetes using the Predator self-serve UI.

      Inferflow

      Setting up inferpipes and feature retrieval graphs through the Inferflow self-serve UI.

      Deploy ML models with confidence

      Comprehensive stack for business-ready ML. Integrates seamlessly with enterprise systems. Robust security and regulatory compliance.

      \ No newline at end of file diff --git a/docs/inferflow/v1.0.0/architecture/index.html b/docs/inferflow/v1.0.0/architecture/index.html new file mode 100644 index 00000000..114b43d4 --- /dev/null +++ b/docs/inferflow/v1.0.0/architecture/index.html @@ -0,0 +1,171 @@ + + + + + +Architecture | BharatMLStack + + + + + + + + +

      BharatMLStack - Inferflow

      +

      Inferflow is part of BharatMLStack, a graph-driven feature retrieval and model inference orchestration engine built in Go. It eliminates the need for custom feature retrieval code by using configurable DAG topologies to dynamically resolve entity relationships, fetch features from the Online Feature Store, and orchestrate model scoring — all driven by configuration stored in etcd.

      +
      +

      Overview

      +

      In a typical ML serving pipeline, every new model requires bespoke code to:

      +
        +
      • Fetch features from multiple entities (user, product, user x category, etc.)
      • +
      • Infer intermediate entity relationships (e.g., extract category from product to fetch user x category data)
      • +
      • Orchestrate one or more model inference calls
      • +
      • Handle I/O, batching, and error propagation
      • +
      +

      Inferflow abstracts all of this behind a config-driven DAG executor. Given a model_config_id and context entities (e.g., userId, productIds), it:

      +
        +
      1. Loads a pre-defined feature retrieval and inference graph from etcd
      2. +
      3. Executes the graph to resolve entity relationships dynamically
      4. +
      5. Retrieves features from the Online Feature Store (OnFS) in parallel
      6. +
      7. Calls model serving endpoints (Predator) and compute services (Numerix)
      8. +
      9. Returns scored results as a structured response
      10. +
      +
      +

      High-Level Architecture

      +

      Inferflow Architecture - DAG Topology Executor

      +

      The diagram shows the internal DAG structure of Inferflow's topology executor. gRPC APIs (Pair, Point, Slate) feed into the DAG, where Feature Init bootstraps the ComponentMatrix. Feature components (FS User, FS Product, FS Region, FS User Cat, FS Region Scat) fetch features from OnFS in parallel and populate columns in the shared 2D Result Matrix. Model components (Model A, Model B) call Predator for inference, and compute components call Numerix for operations like reranking. The entire DAG topology is driven by config loaded from etcd.

      +
      +

      Core Components

      +

      1. gRPC Server

      +

      Inferflow exposes its APIs via a gRPC server, with HTTP health endpoints multiplexed on the same port using cmux. The server provides:

      +
        +
      • Inferflow APIRetrieveModelScore: entity-based feature retrieval and scoring
      • +
      • Predict APIInferPointWise, InferPairWise, InferSlateWise: structured inference with targets, pairs, and slates
      • +
      +

      2. DAG Topology Executor

      +

      The heart of Inferflow. Each model configuration defines a component_dependency map that describes a Directed Acyclic Graph (DAG) of components.

      +

      Execution model:

      +
        +
      • Uses Kahn's algorithm for topological ordering
      • +
      • Components at the same level run concurrently in goroutines
      • +
      • All components share a mutable ComponentMatrix (rows = entity IDs, columns = features/scores)
      • +
      • DAG topologies are cached using Murmur3 hashing with Ristretto cache
      • +
      +

      Validation:

      +
        +
      • Cycle detection via in-degree analysis
      • +
      • Component existence verification against the ComponentProvider
      • +
      +

      3. Component Types

      +

      Inferflow defines four types of DAG components:

      +
      ComponentRoleExternal Dependency
      FeatureInitComponentRoot node — initializes the ComponentMatrix with entity IDs and schemaNone
      FeatureComponentFetches features from the Online Feature Store for a specific entity typeOnFS (gRPC)
      PredatorComponentCalls model serving endpoints for inference scoringPredator / Helix (gRPC)
      NumerixComponentCalls compute engine for operations like rerankingNumerix (gRPC)
      +

      4. ComponentMatrix — The 2D Result Matrix

      +

      The ComponentMatrix is a shared, mutable 2D data structure that flows through the entire DAG. Every component reads from and writes to this matrix, progressively building a complete feature + score row for each entity.

      +

      DAG Execution &amp; 2D Matrix Flow

      +

      How the matrix evolves through the DAG

      +

      The diagram above illustrates the three execution phases and how the 2D matrix grows at each stage:

      +

      Phase 1 — Feature Retrieval

      +

      The init node creates an empty matrix with one row per target entity ID. Feature components then execute — first the top-level entities (entity A, entity B) fetch their features from OnFS and populate their columns (shown as colored blocks). Derived entities (entity C, D, E) resolve their keys from the already-populated columns and add more feature columns. At this point the matrix contains all feature data, with each color representing features from a different entity.

      +

      The right side of the diagram shows the matrix being decomposed — feature columns from different entities are separated into per-model input groups, selecting only the features each model needs.

      +

      Phase 2 — Model Invocation

      +

      Model X and Model Y each receive their decomposed feature slices, call Predator for inference, and write score columns back into the matrix (shown as new colored columns appended to the right). Multiple models can run in parallel if they don't depend on each other's outputs.

      +

      The scores are then decomposed again to prepare inputs for the compute stage.

      +

      Phase 3 — Numerix Compute

      +

      The Score Comb node takes score columns from both models, calls Numerix for a final compute operation (e.g., score combination, reranking), and writes the final score column (shown in dark red) into the matrix. The result is a complete row per entity with all features and all scores.

      +

      Matrix structure

      +
      PropertyDescription
      RowsOne per target entity ID (e.g., each product being scored)
      String columnsHuman-readable values used in responses
      Byte columnsBinary-encoded feature values used for model inputs
      Column namingentity_label:feature_group:feature_name
      +

      Each component only reads the columns it needs and writes to its own columns, enabling safe concurrent execution across independent branches of the DAG.

      +

      For slate-based APIs, a companion SlateData structure holds per-slate matrices and scores, with slate_target_indices mapping slates to rows in the main matrix.

      +

      5. Configuration Management (etcd)

      +

      Model configurations are stored in etcd and hot-reloaded via watchers:

      +
        +
      • Config paths: /config/inferflow/services/, /model-config
      • +
      • Watch mechanism: etcd watchers trigger ReloadModelConfigMapAndRegisterComponents on any change
      • +
      • On reload: Updates ConfigMap, re-initializes feature schemas, and re-registers DAG components
      • +
      +

      This means new models or configuration changes go live without redeployment.

      +

      6. External Integrations

      +

      Online Feature Store (OnFS)

      +
        +
      • gRPC client calling FeatureService.RetrieveFeatures
      • +
      • Batched retrieval with configurable batch size and deadline
      • +
      • Auth via CALLER_ID and CALLER_TOKEN metadata
      • +
      +

      Predator (Model Serving)

      +
        +
      • Uses go-sdk for model inference
      • +
      • Supports percentage-based traffic routing across multiple model endpoints
      • +
      • Configurable calibration and batch sizing
      • +
      +

      Numerix (Compute Engine)

      +
        +
      • Uses go-sdk Numerix client
      • +
      • RPC: NumerixService.Compute with entity score data
      • +
      • Used for compute operations like reranking
      • +
      +

      Kafka (Inference Logging)

      +
        +
      • Async inference log publishing using segmentio/kafka-go
      • +
      • Supports Proto, Arrow, and Parquet serialization formats
      • +
      • Configurable sampling via LoggingPerc and user-based daily sampling
      • +
      +
      +

      Request Flow

      +
      1. Client sends gRPC request with model_config_id + entity IDs

      2. Load ModelConfig from etcd-backed ConfigMap

      3. Adapt proto request → ComponentRequest
      (build ComponentMatrix with entity schema)

      4. Resolve DAG topology from component_dependency config

      5. Execute DAG (Kahn's algorithm, concurrent):

      ├─ FeatureInitComponent: populate matrix with entity IDs + schema

      ├─ FeatureComponents (parallel): fetch features from OnFS → fill matrix columns

      ├─ PredatorComponent: build feature payloads from matrix → call model → write scores

      └─ NumerixComponent: read scores from matrix → call compute → write final scores

      6. Build response from matrix columns per ResponseConfig

      7. (Optional) Async Kafka logging of inference features and scores

      8. Return gRPC response to client
      +
      +

      Observability

      +

      Metrics (StatsD / Telegraf)

      +
      MetricDescription
      inferflow.retrievemodelscore.request.totalTotal RetrieveModelScore requests
      inferflow.retrievemodelscore.latencyEnd-to-end latency
      inferflow.retrievemodelscore.batch.sizeBatch size per request
      predict.infer.request.totalTotal Predict API requests
      predict.infer.latencyPredict API latency
      inferflow.component.execution.totalPer-component execution count
      inferflow.component.execution.latencyPer-component latency
      inferflow.component.execution.errorComponent-level errors
      inferflow.component.feature.countFeature count per component
      inferflow.external.api.request.totalExternal API call count
      inferflow.external.api.latencyExternal API latency
      inferflow.component.inmemorycache.request.totalCache hit/miss total
      inferflow.component.inmemorycache.missCache misses
      inferflow.logging.kafka_sentKafka log messages sent
      +

      Logging

      +
        +
      • Structured JSON logging via zerolog
      • +
      • Configurable log levels
      • +
      +
      +

      Deployment

      +

      Docker

      +

      Inferflow ships as a multi-stage Docker image:

      +
        +
      • Builder: Go 1.19 Alpine with optional Kafka support (librdkafka)
      • +
      • Runtime: Debian 10 slim
      • +
      • Build command: go build -tags musl -ldflags "-extldflags -static" -o server cmd/${module}/main.go
      • +
      +

      Supported Environments

      +
        +
      • Kubernetes (K8s)
      • +
      • Google Kubernetes Engine (GKE)
      • +
      • Amazon EKS
      • +
      +

      Configuration

      +

      All configuration is driven via environment variables (loaded by Viper) and etcd. No config files are required at deployment time.

      +
      +

      Target Users

      +
      UserRole
      Data ScientistsDefine model configs and feature retrieval graphs via config — no code needed
      ML EngineersOnboard new models by updating etcd config; manage DAG topologies
      Backend DevelopersIntegrate via gRPC SDKs for real-time scoring in application services
      Platform EngineersDeploy, scale, and monitor Inferflow clusters
      +
      +

      Benefits

      +
        +
      • No-code feature retrieval — new models need only a config change, not custom code
      • +
      • Feature consistency — same graph-driven retrieval ensures identical features across experiments
      • +
      • Faster iteration — experiment with new models in minutes, not days
      • +
      • Concurrent execution — DAG components run in parallel for minimal latency
      • +
      • Hot reloading — model config changes via etcd go live without redeployment
      • +
      • Multi-API support — PointWise, PairWise, and SlateWise inference patterns out of the box
      • +
      • Production-grade — built in Go with gRPC, designed for millions of QPS
      • +
      +
      +

      Contributing

      +

      We welcome contributions from the community! Please see our Contributing Guide for details on how to get started.

      +

      Community & Support

      + +

      License

      +

      BharatMLStack is open-source software licensed under the BharatMLStack Business Source License 1.1.

      +
      +
      Built with ❤️ for the ML community from Meesho
      +
      If you find this useful, ⭐️ the repo — your support means the world to us!
      + + \ No newline at end of file diff --git a/docs/inferflow/v1.0.0/configuration/index.html b/docs/inferflow/v1.0.0/configuration/index.html new file mode 100644 index 00000000..c59f82fe --- /dev/null +++ b/docs/inferflow/v1.0.0/configuration/index.html @@ -0,0 +1,108 @@ + + + + + +Configuration Guide | BharatMLStack + + + + + + + + +

      Inferflow - Configuration Guide

      +

      Inferflow is fully config-driven. All model onboarding, feature retrieval logic, DAG topology, and inference behavior are controlled through configuration stored in etcd — with zero code changes required.

      +
      +

      Configuration Overview

      +

      Inferflow configuration is organized into two layers:

      +
        +
      1. Static config — Environment variables loaded at startup (via Viper)
      2. +
      3. Dynamic config — Model configurations stored in etcd, hot-reloaded on change
      4. +
      +
      +

      Static Configuration (Environment Variables)

      +

      These are set at deployment time and require a restart to change.

      +

      Server

      +
      VariableDescriptionExample
      APP_PORTgRPC/HTTP server port50051
      APP_ENVEnvironment nameproduction
      +

      etcd

      +
      VariableDescriptionExample
      ETCD_ENDPOINTSComma-separated etcd endpointsetcd-0:2379,etcd-1:2379
      ETCD_DIAL_TIMEOUTConnection timeout5s
      +

      Online Feature Store (OnFS)

      +
      VariableDescriptionExample
      externalServiceOnFs_hostOnFS gRPC hostonfs-api:50051
      externalServiceOnFs_callerIdCaller ID for authinferflow
      externalServiceOnFs_callerTokenCaller token for auth<token>
      externalServiceOnFs_batchSizeBatch size for feature retrieval100
      externalServiceOnFs_deadlineRequest deadline200ms
      +

      Predator (Model Serving)

      +
      VariableDescriptionExample
      externalServicePredator_defaultDeadlineDefault inference deadline100ms
      +

      Numerix (Compute Engine)

      +
      VariableDescriptionExample
      numerixClientV1_hostNumerix gRPC hostnumerix:50052
      numerixClientV1_deadlineRequest deadline100ms
      +

      Kafka (Inference Logging)

      +
      VariableDescriptionExample
      KafkaBootstrapServersKafka broker addresseskafka-0:9092,kafka-1:9092
      KafkaLoggingTopicTopic for inference logsinferflow-logs
      +

      Metrics (StatsD / Telegraf)

      +
      VariableDescriptionExample
      TELEGRAF_HOSTStatsD hosttelegraf
      TELEGRAF_PORTStatsD port8125
      +

      In-Memory Cache

      +
      VariableDescriptionExample
      CACHE_SIZE_MBCache size in MB512
      CACHE_TYPECache implementationfreecache
      +
      +

      Dynamic Configuration (etcd Model Config)

      +

      Model configurations are stored in etcd and hot-reloaded. Each model is identified by a model_config_id.

      +

      Config Structure

      +
      {
      "model_config_id_example": {
      "dag_execution_config": {
      "component_dependency": {
      "feature_initializer": ["fs_user", "fs_product"],
      "fs_user": ["ranker_model"],
      "fs_product": ["ranker_model"],
      "ranker_model": []
      }
      },
      "component_config": {
      "feature_component_config": {
      "fs_user": { ... },
      "fs_product": { ... }
      },
      "predator_component_config": {
      "ranker_model": { ... }
      },
      "numerix_component_config": {},
      "cache_enabled": true,
      "cache_version": "v1",
      "cache_ttl": 300,
      "error_logging_percent": 10
      },
      "response_config": {
      "features": ["ranker_model:score"],
      "model_schema_perc": 100,
      "logging_perc": 5,
      "log_features": ["fs_user:profile:age", "ranker_model:score"],
      "log_batch_size": 100
      }
      }
      }
      +
      +

      DAG Execution Config

      +

      Defines the component dependency graph.

      +
      {
      "component_dependency": {
      "<parent_component>": ["<child_1>", "<child_2>"],
      "<child_1>": ["<grandchild>"],
      "<child_2>": ["<grandchild>"],
      "<grandchild>": []
      }
      }
      +

      Rules:

      +
        +
      • The graph must be a valid DAG (no cycles)
      • +
      • Components with no parents (zero in-degree) execute first
      • +
      • Components with empty dependency arrays [] are leaf nodes
      • +
      • All component names must match registered components in the ComponentConfig
      • +
      +
      +

      Feature Component Config

      +

      Configures how features are fetched from the Online Feature Store.

      +
      {
      "fs_user": {
      "fs_keys": {
      "schema": ["user_id"],
      "col": "context:user:user_id"
      },
      "fs_request": {
      "entity_label": "user",
      "feature_groups": [
      {
      "label": "demographics",
      "feature_labels": ["age", "location", "income_bracket"]
      },
      {
      "label": "behavior",
      "feature_labels": ["click_rate", "purchase_freq"]
      }
      ]
      },
      "fs_flatten_resp_keys": ["user_id"],
      "col_name_prefix": "user",
      "comp_cache_enabled": true,
      "comp_cache_ttl": 600,
      "composite_id": false
      }
      }
      +
      FieldDescription
      fs_keysHow to extract lookup keys from the matrix. schema defines key column names; col references a matrix column
      fs_requestOnFS query: entity label + feature groups with specific features
      fs_flatten_resp_keysKeys to flatten in response mapping
      col_name_prefixPrefix for matrix column names (e.g., user:demographics:age)
      comp_cache_enabledEnable in-memory caching for this component
      comp_cache_ttlCache TTL in seconds
      composite_idWhether entity keys are composite
      +
      +

      Predator Component Config

      +

      Configures model inference endpoints.

      +
      {
      "ranker_model": {
      "model_name": "product_ranker_v3",
      "model_endpoint": "predator-ranker:8080",
      "model_end_points": {
      "predator-ranker-v3:8080": 80,
      "predator-ranker-v4:8080": 20
      },
      "deadline": 100,
      "batch_size": 50,
      "calibration": {
      "enabled": false
      },
      "inputs": {
      "feature_map": {
      "user:demographics:age": "INT32",
      "user:behavior:click_rate": "FP32",
      "product:attributes:category_id": "INT32"
      }
      },
      "outputs": {
      "score_columns": ["score", "confidence"]
      },
      "slate_component": false
      }
      }
      +
      FieldDescription
      model_nameModel identifier on the serving platform
      model_endpointPrimary model serving endpoint
      model_end_pointsMultiple endpoints with percentage-based traffic routing
      deadlineInference timeout in milliseconds
      batch_sizeMax items per inference batch
      calibrationScore calibration settings
      inputs.feature_mapMap of matrix column → data type for model input
      outputs.score_columnsColumn names for model output scores
      slate_componentIf true, runs per-slate inference
      +
      +

      Numerix Component Config

      +

      Configures compute operations (e.g., reranking).

      +
      {
      "reranker": {
      "score_column": "final_score",
      "data_type": "FP32",
      "score_mapping": {
      "ranker_model:score": "FP32",
      "user:behavior:click_rate": "FP32"
      },
      "compute_id": "diversity_rerank_v1",
      "slate_component": false
      }
      }
      +
      FieldDescription
      score_columnOutput column name for the computed score
      data_typeOutput data type
      score_mappingMap of matrix columns to include as compute inputs
      compute_idIdentifies the compute operation on Numerix
      slate_componentIf true, runs per-slate compute
      +
      +

      Response Config

      +

      Controls what data is returned to the client and what is logged.

      +
      {
      "features": ["ranker_model:score", "reranker:final_score"],
      "model_schema_perc": 100,
      "logging_perc": 5,
      "log_features": [
      "user:demographics:age",
      "ranker_model:score",
      "reranker:final_score"
      ],
      "log_batch_size": 100
      }
      +
      FieldDescription
      featuresMatrix columns to include in the gRPC response
      model_schema_percPercentage of requests that include full schema in response
      logging_percPercentage of requests to send to Kafka for logging
      log_featuresSpecific features to include in log messages
      log_batch_sizeBatch size for grouped log messages
      +
      +

      Service-Level Config

      +

      Global settings that apply across all models.

      +
      {
      "v2_logging_type": "proto",
      "compression_enabled": false
      }
      +
      FieldValuesDescription
      v2_logging_typeproto, arrow, parquetSerialization format for Kafka inference logs
      compression_enabledtrue, falseEnable compression for log messages
      +
      +

      Example: Onboarding a New Model

      +

      To onboard a new ranking model, update the etcd config:

      +

      Step 1: Define the feature retrieval graph

      +
      "component_dependency": {
      "feature_initializer": ["fs_user", "fs_product", "fs_user_x_category"],
      "fs_product": ["fs_user_x_category"],
      "fs_user": ["new_ranker"],
      "fs_user_x_category": ["new_ranker"],
      "new_ranker": []
      }
      +

      Here fs_user_x_category depends on fs_product because it needs the category ID extracted from the product entity to resolve the user x category key.

      +

      Step 2: Configure each component (feature groups, model endpoints, etc.)

      +

      Step 3: Push the config to etcd — Inferflow picks it up automatically via watchers.

      +

      No code changes. No redeployment. The new model is live.

      +
      +

      Contributing

      +

      We welcome contributions from the community! Please see our Contributing Guide for details on how to get started.

      +

      Community & Support

      + +

      License

      +

      BharatMLStack is open-source software licensed under the BharatMLStack Business Source License 1.1.

      +
      +
      Built with ❤️ for the ML community from Meesho
      +
      If you find this useful, ⭐️ the repo — your support means the world to us!
      + + \ No newline at end of file diff --git a/docs/inferflow/v1.0.0/functionalities/index.html b/docs/inferflow/v1.0.0/functionalities/index.html new file mode 100644 index 00000000..0bc433fe --- /dev/null +++ b/docs/inferflow/v1.0.0/functionalities/index.html @@ -0,0 +1,210 @@ + + + + + +Key Functionalities | BharatMLStack + + + + + + + + +

      Inferflow - Key Functionalities

      +

      Overview

      +

      Inferflow is a high-performance, config-driven ML inference orchestration engine built in Go. It provides no-code feature retrieval, DAG-based execution, and multi-pattern model inference — enabling ML teams to onboard new models through configuration changes alone.

      +
      +

      Core Capabilities

      +

      Graph-Driven Feature Retrieval

      +

      Inferflow's defining feature is its ability to resolve entity relationships and retrieve features through configurable DAG topologies — no custom code required.

      +

      How it works:

      +
        +
      1. A model_config_id maps to a pre-defined DAG of components
      2. +
      3. Context entity IDs (e.g., userId, productIds) are provided at request time
      4. +
      5. The DAG resolves intermediate entity relationships (e.g., extracting category from product to fetch user x category features)
      6. +
      7. Features are fetched in parallel from the Online Feature Store
      8. +
      9. A 2D feature matrix is assembled and passed to model scoring
      10. +
      +

      Impact:

      +
        +
      • New models require only a config update — no code changes
      • +
      • Feature consistency is guaranteed across experiments
      • +
      • Iteration cycles drop from days to minutes
      • +
      +

      DAG Topology Executor

      +

      The execution engine uses Kahn's algorithm for topological ordering with concurrent goroutine execution at each level:

      +
      component_dependency: {
      "feature_initializer": ["fs_user", "fs_product"],
      "fs_user": ["ranker"],
      "fs_product": ["ranker"],
      "ranker": ["reranker"],
      "reranker": []
      }
      +

      This config defines:

      +
        +
      • feature_initializer runs first (zero in-degree)
      • +
      • fs_user and fs_product run in parallel after init
      • +
      • ranker runs after both feature components complete
      • +
      • reranker runs after the ranker
      • +
      +

      Key properties:

      +
        +
      • Cycle detection via in-degree analysis
      • +
      • DAG topologies cached using Murmur3 hashing (Ristretto cache)
      • +
      • Components are registered and resolved via a ComponentProvider
      • +
      +
      +

      Multi-Pattern Inference APIs

      +

      Inferflow supports three inference patterns via the Predict API, each designed for different ML use cases:

      +

      PointWise Inference

      +

      Score each target independently against context features.

      +
      rpc InferPointWise(PredictRequest) returns (PredictResponse);
      +

      Use cases: Click-through rate prediction, fraud scoring, relevance ranking

      +

      Input: Context features + list of targets (e.g., products) +Output: Per-target scores

      +

      PairWise Inference

      +

      Score pairs of targets relative to each other.

      +
      rpc InferPairWise(PredictRequest) returns (PredictResponse);
      +

      Use cases: Preference learning, comparison-based ranking

      +

      Input: Context features + targets + pair indices (first/second) +Output: Per-pair scores + optional per-target scores

      +

      SlateWise Inference

      +

      Score groups (slates) of targets together, capturing inter-item effects.

      +
      rpc InferSlateWise(PredictRequest) returns (PredictResponse);
      +

      Use cases: Whole-page optimization, slate-level reranking, diversity-aware scoring

      +

      Input: Context features + targets + slate definitions (target indices per slate) +Output: Per-slate scores + optional per-target scores

      +
      +

      Entity & Legacy API

      +

      RetrieveModelScore

      +

      The original Inferflow API for entity-based feature retrieval and scoring:

      +
      service Inferflow {
      rpc RetrieveModelScore(InferflowRequestProto) returns (InferflowResponseProto);
      }
      +

      Request structure:

      +
      FieldDescription
      entitiesList of entity types with their IDs and optional inline features
      model_config_idIdentifies the model configuration (DAG, components, response format)
      tracking_idRequest-level tracing identifier
      +

      Entity structure:

      +
        +
      • entity: Entity type label (e.g., "user", "product")
      • +
      • ids: List of entity IDs
      • +
      • features: Optional inline features (name + per-ID values)
      • +
      +
      +

      Component Types

      +

      FeatureInitComponent

      +

      Role: Root DAG node — initializes the shared ComponentMatrix.

      +
        +
      • Sets up rows from entity IDs
      • +
      • Populates schema columns (string + byte) for all downstream components
      • +
      • For slate APIs: initializes SlateData with slate_target_indices
      • +
      +

      FeatureComponent

      +

      Role: Fetches features from the Online Feature Store (OnFS) for a specific entity type.

      +
        +
      • Reads FSKeys from config to extract lookup keys from the matrix
      • +
      • Batches unique entities and calls OnFS via gRPC
      • +
      • Optional in-memory caching keyed by model_id:version:component:entity
      • +
      • Writes binary feature values into matrix byte columns
      • +
      +

      Column naming convention: entity_label:feature_group:feature_name

      +

      PredatorComponent

      +

      Role: Calls model serving endpoints for inference.

      +
        +
      • Builds feature payloads from matrix columns with type conversion
      • +
      • Supports percentage-based traffic routing across multiple model endpoints
      • +
      • Handles slate-level inference: per-slate matrix → separate inference → scores to SlateData
      • +
      • Configurable calibration and batch sizing
      • +
      +

      NumerixComponent

      +

      Role: Calls the Numerix compute engine for operations like reranking.

      +
        +
      • Uses ScoreMapping config to map matrix columns to compute inputs
      • +
      • Writes a single score column back to the matrix
      • +
      • Supports slate mode for per-slate compute operations
      • +
      +
      +

      Feature Retrieval Pipeline

      +

      Key Resolution

      +

      Feature components use FSKeys configuration to dynamically resolve entity keys:

      +
      {
      "FSKeys": {
      "schema": ["user_id"],
      "col": "user:profile:user_id"
      }
      }
      +

      The component reads key values from the existing matrix columns, enabling chained entity resolution — e.g., fetch product entity first, extract category, then fetch user x category features.

      +

      Batched Retrieval

      +
        +
      • Features are fetched via FeatureService.RetrieveFeatures gRPC call
      • +
      • Requests are batched by unique entity keys
      • +
      • Configurable batch size and deadline per component
      • +
      • Auth via CALLER_ID and CALLER_TOKEN metadata
      • +
      +

      In-Memory Caching

      +

      Optional per-component caching reduces OnFS load:

      +
        +
      • Cache key: model_id:cache_version:component_name:entity_key
      • +
      • Configurable TTL per component
      • +
      • Zero-GC-overhead cache implementation available
      • +
      • Cache hit/miss metrics tracked via StatsD
      • +
      +
      +

      Data Types

      +

      Inferflow supports comprehensive ML data types for feature encoding and model input/output:

      +
      Data TypeVariantsUsage
      Integersint8, int16, int32, int64Categorical encodings, counts, IDs
      Floatsfloat8 (e4m3, e5m2), float16, float32, float64Continuous features, embeddings, scores
      StringsVariable lengthCategories, metadata
      BooleansBit-packedBinary indicators
      VectorsAll scalar typesEmbeddings, feature arrays
      +

      Type conversion is handled by the datatypeconverter package with optimized float8 implementations.

      +
      +

      Inference Logging

      +

      Inferflow supports async inference logging to Kafka for model monitoring and debugging:

      +

      Serialization Formats

      +
      FormatUse Case
      ProtoDefault, compact
      ArrowColumnar analytics
      ParquetLong-term storage, query-friendly
      +

      Sampling Controls

      +
      ConfigDescription
      LoggingPercPercentage of requests to log (0-100)
      LogBatchSizeBatch size for log message grouping
      LogFeaturesSpecific features to include in logs
      +

      Log Content

      +

      Each InferflowLog message includes:

      +
        +
      • user_id, tracking_id, model_config_id
      • +
      • Entity IDs and feature values
      • +
      • Model scores and metadata
      • +
      +
      +

      Configuration Hot-Reload

      +

      Model configurations are stored in etcd and support live updates without redeployment:

      +
        +
      1. Inferflow registers watchers on etcd config paths
      2. +
      3. On config change, watchers trigger ReloadModelConfigMapAndRegisterComponents
      4. +
      5. ConfigMap is updated in memory
      6. +
      7. Feature schemas are re-initialized
      8. +
      9. DAG components are re-registered
      10. +
      +

      This enables:

      +
        +
      • Adding new models in production without restarts
      • +
      • A/B testing with different model configurations
      • +
      • Instant rollback by reverting etcd config
      • +
      +
      +

      Performance Characteristics

      +

      Concurrency Model

      +
        +
      • DAG components at the same level execute concurrently in goroutines
      • +
      • Feature retrieval is parallelized across entity types
      • +
      • External gRPC calls use connection pooling
      • +
      +

      Memory Efficiency

      +
        +
      • Built in Go — significantly lower memory footprint than Java equivalents (~80% reduction)
      • +
      • Object pooling for ComponentMatrix and serialization buffers
      • +
      • In-memory cache with zero-GC-overhead option (freecache)
      • +
      +

      Serialization

      +
        +
      • gRPC with Proto3 for all external communication
      • +
      • Binary feature encoding in the ComponentMatrix for minimal overhead
      • +
      • Configurable compression for Kafka logging
      • +
      +
      +

      Contributing

      +

      We welcome contributions from the community! Please see our Contributing Guide for details on how to get started.

      +

      Community & Support

      + +

      License

      +

      BharatMLStack is open-source software licensed under the BharatMLStack Business Source License 1.1.

      +
      +
      Built with ❤️ for the ML community from Meesho
      +
      If you find this useful, ⭐️ the repo — your support means the world to us!
      + + \ No newline at end of file diff --git a/docs/inferflow/v1.0.0/index.html b/docs/inferflow/v1.0.0/index.html new file mode 100644 index 00000000..369cf8c3 --- /dev/null +++ b/docs/inferflow/v1.0.0/index.html @@ -0,0 +1,19 @@ + + + + + +v1.0.0 | BharatMLStack + + + + + + + + + + + \ No newline at end of file diff --git a/docs/inferflow/v1.0.0/release-notes/index.html b/docs/inferflow/v1.0.0/release-notes/index.html new file mode 100644 index 00000000..2230bb7a --- /dev/null +++ b/docs/inferflow/v1.0.0/release-notes/index.html @@ -0,0 +1,160 @@ + + + + + +Release Notes | BharatMLStack + + + + + + + + +

      Inferflow - Release Notes

      +

      Version 1.0.0

      +

      Release Date: June 2025 +Status: General Availability (GA)

      +

      We're excited to announce the first stable release of Inferflow — a graph-driven feature retrieval and model inference orchestration engine, part of BharatMLStack.

      +
      +

      What's New

      +

      Config-Driven DAG Executor

      +
        +
      • No-code feature retrieval: Onboard new models with config changes only — no custom code required
      • +
      • DAG topology execution: Define component dependency graphs that are executed concurrently using Kahn's algorithm
      • +
      • Hot reload: Model configurations stored in etcd are watched and reloaded live — no redeployment needed
      • +
      • DAG caching: Topologies are cached using Murmur3 hashing with Ristretto for minimal overhead
      • +
      +

      Multi-Pattern Inference APIs

      +

      Three structured inference patterns via the Predict API:

      +
      APIPatternUse Case
      InferPointWiseScore each target independentlyCTR prediction, fraud scoring
      InferPairWiseScore pairs of targetsPreference learning, comparison ranking
      InferSlateWiseScore groups of targets togetherWhole-page optimization, diversity-aware ranking
      +

      Plus the entity-based RetrieveModelScore API for direct feature retrieval and scoring.

      +

      Component System

      +

      Four built-in component types:

      +
        +
      • FeatureInitComponent — Initializes the shared ComponentMatrix
      • +
      • FeatureComponent — Fetches features from the Online Feature Store (OnFS)
      • +
      • PredatorComponent — Calls model serving endpoints with percentage-based traffic routing
      • +
      • NumerixComponent — Calls compute engine for operations like reranking
      • +
      +

      Online Feature Store Integration

      +
        +
      • gRPC-based feature retrieval via FeatureService.RetrieveFeatures
      • +
      • Batched retrieval with configurable batch size and deadline
      • +
      • Token-based authentication
      • +
      • Dynamic key resolution from the ComponentMatrix
      • +
      +

      In-Memory Feature Caching

      +
        +
      • Optional per-component caching to reduce OnFS load
      • +
      • Configurable TTL per component
      • +
      • Zero-GC-overhead cache option (freecache)
      • +
      • Cache hit/miss metrics
      • +
      +

      Inference Logging

      +
        +
      • Async logging to Kafka for model monitoring and debugging
      • +
      • Three serialization formats: Proto, Arrow, Parquet
      • +
      • Configurable sampling rate and feature selection
      • +
      • Batched log message grouping
      • +
      +
      +

      Performance

      +

      Built in Go

      +

      Inferflow is written entirely in Go, delivering:

      +
        +
      • ~80% lower memory usage compared to equivalent Java services
      • +
      • Lower CPU utilization
      • +
      • Faster, more efficient deployments
      • +
      +

      Concurrency

      +
        +
      • DAG components at the same level execute concurrently in goroutines
      • +
      • Feature retrieval parallelized across entity types
      • +
      • Connection pooling for all external gRPC calls
      • +
      +

      Serialization

      +
        +
      • gRPC with Proto3 for all APIs
      • +
      • Binary feature encoding in the ComponentMatrix
      • +
      • Configurable compression for Kafka logging (ZSTD support)
      • +
      +
      +

      APIs & Protocols

      +

      gRPC API

      +

      Inferflow Service:

      +
      service Inferflow {
      rpc RetrieveModelScore(InferflowRequestProto) returns (InferflowResponseProto);
      }
      +

      Predict Service:

      +
      service PredictService {
      rpc InferPointWise(PredictRequest) returns (PredictResponse);
      rpc InferPairWise(PredictRequest) returns (PredictResponse);
      rpc InferSlateWise(PredictRequest) returns (PredictResponse);
      }
      +

      Data Types Supported

      +
      TypeVariants
      Integersint8, int16, int32, int64
      Floatsfloat8 (e4m3, e5m2), float16, float32, float64
      StringsVariable length
      BooleansBit-packed
      VectorsAll scalar types
      +
      +

      Enterprise Features

      +

      Production Readiness

      +
        +
      • Health checks: HTTP health endpoints via cmux
      • +
      • Graceful shutdown: Clean resource cleanup
      • +
      • Structured logging: JSON-formatted logs via zerolog
      • +
      • Signal handling: SIGTERM/SIGINT support for container environments
      • +
      +

      Monitoring & Observability

      +
        +
      • StatsD / Telegraf integration: Request rates, latencies, error rates
      • +
      • Per-component metrics: Execution time, feature counts, cache hit rates
      • +
      • External API metrics: OnFS, Predator, Numerix call tracking
      • +
      • Kafka logging metrics: Messages sent, errors
      • +
      +

      Configuration Management

      +
        +
      • etcd-based: All model configs stored in etcd
      • +
      • Watch & reload: Live config updates without restart
      • +
      • Multi-model support: Multiple model_config_id entries served concurrently
      • +
      +
      +

      Deployment

      +

      Container Support

      +
        +
      • Docker image: Multi-stage build (Go Alpine builder + Debian runtime)
      • +
      • Optional Kafka: librdkafka support via build flag
      • +
      • Static binary: Single binary deployment
      • +
      +

      Supported Environments

      +
        +
      • Kubernetes (K8s)
      • +
      • Google Kubernetes Engine (GKE)
      • +
      • Amazon EKS
      • +
      +
      +

      Compatibility

      +

      Supported Go Versions

      +
        +
      • Minimum: Go 1.19
      • +
      • Recommended: Go 1.24+
      • +
      +

      External Dependencies

      +
      ServiceVersionProtocol
      etcd3.5+gRPC
      Online Feature Store (OnFS)1.0+gRPC
      Predator (Helix)1.0+gRPC
      Numerix1.0+gRPC
      Kafka2.0+TCP
      +
      +

      Download & Installation

      +

      Source Code

      +
      git clone https://github.com/Meesho/BharatMLStack.git
      cd BharatMLStack/inferflow
      +

      Build

      +
      go build -o inferflow-server cmd/inferflow/main.go
      +

      Docker

      +
      docker build -t inferflow:latest .
      +
      +

      Contributing

      +

      We welcome contributions from the community! Please see our Contributing Guide for details on how to get started.

      +

      Community & Support

      + +

      License

      +

      BharatMLStack is open-source software licensed under the BharatMLStack Business Source License 1.1.

      +
      +
      Built with ❤️ for the ML community from Meesho
      +
      If you find this useful, ⭐️ the repo — your support means the world to us!
      + + \ No newline at end of file diff --git a/docs/intro/index.html b/docs/intro/index.html new file mode 100644 index 00000000..47c4a121 --- /dev/null +++ b/docs/intro/index.html @@ -0,0 +1,42 @@ + + + + + +BharatMLStack Documentation | BharatMLStack + + + + + + + + +

      BharatMLStack Documentation

      +

      Welcome to the BharatMLStack documentation. BharatMLStack is an open-source, end-to-end ML infrastructure stack built for scale, speed, and simplicity. Explore the components below to get started.

      +
      +

      Quick Start

      +

      Get up and running with BharatMLStack in minutes. Step-by-step instructions, sample data, and Docker Compose setup for local development and testing.

      +

      Go to Quick Start →

      +
      +

      Online Feature Store

      +

      Sub-10ms, high-throughput access to machine learning features for real-time inference. Supports batch and streaming ingestion, schema validation, and compact versioned feature groups.

      +

      Go to Online Feature Store →

      +
      +

      Inferflow

      +

      Graph-driven feature retrieval and model inference orchestration engine. Dynamically resolves entity relationships, retrieves features, and orchestrates model scoring — all without custom code.

      +

      Go to Inferflow →

      +
      +

      Trufflebox UI

      +

      Modern, feature-rich UI framework for MLOps management. Supports feature catalog, user management, and admin operations with approval flows.

      +

      Go to Trufflebox UI →

      +
      +

      SDKs

      +

      Client libraries for Go and Python to interact with the Online Feature Store and other platform components. Includes gRPC clients, REST APIs, and Apache Spark integration.

      +

      Go to SDKs →

      +
      +

      Numerix

      +

      High-performance compute engine for ultra-fast element-wise matrix operations. Built in Rust with SIMD acceleration for sub-5ms p99 latency.

      +

      Go to Numerix →

      + + \ No newline at end of file diff --git a/docs/markdown-page/index.html b/docs/markdown-page/index.html index 70020cdd..efd2be04 100644 --- a/docs/markdown-page/index.html +++ b/docs/markdown-page/index.html @@ -4,15 +4,15 @@ Markdown page example | BharatMLStack - - - + + + -

      Markdown page example

      -

      You don't need React to write simple standalone pages.

      +

      Markdown page example

      +

      You don't need React to write simple standalone pages.

      \ No newline at end of file diff --git a/docs/numerix/v1.0.0/architecture/index.html b/docs/numerix/v1.0.0/architecture/index.html new file mode 100644 index 00000000..eddc2c8a --- /dev/null +++ b/docs/numerix/v1.0.0/architecture/index.html @@ -0,0 +1,143 @@ + + + + + +Architecture | BharatMLStack + + + + + + + + +

      BharatMLStack - Numerix

      +
      +

      Numerix is a Rust-based compute service in BharatMLStack designed for low-latency evaluation of mathematical expressions over feature matrices. Each request carries a compute_id and a matrix of features; Numerix fetches the corresponding postfix expression, maps variables to feature columns (treated as vectors), and evaluates the expression with a stack-based SIMD-optimized runtime.

      +
      +

      High-Level Components

      +
        +
      • Tonic gRPC server (Rust): exposes Numerix/Compute for low-latency requests. +
          +
        • Accepts feature data as strings (for ease of use) or byte arrays (for efficient transmission).
        • +
        • All input data is converted internally to fp32 or fp64 vectors for evaluation.
        • +
        +
      • +
      • Compute Registry (etcd): stores compute_id (int) → postfix expression mappings.
      • +
      • Stack-based Evaluator: Runs postfix expressions in linear time using a stack based approach over aligned vectors.
      • +
      • Vectorized Math Runtime: No handwritten SIMD intrinsics; relies on LLVM autovectorization. +
          +
        • Operations are intentionally simple and memory-aligned.
        • +
        • Compiler emits SIMD instructions automatically.
        • +
        • Portable across CPU architectures (ARM & AMD).
        • +
        +
      • +
      • Metrics and Health +
          +
        • Latency, RPS, and error rates via Datadog/DogStatsD UDP client.
        • +
        • Minimal HTTP endpoints (/health, optional /metrics) for diagnostics.
        • +
        +
      • +
      +
      +

      What is SIMD?

      +

      SIMD (Single Instruction, Multiple Data) is a CPU feature that allows a single instruction to operate on multiple data points at once. In Numerix, this means that operations on feature vectors can be executed in parallel, making evaluation of mathematical expressions faster and more predictable.

      +

      Why SIMD Matters for Numerix

      +
        +
      • Postfix expressions operate on vectors (columns of the input matrix).
      • +
      • SIMD allows multiple elements of these vectors to be processed in one CPU instruction, rather than element-by-element.
      • +
      • This results in low-latency, high-throughput computation without the need for handwritten intrinsics — the compiler handles the vectorization automatically.
      • +
      +
      +

      Why ARM, Why LLVM

      +

      During design exploration, we tested SIMD on different architectures and found ARM (AArch64) with NEON/SVE/SVE2 provided excellent performance for our workloads.

      +

      Instead of writing custom intrinsics, Numerix compiles with SIMD flags and lets LLVM handle vectorization:

      +
      RUSTFLAGS="-C target-feature=+neon,+sve,+sve2" \
      cargo build --release --target aarch64-unknown-linux-gnu
      +
        +
      • +

        This approach works well because operations are straightforward, data is aligned, and compiler auto-vectorization is reliable.

        +
      • +
      • +

        AMD/x86 builds are equally supported — enabling their SIMD extensions is just a matter of changing build flags.

        +
      • +
      +

      Request Model and Flow

      +
        +
      1. Client calls gRPC numerix.Numerix/Compute with: +
          +
        • schema: ordered feature names
        • +
        • entity_scores: per-entity vectors (string or bytes)
        • +
        • compute_id: integer identifier for the expression
        • +
        • data_type (optional): e.g., fp32 or fp64
        • +
        +
      2. +
      3. Service fetches the postfix expression for compute_id which was pre-fetched from etcd.
      4. +
      5. Request is validated for schema and data shape.
      6. +
      7. The stack-based evaluator executes the expression in O(n) over tokens, with vectorized inner operations.
      8. +
      9. Response returns computation_score_data or a structured error.
      10. +
      +
      +

      Why Postfix Expressions

      +
        +
      • Stored in etcd as postfix (Reverse Polish) notation.
      • +
      • Postfix makes evaluation parser-free and linear time.
      • +
      • Execution uses a stack machine: +
          +
        • Push operands (feature vectors).
        • +
        • Pop, compute, and push results for each operator.
        • +
        +
      • +
      • Benefits: predictable runtime, compiler-friendly loops, cache efficiency.
      • +
      +

      gRPC Interface

      +
        +
      • Service: numerix.Numerix
      • +
      • RPC: Compute(NumerixRequestProto) → NumerixResponseProto
      • +
      • Request fields: schema, entity_scores, compute_id, optional data_type
      • +
      • Response fields: computation_score_data or error
      • +
      +

      Example (grpcurl):

      +
      grpcurl -plaintext \
      -import-path ./numerix/src/protos/proto \
      -proto numerix.proto \
      -d '{
      "entityScoreData": {
      "schema": ["feature1", "feature2"],
      "entityScores": [ { "stringData": { "values": ["1.0", "2.0"] } } ],
      "computeId": "1001",
      "dataType": "fp32"
      }
      }' \
      localhost:8080 numerix.Numerix/Compute
      +
      +

      Observability

      +
        +
      • Datadog (DogStatsD) metrics publication via UDP client: +
          +
        • Latency (P50/P95/P99), error rate, RPS, internal failures
        • +
        • Configurable sampling rate via environment variables
        • +
        +
      • +
      • Optional /metrics HTTP endpoint can be enabled for local debugging.
      • +
      +
      +

      Environments

      +
        +
      • Kubernetes (K8s), including GKE and EKS
      • +
      • Multi-arch builds: amd64, arm64.
      • +
      • ARM builds ship with NEON/SVE/SVE2 enabled.
      • +
      +
      +

      Key Takeaways

      +
        +
      • Minimal service surface: gRPC + etcd.
      • +
      • No custom intrinsics — portable across ARM & AMD via compiler flags.
      • +
      • Supports both string and byte input, internally converted to aligned fp32/fp64 vectors.
      • +
      • Stack-based postfix evaluation : linear time, cache-friendly.
      • +
      • Predictable, ultra-low-latency performance.
      • +
      +

      Contributing

      +

      We welcome contributions from the community! Please see our Contributing Guide for details on how to get started.

      +

      Community & Support

      + +

      License

      +

      BharatMLStack is open-source software licensed under the BharatMLStack Business Source License 1.1.

      +
      +
      Built with ❤️ for the ML community from Meesho
      +
      If you find this useful, ⭐️ the repo — your support means the world to us!
      + + \ No newline at end of file diff --git a/docs/numerix/v1.0.0/benchmarks/index.html b/docs/numerix/v1.0.0/benchmarks/index.html new file mode 100644 index 00000000..0a893dac --- /dev/null +++ b/docs/numerix/v1.0.0/benchmarks/index.html @@ -0,0 +1,48 @@ + + + + + +Benchmarks | BharatMLStack + + + + + + + + +

      Benchmarks (PoC)

      +

      This PoC measures the performance of vector addition in Rust with and without compiler SIMD optimizations. Requests consist of repeated fixed-size vector addition operations processed in parallel by the CPU. These results provide perspective on how much faster SIMD makes vectorized computations, and similar improvements are expected for other vectorized operations in Numerix.

      +

      System Configuration

      +
        +
      • Instance Type: c4a-highcpu-16
      • +
      • Processor: Google Axion (ARMv9, 64-bit)
      • +
      • SIMD Extension: SVE2
      • +
      • OS: Linux (Ubuntu 22.04)
      • +
      • Rust Version: rustc 1.80.0
      • +
      • Target Triple: aarch64-unknown-linux-gnu
      • +
      +

      Vector Addition Performance

      +

      With SIMD

      +
      Vector Dimns per opIterationsThroughput (GiB/s)Total CPU (raw)Total CPU (normalized)
      100.39626 ns170,057,457,941376.041564%97.75%
      500.6641 ns94,342,709,0951121.91590%99.38%
      1001.1522 ns51,705,835,3971286.91560%97.50%
      5005.0649 ns12,061,753,66114711538%96.12%
      10009.648 ns6,488,848,7051544.51570%98.12%
      500052.925 ns1,169,316,8131407.81590%99.38%
      10000114.68 ns555,779,9811299.41592%99.50%
      50000644.60 ns94,372,1531155.91560%97.50%
      1000001.4530 µs42,502,2011025.51526%95.38%
      +

      Without SIMD

      +
      Vector Dimns per opIterationsThroughput (GiB/s)Total CPU (raw)Total CPU (normalized)
      103.196 ns1,000,000,00025.031313%82.06%
      503.866 ns1,000,000,000103.461417%88.56%
      1005.867 ns1,000,000,000136.351495%93.44%
      50019.25 ns1,000,000,000207.811600%100.00%
      100033.91 ns1,000,000,000235.921600%100.00%
      5000162.1 ns448,785,386246.711600%100.00%
      10000332.0 ns208,428,151240.941600%100.00%
      500001,740 ns39,247,646229.931600%100.00%
      1000003,401 ns19,598,293235.241600%100.00%
      +
      +

      Normalization: Total CPU (normalized) = Total CPU (raw) / 16, since 1600% equals full utilization on a 16‑core machine.

      +
      +

      Observations

      +
        +
      • SIMD provides large speedups across all vector sizes: overall throughput improvements range from roughly 4–15× versus Without SIMD.
      • +
      • For small vectors (10–100), throughput gains are about 9–15×, with ns/op reduced proportionally.
      • +
      • For larger vectors (500–100000), speedups stabilize around ~4–7× as memory bandwidth pressure increases.
      • +
      • CPU saturation: Without SIMD reaches 100% normalized CPU at and beyond 500 elements, whereas With SIMD typically operates at ~95–99% normalized CPU yet delivers substantially higher throughput at similar CPU.
      • +
      • Per‑CPU efficiency: With SIMD, throughput per unit of CPU is much higher, reflecting better vector unit utilization and fewer instructions per element.
      • +
      • Absolute values depend on hardware and load; the relative differential reflects the benefit of compiler SIMD optimizations.
      • +
      +
      +

      ⚠ Note: Absolute numbers depend on CPU frequency, memory locality, and system load. These results are meant +to show relative SIMD benefits.

      +
      + + \ No newline at end of file diff --git a/docs/numerix/v1.0.0/functionalities/index.html b/docs/numerix/v1.0.0/functionalities/index.html new file mode 100644 index 00000000..3fadd62a --- /dev/null +++ b/docs/numerix/v1.0.0/functionalities/index.html @@ -0,0 +1,85 @@ + + + + + +Key Functionalities | BharatMLStack + + + + + + + + +

      Numerix — Key Functionalities

      +

      Overview

      +

      Numerix evaluates mathematical expressions over feature matrices with a simple, low-latency gRPC surface. Each request references a compute_id; Numerix resolves a postfix expression, maps variables to input columns, and evaluates it over fp32/fp64 vectors with compiler-assisted SIMD.

      +

      🚀 Core Capabilities

      +

      Expression Evaluation

      +
        +
      • Postfix execution: Linear-time, stack-based evaluation over aligned vectors.
      • +
      • Vectorized math: Compiler autovectorization (NEON/SVE on ARM, SSE/AVX on x86) — no handwritten intrinsics.
      • +
      • Typed compute: Inputs converted to fp32 or fp64 for predictable performance.
      • +
      +

      Input Formats

      +
        +
      • Strings: Easy-to-produce feature values (converted internally).
      • +
      • Bytes: Efficient wire format for high-throughput paths.
      • +
      +

      Request Patterns

      +
        +
      • Single entity or batch: Multiple entity_scores per call.
      • +
      • Schema-driven: Column order in schema drives variable mapping in expressions.
      • +
      +

      🎯 Developer Experience

      +
        +
      • gRPC API: Simple single RPC — Numerix/Compute.
      • +
      • Protobuf schema: Language-agnostic client generation.
      • +
      • Deterministic behavior: No parsing at request time; expression resolved from registry.
      • +
      +

      gRPC Service

      +
      service Numerix {
      rpc Compute(NumerixRequestProto) returns (NumerixResponseProto);
      }
      +

      Example Call (grpcurl)

      +
      grpcurl -plaintext \
      -import-path ./numerix/src/protos/proto \
      -proto numerix.proto \
      -d '{
      "entityScoreData": {
      "schema": ["feature1", "feature2"],
      "entityScores": [ { "stringData": { "values": ["1.0", "2.0"] } } ],
      "computeId": "1001",
      "dataType": "fp32"
      }
      }' \
      localhost:8080 numerix.Numerix/Compute
      +

      📊 Observability

      +
        +
      • Datadog/DogStatsD: Latency (P50/P95/P99), RPS, error rate via UDP client.
      • +
      • Optional /metrics endpoint for local/adhoc debugging.
      • +
      +

      ⚙️ Configuration & Registry

      +
        +
      • etcd registry: compute_id (int) → postfix expression mapping.
      • +
      • Environment-driven config: endpoints, timeouts, sampling rate.
      • +
      +

      🧪 Example Scenarios

      +

      Batched evaluation

      +
        +
      • Submit multiple entities in one call to reduce RPC overhead; evaluate the same compute_id across all rows.
      • +
      +

      Mixed input formats

      +
        +
      • Start with string inputs for ease; migrate to bytes for performance without changing the expression or API.
      • +
      +

      🔧 Tuning Knobs

      +
        +
      • Data type: choose fp32 (speed) vs fp64 (precision).
      • +
      • Batch size: tune number of entities per call for your p99 vs throughput goals.
      • +
      • Sampling rate: adjust Datadog metric sampling to balance signal vs cost.
      • +
      +
      +

      Contributing

      +

      We welcome contributions from the community! Please see our Contributing Guide for details on how to get started.

      +

      Community & Support

      + +

      License

      +

      BharatMLStack is open-source software licensed under the BharatMLStack Business Source License 1.1.

      +
      +
      Built with ❤️ for the ML community from Meesho
      +
      If you find this useful, ⭐️ the repo — your support means the world to us!
      + + \ No newline at end of file diff --git a/docs/numerix/v1.0.0/index.html b/docs/numerix/v1.0.0/index.html new file mode 100644 index 00000000..4db9a0cd --- /dev/null +++ b/docs/numerix/v1.0.0/index.html @@ -0,0 +1,19 @@ + + + + + +v1.0.0 | BharatMLStack + + + + + + + + + + + \ No newline at end of file diff --git a/docs/numerix/v1.0.0/release-notes/index.html b/docs/numerix/v1.0.0/release-notes/index.html new file mode 100644 index 00000000..7e9490a7 --- /dev/null +++ b/docs/numerix/v1.0.0/release-notes/index.html @@ -0,0 +1,89 @@ + + + + + +Release Notes | BharatMLStack + + + + + + + + +

      Numerix - Release Notes

      +

      Version 1.0.0 🚀

      +

      Release Date: September 2025
      +Status: General Availability (GA)

      +

      The first stable release of Numerix — a Rust-based compute service for evaluating mathematical expressions over feature matrices with very low latency. Numerix executes postfix expressions from an etcd-backed registry using a stack-based evaluator and compiler-assisted SIMD.

      +
      +

      🎯 What's New

      +

      Core Engine

      +
        +
      • Postfix Expression Execution: compute_id → postfix mapping in etcd; parser-free request path.
      • +
      • Stack-Based Evaluator: Linear-time execution over aligned vectors for predictable latency.
      • +
      • Compiler-Assisted SIMD: Relies on LLVM autovectorization (NEON/SVE on ARM; SSE/AVX on x86); no handwritten intrinsics.
      • +
      • Typed Evaluation: Internal conversion to fp32/fp64 for consistent performance/precision.
      • +
      +

      API Surface

      +
        +
      • gRPC: Single RPC — numerix.Numerix/Compute.
      • +
      • Input Formats: Strings for ease, bytes for performance; both map to vectorized math internally.
      • +
      +

      Observability

      +
        +
      • Datadog/DogStatsD metrics: Latency (P50/P95/P99), RPS, error rate.
      • +
      • Minimal HTTP diagnostics: /health (and optional /metrics).
      • +
      +
      +

      🚀 Performance & Optimization

      +
        +
      • Autovectorized Loops: Tight loops over contiguous memory enable the compiler to emit SIMD instructions automatically.
      • +
      • ARM Focus Option: Excellent results with AArch64; builds can enable NEON/SVE/SVE2:
      • +
      +
      RUSTFLAGS="-C target-feature=+neon,+sve,+sve2" \
      cargo build --release --target aarch64-unknown-linux-gnu
      +
        +
      • Deterministic Runtime: No dynamic parsing in hot path; O(n) across tokens with vectorized inner ops.
      • +
      +
      +

      🛠️ APIs

      +

      gRPC

      +
      service Numerix {
      rpc Compute(NumerixRequestProto) returns (NumerixResponseProto);
      }
      +

      Example call:

      +
      grpcurl -plaintext \
      -import-path ./numerix/src/protos/proto \
      -proto numerix.proto \
      -d '{
      "entityScoreData": {
      "schema": ["feature1", "feature2"],
      "entityScores": [ { "stringData": { "values": ["1.0", "2.0"] } } ],
      "computeId": "1001",
      "dataType": "fp32"
      }
      }' \
      localhost:8080 numerix.Numerix/Compute
      +
      +

      🏗️ Deployment & Configuration

      +

      Environment

      +
      APPLICATION_PORT=8083
      APP_ENV=prd
      APP_LOG_LEVEL=ERROR
      APP_NAME=numerix

      # Performance
      CHANNEL_BUFFER_SIZE=10000

      # etcd
      ETCD_SERVERS=127.0.0.1:2379

      # Metrics
      METRIC_SAMPLING_RATE=1
      TELEGRAF_UDP_HOST=127.0.0.1
      TELEGRAF_UDP_PORT=8125
      +

      Containers

      +
        +
      • Multi-arch images: linux/amd64, linux/arm64.
      • +
      • Build targets example: x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu.
      • +
      +
      +

      🔄 Compatibility

      +
        +
      • Clients: Any language with gRPC + generated stubs.
      • +
      • Architectures: amd64 and arm64; ARM builds can enable NEON/SVE/SVE2.
      • +
      +
      +

      🐛 Known Issues

      +
        +
      1. Introduce a configurable log sampling rate to reduce pod memory usage during computation errors.
      2. +
      +

      Contributing

      +

      We welcome contributions from the community! Please see our Contributing Guide for details on how to get started.

      +

      Community & Support

      + +

      License

      +

      BharatMLStack is open-source software licensed under the BharatMLStack Business Source License 1.1.

      +
      +
      Built with ❤️ for the ML community from Meesho
      +
      If you find this useful, ⭐️ the repo — your support means the world to us!
      + + \ No newline at end of file diff --git a/docs/online-feature-store/v1.0.0/architecture/index.html b/docs/online-feature-store/v1.0.0/architecture/index.html index e77710ef..54c81159 100644 --- a/docs/online-feature-store/v1.0.0/architecture/index.html +++ b/docs/online-feature-store/v1.0.0/architecture/index.html @@ -4,15 +4,15 @@ Architecture | BharatMLStack - - - + + + -

      BharatMLStack - Online Feature Store (OnFS)

      +

      BharatMLStack - Online Feature Store (OnFS)

      The Online Feature Store (OnFS) is part of BharatMLStack, designed to support real-time ML workloads through low-latency feature retrieval and flexible feature ingestion pipelines. It ensures that features generated offline or online are immediately accessible for inference.


      BharatMLStack&#39;s Online-feature-store Architecture

      @@ -121,6 +121,6 @@

      LicenseBharatMLStack Business Source License 1.1.


      Built with ❤️ for the ML community from Meesho
      -
      If you find this useful, ⭐️ the repo — your support means the world to us!

      +
      If you find this useful, ⭐️ the repo — your support means the world to us!
      \ No newline at end of file diff --git a/docs/online-feature-store/v1.0.0/benchmarks/index.html b/docs/online-feature-store/v1.0.0/benchmarks/index.html index b66a3f67..adbf2dbf 100644 --- a/docs/online-feature-store/v1.0.0/benchmarks/index.html +++ b/docs/online-feature-store/v1.0.0/benchmarks/index.html @@ -4,15 +4,15 @@ Benchmarks | BharatMLStack - - - + + + -

      Serialization Performance Benchmarks

      +

      Serialization Performance Benchmarks

      Summary

      This report presents comprehensive benchmark results comparing three serialization formats for the BharatML Online Feature Store:

        @@ -88,9 +88,9 @@

        Technical Implementation Notes

        PSDB Optimizations

        -
        // Object pooling for zero allocations
        var psdbPool = GetPSDBPool()

        // Direct buffer allocation
        headerSize := PSDBLayout1LengthBytes // 9 bytes
        dataSize := len(data) * 4 // 4 bytes per int32

        // No compression for maximum speed
        compressionType = compression.TypeNone
        +
        // Object pooling for zero allocations
        var psdbPool = GetPSDBPool()

        // Direct buffer allocation
        headerSize := PSDBLayout1LengthBytes // 9 bytes
        dataSize := len(data) * 4 // 4 bytes per int32

        // No compression for maximum speed
        compressionType = compression.TypeNone

        Memory Layout Comparison

        -
        PSDB Layout:    [9-byte header][raw data]
        Proto3 Layout: [varint lengths][encoded data][padding]
        Arrow Layout: [schema][metadata][buffers][padding]
        +
        PSDB Layout:    [9-byte header][raw data]
        Proto3 Layout: [varint lengths][encoded data][padding]
        Arrow Layout: [schema][metadata][buffers][padding]

        Conclusion

        The optimal format depends on your use case and scale:

        PSDB: Best for Small-Medium Scale (≤1,000 features)

        @@ -114,8 +114,8 @@

        Raw Benchmark Output [Uncompressed Data]

        -
        goos: darwin
        goarch: arm64
        pkg: github.com/Meesho/BharatMLStack/online-feature-store/internal/data/blocks
        BenchmarkInt32SerializationPSDB/PSDB/Size-100-10 1940238 625.3 ns/op 409.0 bytes 461 B/op 4 allocs/op
        BenchmarkInt32SerializationPSDB/PSDB/Size-1000-10 288300 4056 ns/op 4009 bytes 4143 B/op 4 allocs/op
        BenchmarkInt32SerializationPSDB/PSDB/Size-10000-10 32144 37357 ns/op 40009 bytes 41032 B/op 4 allocs/op
        BenchmarkInt32SerializationPSDB/PSDB/Size-100000-10 3244 359932 ns/op 400009 bytes 401572 B/op 4 allocs/op
        BenchmarkInt32SerializationProto3/Proto3/Size-100-10 1703066 695.9 ns/op 486.0 bytes 768 B/op 2 allocs/op
        BenchmarkInt32SerializationProto3/Proto3/Size-1000-10 194142 6004 ns/op 4885 bytes 5632 B/op 2 allocs/op
        BenchmarkInt32SerializationProto3/Proto3/Size-10000-10 20937 57674 ns/op 48734 bytes 49408 B/op 2 allocs/op
        BenchmarkInt32SerializationProto3/Proto3/Size-100000-10 2085 556541 ns/op 487263 bytes 491776 B/op 2 allocs/op
        BenchmarkInt32SerializationArrow/Arrow/Size-100-10 302257 3831 ns/op 680.0 bytes 7032 B/op 66 allocs/op
        BenchmarkInt32SerializationArrow/Arrow/Size-1000-10 228718 5191 ns/op 4280 bytes 15544 B/op 66 allocs/op
        BenchmarkInt32SerializationArrow/Arrow/Size-10000-10 52482 23173 ns/op 40280 bytes 122617 B/op 66 allocs/op
        BenchmarkInt32SerializationArrow/Arrow/Size-100000-10 9765 120081 ns/op 400280 bytes 957948 B/op 66 allocs/op
        BenchmarkInt32SerializationComparison/Comparison/Size-100/PSDB-10 1919401 670.2 ns/op 409.0 bytes 461 B/op 4 allocs/op
        BenchmarkInt32SerializationComparison/Comparison/Size-100/Proto3-10 1733599 693.2 ns/op 490.0 bytes 768 B/op 2 allocs/op
        BenchmarkInt32SerializationComparison/Comparison/Size-100/Arrow-10 304066 3896 ns/op 680.0 bytes 7032 B/op 66 allocs/op
        BenchmarkInt32SerializationComparison/Comparison/Size-1000/PSDB-10 290784 4074 ns/op 4009 bytes 4143 B/op 4 allocs/op
        BenchmarkInt32SerializationComparison/Comparison/Size-1000/Proto3-10 196962 6034 ns/op 4882 bytes 5632 B/op 2 allocs/op
        BenchmarkInt32SerializationComparison/Comparison/Size-1000/Arrow-10 227908 5240 ns/op 4280 bytes 15544 B/op 66 allocs/op
        BenchmarkInt32SerializationComparison/Comparison/Size-10000/PSDB-10 31732 38064 ns/op 40009 bytes 41024 B/op 4 allocs/op
        BenchmarkInt32SerializationComparison/Comparison/Size-10000/Proto3-10 20827 57670 ns/op 48745 bytes 49408 B/op 2 allocs/op
        BenchmarkInt32SerializationComparison/Comparison/Size-10000/Arrow-10 52000 23557 ns/op 40280 bytes 122617 B/op 66 allocs/op
        BenchmarkInt32SerializationComparison/Comparison/Size-100000/PSDB-10 3268 363817 ns/op 400009 bytes 401575 B/op 4 allocs/op
        BenchmarkInt32SerializationComparison/Comparison/Size-100000/Proto3-10 2097 559621 ns/op 487247 bytes 491776 B/op 2 allocs/op
        BenchmarkInt32SerializationComparison/Comparison/Size-100000/Arrow-10 10000 118489 ns/op 400280 bytes 957947 B/op 66 allocs/op
        BenchmarkInt32SizeComparison/SizeOnly/Size-100-10 1000000000 0.0000223 ns/op 680.0 arrow_bytes 170.0 arrow_ratio_pct 490.0 proto3_bytes 122.5 proto3_ratio_pct 409.0 psdb_bytes 102.2 psdb_ratio_pct 400.0 raw_bytes
        BenchmarkInt32SizeComparison/SizeOnly/Size-1000-10 1000000000 0.0000379 ns/op 4280 arrow_bytes 107.0 arrow_ratio_pct 4881 proto3_bytes 122.0 proto3_ratio_pct 4009 psdb_bytes 100.2 psdb_ratio_pct 4000 raw_bytes
        BenchmarkInt32SizeComparison/SizeOnly/Size-10000-10 1000000000 0.0001182 ns/op 40280 arrow_bytes 100.7 arrow_ratio_pct 48717 proto3_bytes 121.8 proto3_ratio_pct 40009 psdb_bytes 100.0 psdb_ratio_pct 40000 raw_bytes
        BenchmarkInt32SizeComparison/SizeOnly/Size-100000-10 1000000000 0.001034 ns/op 400280 arrow_bytes 100.1 arrow_ratio_pct 487225 proto3_bytes 121.8 proto3_ratio_pct 400009 psdb_bytes 100.0 psdb_ratio_pct 400000 raw_bytes
        BenchmarkInt32MemoryEfficiency/Memory/Size-100/PSDB_Pooled-10 1926676 622.4 ns/op 461 B/op 4 allocs/op
        BenchmarkInt32MemoryEfficiency/Memory/Size-100/Proto3-10 1713428 685.0 ns/op 768 B/op 2 allocs/op
        BenchmarkInt32MemoryEfficiency/Memory/Size-100/Arrow-10 312584 4029 ns/op 7032 B/op 66 allocs/op
        BenchmarkInt32MemoryEfficiency/Memory/Size-1000/PSDB_Pooled-10 290197 4189 ns/op 4143 B/op 4 allocs/op
        BenchmarkInt32MemoryEfficiency/Memory/Size-1000/Proto3-10 195694 6078 ns/op 5632 B/op 2 allocs/op
        BenchmarkInt32MemoryEfficiency/Memory/Size-1000/Arrow-10 224722 5190 ns/op 15544 B/op 66 allocs/op
        BenchmarkInt32MemoryEfficiency/Memory/Size-10000/PSDB_Pooled-10 31898 37684 ns/op 41029 B/op 4 allocs/op
        BenchmarkInt32MemoryEfficiency/Memory/Size-10000/Proto3-10 20840 58032 ns/op 49408 B/op 2 allocs/op
        BenchmarkInt32MemoryEfficiency/Memory/Size-10000/Arrow-10 51440 24049 ns/op 122617 B/op 66 allocs/op
        BenchmarkInt32MemoryEfficiency/Memory/Size-100000/PSDB_Pooled-10 3325 357690 ns/op 401814 B/op 4 allocs/op
        BenchmarkInt32MemoryEfficiency/Memory/Size-100000/Proto3-10 2158 559694 ns/op 491776 B/op 2 allocs/op
        BenchmarkInt32MemoryEfficiency/Memory/Size-100000/Arrow-10 9622 117515 ns/op 957948 B/op 66 allocs/op
        BenchmarkInt32Throughput/Throughput/PSDB-10 290912 4101 ns/op 975.31 MB/s 4143 B/op 4 allocs/op
        BenchmarkInt32Throughput/Throughput/Proto3-10 199087 6005 ns/op 666.12 MB/s 5632 B/op 2 allocs/op
        BenchmarkInt32Throughput/Throughput/Arrow-10 229594 5207 ns/op 768.25 MB/s 15544 B/op 66 allocs/op
        BenchmarkGetPSDBPoolWithoutPool-10 23836599 50.64 ns/op 192 B/op 1 allocs/op
        BenchmarkGetPSDBPoolWithPool-10 100000000 10.76 ns/op 0 B/op 0 allocs/op
        PASS
        ok github.com/Meesho/BharatMLStack/online-feature-store/internal/data/blocks 58.891s
        +
        goos: darwin
        goarch: arm64
        pkg: github.com/Meesho/BharatMLStack/online-feature-store/internal/data/blocks
        BenchmarkInt32SerializationPSDB/PSDB/Size-100-10 1940238 625.3 ns/op 409.0 bytes 461 B/op 4 allocs/op
        BenchmarkInt32SerializationPSDB/PSDB/Size-1000-10 288300 4056 ns/op 4009 bytes 4143 B/op 4 allocs/op
        BenchmarkInt32SerializationPSDB/PSDB/Size-10000-10 32144 37357 ns/op 40009 bytes 41032 B/op 4 allocs/op
        BenchmarkInt32SerializationPSDB/PSDB/Size-100000-10 3244 359932 ns/op 400009 bytes 401572 B/op 4 allocs/op
        BenchmarkInt32SerializationProto3/Proto3/Size-100-10 1703066 695.9 ns/op 486.0 bytes 768 B/op 2 allocs/op
        BenchmarkInt32SerializationProto3/Proto3/Size-1000-10 194142 6004 ns/op 4885 bytes 5632 B/op 2 allocs/op
        BenchmarkInt32SerializationProto3/Proto3/Size-10000-10 20937 57674 ns/op 48734 bytes 49408 B/op 2 allocs/op
        BenchmarkInt32SerializationProto3/Proto3/Size-100000-10 2085 556541 ns/op 487263 bytes 491776 B/op 2 allocs/op
        BenchmarkInt32SerializationArrow/Arrow/Size-100-10 302257 3831 ns/op 680.0 bytes 7032 B/op 66 allocs/op
        BenchmarkInt32SerializationArrow/Arrow/Size-1000-10 228718 5191 ns/op 4280 bytes 15544 B/op 66 allocs/op
        BenchmarkInt32SerializationArrow/Arrow/Size-10000-10 52482 23173 ns/op 40280 bytes 122617 B/op 66 allocs/op
        BenchmarkInt32SerializationArrow/Arrow/Size-100000-10 9765 120081 ns/op 400280 bytes 957948 B/op 66 allocs/op
        BenchmarkInt32SerializationComparison/Comparison/Size-100/PSDB-10 1919401 670.2 ns/op 409.0 bytes 461 B/op 4 allocs/op
        BenchmarkInt32SerializationComparison/Comparison/Size-100/Proto3-10 1733599 693.2 ns/op 490.0 bytes 768 B/op 2 allocs/op
        BenchmarkInt32SerializationComparison/Comparison/Size-100/Arrow-10 304066 3896 ns/op 680.0 bytes 7032 B/op 66 allocs/op
        BenchmarkInt32SerializationComparison/Comparison/Size-1000/PSDB-10 290784 4074 ns/op 4009 bytes 4143 B/op 4 allocs/op
        BenchmarkInt32SerializationComparison/Comparison/Size-1000/Proto3-10 196962 6034 ns/op 4882 bytes 5632 B/op 2 allocs/op
        BenchmarkInt32SerializationComparison/Comparison/Size-1000/Arrow-10 227908 5240 ns/op 4280 bytes 15544 B/op 66 allocs/op
        BenchmarkInt32SerializationComparison/Comparison/Size-10000/PSDB-10 31732 38064 ns/op 40009 bytes 41024 B/op 4 allocs/op
        BenchmarkInt32SerializationComparison/Comparison/Size-10000/Proto3-10 20827 57670 ns/op 48745 bytes 49408 B/op 2 allocs/op
        BenchmarkInt32SerializationComparison/Comparison/Size-10000/Arrow-10 52000 23557 ns/op 40280 bytes 122617 B/op 66 allocs/op
        BenchmarkInt32SerializationComparison/Comparison/Size-100000/PSDB-10 3268 363817 ns/op 400009 bytes 401575 B/op 4 allocs/op
        BenchmarkInt32SerializationComparison/Comparison/Size-100000/Proto3-10 2097 559621 ns/op 487247 bytes 491776 B/op 2 allocs/op
        BenchmarkInt32SerializationComparison/Comparison/Size-100000/Arrow-10 10000 118489 ns/op 400280 bytes 957947 B/op 66 allocs/op
        BenchmarkInt32SizeComparison/SizeOnly/Size-100-10 1000000000 0.0000223 ns/op 680.0 arrow_bytes 170.0 arrow_ratio_pct 490.0 proto3_bytes 122.5 proto3_ratio_pct 409.0 psdb_bytes 102.2 psdb_ratio_pct 400.0 raw_bytes
        BenchmarkInt32SizeComparison/SizeOnly/Size-1000-10 1000000000 0.0000379 ns/op 4280 arrow_bytes 107.0 arrow_ratio_pct 4881 proto3_bytes 122.0 proto3_ratio_pct 4009 psdb_bytes 100.2 psdb_ratio_pct 4000 raw_bytes
        BenchmarkInt32SizeComparison/SizeOnly/Size-10000-10 1000000000 0.0001182 ns/op 40280 arrow_bytes 100.7 arrow_ratio_pct 48717 proto3_bytes 121.8 proto3_ratio_pct 40009 psdb_bytes 100.0 psdb_ratio_pct 40000 raw_bytes
        BenchmarkInt32SizeComparison/SizeOnly/Size-100000-10 1000000000 0.001034 ns/op 400280 arrow_bytes 100.1 arrow_ratio_pct 487225 proto3_bytes 121.8 proto3_ratio_pct 400009 psdb_bytes 100.0 psdb_ratio_pct 400000 raw_bytes
        BenchmarkInt32MemoryEfficiency/Memory/Size-100/PSDB_Pooled-10 1926676 622.4 ns/op 461 B/op 4 allocs/op
        BenchmarkInt32MemoryEfficiency/Memory/Size-100/Proto3-10 1713428 685.0 ns/op 768 B/op 2 allocs/op
        BenchmarkInt32MemoryEfficiency/Memory/Size-100/Arrow-10 312584 4029 ns/op 7032 B/op 66 allocs/op
        BenchmarkInt32MemoryEfficiency/Memory/Size-1000/PSDB_Pooled-10 290197 4189 ns/op 4143 B/op 4 allocs/op
        BenchmarkInt32MemoryEfficiency/Memory/Size-1000/Proto3-10 195694 6078 ns/op 5632 B/op 2 allocs/op
        BenchmarkInt32MemoryEfficiency/Memory/Size-1000/Arrow-10 224722 5190 ns/op 15544 B/op 66 allocs/op
        BenchmarkInt32MemoryEfficiency/Memory/Size-10000/PSDB_Pooled-10 31898 37684 ns/op 41029 B/op 4 allocs/op
        BenchmarkInt32MemoryEfficiency/Memory/Size-10000/Proto3-10 20840 58032 ns/op 49408 B/op 2 allocs/op
        BenchmarkInt32MemoryEfficiency/Memory/Size-10000/Arrow-10 51440 24049 ns/op 122617 B/op 66 allocs/op
        BenchmarkInt32MemoryEfficiency/Memory/Size-100000/PSDB_Pooled-10 3325 357690 ns/op 401814 B/op 4 allocs/op
        BenchmarkInt32MemoryEfficiency/Memory/Size-100000/Proto3-10 2158 559694 ns/op 491776 B/op 2 allocs/op
        BenchmarkInt32MemoryEfficiency/Memory/Size-100000/Arrow-10 9622 117515 ns/op 957948 B/op 66 allocs/op
        BenchmarkInt32Throughput/Throughput/PSDB-10 290912 4101 ns/op 975.31 MB/s 4143 B/op 4 allocs/op
        BenchmarkInt32Throughput/Throughput/Proto3-10 199087 6005 ns/op 666.12 MB/s 5632 B/op 2 allocs/op
        BenchmarkInt32Throughput/Throughput/Arrow-10 229594 5207 ns/op 768.25 MB/s 15544 B/op 66 allocs/op
        BenchmarkGetPSDBPoolWithoutPool-10 23836599 50.64 ns/op 192 B/op 1 allocs/op
        BenchmarkGetPSDBPoolWithPool-10 100000000 10.76 ns/op 0 B/op 0 allocs/op
        PASS
        ok github.com/Meesho/BharatMLStack/online-feature-store/internal/data/blocks 58.891s

        -

        Benchmarks run on Apple Silicon (ARM64) with Go 1.22.12. Results may vary on different architectures and Go versions.

      +

      Benchmarks run on Apple Silicon (ARM64) with Go 1.22.12. Results may vary on different architectures and Go versions.

      \ No newline at end of file diff --git a/docs/online-feature-store/v1.0.0/data-formats/index.html b/docs/online-feature-store/v1.0.0/data-formats/index.html index 300d6d37..c7d2612f 100644 --- a/docs/online-feature-store/v1.0.0/data-formats/index.html +++ b/docs/online-feature-store/v1.0.0/data-formats/index.html @@ -4,15 +4,15 @@ Data Formats | BharatMLStack - - - + + + -

      Data Format for Permanent & Cache Storage

      +

      Data Format for Permanent & Cache Storage

      In this section we will go through the data-formats which is at the hear of online-feature-store, it's inspired form other storage efficient formats like parquet & arrow, but custom made to deliver in constraint environment. The two key data-formats are:

      • PSDB - Permanent Storage Data Block used wile storing data in ScyllaDB
      • @@ -77,7 +77,7 @@

        Conceptu

        PSDB encodes vector data by flattening multi-dimensional arrays into a single contiguous byte buffer while preserving the ability to reconstruct the original vector boundaries.

        Vector Length Metadata

        Each feature group maintains metadata about vector dimensions in the Feature Registry. For example, if a feature group has:

        -
        fg1:
        version-2:
        features:
        f1: { vector_len: 6, default: [bytes] }
        f2: { vector_len: 3, default: [bytes] }
        version-1:
        features:
        f1: { vector_len: 6, default: [bytes] }
        +
        fg1:
        version-2:
        features:
        f1: { vector_len: 6, default: [bytes] }
        f2: { vector_len: 3, default: [bytes] }
        version-1:
        features:
        f1: { vector_len: 6, default: [bytes] }
        • Feature f1 with vector_len: 6
        • Feature f2 with vector_len: 3
        • @@ -136,7 +136,7 @@

          OverviewStructure and Purpose

          Each CSDB contains a mapping of feature group IDs (FG IDs) to deserialized PSDBs. For distributed systems, this structure is flattened into a serialized byte slice. The CSDB supports layout versioning for backward compatibility and negative caching for feature groups with no associated data.

          Core Fields and Memory Layout

          -
          type CacheStorageDataBlock struct {
          // 8-byte aligned map pointer
          FGIdToDDB map[int]*DeserializedPSDB // offset: 0

          // 24-byte slice (ptr, len, cap)
          serializedCSDB []byte // offset: 8

          // 4-byte fields
          TTL uint32 // offset: 32

          // 1-byte fields
          layoutVersion uint8 // offset: 36
          cacheType CacheType // offset: 37
          // 2 bytes padding to maintain 4-byte alignment
          }
          +
          type CacheStorageDataBlock struct {
          // 8-byte aligned map pointer
          FGIdToDDB map[int]*DeserializedPSDB // offset: 0

          // 24-byte slice (ptr, len, cap)
          serializedCSDB []byte // offset: 8

          // 4-byte fields
          TTL uint32 // offset: 32

          // 1-byte fields
          layoutVersion uint8 // offset: 36
          cacheType CacheType // offset: 37
          // 2 bytes padding to maintain 4-byte alignment
          }

          The structure is memory-aligned for optimal performance:

          • Pointers and slices are 8-byte aligned
          • @@ -150,7 +150,7 @@

            Cache Types

            Format & Encoding

            CSDB Binary Layout: Serialized CSDBs follow this compact format:

            -
            [LayoutVersion (1 byte)][FGID (2 bytes)][DataLen (2 bytes)][Data ...]   → repeated per feature group
            +
            [LayoutVersion (1 byte)][FGID (2 bytes)][DataLen (2 bytes)][Data ...]   → repeated per feature group
            • FGID and DataLen are encoded as uint16
            • If DataLen == 0, it denotes a negative cache (no data available for that FG)
            • @@ -167,6 +167,6 @@

              Opti
            • Versioning Support: Layout version is stored as the first byte to enable format upgrades while maintaining backward compatibility.

            Diagram below explains how compute cycles are saved by partial de-compression.

            -

            CSDB Partial Decompression

      +

      CSDB Partial Decompression

      \ No newline at end of file diff --git a/docs/online-feature-store/v1.0.0/functionalities/index.html b/docs/online-feature-store/v1.0.0/functionalities/index.html index 4b66f528..a76199e9 100644 --- a/docs/online-feature-store/v1.0.0/functionalities/index.html +++ b/docs/online-feature-store/v1.0.0/functionalities/index.html @@ -4,15 +4,15 @@ Key Functionalities | BharatMLStack - - - + + + -

      Online Feature Store - Key Functionalities

      +

      Online Feature Store - Key Functionalities

      Overview

      The BharatML Online Feature Store is a high-performance, production-ready system designed to serve machine learning features with sub-10ms P99 latency and 1M+ RPS capacity. It bridges the gap between offline feature engineering and real-time model inference.

      🚀 Core Capabilities

      @@ -65,11 +65,11 @@

      Pro

    📊 Use Cases

    Real-Time ML Inference

    -
    // Fetch user features for recommendation model
    query := &onfs.Query{
    EntityLabel: "user",
    FeatureGroups: []onfs.FeatureGroup{
    {
    Label: "demographics",
    FeatureLabels: []string{"age", "location", "income"},
    },
    {
    Label: "behavior",
    FeatureLabels: []string{"click_rate", "purchase_history"},
    },
    },
    KeysSchema: []string{"user_id"},
    Keys: []onfs.Keys{
    {Cols: []string{"user_123"}},
    },
    }

    result, err := client.RetrieveFeatures(ctx, query)
    +
    // Fetch user features for recommendation model
    query := &onfs.Query{
    EntityLabel: "user",
    FeatureGroups: []onfs.FeatureGroup{
    {
    Label: "demographics",
    FeatureLabels: []string{"age", "location", "income"},
    },
    {
    Label: "behavior",
    FeatureLabels: []string{"click_rate", "purchase_history"},
    },
    },
    KeysSchema: []string{"user_id"},
    Keys: []onfs.Keys{
    {Cols: []string{"user_123"}},
    },
    }

    result, err := client.RetrieveFeatures(ctx, query)

    Batch Feature Serving

    -
    // Bulk feature retrieval for model training
    query := &onfs.Query{
    EntityLabel: "transaction",
    FeatureGroups: []onfs.FeatureGroup{
    {
    Label: "transaction_history",
    FeatureLabels: []string{"amount", "frequency", "merchant_type"},
    },
    {
    Label: "risk_scores",
    FeatureLabels: []string{"fraud_score", "credit_score"},
    },
    },
    KeysSchema: []string{"transaction_id"},
    Keys: []onfs.Keys{
    {Cols: []string{"txn_001"}},
    {Cols: []string{"txn_002"}},
    // ... 100s of transaction IDs
    },
    }

    result, err := client.RetrieveFeatures(ctx, query)
    +
    // Bulk feature retrieval for model training
    query := &onfs.Query{
    EntityLabel: "transaction",
    FeatureGroups: []onfs.FeatureGroup{
    {
    Label: "transaction_history",
    FeatureLabels: []string{"amount", "frequency", "merchant_type"},
    },
    {
    Label: "risk_scores",
    FeatureLabels: []string{"fraud_score", "credit_score"},
    },
    },
    KeysSchema: []string{"transaction_id"},
    Keys: []onfs.Keys{
    {Cols: []string{"txn_001"}},
    {Cols: []string{"txn_002"}},
    // ... 100s of transaction IDs
    },
    }

    result, err := client.RetrieveFeatures(ctx, query)

    A/B Testing Support

    -
    // Version-aware feature retrieval with decoded values
    query := &onfs.Query{
    EntityLabel: "experiment",
    FeatureGroups: []onfs.FeatureGroup{
    {
    Label: "model_features_v2", // Specific version
    FeatureLabels: []string{"feature_a", "feature_b", "feature_c"},
    },
    },
    KeysSchema: []string{"user_id"},
    Keys: []onfs.Keys{
    {Cols: []string{"user_123"}},
    },
    }

    // Get string-decoded values for easier debugging/analysis
    decodedResult, err := client.RetrieveDecodedFeatures(ctx, query)
    +
    // Version-aware feature retrieval with decoded values
    query := &onfs.Query{
    EntityLabel: "experiment",
    FeatureGroups: []onfs.FeatureGroup{
    {
    Label: "model_features_v2", // Specific version
    FeatureLabels: []string{"feature_a", "feature_b", "feature_c"},
    },
    },
    KeysSchema: []string{"user_id"},
    Keys: []onfs.Keys{
    {Cols: []string{"user_123"}},
    },
    }

    // Get string-decoded values for easier debugging/analysis
    decodedResult, err := client.RetrieveDecodedFeatures(ctx, query)

    🎛️ Configuration Options

    Performance Tuning

    +
    If you find this useful, ⭐️ the repo — your support means the world to us!
    \ No newline at end of file diff --git a/docs/online-feature-store/v1.0.0/index.html b/docs/online-feature-store/v1.0.0/index.html index 038a7ea3..db0c938d 100644 --- a/docs/online-feature-store/v1.0.0/index.html +++ b/docs/online-feature-store/v1.0.0/index.html @@ -1,17 +1,19 @@ - + -v1.0.0 | BharatMLStack - - - +v1.0.0 | BharatMLStack + + + - + \ No newline at end of file diff --git a/docs/online-feature-store/v1.0.0/release-notes/index.html b/docs/online-feature-store/v1.0.0/release-notes/index.html index 4773cd02..9acb4730 100644 --- a/docs/online-feature-store/v1.0.0/release-notes/index.html +++ b/docs/online-feature-store/v1.0.0/release-notes/index.html @@ -4,15 +4,15 @@ Release Notes | BharatMLStack - - - + + + -

    Online Feature Store - Release Notes

    +

    Online Feature Store - Release Notes

    Version 1.0.0 🚀

    Release Date: June 2025
    Status: General Availability (GA)

    @@ -61,7 +61,7 @@

    🛠️ APIs & SDKs

    gRPC API

    High-performance, language-agnostic interface:

    -
    service FeatureStoreService {
    rpc RetrieveFeatures(Query) returns (QueryResult);
    rpc RetrieveDecodedFeatures(Query) returns (DecodedQueryResult);
    rpc PersistFeatures(PersistFeaturesRequest) returns (Result);
    }
    +
    service FeatureStoreService {
    rpc RetrieveFeatures(Query) returns (QueryResult);
    rpc RetrieveDecodedFeatures(Query) returns (DecodedQueryResult);
    rpc PersistFeatures(PersistFeaturesRequest) returns (Result);
    }

    Go SDK v1.0.0

    Native Go client with enterprise features:

      @@ -141,7 +141,7 @@

      Workarou

      💾 Download & Installation

      Container Images

      -
      # Pull the latest image
      docker pull ghcr.io/meesho/onfs-api-server:latest
      docker pull ghcr.io/meesho/onfs-consumer:latest
      docker pull ghcr.io/meesho/horizon:latest
      docker pull ghcr.io/meesho/trufflebox-ui:latest

      +
      # Pull the latest image
      docker pull ghcr.io/meesho/onfs-api-server:latest
      docker pull ghcr.io/meesho/onfs-consumer:latest
      docker pull ghcr.io/meesho/horizon:latest
      docker pull ghcr.io/meesho/trufflebox-ui:latest

      Arch Supported

      • Linux (amd64)
      • @@ -151,7 +151,7 @@

        Arch

      Checkout Packages

      Source Code

      -
      git clone https://github.com/Meesho/BharatMLStack.git
      cd BharatMLStack/online-feature-store
      git checkout release/1.0.0
      +
      git clone https://github.com/Meesho/BharatMLStack.git
      cd BharatMLStack/online-feature-store
      git checkout release/1.0.0

      Contributing

      We welcome contributions from the community! Please see our Contributing Guide for details on how to get started.

      Community & Support

      @@ -164,6 +164,6 @@

      LicenseBharatMLStack Business Source License 1.1.


      Built with ❤️ for the ML community from Meesho
      -
      If you find this useful, ⭐️ the repo — your support means the world to us!

    +
    If you find this useful, ⭐️ the repo — your support means the world to us!
    \ No newline at end of file diff --git a/docs/predator/v1.0.0/architecture/index.html b/docs/predator/v1.0.0/architecture/index.html new file mode 100644 index 00000000..c4bdfde5 --- /dev/null +++ b/docs/predator/v1.0.0/architecture/index.html @@ -0,0 +1,143 @@ + + + + + +Architecture | BharatMLStack + + + + + + + + +

    BharatMLStack - Predator

    +

    Predator is a scalable, high-performance model inference service built as a wrapper around the NVIDIA Triton Inference Server. It is designed to serve a variety of machine learning models (Deep Learning, Tree-based, etc.) with low latency in a Kubernetes (K8s) environment.

    +

    The system integrates seamlessly with the Online Feature Store (OnFS) for real-time feature retrieval and uses Horizon as the deployment orchestration layer. Deployments follow a GitOps pipeline — Horizon generates Helm configurations, commits them to GitHub, and Argo Sync reconciles the desired state onto Kubernetes.

    +
    +

    High-Level Design

    +

    Predator HLD - End-to-end deployment and inference architecture

    +

    End-to-End Flow

    +
      +
    1. +

      Model Deployment Trigger: An actor initiates deployment through Trufflebox UI, specifying the GCS path (gcs://) of the trained model. Separately, post-training pipelines write model artifacts to GCS Artifactory.

      +
    2. +
    3. +

      Orchestration via Horizon: Trufflebox UI communicates with Horizon, the deployment orchestration layer. Horizon generates the appropriate Helm chart configuration for the inference service.

      +
    4. +
    5. +

      GitOps Pipeline: Horizon commits the Helm values to a GitHub repository. Argo Sync watches the repo and reconciles the desired state onto the Kubernetes cluster, creating or updating deployable units.

      +
    6. +
    7. +

      Deployable Units (Deployable 1 … N): Each deployable is an independent Kubernetes deployment that:

      +
        +
      • Downloads model artifacts from GCS at startup via an init.sh script.
      • +
      • Launches a Triton Inference Server instance loaded with the model.
      • +
      • Runs one or more pods, each containing the inference runtime and configured backends.
      • +
      +
    8. +
    9. +

      Triton Backends: Each Triton instance supports pluggable backends based on the model type:

      +
        +
      • FIL — GPU-accelerated tree-based models (XGBoost, LightGBM, Random Forest).
      • +
      • PyTorch — Native PyTorch models via LibTorch.
      • +
      • Python — Custom preprocessing/postprocessing or unsupported model formats.
      • +
      • TRT (TensorRT) — GPU-optimized serialized TensorRT engines.
      • +
      • ONNX — Framework-agnostic execution via ONNX Runtime.
      • +
      • DALI — GPU-accelerated data preprocessing (image, audio, video).
      • +
      +
    10. +
    11. +

      Autoscaling with KEDA: The cluster uses KEDA (Kubernetes Event-Driven Autoscaling) to scale deployable pods based on custom metrics (CPU utilization, GPU utilization via DCGM, queue depth, etc.). The underlying Kubernetes scheduler places pods across GPU/CPU node pools.

      +
    12. +
    +

    Key Design Principles

    +
      +
    • GitOps-driven: All deployment state is version-controlled in Git; Argo Sync ensures cluster state matches the declared configuration.
    • +
    • Isolation per deployable: Each model or model group gets its own deployable unit, preventing noisy-neighbor interference.
    • +
    • Init-based model loading: Models are materialized to local disk before Triton starts, ensuring deterministic startup and no runtime dependency on remote storage.
    • +
    • Pluggable backends: The same infrastructure serves deep learning, tree-based, and custom models through Triton's backend abstraction.
    • +
    +
    +

    Inference Engine: Triton Inference Server

    +

    NVIDIA Triton Inference Server is a high-performance model serving system designed to deploy ML and deep learning models at scale across CPUs and GPUs. It provides a unified inference runtime that supports multiple frameworks, optimized execution, and production-grade scheduling.

    +

    Triton operates as a standalone server that loads models from a model repository and exposes standardized HTTP/gRPC APIs. Predator uses gRPC for efficient request and response handling via the helix client.

    +

    Core Components

    +
      +
    • Model Repository: Central directory where models are stored. Predator typically materializes the model repository onto local disk via an init container, enabling fast model loading and eliminating runtime dependency on remote storage during inference.
    • +
    +

    Backends

    +

    A backend is the runtime responsible for executing a model. Each model specifies which backend runs it via configuration.

    +
    BackendDescription
    TensorRTGPU-optimized; executes serialized TensorRT engines (kernel fusion, FP16/INT8).
    PyTorchServes native PyTorch models via LibTorch.
    ONNX RuntimeFramework-agnostic ONNX execution with TensorRT and other accelerators.
    TensorFlowRuns TensorFlow SavedModel format.
    Python backendCustom Python code for preprocessing, postprocessing, or unsupported models.
    Custom backendsC++/Python backends for specialized or proprietary runtimes.
    DALIGPU-accelerated data preprocessing (image, audio, video).
    FIL (Forest Inference Library)GPU-accelerated tree-based models (XGBoost, LightGBM, Random Forest).
    +

    Key Features

    +
      +
    • Dynamic batching: Combines multiple requests into a single batch at runtime — higher GPU utilization, improved throughput, reduced latency variance.
    • +
    • Concurrent model execution: Run multiple models or multiple instances of the same model; distribute load across GPUs.
    • +
    • Model versioning: Support multiple versions per model.
    • +
    • Ensemble models: Pipeline of models as an ensemble; eliminates intermediate network hops, reduces latency.
    • +
    • Model instance scaling: Multiple copies of a model for parallel inference and load isolation.
    • +
    • Observability: Prometheus metrics, granular latency, throughput, GPU utilization.
    • +
    • Warmup requests: Preload kernels and avoid cold-start latency.
    • +
    +
    +

    Model Repository Structure

    +
    model_repository/
    ├── model_A/
    │ ├── config.pbtxt
    │ ├── 1/
    │ │ └── model.plan
    │ ├── 2/
    │ │ └── model.plan
    ├── model_B/
    │ ├── config.pbtxt
    │ ├── 1/
    │ └── model.py
    +

    The config.pbtxt file defines how Triton loads and executes a model: input/output tensors, batch settings, hardware execution, backend runtime, and optimization parameters. At minimum it defines: backend/platform, max_batch_size, inputs, outputs.

    +

    Sample config.pbtxt

    +
    name: "product_ranking_model"
    platform: "tensorrt_plan"
    max_batch_size: 64
    input [ { name: "input_embeddings" data_type: TYPE_FP16 dims: [ 128 ] }, { name: "context_features" data_type: TYPE_FP32 dims: [ 32 ] } ]
    output [ { name: "scores" data_type: TYPE_FP32 dims: [ 1 ] } ]
    instance_group [ { kind: KIND_GPU count: 2 gpus: [0] } ]
    dynamic_batching { preferred_batch_size: [8,16,32,64] max_queue_delay_microseconds: 2000 }
    +
    +

    Kubernetes Deployment Architecture

    +

    Predator inference services are deployed on Kubernetes using Helm-based deployments for standardized, scalable, GPU-optimized model serving. Each deployment consists of Triton Inference Server wrapped within a Predator runtime, with autoscaling driven by CPU and GPU utilization.

    +

    Pod Architecture

    +
    Predator Pod
    ├── Init Container (Model Sync)
    ├── Triton Inference Server Container
    +

    Model artifacts and runtime are initialized before inference traffic is accepted.

    +

    Init Container

    +
      +
    • Download model artifacts from cloud storage (GCS).
    • +
    • Populate the Triton model repository directory.
    • +
    • Example: gcloud storage cp -r gs://.../model-path/* /models
    • +
    +

    Benefits: deterministic startup (Triton starts only after models are available), separation of concerns (image = runtime, repository = data).

    +

    Triton Inference Server Container

    +
      +
    • Load model artifacts from local repository.
    • +
    • Manage inference scheduling, request/response handling, and expose inference endpoints.
    • +
    +

    Triton Server Image Strategy

    +

    The Helm chart uses the Triton container image from the internal artifact registry. Production uses custom-built images (only required backends, e.g. TensorRT, Python) to reduce size and startup time. Unnecessary components are excluded; images are built internally and pushed to the registry.

    +

    Response Caching: Custom cache plugins can be added at image build time for optional inference response caching — reducing redundant execution and GPU use for repeated inputs.

    +

    Image Distribution Optimization

    +
      +
    • Secondary boot disk image caching: Images are pre-cached on GPU node pool secondary boot disks to avoid repeated pulls during scale-up and reduce pod startup time and cold-start latency.
    • +
    • Image streaming: Can be used to progressively pull layers for faster time-to-readiness during scaling.
    • +
    +

    Health Probes

    +

    Readiness and liveness use /v2/health/ready. Triton receives traffic only after model loading; failed instances are restarted automatically.

    +

    Resource Configuration

    +

    Sample GPU resource config:

    +
    limits:
    cpu: 7000m
    memory: 28Gi
    gpu: 1
    +

    Autoscaling Architecture

    +

    Predator uses KEDA (Kubernetes Event-Driven Autoscaling) for scaling deployable pods. KEDA supports custom metric sources including:

    +
      +
    • CPU / Memory utilization for CPU-based deployments.
    • +
    • GPU utilization via DCGM (Data Center GPU Manager) for GPU pods — covering utilization, memory, power, etc.
    • +
    • Custom Prometheus queries for application-level scaling signals (e.g., inference queue depth, request latency).
    • +
    +

    KEDA ScaledObjects are configured per deployable, enabling fine-grained, independent scaling for each model or model group.

    +
    +

    Contributing

    +

    We welcome contributions! See the Contributing Guide.

    +

    Community & Support

    + +

    License

    +

    BharatMLStack is open-source under the BharatMLStack Business Source License 1.1.

    +
    +
    Built with ❤️ for the ML community from Meesho
    +
    If you find this useful, ⭐️ the repo — your support means the world to us!
    + + \ No newline at end of file diff --git a/docs/predator/v1.0.0/functionalities/index.html b/docs/predator/v1.0.0/functionalities/index.html new file mode 100644 index 00000000..f09d3af8 --- /dev/null +++ b/docs/predator/v1.0.0/functionalities/index.html @@ -0,0 +1,97 @@ + + + + + +Key Functionalities | BharatMLStack + + + + + + + + +

    Predator - Key Functionalities

    +

    Overview

    +

    Predator is a scalable, high-performance model inference service built as a wrapper around NVIDIA Triton Inference Server. It serves Deep Learning and tree-based models with low latency in Kubernetes, integrates with the Online Feature Store (OnFS) and uses Interflow for orchestration between clients, feature store, and inference engine. Clients send inference requests via the Helix client over gRPC.

    +
    +

    Core Capabilities

    +

    Multi-Backend Inference

    +

    Predator leverages Triton's pluggable backends so you can serve a variety of model types from a single deployment:

    +
    BackendUse Case
    TensorRTGPU-optimized DL; serialized engines (FP16/INT8)
    PyTorchNative PyTorch via LibTorch
    ONNX RuntimeFramework-agnostic ONNX with TensorRT/GPU
    TensorFlowSavedModel format
    PythonCustom preprocessing, postprocessing, or unsupported models
    FILTree-based models (XGBoost, LightGBM, Random Forest) on GPU
    DALIGPU-accelerated data preprocessing (image, audio, video)
    CustomC++/Python backends for proprietary or specialized runtimes
    +

    Dynamic Batching

    +

    Triton combines multiple incoming requests into a single batch at runtime.

    +
      +
    • Higher GPU utilization and improved throughput
    • +
    • Reduced latency variance
    • +
    • Configurable preferred_batch_size and max_queue_delay_microseconds in config.pbtxt
    • +
    +

    Concurrent Model Execution

    +
      +
    • Run multiple models simultaneously
    • +
    • Run multiple instances of the same model
    • +
    • Distribute load across GPUs via instance_group in model config
    • +
    +

    Model Versioning & Ensembles

    +
      +
    • Versioning: Multiple versions per model (e.g. 1/, 2/ in the model repository)
    • +
    • Ensembles: Define a pipeline of models as an ensemble; eliminates intermediate network hops and reduces latency
    • +
    +

    Model Instance Scaling

    +
      +
    • Deploy multiple copies of a model for parallel inference and load isolation
    • +
    • Configured via instance_group
    • +
    +
    +

    Inference & API

    +

    gRPC via Helix Client

    +

    Predator uses gRPC for efficient request/response handling. Client applications (e.g. Realestate, IOP) send inference requests through the Helix client, which talks to the Triton Inference Server inside the Predator pod.

    +

    Model Repository

    +

    Models are stored in a local model repository. Predator materializes this via an Init Container that downloads artifacts from cloud storage (e.g. GCS) so Triton has no runtime dependency on remote storage during inference.

    +
    +

    Deployment & Operational Features

    +

    Custom Triton Images

    +
      +
    • Production uses custom-built Triton images (only required backends) for smaller size and faster startup
    • +
    • Images built on GCP VM, pushed to Artifact Registry, and referenced in Helm deployments
    • +
    • Optional response caching via custom cache plugins added at image build time
    • +
    +

    Image Distribution

    +
      +
    • Secondary boot disk caching: Triton image pre-cached on GPU node pool to reduce pod startup and scale-up latency
    • +
    • Image streaming: Optionally used for faster time-to-readiness during scaling
    • +
    +

    Health Probes

    +
      +
    • Readiness and liveness use /v2/health/ready
    • +
    • Triton receives traffic only after models are loaded; failed instances are restarted automatically
    • +
    +

    Autoscaling

    +
      +
    • CPU-based scaling for generic load
    • +
    • GPU-based scaling using DCGM metrics (utilization, memory, power); custom queries drive scale-up/scale-down
    • +
    +
    +

    Observability

    +
      +
    • Prometheus metrics: Latency, throughput, GPU utilization, and more
    • +
    • Metrics emitted from the Triton Inference Container and visualized in Grafana
    • +
    • Warmup requests: Configurable to preload kernels and avoid cold-start latency
    • +
    +
    +

    Contributing

    +

    We welcome contributions! See the Contributing Guide.

    +

    Community & Support

    + +

    License

    +

    BharatMLStack is open-source under the BharatMLStack Business Source License 1.1.

    +
    +
    Built with ❤️ for the ML community from Meesho
    +
    If you find this useful, ⭐️ the repo — your support means the world to us!
    + + \ No newline at end of file diff --git a/docs/predator/v1.0.0/index.html b/docs/predator/v1.0.0/index.html new file mode 100644 index 00000000..b0d0ab02 --- /dev/null +++ b/docs/predator/v1.0.0/index.html @@ -0,0 +1,19 @@ + + + + + +v1.0.0 | BharatMLStack + + + + + + + + + + + \ No newline at end of file diff --git a/docs/predator/v1.0.0/release-notes/index.html b/docs/predator/v1.0.0/release-notes/index.html new file mode 100644 index 00000000..01d3d9e8 --- /dev/null +++ b/docs/predator/v1.0.0/release-notes/index.html @@ -0,0 +1,29 @@ + + + + + +Release Notes | BharatMLStack + + + + + + + + +

    Predator - Release Notes

    +

    Version 1.0.0

    +

    Release Date: June 2025
    +Status: General Availability (GA)

    +

    First stable release of Predator — scalable model inference service built around NVIDIA Triton Inference Server, part of BharatMLStack. Serves Deep Learning and tree-based models with low latency in Kubernetes; integrates with OnFS and Interflow; clients use the Helix client over gRPC.

    +

    What's New

    +
      +
    • Triton inference engine: Unified runtime for DL and tree-based models on CPU/GPU; model repository via Init Container from GCS; gRPC API via Helix client.
    • +
    • Multi-backend support: TensorRT, PyTorch, ONNX Runtime, TensorFlow, Python, FIL, DALI, Custom.
    • +
    • Dynamic batching & concurrency: Configurable via config.pbtxt; model versioning and ensembles.
    • +
    • Kubernetes deployment: Helm-based; Init Container + Triton container; custom Triton images from Artifact Registry; health probes; CPU/GPU autoscaling.
    • +
    • Observability: Prometheus metrics, Grafana; warmup requests for cold-start avoidance.
    • +
    + + \ No newline at end of file diff --git a/docs/quick-start/v1.0.0/index.html b/docs/quick-start/v1.0.0/index.html new file mode 100644 index 00000000..8e9988b0 --- /dev/null +++ b/docs/quick-start/v1.0.0/index.html @@ -0,0 +1,19 @@ + + + + + +v1.0.0 | BharatMLStack + + + + + + + + +
    + + \ No newline at end of file diff --git a/docs/quick-start/v1.0.0/quick-start/index.html b/docs/quick-start/v1.0.0/quick-start/index.html index 43b3a215..03a3e2f9 100644 --- a/docs/quick-start/v1.0.0/quick-start/index.html +++ b/docs/quick-start/v1.0.0/quick-start/index.html @@ -3,16 +3,16 @@ -Quick Start | BharatMLStack - - - +Quick Start | BharatMLStack + + + -

    BharatML Stack Quick Start Guide

    +

    BharatML Stack Quick Start Guide

    Discord

    A quick way to get the BharatML Stack Online Feature Store platform up and running locally for development and testing.

    Prerequisites

    @@ -43,10 +43,10 @@

    System Com

    Quick Start

    Starting the System

    Run the start script to set up your workspace and launch all services:

    -
    ./start.sh
    +
    ./start.sh

    Testing Different Versions

    You can easily test different versions of the application services by setting environment variables:

    -
    # Test specific versions [Replace with actual versions]
    ONFS_VERSION=v1.2.3 HORIZON_VERSION=v2.1.0 TRUFFLEBOX_VERSION=v1.0.5 ./start.sh

    # Or set them in your workspace and run docker-compose directly
    cd workspace
    ONFS_VERSION=main docker-compose up -d onfs-api-server
    +
    # Test specific versions [Replace with actual versions]
    ONFS_VERSION=v1.2.3 HORIZON_VERSION=v2.1.0 TRUFFLEBOX_VERSION=v1.0.5 ./start.sh

    # Or set them in your workspace and run docker-compose directly
    cd workspace
    ONFS_VERSION=main docker-compose up -d onfs-api-server

    Available version formats:

    • latest (default) - Latest stable release
    • @@ -72,9 +72,9 @@

      T

    Stopping the System

    To stop all services:

    -
    ./stop.sh
    +
    ./stop.sh

    To stop and completely purge all containers, volumes, and workspace:

    -
    ./stop.sh --purge
    +
    ./stop.sh --purge

    Accessing Services

    Frontend UI

      @@ -138,24 +138,24 @@

      F

      gRPC API Commands

      Use the following grpcurl commands to interact with the Online Feature Store gRPC API:

      Persist Features:

      -
      grpcurl -plaintext -H "online-feature-store-caller-id: <caller-id>" -H "online-feature-store-auth-token: <auth-token>" -d '<request-body>' localhost:8089 persist.FeatureService/PersistFeatures
      +
      grpcurl -plaintext -H "online-feature-store-caller-id: <caller-id>" -H "online-feature-store-auth-token: <auth-token>" -d '<request-body>' localhost:8089 persist.FeatureService/PersistFeatures

      Retrieve Features (Decoded):

      -
      grpcurl -plaintext -H "online-feature-store-caller-id: <caller-id>" -H "online-feature-store-auth-token: <auth-token>" -d '<request-body>' localhost:8089 retrieve.FeatureService/RetrieveDecodedResult
      +
      grpcurl -plaintext -H "online-feature-store-caller-id: <caller-id>" -H "online-feature-store-auth-token: <auth-token>" -d '<request-body>' localhost:8089 retrieve.FeatureService/RetrieveDecodedResult

      Retrieve Features (Binary):

      -
      grpcurl -plaintext -H "online-feature-store-caller-id: <caller-id>" -H "online-feature-store-auth-token: <auth-token>" -d '<request-body>' localhost:8089 retrieve.FeatureService/RetrieveFeatures
      +
      grpcurl -plaintext -H "online-feature-store-caller-id: <caller-id>" -H "online-feature-store-auth-token: <auth-token>" -d '<request-body>' localhost:8089 retrieve.FeatureService/RetrieveFeatures

      Sample Request Bodies

      Single Feature Group Persist:

      -
      {
      "data": [{
      "key_values": ["10"],
      "feature_values": [{
      "values": {"fp32_values": [123.45]}
      }]
      }],
      "entity_label": "catalog",
      "feature_group_schema": [{
      "label": "int_fg",
      "feature_labels": ["id"]
      }],
      "keys_schema": ["catalog_id"]
      }
      +
      {
      "data": [{
      "key_values": ["10"],
      "feature_values": [{
      "values": {"fp32_values": [123.45]}
      }]
      }],
      "entity_label": "catalog",
      "feature_group_schema": [{
      "label": "int_fg",
      "feature_labels": ["id"]
      }],
      "keys_schema": ["catalog_id"]
      }

      Single Feature Group Retrieve:

      -
      {
      "entity_label": "catalog",
      "feature_groups": [{
      "label": "int_fg",
      "feature_labels": ["id"]
      }],
      "keys_schema": ["catalog_id"],
      "keys": [{"cols": ["10"]}]
      }
      +
      {
      "entity_label": "catalog",
      "feature_groups": [{
      "label": "int_fg",
      "feature_labels": ["id"]
      }],
      "keys_schema": ["catalog_id"],
      "keys": [{"cols": ["10"]}]
      }

      Multiple Feature Groups Persist:

      -
      {
      "data": [
      {
      "key_values": ["1"],
      "feature_values": [
      {"values": {"fp32_values": [28.5]}},
      {"values": {"string_values": ["Bharat"]}}
      ]
      },
      {
      "key_values": ["2"],
      "feature_values": [
      {"values": {"fp32_values": [32.0]}},
      {"values": {"string_values": ["India"]}}
      ]
      }
      ],
      "entity_label": "catalog",
      "feature_group_schema": [
      {"label": "int_fg", "feature_labels": ["id"]},
      {"label": "string_fg", "feature_labels": ["name"]}
      ],
      "keys_schema": ["catalog_id"]
      }
      +
      {
      "data": [
      {
      "key_values": ["1"],
      "feature_values": [
      {"values": {"fp32_values": [28.5]}},
      {"values": {"string_values": ["Bharat"]}}
      ]
      },
      {
      "key_values": ["2"],
      "feature_values": [
      {"values": {"fp32_values": [32.0]}},
      {"values": {"string_values": ["India"]}}
      ]
      }
      ],
      "entity_label": "catalog",
      "feature_group_schema": [
      {"label": "int_fg", "feature_labels": ["id"]},
      {"label": "string_fg", "feature_labels": ["name"]}
      ],
      "keys_schema": ["catalog_id"]
      }

      Multiple Feature Groups Retrieve:

      -
      {
      "entity_label": "catalog",
      "feature_groups": [
      {"label": "int_fg", "feature_labels": ["id"]},
      {"label": "string_fg", "feature_labels": ["name"]}
      ],
      "keys_schema": ["catalog_id"],
      "keys": [
      {"cols": ["1"]},
      {"cols": ["2"]}
      ]
      }
      +
      {
      "entity_label": "catalog",
      "feature_groups": [
      {"label": "int_fg", "feature_labels": ["id"]},
      {"label": "string_fg", "feature_labels": ["name"]}
      ],
      "keys_schema": ["catalog_id"],
      "keys": [
      {"cols": ["1"]},
      {"cols": ["2"]}
      ]
      }

      Vector Feature Group Persist:

      -
      {
      "data": [{
      "key_values": ["123"],
      "feature_values": [{
      "values": {
      "vector": [{
      "values": {"fp32_values": [1.0, 2.0, 3.0, 4.0]}
      }]
      }
      }]
      }],
      "entity_label": "catalog",
      "feature_group_schema": [{
      "label": "vector_fg",
      "feature_labels": ["embedding"]
      }],
      "keys_schema": ["catalog_id"]
      }
      +
      {
      "data": [{
      "key_values": ["123"],
      "feature_values": [{
      "values": {
      "vector": [{
      "values": {"fp32_values": [1.0, 2.0, 3.0, 4.0]}
      }]
      }
      }]
      }],
      "entity_label": "catalog",
      "feature_group_schema": [{
      "label": "vector_fg",
      "feature_labels": ["embedding"]
      }],
      "keys_schema": ["catalog_id"]
      }

      Vector Feature Group Retrieve:

      -
      {
      "entity_label": "catalog",
      "feature_groups": [{
      "label": "vector_fg",
      "feature_labels": ["embedding"]
      }],
      "keys_schema": ["catalog_id"],
      "keys": [{"cols": ["123"]}]
      }
      +
      {
      "entity_label": "catalog",
      "feature_groups": [{
      "label": "vector_fg",
      "feature_labels": ["embedding"]
      }],
      "keys_schema": ["catalog_id"],
      "keys": [{"cols": ["123"]}]
      }

      Key Points

      Only one type per feature value block:

        @@ -182,9 +182,9 @@

      Managing Services

      Viewing Logs

      -
      # View logs for all services
      cd workspace && docker-compose logs -f

      # View logs for specific services
      cd workspace && docker-compose logs -f horizon
      cd workspace && docker-compose logs -f trufflebox-ui
      cd workspace && docker-compose logs -f onfs-api-server
      +
      # View logs for all services
      cd workspace && docker-compose logs -f

      # View logs for specific services
      cd workspace && docker-compose logs -f horizon
      cd workspace && docker-compose logs -f trufflebox-ui
      cd workspace && docker-compose logs -f onfs-api-server

      Service Management

      -
      # Restart a specific service
      cd workspace && docker-compose restart horizon

      # Stop all services
      cd workspace && docker-compose down

      # Start services again
      cd workspace && docker-compose up -d

      # Check service status
      cd workspace && docker-compose ps
      +
      # Restart a specific service
      cd workspace && docker-compose restart horizon

      # Stop all services
      cd workspace && docker-compose down

      # Start services again
      cd workspace && docker-compose up -d

      # Check service status
      cd workspace && docker-compose ps

      Troubleshooting

      Common Issues

        @@ -193,15 +193,15 @@

        Common Issues<
      1. Docker network issues: If containers can't communicate, try recreating:

        -
        docker network rm onfs-network
        docker network create onfs-network
        +
        docker network rm onfs-network
        docker network create onfs-network
      2. Service health checks failing: Check if all infrastructure services (databases) are running:

        -
        cd workspace && docker-compose ps
        +
        cd workspace && docker-compose ps
      3. Image pull issues: Ensure you have access to GitHub Container Registry:

        -
        docker login ghcr.io
        +
        docker login ghcr.io
      4. How to use Etcd Workbench ?

        @@ -235,6 +235,6 @@

        LicenseBharatMLStack Business Source License 1.1.


        Built with ❤️ for the ML community from Meesho
        -
        If you find this useful, ⭐️ the repo — your support means the world to us!

    +
    If you find this useful, ⭐️ the repo — your support means the world to us!
    \ No newline at end of file diff --git a/docs/sdks/go/v1.0.0/feature_client/index.html b/docs/sdks/go/v1.0.0/feature_client/index.html index 63ed7f2b..52e0d956 100644 --- a/docs/sdks/go/v1.0.0/feature_client/index.html +++ b/docs/sdks/go/v1.0.0/feature_client/index.html @@ -3,16 +3,16 @@ -GRPC Feature client | BharatMLStack - - - +GRPC Feature client | BharatMLStack + + + -

    Build Status +

    Build Status Static Badge Discord

    BharatMLStack Go SDK

    @@ -30,24 +30,24 @@

    FeaturesInstallation

    -
    go get github.com/Meesho/BharatMLStack/go-sdk
    +
    go get github.com/Meesho/BharatMLStack/go-sdk

    Configuration

    The SDK requires a configuration object with the following fields:

    FieldTypeRequiredDescription
    HoststringYesServer hostname (e.g., "localhost", "feature-store.example.com")
    PortstringYesServer port (e.g., "8080", "443")
    CallerIdstringYesUnique identifier for your service/application
    CallerTokenstringYesAuthentication token for API access
    DeadLineintNoRequest timeout in milliseconds (default: 5000)
    PlainTextboolNoUse plaintext connection instead of TLS (default: false)
    BatchSizeintNoMaximum batch size for bulk operations (default: 50)

    Usage

    Basic Usage

    -
    package main

    import (
    "context"
    "log"

    "github.com/Meesho/BharatMLStack/go-sdk/pkg/onfs"
    )

    func main() {
    config := &onfs.Config{
    Host: "localhost",
    Port: "8080",
    PlainText: true, // For local development
    CallerId: "my-service",
    CallerToken: "my-token",
    }

    // Initialize client (timing and count can be nil)
    client := onfs.NewClientV1(config, nil, nil)

    // Your feature operations here...
    }
    +
    package main

    import (
    "context"
    "log"

    "github.com/Meesho/BharatMLStack/go-sdk/pkg/onfs"
    )

    func main() {
    config := &onfs.Config{
    Host: "localhost",
    Port: "8080",
    PlainText: true, // For local development
    CallerId: "my-service",
    CallerToken: "my-token",
    }

    // Initialize client (timing and count can be nil)
    client := onfs.NewClientV1(config, nil, nil)

    // Your feature operations here...
    }

    Complete Example

    -
    package main

    import (
    "context"
    "log"
    "time"

    "github.com/Meesho/BharatMLStack/go-sdk/pkg/onfs"
    )

    func main() {
    // Create configuration
    config := &onfs.Config{
    Host: "localhost",
    Port: "8080",
    DeadLine: 5000, // 5 seconds timeout in milliseconds
    PlainText: true, // Use plaintext connection for local development
    BatchSize: 50, // Optional: batch size for requests
    CallerId: "your-service-id",
    CallerToken: "your-auth-token",
    }

    // Timing and count functions (can be nil for basic usage)
    timing := func(name string, value time.Duration, tags []string) {
    log.Printf("Timing: %s took %v with tags %v", name, value, tags)
    }
    count := func(name string, value int64, tags []string) {
    log.Printf("Count: %s = %d with tags %v", name, value, tags)
    }

    // Initialize the client
    client := onfs.InitClient(onfs.Version1, config, timing, count)
    // Or alternatively use: client := onfs.NewClientV1(config, timing, count)

    ctx := context.Background()

    // Example: Retrieve features
    query := &onfs.Query{
    EntityLabel: "user",
    FeatureGroups: []onfs.FeatureGroup{
    {
    Label: "user_features",
    FeatureLabels: []string{"age", "location", "preferences"},
    },
    },
    KeysSchema: []string{"user_id"},
    Keys: []onfs.Keys{
    {Cols: []string{"12345"}},
    {Cols: []string{"67890"}},
    },
    }

    result, err := client.RetrieveFeatures(ctx, query)
    if err != nil {
    log.Fatalf("Failed to retrieve features: %v", err)
    }

    log.Printf("Retrieved %d rows for entity %s", len(result.Rows), result.EntityLabel)

    // Example: Retrieve decoded features (string values)
    decodedResult, err := client.RetrieveDecodedFeatures(ctx, query)
    if err != nil {
    log.Fatalf("Failed to retrieve decoded features: %v", err)
    }

    log.Printf("Retrieved %d decoded rows", len(decodedResult.Rows))

    // Example: Persist features
    persistRequest := &onfs.PersistFeaturesRequest{
    EntityLabel: "user",
    KeysSchema: []string{"user_id"},
    FeatureGroups: []onfs.FeatureGroupSchema{
    {
    Label: "user_features",
    FeatureLabels: []string{"age", "location"},
    },
    },
    Data: []onfs.Data{
    {
    KeyValues: []string{"12345"},
    FeatureValues: []onfs.FeatureValues{
    {
    Values: onfs.Values{
    Int32Values: []int32{25},
    StringValues: []string{"New York"},
    },
    },
    },
    },
    },
    }

    persistResponse, err := client.PersistFeatures(ctx, persistRequest)
    if err != nil {
    log.Fatalf("Failed to persist features: %v", err)
    }

    log.Printf("Persist result: %s", persistResponse.Message)
    }
    +
    package main

    import (
    "context"
    "log"
    "time"

    "github.com/Meesho/BharatMLStack/go-sdk/pkg/onfs"
    )

    func main() {
    // Create configuration
    config := &onfs.Config{
    Host: "localhost",
    Port: "8080",
    DeadLine: 5000, // 5 seconds timeout in milliseconds
    PlainText: true, // Use plaintext connection for local development
    BatchSize: 50, // Optional: batch size for requests
    CallerId: "your-service-id",
    CallerToken: "your-auth-token",
    }

    // Timing and count functions (can be nil for basic usage)
    timing := func(name string, value time.Duration, tags []string) {
    log.Printf("Timing: %s took %v with tags %v", name, value, tags)
    }
    count := func(name string, value int64, tags []string) {
    log.Printf("Count: %s = %d with tags %v", name, value, tags)
    }

    // Initialize the client
    client := onfs.InitClient(onfs.Version1, config, timing, count)
    // Or alternatively use: client := onfs.NewClientV1(config, timing, count)

    ctx := context.Background()

    // Example: Retrieve features
    query := &onfs.Query{
    EntityLabel: "user",
    FeatureGroups: []onfs.FeatureGroup{
    {
    Label: "user_features",
    FeatureLabels: []string{"age", "location", "preferences"},
    },
    },
    KeysSchema: []string{"user_id"},
    Keys: []onfs.Keys{
    {Cols: []string{"12345"}},
    {Cols: []string{"67890"}},
    },
    }

    result, err := client.RetrieveFeatures(ctx, query)
    if err != nil {
    log.Fatalf("Failed to retrieve features: %v", err)
    }

    log.Printf("Retrieved %d rows for entity %s", len(result.Rows), result.EntityLabel)

    // Example: Retrieve decoded features (string values)
    decodedResult, err := client.RetrieveDecodedFeatures(ctx, query)
    if err != nil {
    log.Fatalf("Failed to retrieve decoded features: %v", err)
    }

    log.Printf("Retrieved %d decoded rows", len(decodedResult.Rows))

    // Example: Persist features
    persistRequest := &onfs.PersistFeaturesRequest{
    EntityLabel: "user",
    KeysSchema: []string{"user_id"},
    FeatureGroups: []onfs.FeatureGroupSchema{
    {
    Label: "user_features",
    FeatureLabels: []string{"age", "location"},
    },
    },
    Data: []onfs.Data{
    {
    KeyValues: []string{"12345"},
    FeatureValues: []onfs.FeatureValues{
    {
    Values: onfs.Values{
    Int32Values: []int32{25},
    StringValues: []string{"New York"},
    },
    },
    },
    },
    },
    }

    persistResponse, err := client.PersistFeatures(ctx, persistRequest)
    if err != nil {
    log.Fatalf("Failed to persist features: %v", err)
    }

    log.Printf("Persist result: %s", persistResponse.Message)
    }

    Development

    Prerequisites

    • Go 1.22 or later (as specified in go.mod)

    Building

    -
    # Build all packages
    go build ./...

    # Run tests
    go test ./...

    # Run tests with coverage
    go test -v -coverprofile=coverage.out ./...
    go tool cover -html=coverage.out
    +
    # Build all packages
    go build ./...

    # Run tests
    go test ./...

    # Run tests with coverage
    go test -v -coverprofile=coverage.out ./...
    go tool cover -html=coverage.out

    Testing

    -
    # Run all tests
    go test -v ./...

    # Run specific package tests
    go test -v ./pkg/onfs

    # Run with race detection
    go test -race ./...
    +
    # Run all tests
    go test -v ./...

    # Run specific package tests
    go test -v ./pkg/onfs

    # Run with race detection
    go test -race ./...

    Contributing

    We welcome contributions from the community! Please see our Contributing Guide for details on how to get started.

    Community & Support

    @@ -60,6 +60,6 @@

    LicenseBharatMLStack Business Source License 1.1.


    Built with ❤️ for the ML community from Meesho
    -
    If you find this useful, ⭐️ the repo — your support means the world to us!

    +
    If you find this useful, ⭐️ the repo — your support means the world to us!
    \ No newline at end of file diff --git a/docs/sdks/go/v1.0.0/index.html b/docs/sdks/go/v1.0.0/index.html new file mode 100644 index 00000000..76d6b09f --- /dev/null +++ b/docs/sdks/go/v1.0.0/index.html @@ -0,0 +1,19 @@ + + + + + +v1.0.0 | BharatMLStack + + + + + + + + +
    + + \ No newline at end of file diff --git a/docs/sdks/python/v1.0.0/grpc_feature_client/index.html b/docs/sdks/python/v1.0.0/grpc_feature_client/index.html index 8de4755e..5d93465a 100644 --- a/docs/sdks/python/v1.0.0/grpc_feature_client/index.html +++ b/docs/sdks/python/v1.0.0/grpc_feature_client/index.html @@ -3,16 +3,16 @@ -GRPC Feature client | BharatMLStack - - - +GRPC Feature client | BharatMLStack + + + -

    GRPC Feature Client

    +

    GRPC Feature Client

    PyPI version Build Status Python 3.7+ @@ -20,7 +20,7 @@ License

    High-performance gRPC client for BharatML Stack real-time feature operations with direct API access.

    Installation

    -
    pip install grpc_feature_client
    +
    pip install grpc_feature_client

    Dependencies

    This package depends on:

      @@ -38,19 +38,19 @@

      FeaturesQuick Start

      -
      from grpc_feature_client import GRPCFeatureClient, GRPCClientConfig

      # Configure for real-time operations
      config = GRPCClientConfig(
      server_address="localhost:50051",
      job_id="realtime-service",
      job_token="api-token"
      )

      client = GRPCFeatureClient(config)

      # Direct API operations
      result = client.persist_features(entity_label, keys_schema, feature_groups, data)
      features = client.retrieve_decoded_features(entity_label, feature_groups, keys, entity_keys)
      +
      from grpc_feature_client import GRPCFeatureClient, GRPCClientConfig

      # Configure for real-time operations
      config = GRPCClientConfig(
      server_address="localhost:50051",
      job_id="realtime-service",
      job_token="api-token"
      )

      client = GRPCFeatureClient(config)

      # Direct API operations
      result = client.persist_features(entity_label, keys_schema, feature_groups, data)
      features = client.retrieve_decoded_features(entity_label, feature_groups, keys, entity_keys)

      API Reference

      GRPCFeatureClient

      -
      class GRPCFeatureClient:
      def __init__(self, config: GRPCClientConfig)

      def persist_features(
      self,
      entity_label: str,
      keys_schema: List[str],
      feature_group_schemas: List[Dict[str, Any]],
      data_rows: List[Dict[str, Any]],
      timeout: Optional[float] = None
      ) -> Dict[str, Any]

      def retrieve_features(
      self,
      entity_label: str,
      feature_groups: List[Dict[str, Any]],
      keys_schema: List[str],
      entity_keys: List[List[str]],
      timeout: Optional[float] = None
      ) -> Dict[str, Any]

      def retrieve_decoded_features(
      self,
      entity_label: str,
      feature_groups: List[Dict[str, Any]],
      keys_schema: List[str],
      entity_keys: List[List[str]],
      timeout: Optional[float] = None
      ) -> Dict[str, Any]
      +
      class GRPCFeatureClient:
      def __init__(self, config: GRPCClientConfig)

      def persist_features(
      self,
      entity_label: str,
      keys_schema: List[str],
      feature_group_schemas: List[Dict[str, Any]],
      data_rows: List[Dict[str, Any]],
      timeout: Optional[float] = None
      ) -> Dict[str, Any]

      def retrieve_features(
      self,
      entity_label: str,
      feature_groups: List[Dict[str, Any]],
      keys_schema: List[str],
      entity_keys: List[List[str]],
      timeout: Optional[float] = None
      ) -> Dict[str, Any]

      def retrieve_decoded_features(
      self,
      entity_label: str,
      feature_groups: List[Dict[str, Any]],
      keys_schema: List[str],
      entity_keys: List[List[str]],
      timeout: Optional[float] = None
      ) -> Dict[str, Any]

      GRPCClientConfig

      -
      class GRPCClientConfig:
      def __init__(
      self,
      server_address: str,
      job_id: str,
      job_token: str,
      use_tls: bool = False,
      timeout_seconds: float = 30.0,
      metadata: Dict[str, str] = None,
      max_receive_message_length: int = 4 * 1024 * 1024,
      max_send_message_length: int = 4 * 1024 * 1024
      )
      +
      class GRPCClientConfig:
      def __init__(
      self,
      server_address: str,
      job_id: str,
      job_token: str,
      use_tls: bool = False,
      timeout_seconds: float = 30.0,
      metadata: Dict[str, str] = None,
      max_receive_message_length: int = 4 * 1024 * 1024,
      max_send_message_length: int = 4 * 1024 * 1024
      )

      Usage Examples

      Persisting Features

      -
      from grpc_feature_client import GRPCFeatureClient, GRPCClientConfig

      config = GRPCClientConfig(
      server_address="feature-store.example.com:50051",
      job_id="predator",
      job_token="api-token"
      )

      client = GRPCFeatureClient(config)

      # Persist real-time features
      result = client.persist_features(
      entity_label="user_interaction",
      keys_schema=["user_id", "session_id"],
      feature_group_schemas=[{
      "label": "realtime_features",
      "feature_labels": ["click_count", "page_views"]
      }],
      data_rows=[{
      "user_id": "u123",
      "session_id": "s456",
      "click_count": 5,
      "page_views": 3
      }]
      )

      print(f"Persist result: {result}")
      +
      from grpc_feature_client import GRPCFeatureClient, GRPCClientConfig

      config = GRPCClientConfig(
      server_address="feature-store.example.com:50051",
      job_id="predator-service",
      job_token="api-token"
      )

      client = GRPCFeatureClient(config)

      # Persist real-time features
      result = client.persist_features(
      entity_label="user_interaction",
      keys_schema=["user_id", "session_id"],
      feature_group_schemas=[{
      "label": "realtime_features",
      "feature_labels": ["click_count", "page_views"]
      }],
      data_rows=[{
      "user_id": "u123",
      "session_id": "s456",
      "click_count": 5,
      "page_views": 3
      }]
      )

      print(f"Persist result: {result}")

      Retrieving Features

      -
      # Retrieve features for ML model inference
      features = client.retrieve_decoded_features(
      entity_label="user_interaction",
      feature_groups=[{
      "label": "user_features",
      "feature_labels": ["age", "location"]
      }],
      keys_schema=["user_id"],
      entity_keys=[["u123"], ["u456"]]
      )

      print(f"Retrieved features: {features}")
      +
      # Retrieve features for ML model inference
      features = client.retrieve_decoded_features(
      entity_label="user_interaction",
      feature_groups=[{
      "label": "user_features",
      "feature_labels": ["age", "location"]
      }],
      keys_schema=["user_id"],
      entity_keys=[["u123"], ["u456"]]
      )

      print(f"Retrieved features: {features}")

      With Context Management

      -
      # Use client with automatic cleanup
      with GRPCFeatureClient(config) as client:
      result = client.persist_features(...)
      features = client.retrieve_decoded_features(...)
      # Connection automatically closed
      +
      # Use client with automatic cleanup
      with GRPCFeatureClient(config) as client:
      result = client.persist_features(...)
      features = client.retrieve_decoded_features(...)
      # Connection automatically closed

      When to Use

      Use grpc_feature_client for:

    +
    If you find this useful, ⭐️ the repo — your support means the world to us!
    \ No newline at end of file diff --git a/docs/sdks/python/v1.0.0/index.html b/docs/sdks/python/v1.0.0/index.html new file mode 100644 index 00000000..ae05960b --- /dev/null +++ b/docs/sdks/python/v1.0.0/index.html @@ -0,0 +1,19 @@ + + + + + +v1.0.0 | BharatMLStack + + + + + + + + +
    + + \ No newline at end of file diff --git a/docs/sdks/python/v1.0.0/spark_feature_push_client/index.html b/docs/sdks/python/v1.0.0/spark_feature_push_client/index.html index 140deb5f..ea9238f9 100644 --- a/docs/sdks/python/v1.0.0/spark_feature_push_client/index.html +++ b/docs/sdks/python/v1.0.0/spark_feature_push_client/index.html @@ -3,16 +3,16 @@ -Spark client | BharatMLStack - - - +Spark client | BharatMLStack + + + -

    Spark Feature Push Client

    +

    Spark Feature Push Client

    PyPI version Build Status Python 3.7+ @@ -20,7 +20,7 @@ License

    Apache Spark-based client for pushing ML features from offline batch sources to the BharatML Stack Online Feature Store via Kafka. This client is designed for data pipeline operations - reading from batch sources and publishing to Kafka for online consumption.

    Installation

    -
    pip install spark_feature_push_client
    +
    pip install spark_feature_push_client

    Dependencies

    This package depends on:

    Architecture Role

    -
    ┌─────────────────┐    ┌──────────────────────┐    ┌─────────────┐    ┌─────────────────┐
    │ Batch Sources │───▶│ Spark Feature Push │───▶│ Kafka │───▶│ Online Feature │
    │ • Tables │ │ Client │ │ │ │ Store │
    │ • Parquet │ │ • Read & Transform │ │ │ │ │
    │ • Delta │ │ • Protobuf Serialize │ │ │ │ │
    │ • S3/GCS/ADLS │ │ • Batch Processing │ │ │ │ │
    └─────────────────┘ └──────────────────────┘ └─────────────┘ └─────────────────┘


    ┌─────────────────┐
    │ grpc_feature_ │
    │ client │
    │ (Real-time) │
    └─────────────────┘
    +
    ┌─────────────────┐    ┌──────────────────────┐    ┌─────────────┐    ┌─────────────────┐
    │ Batch Sources │───▶│ Spark Feature Push │───▶│ Kafka │───▶│ Online Feature │
    │ • Tables │ │ Client │ │ │ │ Store │
    │ • Parquet │ │ • Read & Transform │ │ │ │ │
    │ • Delta │ │ • Protobuf Serialize │ │ │ │ │
    │ • S3/GCS/ADLS │ │ • Batch Processing │ │ │ │ │
    └─────────────────┘ └──────────────────────┘ └─────────────┘ └─────────────────┘


    ┌─────────────────┐
    │ grpc_feature_ │
    │ client │
    │ (Real-time) │
    └─────────────────┘

    Features

    • Batch Source Integration: Read from Tables (Hive/Delta), Parquet, and Delta files on cloud storage
    • @@ -56,7 +56,7 @@

      When
    • 💨 Single Records: Persisting individual feature records

    Quick Start

    -
    from spark_feature_push_client import OnlineFeatureStorePyClient

    # Initialize client with metadata source
    client = OnlineFeatureStorePyClient(
    features_metadata_source_url="https://api.example.com/metadata",
    job_id="feature-pipeline-job",
    job_token="your-auth-token"
    )

    # Get feature configuration
    feature_details = client.get_features_details()

    # Process your Spark DataFrame
    proto_df = client.generate_df_with_protobuf_messages(your_spark_df)

    # Push to Kafka
    client.write_protobuf_df_to_kafka(
    proto_df,
    kafka_bootstrap_servers="localhost:9092",
    kafka_topic="features.user_features"
    )
    +
    from spark_feature_push_client import OnlineFeatureStorePyClient

    # Initialize client with metadata source
    client = OnlineFeatureStorePyClient(
    features_metadata_source_url="https://api.example.com/metadata",
    job_id="feature-pipeline-job",
    job_token="your-auth-token"
    )

    # Get feature configuration
    feature_details = client.get_features_details()

    # Process your Spark DataFrame
    proto_df = client.generate_df_with_protobuf_messages(your_spark_df)

    # Push to Kafka
    client.write_protobuf_df_to_kafka(
    proto_df,
    kafka_bootstrap_servers="localhost:9092",
    kafka_topic="features.user_features"
    )

    This package is part of the BharatML Stack ecosystem:

      @@ -74,85 +74,85 @@

      Prerequisites<
    • Java 8/11: Required by Spark
    • bharatml_common: For protobuf schemas
    -
    # Example Spark session setup
    spark = SparkSession.builder \
    .appName("FeaturePipeline") \
    .config("spark.jars.packages", "org.apache.spark:spark-sql-kafka-0-10_2.12:3.4.0") \
    .getOrCreate()
    +
    # Example Spark session setup
    spark = SparkSession.builder \
    .appName("FeaturePipeline") \
    .config("spark.jars.packages", "org.apache.spark:spark-sql-kafka-0-10_2.12:3.4.0") \
    .getOrCreate()

    Supported Data Sources

    1. Database Tables

    -
    # Hive/Delta tables
    df = spark.sql("SELECT * FROM feature_db.user_features")
    +
    # Hive/Delta tables
    df = spark.sql("SELECT * FROM feature_db.user_features")

    2. Cloud Storage - Parquet

    -
    # AWS S3
    df = spark.read.parquet("s3a://bucket/path/to/features/")

    # Google Cloud Storage
    df = spark.read.parquet("gs://bucket/path/to/features/")

    # Azure Data Lake
    df = spark.read.parquet("abfss://container@account.dfs.core.windows.net/path/")
    +
    # AWS S3
    df = spark.read.parquet("s3a://bucket/path/to/features/")

    # Google Cloud Storage
    df = spark.read.parquet("gs://bucket/path/to/features/")

    # Azure Data Lake
    df = spark.read.parquet("abfss://container@account.dfs.core.windows.net/path/")

    3. Cloud Storage - Delta

    -
    # Delta format on cloud storage
    df = spark.read.format("delta").load("s3a://bucket/delta-table/")
    +
    # Delta format on cloud storage
    df = spark.read.format("delta").load("s3a://bucket/delta-table/")

    Configuration Examples

    Basic Pipeline

    -
    from pyspark.sql import SparkSession
    from spark_feature_push_client import OnlineFeatureStorePyClient

    # Create Spark session
    spark = SparkSession.builder \
    .appName("FeatureETL") \
    .config("spark.jars.packages", "org.apache.spark:spark-sql-kafka-0-10_2.12:3.4.0") \
    .getOrCreate()

    # Initialize client
    client = OnlineFeatureStorePyClient(
    features_metadata_source_url="https://metadata-service.example.com/api/v1/features",
    job_id="daily-feature-pipeline",
    job_token="pipeline-secret-token",
    fgs_to_consider=["user_demographics", "user_behavior"] # Optional: filter feature groups
    )

    # Get metadata and column mappings
    (
    offline_src_type_columns,
    offline_col_to_default_values_map,
    entity_column_names
    ) = client.get_features_details()

    print(f"Entity columns: {entity_column_names}")
    print(f"Feature mappings: {offline_src_type_columns}")
    +
    from pyspark.sql import SparkSession
    from spark_feature_push_client import OnlineFeatureStorePyClient

    # Create Spark session
    spark = SparkSession.builder \
    .appName("FeatureETL") \
    .config("spark.jars.packages", "org.apache.spark:spark-sql-kafka-0-10_2.12:3.4.0") \
    .getOrCreate()

    # Initialize client
    client = OnlineFeatureStorePyClient(
    features_metadata_source_url="https://metadata-service.example.com/api/v1/features",
    job_id="daily-feature-pipeline",
    job_token="pipeline-secret-token",
    fgs_to_consider=["user_demographics", "user_behavior"] # Optional: filter feature groups
    )

    # Get metadata and column mappings
    (
    offline_src_type_columns,
    offline_col_to_default_values_map,
    entity_column_names
    ) = client.get_features_details()

    print(f"Entity columns: {entity_column_names}")
    print(f"Feature mappings: {offline_src_type_columns}")

    Reading from Multiple Sources

    -
    def get_features_from_all_sources(spark, entity_columns, feature_mapping, default_values):
    """
    Read and combine features from multiple offline sources
    """
    dataframes = []

    for source_info in feature_mapping:
    table_name, source_type, feature_list = source_info

    if source_type == "TABLE":
    # Read from Hive/Delta table
    df = spark.table(table_name)

    elif source_type.startswith("PARQUET_"):
    # Read from Parquet files
    df = spark.read.parquet(table_name)

    elif source_type.startswith("DELTA_"):
    # Read from Delta files
    df = spark.read.format("delta").load(table_name)

    # Select and rename columns
    select_cols = entity_columns.copy()
    for original_col, renamed_col in feature_list:
    if original_col in df.columns:
    df = df.withColumnRenamed(original_col, renamed_col)
    select_cols.append(renamed_col)

    df = df.select(select_cols)
    dataframes.append(df)

    # Union all dataframes
    if dataframes:
    combined_df = dataframes[0]
    for df in dataframes[1:]:
    combined_df = combined_df.unionByName(df, allowMissingColumns=True)

    # Fill missing values with defaults
    for col, default_val in default_values.items():
    if col in combined_df.columns:
    combined_df = combined_df.fillna({col: default_val})

    return combined_df

    return None

    # Use the function
    df = get_features_from_all_sources(
    spark,
    entity_column_names,
    offline_src_type_columns,
    offline_col_to_default_values_map
    )
    +
    def get_features_from_all_sources(spark, entity_columns, feature_mapping, default_values):
    """
    Read and combine features from multiple offline sources
    """
    dataframes = []

    for source_info in feature_mapping:
    table_name, source_type, feature_list = source_info

    if source_type == "TABLE":
    # Read from Hive/Delta table
    df = spark.table(table_name)

    elif source_type.startswith("PARQUET_"):
    # Read from Parquet files
    df = spark.read.parquet(table_name)

    elif source_type.startswith("DELTA_"):
    # Read from Delta files
    df = spark.read.format("delta").load(table_name)

    # Select and rename columns
    select_cols = entity_columns.copy()
    for original_col, renamed_col in feature_list:
    if original_col in df.columns:
    df = df.withColumnRenamed(original_col, renamed_col)
    select_cols.append(renamed_col)

    df = df.select(select_cols)
    dataframes.append(df)

    # Union all dataframes
    if dataframes:
    combined_df = dataframes[0]
    for df in dataframes[1:]:
    combined_df = combined_df.unionByName(df, allowMissingColumns=True)

    # Fill missing values with defaults
    for col, default_val in default_values.items():
    if col in combined_df.columns:
    combined_df = combined_df.fillna({col: default_val})

    return combined_df

    return None

    # Use the function
    df = get_features_from_all_sources(
    spark,
    entity_column_names,
    offline_src_type_columns,
    offline_col_to_default_values_map
    )

    Protobuf Serialization & Kafka Publishing

    -
    # Convert DataFrame to protobuf messages
    # This creates binary protobuf messages suitable for Kafka
    proto_df = client.generate_df_with_protobuf_messages(
    df,
    intra_batch_size=20 # Batch size for serialization
    )

    # The proto_df has schema: [value: binary, intra_batch_id: long]
    proto_df.printSchema()
    # root
    # |-- value: binary (nullable = false)
    # |-- intra_batch_id: long (nullable = false)

    # Write to Kafka with batching for better throughput
    client.write_protobuf_df_to_kafka(
    proto_df,
    kafka_bootstrap_servers="broker1:9092,broker2:9092,broker3:9092",
    kafka_topic="features.user_features",
    additional_options={
    "kafka.acks": "all",
    "kafka.retries": "3",
    "kafka.compression.type": "snappy"
    },
    kafka_num_batches=4 # Split into 4 parallel Kafka writes
    )
    +
    # Convert DataFrame to protobuf messages
    # This creates binary protobuf messages suitable for Kafka
    proto_df = client.generate_df_with_protobuf_messages(
    df,
    intra_batch_size=20 # Batch size for serialization
    )

    # The proto_df has schema: [value: binary, intra_batch_id: long]
    proto_df.printSchema()
    # root
    # |-- value: binary (nullable = false)
    # |-- intra_batch_id: long (nullable = false)

    # Write to Kafka with batching for better throughput
    client.write_protobuf_df_to_kafka(
    proto_df,
    kafka_bootstrap_servers="broker1:9092,broker2:9092,broker3:9092",
    kafka_topic="features.user_features",
    additional_options={
    "kafka.acks": "all",
    "kafka.retries": "3",
    "kafka.compression.type": "snappy"
    },
    kafka_num_batches=4 # Split into 4 parallel Kafka writes
    )

    Data Type Handling

    The client automatically handles the protobuf data type mappings:

    Scalar Types

    -
    # Example DataFrame with different types
    data = [
    ("user123", 25, 185.5, True, "premium"), # int, float, bool, string
    ("user456", 30, 170.0, False, "basic")
    ]
    df = spark.createDataFrame(data, ["user_id", "age", "height", "is_premium", "tier"])

    # Automatically mapped to protobuf:
    # age -> int32_values
    # height -> fp32_values
    # is_premium -> bool_values
    # tier -> string_values
    +
    # Example DataFrame with different types
    data = [
    ("user123", 25, 185.5, True, "premium"), # int, float, bool, string
    ("user456", 30, 170.0, False, "basic")
    ]
    df = spark.createDataFrame(data, ["user_id", "age", "height", "is_premium", "tier"])

    # Automatically mapped to protobuf:
    # age -> int32_values
    # height -> fp32_values
    # is_premium -> bool_values
    # tier -> string_values

    Vector Types

    -
    # Example with vector/array features
    from pyspark.sql.functions import array, lit

    df = spark.createDataFrame([
    ("user123", [0.1, 0.2, 0.3], ["tech", "sports"], [1, 2, 3])
    ], ["user_id", "embeddings", "interests", "scores"])

    # Automatically mapped to protobuf vectors:
    # embeddings -> fp32_values in Vector
    # interests -> string_values in Vector
    # scores -> int32_values in Vector
    +
    # Example with vector/array features
    from pyspark.sql.functions import array, lit

    df = spark.createDataFrame([
    ("user123", [0.1, 0.2, 0.3], ["tech", "sports"], [1, 2, 3])
    ], ["user_id", "embeddings", "interests", "scores"])

    # Automatically mapped to protobuf vectors:
    # embeddings -> fp32_values in Vector
    # interests -> string_values in Vector
    # scores -> int32_values in Vector

    Production Pipeline Example

    -
    def run_feature_pipeline():
    """
    Complete feature pipeline from batch sources to Kafka
    """

    # 1. Initialize Spark
    spark = SparkSession.builder \
    .appName("DailyFeaturePipeline") \
    .config("spark.sql.adaptive.enabled", "true") \
    .config("spark.jars.packages", "org.apache.spark:spark-sql-kafka-0-10_2.12:3.4.0") \
    .getOrCreate()

    try:
    # 2. Initialize feature client
    client = OnlineFeatureStorePyClient(
    features_metadata_source_url=os.getenv("METADATA_URL"),
    job_id=os.getenv("JOB_ID"),
    job_token=os.getenv("JOB_TOKEN")
    )

    # 3. Get feature configuration
    feature_mapping, default_values, entity_columns = client.get_features_details()

    # 4. Read and process data
    df = get_features_from_all_sources(spark, entity_columns, feature_mapping, default_values)

    if df is None or df.count() == 0:
    raise ValueError("No data found in sources")

    # 5. Convert to protobuf
    proto_df = client.generate_df_with_protobuf_messages(df, intra_batch_size=50)

    # 6. Publish to Kafka
    client.write_protobuf_df_to_kafka(
    proto_df,
    kafka_bootstrap_servers=os.getenv("KAFKA_BROKERS"),
    kafka_topic=os.getenv("KAFKA_TOPIC"),
    additional_options={
    "kafka.acks": "all",
    "kafka.compression.type": "snappy",
    "kafka.max.request.size": "10485760" # 10MB
    },
    kafka_num_batches=int(os.getenv("KAFKA_BATCHES", "4"))
    )

    print(f"✅ Successfully processed {df.count()} records")

    finally:
    spark.stop()

    if __name__ == "__main__":
    run_feature_pipeline()
    +
    def run_feature_pipeline():
    """
    Complete feature pipeline from batch sources to Kafka
    """

    # 1. Initialize Spark
    spark = SparkSession.builder \
    .appName("DailyFeaturePipeline") \
    .config("spark.sql.adaptive.enabled", "true") \
    .config("spark.jars.packages", "org.apache.spark:spark-sql-kafka-0-10_2.12:3.4.0") \
    .getOrCreate()

    try:
    # 2. Initialize feature client
    client = OnlineFeatureStorePyClient(
    features_metadata_source_url=os.getenv("METADATA_URL"),
    job_id=os.getenv("JOB_ID"),
    job_token=os.getenv("JOB_TOKEN")
    )

    # 3. Get feature configuration
    feature_mapping, default_values, entity_columns = client.get_features_details()

    # 4. Read and process data
    df = get_features_from_all_sources(spark, entity_columns, feature_mapping, default_values)

    if df is None or df.count() == 0:
    raise ValueError("No data found in sources")

    # 5. Convert to protobuf
    proto_df = client.generate_df_with_protobuf_messages(df, intra_batch_size=50)

    # 6. Publish to Kafka
    client.write_protobuf_df_to_kafka(
    proto_df,
    kafka_bootstrap_servers=os.getenv("KAFKA_BROKERS"),
    kafka_topic=os.getenv("KAFKA_TOPIC"),
    additional_options={
    "kafka.acks": "all",
    "kafka.compression.type": "snappy",
    "kafka.max.request.size": "10485760" # 10MB
    },
    kafka_num_batches=int(os.getenv("KAFKA_BATCHES", "4"))
    )

    print(f"✅ Successfully processed {df.count()} records")

    finally:
    spark.stop()

    if __name__ == "__main__":
    run_feature_pipeline()

    Configuration Options

    Client Configuration

    -
    client = OnlineFeatureStorePyClient(
    features_metadata_source_url="https://api.example.com/metadata", # Required
    job_id="pipeline-job-001", # Required
    job_token="secret-token-123", # Required
    fgs_to_consider=["user_features", "item_features"] # Optional: filter feature groups
    )
    +
    client = OnlineFeatureStorePyClient(
    features_metadata_source_url="https://api.example.com/metadata", # Required
    job_id="pipeline-job-001", # Required
    job_token="secret-token-123", # Required
    fgs_to_consider=["user_features", "item_features"] # Optional: filter feature groups
    )

    Protobuf Serialization Options

    -
    proto_df = client.generate_df_with_protobuf_messages(
    df,
    intra_batch_size=20 # Records per protobuf message (default: 20)
    )
    +
    proto_df = client.generate_df_with_protobuf_messages(
    df,
    intra_batch_size=20 # Records per protobuf message (default: 20)
    )

    Kafka Publishing Options

    -
    client.write_protobuf_df_to_kafka(
    proto_df,
    kafka_bootstrap_servers="localhost:9092",
    kafka_topic="features.topic",
    additional_options={
    "kafka.acks": "all", # Acknowledgment level
    "kafka.retries": "3", # Retry attempts
    "kafka.compression.type": "snappy", # Compression
    "kafka.batch.size": "16384", # Batch size
    "kafka.linger.ms": "100", # Batching delay
    "kafka.max.request.size": "10485760" # Max message size
    },
    kafka_num_batches=1 # Number of parallel Kafka writers (default: 1)
    )
    +
    client.write_protobuf_df_to_kafka(
    proto_df,
    kafka_bootstrap_servers="localhost:9092",
    kafka_topic="features.topic",
    additional_options={
    "kafka.acks": "all", # Acknowledgment level
    "kafka.retries": "3", # Retry attempts
    "kafka.compression.type": "snappy", # Compression
    "kafka.batch.size": "16384", # Batch size
    "kafka.linger.ms": "100", # Batching delay
    "kafka.max.request.size": "10485760" # Max message size
    },
    kafka_num_batches=1 # Number of parallel Kafka writers (default: 1)
    )

    Performance Tuning

    Spark Optimizations

    -
    spark = SparkSession.builder \
    .appName("FeaturePipeline") \
    .config("spark.sql.adaptive.enabled", "true") \
    .config("spark.sql.adaptive.coalescePartitions.enabled", "true") \
    .config("spark.sql.adaptive.skewJoin.enabled", "true") \
    .config("spark.serializer", "org.apache.spark.serializer.KryoSerializer") \
    .getOrCreate()
    +
    spark = SparkSession.builder \
    .appName("FeaturePipeline") \
    .config("spark.sql.adaptive.enabled", "true") \
    .config("spark.sql.adaptive.coalescePartitions.enabled", "true") \
    .config("spark.sql.adaptive.skewJoin.enabled", "true") \
    .config("spark.serializer", "org.apache.spark.serializer.KryoSerializer") \
    .getOrCreate()

    Memory Management

    -
    # For large datasets, consider:
    df = df.repartition(200) # Optimal partition count
    df.cache() # Cache if reused multiple times
    +
    # For large datasets, consider:
    df = df.repartition(200) # Optimal partition count
    df.cache() # Cache if reused multiple times

    Kafka Throughput

    -
    # For high-throughput scenarios:
    client.write_protobuf_df_to_kafka(
    proto_df,
    kafka_bootstrap_servers="brokers",
    kafka_topic="topic",
    kafka_num_batches=8, # Increase parallel writers
    additional_options={
    "kafka.batch.size": "65536", # Larger batches
    "kafka.linger.ms": "100", # Allow batching delay
    "kafka.compression.type": "lz4" # Fast compression
    }
    )
    +
    # For high-throughput scenarios:
    client.write_protobuf_df_to_kafka(
    proto_df,
    kafka_bootstrap_servers="brokers",
    kafka_topic="topic",
    kafka_num_batches=8, # Increase parallel writers
    additional_options={
    "kafka.batch.size": "65536", # Larger batches
    "kafka.linger.ms": "100", # Allow batching delay
    "kafka.compression.type": "lz4" # Fast compression
    }
    )

    Monitoring & Debugging

    DataFrame Inspection

    -
    # Check data before processing
    print(f"Records: {df.count()}")
    print(f"Columns: {df.columns}")
    df.printSchema()
    df.show(5)

    # Check protobuf output
    proto_df.show(5, truncate=False)
    print(f"Protobuf messages: {proto_df.count()}")
    +
    # Check data before processing
    print(f"Records: {df.count()}")
    print(f"Columns: {df.columns}")
    df.printSchema()
    df.show(5)

    # Check protobuf output
    proto_df.show(5, truncate=False)
    print(f"Protobuf messages: {proto_df.count()}")

    Error Handling

    -
    try:
    proto_df = client.generate_df_with_protobuf_messages(df)
    client.write_protobuf_df_to_kafka(proto_df, brokers, topic)

    except Exception as e:
    print(f"Pipeline failed: {e}")
    # Log to monitoring system
    # Send alerts
    raise
    +
    try:
    proto_df = client.generate_df_with_protobuf_messages(df)
    client.write_protobuf_df_to_kafka(proto_df, brokers, topic)

    except Exception as e:
    print(f"Pipeline failed: {e}")
    # Log to monitoring system
    # Send alerts
    raise

    Integration with Other SDKs

    With gRPC Feature Client

    -
    # Spark client pushes features to Kafka
    spark_client = OnlineFeatureStorePyClient(...)
    spark_client.write_protobuf_df_to_kafka(proto_df, brokers, topic)

    # gRPC client retrieves features in real-time
    from grpc_feature_client import GRPCFeatureClient
    grpc_client = GRPCFeatureClient(config)
    features = grpc_client.retrieve_decoded_features(...)
    +
    # Spark client pushes features to Kafka
    spark_client = OnlineFeatureStorePyClient(...)
    spark_client.write_protobuf_df_to_kafka(proto_df, brokers, topic)

    # gRPC client retrieves features in real-time
    from grpc_feature_client import GRPCFeatureClient
    grpc_client = GRPCFeatureClient(config)
    features = grpc_client.retrieve_decoded_features(...)

    With HTTP Feature Client (bharatml_common)

    -
    # Use HTTP client for metadata validation
    from bharatml_common import HTTPFeatureClient
    http_client = HTTPFeatureClient(base_url, job_id, token)
    metadata = http_client.get_feature_metadata()

    # Validate feature names using shared utilities
    from bharatml_common import clean_column_name
    clean_features = [clean_column_name(name) for name in feature_names]

    # Process with Spark client
    spark_client.generate_df_with_protobuf_messages(df)
    +
    # Use HTTP client for metadata validation
    from bharatml_common import HTTPFeatureClient
    http_client = HTTPFeatureClient(base_url, job_id, token)
    metadata = http_client.get_feature_metadata()

    # Validate feature names using shared utilities
    from bharatml_common import clean_column_name
    clean_features = [clean_column_name(name) for name in feature_names]

    # Process with Spark client
    spark_client.generate_df_with_protobuf_messages(df)

    Common Use Cases

    1. Daily Batch ETL

    -
    # Cron job: 0 2 * * * (daily at 2 AM)
    spark-submit \
    --packages org.apache.spark:spark-sql-kafka-0-10_2.12:3.4.0 \
    --conf spark.sql.adaptive.enabled=true \
    daily_feature_pipeline.py
    +
    # Cron job: 0 2 * * * (daily at 2 AM)
    spark-submit \
    --packages org.apache.spark:spark-sql-kafka-0-10_2.12:3.4.0 \
    --conf spark.sql.adaptive.enabled=true \
    daily_feature_pipeline.py

    2. Historical Backfill

    -
    # Backfill last 30 days
    from datetime import datetime, timedelta

    for i in range(30):
    date = datetime.now() - timedelta(days=i)
    df = spark.sql(f"""
    SELECT * FROM features
    WHERE date = '{date.strftime('%Y-%m-%d')}'
    """)

    proto_df = client.generate_df_with_protobuf_messages(df)
    client.write_protobuf_df_to_kafka(proto_df, brokers, f"backfill.{date.strftime('%Y%m%d')}")
    +
    # Backfill last 30 days
    from datetime import datetime, timedelta

    for i in range(30):
    date = datetime.now() - timedelta(days=i)
    df = spark.sql(f"""
    SELECT * FROM features
    WHERE date = '{date.strftime('%Y-%m-%d')}'
    """)

    proto_df = client.generate_df_with_protobuf_messages(df)
    client.write_protobuf_df_to_kafka(proto_df, brokers, f"backfill.{date.strftime('%Y%m%d')}")

    3. Real-time Streaming (Advanced)

    -
    # Read from streaming source, process, and publish
    streaming_df = spark.readStream \
    .format("kafka") \
    .option("kafka.bootstrap.servers", input_brokers) \
    .option("subscribe", input_topic) \
    .load()

    # Process streaming DataFrame
    processed_df = streaming_df.select(...)

    # Write to output Kafka (requires structured streaming)
    query = processed_df.writeStream \
    .format("kafka") \
    .option("kafka.bootstrap.servers", output_brokers) \
    .option("topic", output_topic) \
    .start()
    +
    # Read from streaming source, process, and publish
    streaming_df = spark.readStream \
    .format("kafka") \
    .option("kafka.bootstrap.servers", input_brokers) \
    .option("subscribe", input_topic) \
    .load()

    # Process streaming DataFrame
    processed_df = streaming_df.select(...)

    # Write to output Kafka (requires structured streaming)
    query = processed_df.writeStream \
    .format("kafka") \
    .option("kafka.bootstrap.servers", output_brokers) \
    .option("topic", output_topic) \
    .start()

    Troubleshooting

    Common Issues

    1. OutOfMemoryError

      -
      # Increase driver memory or reduce partition size
      spark.conf.set("spark.sql.adaptive.coalescePartitions.minPartitionNum", "50")
      +
      # Increase driver memory or reduce partition size
      spark.conf.set("spark.sql.adaptive.coalescePartitions.minPartitionNum", "50")
    2. Kafka Connection Timeout

      -
      # Check network connectivity and broker addresses
      additional_options = {
      "kafka.request.timeout.ms": "60000",
      "kafka.session.timeout.ms": "30000"
      }
      +
      # Check network connectivity and broker addresses
      additional_options = {
      "kafka.request.timeout.ms": "60000",
      "kafka.session.timeout.ms": "30000"
      }
    3. Protobuf Serialization Errors

      -
      # Check data types and null values
      df = df.fillna({"string_col": "", "numeric_col": 0})
      +
      # Check data types and null values
      df = df.fillna({"string_col": "", "numeric_col": 0})
    4. Metadata API Errors

      -
      # Verify job_id, job_token, and URL
      # Check API server logs
      +
      # Verify job_id, job_token, and URL
      # Check API server logs

    Debug Mode

    -
    import logging
    logging.basicConfig(level=logging.DEBUG)

    # Enable Spark SQL logging
    spark.sparkContext.setLogLevel("INFO")
    +
    import logging
    logging.basicConfig(level=logging.DEBUG)

    # Enable Spark SQL logging
    spark.sparkContext.setLogLevel("INFO")

    Migration from Legacy Clients

    If migrating from older versions:

    -
    # Old import
    # from online_feature_store_py_client import OnlineFeatureStorePyClient

    # New import (same interface)
    from spark_feature_push_client import OnlineFeatureStorePyClient

    # API remains the same - no code changes needed!
    +
    # Old import
    # from online_feature_store_py_client import OnlineFeatureStorePyClient

    # New import (same interface)
    from spark_feature_push_client import OnlineFeatureStorePyClient

    # API remains the same - no code changes needed!

    Best Practices

    1. Resource Management: Always stop Spark sessions
    2. @@ -175,6 +175,6 @@

      LicenseBharatMLStack Business Source License 1.1.


      Built with ❤️ for the ML community from Meesho
      -
      If you find this useful, ⭐️ the repo — your support means the world to us!

    +
    If you find this useful, ⭐️ the repo — your support means the world to us!
    \ No newline at end of file diff --git a/docs/sitemap.xml b/docs/sitemap.xml index 215a1dea..728419cd 100644 --- a/docs/sitemap.xml +++ b/docs/sitemap.xml @@ -1 +1 @@ -https://meesho.github.io/BharatMLStack/blogweekly0.5https://meesho.github.io/BharatMLStack/blog/archiveweekly0.5https://meesho.github.io/BharatMLStack/blog/authorsweekly0.5https://meesho.github.io/BharatMLStack/blog/post-oneweekly0.5https://meesho.github.io/BharatMLStack/blog/tagsweekly0.5https://meesho.github.io/BharatMLStack/blog/tags/interaction-storeweekly0.5https://meesho.github.io/BharatMLStack/blog/tags/meeshoweekly0.5https://meesho.github.io/BharatMLStack/blog/tags/mlplatformweekly0.5https://meesho.github.io/BharatMLStack/blog/tags/online-feature-storeweekly0.5https://meesho.github.io/BharatMLStack/markdown-pageweekly0.5https://meesho.github.io/BharatMLStack/weekly0.5https://meesho.github.io/BharatMLStack/category/go-sdkweekly0.5https://meesho.github.io/BharatMLStack/category/online-feature-storeweekly0.5https://meesho.github.io/BharatMLStack/category/python-sdkweekly0.5https://meesho.github.io/BharatMLStack/category/quick-startweekly0.5https://meesho.github.io/BharatMLStack/category/sdksweekly0.5https://meesho.github.io/BharatMLStack/category/trufflebox-uiweekly0.5https://meesho.github.io/BharatMLStack/category/v100weekly0.5https://meesho.github.io/BharatMLStack/online-feature-store/v1.0.0weekly0.5https://meesho.github.io/BharatMLStack/online-feature-store/v1.0.0/architectureweekly0.5https://meesho.github.io/BharatMLStack/online-feature-store/v1.0.0/benchmarksweekly0.5https://meesho.github.io/BharatMLStack/online-feature-store/v1.0.0/data-formatsweekly0.5https://meesho.github.io/BharatMLStack/online-feature-store/v1.0.0/functionalitiesweekly0.5https://meesho.github.io/BharatMLStack/online-feature-store/v1.0.0/release-notesweekly0.5https://meesho.github.io/BharatMLStack/quick-start/v1.0.0/quick-startweekly0.5https://meesho.github.io/BharatMLStack/sdks/go/v1.0.0/feature_clientweekly0.5https://meesho.github.io/BharatMLStack/sdks/python/v1.0.0/grpc_feature_clientweekly0.5https://meesho.github.io/BharatMLStack/sdks/python/v1.0.0/spark_feature_push_clientweekly0.5https://meesho.github.io/BharatMLStack/trufflebox-ui/v1.0.0/userguideweekly0.5 \ No newline at end of file +https://meesho.github.io/BharatMLStack/blogweekly0.5https://meesho.github.io/BharatMLStack/blog/archiveweekly0.5https://meesho.github.io/BharatMLStack/blog/authorsweekly0.5https://meesho.github.io/BharatMLStack/blog/building-meeshos-mlplatformweekly0.5https://meesho.github.io/BharatMLStack/blog/building-meeshos-mlplatform-lessons-from-first-genweekly0.5https://meesho.github.io/BharatMLStack/blog/episodic-memory-for-agentsweekly0.5https://meesho.github.io/BharatMLStack/blog/llm-inference-optimization-sub-sec-latencyweekly0.5https://meesho.github.io/BharatMLStack/blog/multi-engine-llm-inferencing-platformweekly0.5https://meesho.github.io/BharatMLStack/blog/scaling-model-inference-and-embedding-searchweekly0.5https://meesho.github.io/BharatMLStack/blog/tagsweekly0.5https://meesho.github.io/BharatMLStack/blog/tags/ai-agentsweekly0.5https://meesho.github.io/BharatMLStack/blog/tags/architectureweekly0.5https://meesho.github.io/BharatMLStack/blog/tags/bharatmlstackweekly0.5https://meesho.github.io/BharatMLStack/blog/tags/embedding-searchweekly0.5https://meesho.github.io/BharatMLStack/blog/tags/episodic-memoryweekly0.5https://meesho.github.io/BharatMLStack/blog/tags/inferflowweekly0.5https://meesho.github.io/BharatMLStack/blog/tags/interaction-storeweekly0.5https://meesho.github.io/BharatMLStack/blog/tags/llmweekly0.5https://meesho.github.io/BharatMLStack/blog/tags/meeshoweekly0.5https://meesho.github.io/BharatMLStack/blog/tags/memoryweekly0.5https://meesho.github.io/BharatMLStack/blog/tags/mlplatformweekly0.5https://meesho.github.io/BharatMLStack/blog/tags/model-inferenceweekly0.5https://meesho.github.io/BharatMLStack/blog/tags/online-feature-storeweekly0.5https://meesho.github.io/BharatMLStack/blog/tags/tensorrt-llmweekly0.5https://meesho.github.io/BharatMLStack/blog/tags/vllmweekly0.5https://meesho.github.io/BharatMLStack/markdown-pageweekly0.5https://meesho.github.io/BharatMLStack/weekly0.5https://meesho.github.io/BharatMLStack/category/go-sdkweekly0.5https://meesho.github.io/BharatMLStack/category/inferflowweekly0.5https://meesho.github.io/BharatMLStack/category/numerixweekly0.5https://meesho.github.io/BharatMLStack/category/online-feature-storeweekly0.5https://meesho.github.io/BharatMLStack/category/predatorweekly0.5https://meesho.github.io/BharatMLStack/category/python-sdkweekly0.5https://meesho.github.io/BharatMLStack/category/quick-startweekly0.5https://meesho.github.io/BharatMLStack/category/sdksweekly0.5https://meesho.github.io/BharatMLStack/category/skyeweekly0.5https://meesho.github.io/BharatMLStack/category/trufflebox-uiweekly0.5https://meesho.github.io/BharatMLStack/inferflow/v1.0.0weekly0.5https://meesho.github.io/BharatMLStack/inferflow/v1.0.0/architectureweekly0.5https://meesho.github.io/BharatMLStack/inferflow/v1.0.0/configurationweekly0.5https://meesho.github.io/BharatMLStack/inferflow/v1.0.0/functionalitiesweekly0.5https://meesho.github.io/BharatMLStack/inferflow/v1.0.0/release-notesweekly0.5https://meesho.github.io/BharatMLStack/introweekly0.5https://meesho.github.io/BharatMLStack/numerix/v1.0.0weekly0.5https://meesho.github.io/BharatMLStack/numerix/v1.0.0/architectureweekly0.5https://meesho.github.io/BharatMLStack/numerix/v1.0.0/benchmarksweekly0.5https://meesho.github.io/BharatMLStack/numerix/v1.0.0/functionalitiesweekly0.5https://meesho.github.io/BharatMLStack/numerix/v1.0.0/release-notesweekly0.5https://meesho.github.io/BharatMLStack/online-feature-store/v1.0.0weekly0.5https://meesho.github.io/BharatMLStack/online-feature-store/v1.0.0/architectureweekly0.5https://meesho.github.io/BharatMLStack/online-feature-store/v1.0.0/benchmarksweekly0.5https://meesho.github.io/BharatMLStack/online-feature-store/v1.0.0/data-formatsweekly0.5https://meesho.github.io/BharatMLStack/online-feature-store/v1.0.0/functionalitiesweekly0.5https://meesho.github.io/BharatMLStack/online-feature-store/v1.0.0/release-notesweekly0.5https://meesho.github.io/BharatMLStack/predator/v1.0.0weekly0.5https://meesho.github.io/BharatMLStack/predator/v1.0.0/architectureweekly0.5https://meesho.github.io/BharatMLStack/predator/v1.0.0/functionalitiesweekly0.5https://meesho.github.io/BharatMLStack/predator/v1.0.0/release-notesweekly0.5https://meesho.github.io/BharatMLStack/quick-start/v1.0.0weekly0.5https://meesho.github.io/BharatMLStack/quick-start/v1.0.0/quick-startweekly0.5https://meesho.github.io/BharatMLStack/sdks/go/v1.0.0weekly0.5https://meesho.github.io/BharatMLStack/sdks/go/v1.0.0/feature_clientweekly0.5https://meesho.github.io/BharatMLStack/sdks/python/v1.0.0weekly0.5https://meesho.github.io/BharatMLStack/sdks/python/v1.0.0/grpc_feature_clientweekly0.5https://meesho.github.io/BharatMLStack/sdks/python/v1.0.0/spark_feature_push_clientweekly0.5https://meesho.github.io/BharatMLStack/skye/v1.0.0weekly0.5https://meesho.github.io/BharatMLStack/skye/v1.0.0/architectureweekly0.5https://meesho.github.io/BharatMLStack/skye/v1.0.0/functionalitiesweekly0.5https://meesho.github.io/BharatMLStack/skye/v1.0.0/release-notesweekly0.5https://meesho.github.io/BharatMLStack/trufflebox-ui/v1.0.0weekly0.5https://meesho.github.io/BharatMLStack/trufflebox-ui/v1.0.0/userguideweekly0.5 \ No newline at end of file diff --git a/docs/skye/v1.0.0/architecture/index.html b/docs/skye/v1.0.0/architecture/index.html new file mode 100644 index 00000000..398a0d1d --- /dev/null +++ b/docs/skye/v1.0.0/architecture/index.html @@ -0,0 +1,145 @@ + + + + + +Architecture | BharatMLStack + + + + + + + + +

    Skye - Vector Similarity Search Platform

    +

    Skye is BharatMLStack's vector similarity search platform that enables fast semantic retrieval by representing data as vectors and querying nearest matches in high-dimensional space. It is composed of three runnable components: skye-admin, skye-consumers, and skye-serving.

    +
    +

    System Overview

    +

    Skye System Architecture

    +

    Skye provides a critical platform for managing data aggregation, model onboarding, and embedding support at production scale. The architecture is designed around three core pillars:

    +
      +
    • Pluggable Vector Databases: Support for multiple vector database backends (Qdrant and extensible to others) via a generic abstraction layer.
    • +
    • Tenant-Level Index Isolation with Shared Embeddings: Models are stored once but can serve multiple tenants (variants), reducing data redundancy.
    • +
    • Event-Driven Administration: Model lifecycle management is handled through Kafka-based event flows for resilience and fault tolerance.
    • +
    +

    Component Architecture

    +
    ComponentRole
    skye-servingHandles real-time similarity search queries with in-memory caching and vector DB lookups
    skye-consumersProcesses embedding ingestion (reset/delta jobs) and real-time aggregation events from Kafka
    skye-adminManages model lifecycle, onboarding, variant registration, and coordinates Databricks jobs
    +
    +

    Data Model

    +

    Model and Variant Hierarchy

    +

    Skye uses a model-first hierarchy rather than a tenant-first approach. Models sit at the base level with variants (formerly tenants) nested within each model. This eliminates embedding duplication across tenants.

    +
    model (e.g., intent_model)
    ├── model_config (distance_function, vector_dimension, etc.)
    ├── embedding_store (shared embeddings for all variants)
    ├── variant_1 (e.g., organic)
    │ ├── vss_filter (criteria for index inclusion)
    │ ├── vectordb_type (QDRANT, etc.)
    │ ├── vectordb_config (host, port, replication, sharding)
    │ ├── read_version / write_version
    │ └── job_frequency (FREQ_1D, FREQ_3H, etc.)
    └── variant_2 (e.g., ad)
    ├── vss_filter
    ├── vectordb_type
    └── ...
    +

    Key benefit: If a model consumes 30M embeddings and is used by two variants, the embeddings are stored once (30M) instead of duplicated (60M).

    +

    Entity-Based Data Split

    +

    Data is split at the entity level (catalog, product, user) into separate tables for both embeddings and aggregator data:

    +

    Embedding Tables (per entity):

    +
    CREATE TABLE catalog_embeddings (
    model_name text,
    version int,
    id text,
    embedding frozen<list<decimal>>,
    search_embedding frozen<list<decimal>>,
    to_be_indexed_variant_1 boolean,
    to_be_indexed_variant_2 boolean,
    PRIMARY KEY ((model_name, version), id)
    );
    +

    Aggregator Tables (per entity):

    +
    CREATE TABLE catalog_aggregator (
    id text,
    is_live_ad text,
    out_of_stock text,
    PRIMARY KEY (id)
    );
    +

    Each entity is mapped via a store configuration:

    +
    {
    "db_conf_id": "1",
    "embeddings_table": "catalog_embeddings",
    "aggregator_table": "catalog_aggregator"
    }
    +
    +

    Serving Flow

    +

    The serving path is optimized for low latency with multiple caching layers:

    +
      +
    1. Request arrives at skye-serving via gRPC
    2. +
    3. ConfigRepo resolves the model configuration, variant filters, and vector DB connection
    4. +
    5. In-memory cache is checked first to reduce load on distributed cache
    6. +
    7. Distributed cache (Redis) is checked next for cached similarity results
    8. +
    9. Vector DB query executes if cache misses, using search_indexed_only flag for optimal searches within indexed space
    10. +
    11. Aggregator data is fetched from ScyllaDB to apply variant-level filters
    12. +
    13. Response returns ranked similar candidates with scores
    14. +
    +

    Configuration Bootstrap

    +

    On startup, ConfigRepo creates:

    +
      +
    • A map of each model with its configurations (embedding table, vector DB channel)
    • +
    • A map of each entity to its aggregator table
    • +
    +
    {
    "intent_model": {
    "db_conf_id": "1",
    "index_embedding_table": "catalog_embeddings",
    "vector_db_grpc_channel": "<grpc_channel_info>"
    }
    }
    +
    +

    Admin Flows

    +

    Skye uses an event-driven approach for model lifecycle management:

    +
      +
    • All admin operations are processed through Kafka consumers asynchronously
    • +
    • A SQL database behind the admin stores all model states
    • +
    • Pod termination does not affect in-progress operations (events are re-consumed on failure)
    • +
    • Databricks jobs are triggered and monitored via the admin API
    • +
    +

    API Contracts

    +

    Register Model

    +
    POST /register-model
    +
    {
    "entity": "catalog",
    "ingestion_column_mapping": "{\"id_column\":\"id\",\"embedding_column\":\"features\",\"to_be_indexed_column\":\"to_be_indexed\"}",
    "embedding_store_enabled": true,
    "embedding_store_ttl": 604800,
    "mq_id": 804,
    "model_config": "{\"distance_function\":\"DOT\",\"vector_dimension\":32}",
    "store_id": 1,
    "training_data_path": "gcs_path"
    }
    +

    Register Variant

    +
    POST /register-variant
    +
    {
    "entity": "catalog",
    "model_name": "intent_model",
    "vss_filter": "{...filter criteria...}",
    "vectordb_type": "QDRANT",
    "vectordb_config": "{...connection config...}",
    "job_frequency": "FREQ_1D"
    }
    +

    Reset Model

    +
    POST /reset-model
    +
    {
    "entity": "catalog",
    "model_name": "intent_model",
    "frequency": "FREQ_1D"
    }
    +

    Response includes variant version mappings, MQ ID, and training data path for the Databricks job.

    +

    Trigger Model Machine

    +
    POST /trigger-model-machine
    +
    {
    "entity": "catalog",
    "model_name": "intent_model",
    "variant": "organic"
    }
    +

    Promote Model / Variant to Scale-Up Cluster

    +
    POST /promote-model
    POST /promote-variant
    +

    Used to transition successful experiments from experiment clusters to production clusters.

    +
    +

    Consumer Flows

    +

    Skye Real-Time Consumer Flow

    +

    Reset/Delta Ingestion

    +

    Embedding ingestion occurs once per model and executes in parallel for each variant. The Kafka event contract supports:

    +
      +
    • Multiple variants per event: A single embedding event specifies which variants should index the data
    • +
    • Separate search and index embeddings: Models can have different embeddings for search space vs index space
    • +
    • EOF handling: EOF is sent to all partitions to ensure all data is consumed before completion
    • +
    +
    {
    "entity": "catalog",
    "model_name": "intent_model",
    "candidate_id": "48869419",
    "version": "1",
    "index_space": {
    "variants_version_map": "{'organic':1,'ad':2}",
    "embedding": [0.036, -0.048, ...],
    "variants_index_map": "{'organic':true,'ad':false}",
    "operation": "A",
    "payload": "{'sscat_id':700}"
    },
    "search_space": {
    "embedding": [0.036, -0.048, ...]
    }
    }
    +

    Real-Time Consumers

    +

    A generic Kafka schema is used for all real-time consumers, simplifying new integrations:

    +
    {
    "timestamp": 1719308350,
    "entity_label": "catalog",
    "data": [
    {
    "id": "125138466",
    "label": "is_live_ad",
    "value": "true"
    }
    ]
    }
    +

    Retry Topic

    +

    Failed ingestion events are published to a retry topic for reprocessing, ensuring no data loss:

    +
    {
    "timestamp": 1719308350,
    "entity_label": "catalog",
    "model_name": "intent_model",
    "variant": "organic",
    "data": [
    {
    "id": "125138466",
    "label": "is_live_ad",
    "value": "true"
    }
    ]
    }
    +
    +

    Key Design Decisions

    +

    Pluggable Vector Database Support

    +

    Skye introduces a generic vector_db_type configuration and converts vendor-specific configs to a generic vector_config, enabling support for multiple vector database backends beyond Qdrant.

    +

    Variant-Based Model Sharing

    +

    By eliminating the tenant-based construct and introducing variants, Skye allows:

    +
      +
    • Models to be shared across tenants without duplication
    • +
    • Each variant to have its own filter criteria, vector DB config, and job frequency
    • +
    • Independent read/write version tracking per variant
    • +
    +

    ScyllaDB for Real-Time Aggregation

    +

    Replaced Delta Lake with self-hosted ScyllaDB for cost efficiency. The aggregator is entity-generic (not model/version-specific) since all real-time data is consistent across models.

    +

    Event-Driven State Management

    +

    Model state transitions are handled via Kafka events with a SQL database backing store. This eliminates:

    +
      +
    • Single points of failure in admin/ingestion flows
    • +
    • Models getting stuck during pod restarts
    • +
    • Manual intervention for consumer pause/resume
    • +
    +
    +

    Resiliency

    +
    MechanismDescription
    Retry TopicsFailed ingestion messages are captured in a failure topic for reprocessing
    Circuit BreakersApplied to similarity search API calls to throttle RPS during failures
    Snapshot BackupsPeriodic collection snapshots enable quick restore during downtime
    Automated Cluster SetupScripted provisioning eliminates configuration inconsistencies
    Databricks Job RetriesLambda functions with retry mechanisms for failed ingestion jobs
    +
    +

    Scalability

    +
      +
    • Vector DB Scaling: Generic scripts for adding nodes to existing clusters, enabling horizontal scaling based on load and RPS
    • +
    • Service Scaling: Hosted on EKS with CPU-based autoscaling
    • +
    • Experiment Isolation: Experiments run on separate EKS and vector DB clusters, reducing production cluster complexity
    • +
    • Indexed-Only Search: The search_indexed_only flag ensures queries only search indexed space, avoiding latency from brute-force searches on partially built indexes
    • +
    +
    +

    Observability

    +

    Metrics (per model + variant)

    +
    MetricDescription
    avg_similar_candidatesAverage number of similarity candidates returned
    avg_recallScore of the first similar catalog returned
    Service LatencyP99.9 / P99 / P95 / P50
    Service 5xx CountError rate monitoring
    Vector DB LatencyP99.9 / P99 / P95 / P50
    Vector DB QPSThroughput monitoring
    ScyllaDB LatencyP99.9 / P99 / P95 / P90
    Redis LatencyP99.9 / P99 / P95 / P90
    Redis Hit %Cache effectiveness
    +

    Alerts

    +
    AlertThreshold
    Indexed Vector Count< 95%
    Events to Failure TopicRate > 0
    Service 5xx< 10
    Service LatencyModel-dependent SLA
    +
    +

    Technology Stack

    +
    ComponentTechnology
    LanguageGo
    Vector DatabaseQdrant (pluggable)
    Embedding StorageScyllaDB
    Real-Time AggregationScyllaDB
    CachingRedis + In-Memory
    Message QueueKafka
    ConfigurationZooKeeper / etcd
    Container OrchestrationKubernetes (EKS)
    Job OrchestrationDatabricks
    + + \ No newline at end of file diff --git a/docs/skye/v1.0.0/functionalities/index.html b/docs/skye/v1.0.0/functionalities/index.html new file mode 100644 index 00000000..7f233199 --- /dev/null +++ b/docs/skye/v1.0.0/functionalities/index.html @@ -0,0 +1,113 @@ + + + + + +Functionalities | BharatMLStack + + + + + + + + +

    Skye - Functionalities

    +

    Core Capabilities

    + +

    Skye provides real-time nearest-neighbor search across high-dimensional vector spaces. It supports:

    +
      +
    • Configurable distance functions: DOT product, Cosine similarity, Euclidean distance
    • +
    • Configurable vector dimensions: Per-model vector dimension settings
    • +
    • Indexed-only search: Queries only search within fully indexed space, avoiding brute-force fallback on partially built indexes
    • +
    • Pagination support: Service-level pagination for clients, even when the underlying vector DB does not natively support it
    • +
    +

    2. Pluggable Vector Database Support

    +

    The platform is designed to be vector DB agnostic:

    +
      +
    • Generic vector config: A vector_db_type field and generic vectordb_config replace vendor-specific configurations
    • +
    • Current support: Qdrant with official Go client
    • +
    • Extensibility: New vector databases can be integrated by implementing the vector DB interface
    • +
    +

    3. Model and Variant Management

    +

    Model Registration

    +
      +
    • Models are registered via API with entity type, embedding configuration, distance function, vector dimension, and training data path
    • +
    • Each model is associated with a store ID mapping to specific embedding and aggregator tables
    • +
    +

    Variant Registration

    +
      +
    • Variants represent different views/filters of the same model (e.g., organic, ad, commerce)
    • +
    • Each variant has its own filter criteria, vector DB cluster, job frequency, and version tracking
    • +
    • Variants share the same embeddings, eliminating data redundancy
    • +
    +

    Model Promotion

    +
      +
    • Successful experiments can be promoted from experiment clusters to production clusters via API
    • +
    +

    4. Embedding Ingestion

    +

    Batch Ingestion (Reset/Delta Jobs)

    +
      +
    • Triggered via Databricks jobs that read from GCS paths
    • +
    • Supports separate index-space and search-space embeddings
    • +
    • Per-variant to_be_indexed flags control which embeddings are indexed for each variant
    • +
    • EOF markers sent to all Kafka partitions ensure complete data consumption
    • +
    +

    Real-Time Ingestion

    +
      +
    • Generic Kafka schema for all real-time consumers
    • +
    • Entity-based aggregation data (e.g., is_live_ad, out_of_stock) updates in real time
    • +
    • During model resets, real-time consumers continue pushing data to the latest collection (no pausing)
    • +
    +

    5. Real-Time Data Aggregation

    +
      +
    • Entity-wise (catalog, product, user) real-time aggregation via ScyllaDB
    • +
    • Generic approach: aggregator tables are entity-level, not model/version-specific
    • +
    • All real-time data is consistent across models sharing the same entity
    • +
    +

    6. Intelligent Caching

    +
      +
    • In-memory cache: First layer, reduces load on distributed cache
    • +
    • Distributed cache (Redis): Second layer for cached similarity results
    • +
    • Hit rate monitoring and cache effectiveness metrics per model
    • +
    +

    7. Embedded Storage

    +
      +
    • Optional embedding storage with configurable TTL
    • +
    • Enables embedding lookup APIs for downstream consumers
    • +
    • Stored in ScyllaDB with efficient binary serialization
    • +
    +

    8. Retry and Fault Tolerance

    +
      +
    • Retry topic: Failed ingestion events are published to a dedicated retry topic
    • +
    • Event-driven state management: Model states persist in SQL DB, surviving pod restarts
    • +
    • Kafka-based admin: Asynchronous processing with automatic re-consumption on failure
    • +
    +

    9. Experiment Isolation

    +
      +
    • Dedicated EKS cluster (skye-service-experiments) for experiments
    • +
    • Dedicated vector DB cluster for experiment workloads
    • +
    • Clean separation from production: experiments do not impact production performance
    • +
    • Promotion path from experiment to production after load analysis
    • +
    +

    10. Centralized Cluster Management

    +
      +
    • Automated cluster provisioning via scripts (collaboration with DevOps)
    • +
    • Consistent configurations across all clusters (eliminates consensus issues)
    • +
    • Horizontal scaling support: generic scripts for adding nodes to existing clusters
    • +
    +
    +

    Onboarding Flow

    +

    Step-by-step Process

    +
      +
    1. Data Scientist provides a base GCS path where model embeddings will be pushed
    2. +
    3. Register Model via POST /register-model with entity type, column mappings, model config
    4. +
    5. Register Variant(s) via POST /register-variant with filter criteria, vector DB config, job frequency
    6. +
    7. Schedule Databricks Job to read data from GCS path and ingest into Skye platform
    8. +
    9. Reset Model via POST /reset-model to trigger the first full ingestion
    10. +
    11. Trigger Model Machine via POST /trigger-model-machine to start the indexing pipeline
    12. +
    +

    Extending to New Tenants

    +

    With the variant system, extending a model to a new tenant only requires registering a new variant with appropriate filters -- no re-ingestion of embeddings is needed.

    + + \ No newline at end of file diff --git a/docs/skye/v1.0.0/index.html b/docs/skye/v1.0.0/index.html new file mode 100644 index 00000000..347bb9ee --- /dev/null +++ b/docs/skye/v1.0.0/index.html @@ -0,0 +1,19 @@ + + + + + +v1.0.0 | BharatMLStack + + + + + + + + + + + \ No newline at end of file diff --git a/docs/skye/v1.0.0/release-notes/index.html b/docs/skye/v1.0.0/release-notes/index.html new file mode 100644 index 00000000..b51b7ac8 --- /dev/null +++ b/docs/skye/v1.0.0/release-notes/index.html @@ -0,0 +1,67 @@ + + + + + +Release Notes | BharatMLStack + + + + + + + + +

    Skye - Release Notes

    +

    v1.0.0

    +

    Overview

    +

    Initial open-source release of Skye, BharatMLStack's vector similarity search platform. This release represents a complete re-architecture of the internal VSS (Vector Similarity Search) service, addressing scalability, resilience, and operational efficiency challenges from the previous generation.

    +

    What's New

    +

    Architecture

    +
      +
    • Model-first hierarchy: Models at the base level with variants nested within, eliminating embedding duplication across tenants
    • +
    • Entity-based data split: Separate embedding and aggregator tables per entity type (catalog, product, user)
    • +
    • Event-driven admin flows: Kafka-based model lifecycle management with SQL-backed state persistence
    • +
    • Pluggable vector DB support: Generic vector database abstraction replacing vendor-specific tight coupling
    • +
    +

    Serving

    +
      +
    • Multi-layer caching: In-memory cache + Redis distributed cache for low-latency similarity search
    • +
    • Indexed-only search: search_indexed_only flag prevents brute-force fallback on partially indexed collections
    • +
    • Pagination support: Service-level pagination for clients
    • +
    • Separate search/index embeddings: Models can use different embedding spaces for search and indexing
    • +
    +

    Ingestion

    +
      +
    • Shared embeddings across variants: Single ingestion per model with parallel variant processing
    • +
    • Generic RT consumer schema: Simplified onboarding for new real-time data sources
    • +
    • Retry topic: Automatic capture and reprocessing of failed ingestion events
    • +
    • EOF to all partitions: Ensures complete data consumption before processing completion
    • +
    +

    Operations

    +
      +
    • API-based model onboarding: Register models and variants via REST API (replaces manual Databricks-only flow)
    • +
    • Automated cluster provisioning: Scripted setup for consistent vector DB cluster configurations
    • +
    • Experiment isolation: Dedicated EKS and vector DB clusters for experiments
    • +
    • Comprehensive observability: Per-model + per-variant metrics for latency, throughput, error rates, and cache effectiveness
    • +
    +

    Improvements Over Previous Architecture

    +
    AreaBeforeAfter
    Embedding storageDuplicated per tenantShared per model
    Vector DB couplingTightly coupled to QdrantPluggable via generic interface
    State managementIn-pod synchronous threadEvent-driven with SQL backing
    Consumer handlingPaused during ingestionNo pausing; concurrent writes
    Cluster setupManual, error-proneAutomated, consistent
    Experiment infraShared with productionIsolated clusters
    Failure recoveryManual interventionRetry topics + snapshots
    ObservabilityGeneric alertsModel + variant level metrics
    +

    Known Limitations

    +
      +
    • Snapshot restore is currently supported for smaller indexes only
    • +
    • Pagination is handled at the service level (not natively by the vector DB)
    • +
    • Horizontal scaling of vector DB clusters requires running provisioning scripts
    • +
    +

    Technology Stack

    +
      +
    • Language: Go
    • +
    • Vector Database: Qdrant (pluggable)
    • +
    • Storage: ScyllaDB
    • +
    • Cache: Redis + In-Memory
    • +
    • Message Queue: Kafka
    • +
    • Configuration: ZooKeeper / etcd
    • +
    • Orchestration: Kubernetes (EKS)
    • +
    + + \ No newline at end of file diff --git a/docs/trufflebox-ui/v1.0.0/index.html b/docs/trufflebox-ui/v1.0.0/index.html new file mode 100644 index 00000000..13a938ff --- /dev/null +++ b/docs/trufflebox-ui/v1.0.0/index.html @@ -0,0 +1,19 @@ + + + + + +v1.0.0 | BharatMLStack + + + + + + + + + + + \ No newline at end of file diff --git a/docs/trufflebox-ui/v1.0.0/userguide/index.html b/docs/trufflebox-ui/v1.0.0/userguide/index.html index 4933b1fe..24898469 100644 --- a/docs/trufflebox-ui/v1.0.0/userguide/index.html +++ b/docs/trufflebox-ui/v1.0.0/userguide/index.html @@ -3,16 +3,16 @@ -User Manual | BharatMLStack - - - +User Manual | BharatMLStack + + + -

    Usage Guide

    +

    Usage Guide

    This guide covers the complete setup and usage of the Online Feature Store system, including the core services (Online Feature Store and Horizon) and the TruffleBox UI for feature management.

    Table of Contents

      @@ -44,10 +44,10 @@

      Environmen

      Online Feature Store Configuration

      The Online Feature Store requires several environment variables to configure storage backends, caching, and service settings.

      Core Application Settings

      -
      APP_ENV=prod
      APP_LOG_LEVEL=DEBUG
      APP_METRIC_SAMPLING_RATE=1
      APP_NAME=online-feature-store
      APP_PORT=8005
      AUTH_TOKEN=ofs-token
      +
      APP_ENV=prod
      APP_LOG_LEVEL=DEBUG
      APP_METRIC_SAMPLING_RATE=1
      APP_NAME=online-feature-store
      APP_PORT=8005
      AUTH_TOKEN=ofs-token

      Storage Configuration

      ScyllaDB Storage (Primary Storage)

      -
      # Primary ScyllaDB cluster
      STORAGE_SCYLLA_1_CONTACT_POINTS=localhost
      STORAGE_SCYLLA_1_KEYSPACE=ofs
      STORAGE_SCYLLA_1_NUM_CONNS=1
      STORAGE_SCYLLA_1_PORT=9042
      STORAGE_SCYLLA_1_TIMEOUT_IN_MS=300000
      STORAGE_SCYLLA_1_PASSWORD=
      STORAGE_SCYLLA_1_USERNAME=ofs

      # Secondary ScyllaDB cluster
      STORAGE_SCYLLA_5_CONTACT_POINTS=localhost
      STORAGE_SCYLLA_5_KEYSPACE=onfs
      STORAGE_SCYLLA_5_NUM_CONNS=1
      STORAGE_SCYLLA_5_PASSWORD=
      STORAGE_SCYLLA_5_PORT=9042
      STORAGE_SCYLLA_5_TIMEOUT_IN_MS=300000
      STORAGE_SCYLLA_5_USERNAME=

      # Active ScyllaDB configurations
      STORAGE_SCYLLA_ACTIVE_CONFIG_IDS=1,5
      +
      # Primary ScyllaDB cluster
      STORAGE_SCYLLA_1_CONTACT_POINTS=localhost
      STORAGE_SCYLLA_1_KEYSPACE=ofs
      STORAGE_SCYLLA_1_NUM_CONNS=1
      STORAGE_SCYLLA_1_PORT=9042
      STORAGE_SCYLLA_1_TIMEOUT_IN_MS=300000
      STORAGE_SCYLLA_1_PASSWORD=
      STORAGE_SCYLLA_1_USERNAME=ofs

      # Secondary ScyllaDB cluster
      STORAGE_SCYLLA_5_CONTACT_POINTS=localhost
      STORAGE_SCYLLA_5_KEYSPACE=onfs
      STORAGE_SCYLLA_5_NUM_CONNS=1
      STORAGE_SCYLLA_5_PASSWORD=
      STORAGE_SCYLLA_5_PORT=9042
      STORAGE_SCYLLA_5_TIMEOUT_IN_MS=300000
      STORAGE_SCYLLA_5_USERNAME=

      # Active ScyllaDB configurations
      STORAGE_SCYLLA_ACTIVE_CONFIG_IDS=1,5

      Redis Storage Configuration

      Redis serves dual purposes in the Online Feature Store:

        @@ -55,21 +55,21 @@

        Storag
      1. Distributed Cache Layer: For improved performance and reduced latency

      Redis configurations can be referenced by their IDs in Store configurations, similar to ScyllaDB. Each Redis configuration can be independently used as either a storage backend or cache layer.

      -
      # Redis Failover Configuration 1 (ID: 2)
      STORAGE_REDIS_FAILOVER_2_SENTINEL_ADDRESSES=localhost:26379
      STORAGE_REDIS_FAILOVER_2_DB=0
      STORAGE_REDIS_FAILOVER_2_DISABLE_IDENTITY=true
      STORAGE_REDIS_FAILOVER_2_MASTER_NAME=mymaster
      STORAGE_REDIS_FAILOVER_2_MAX_IDLE_CONN=32
      STORAGE_REDIS_FAILOVER_2_MIN_IDLE_CONN=20
      STORAGE_REDIS_FAILOVER_2_MAX_ACTIVE_CONN=32
      STORAGE_REDIS_FAILOVER_2_MAX_RETRY=-1
      STORAGE_REDIS_FAILOVER_2_POOL_FIFO=false
      STORAGE_REDIS_FAILOVER_2_READ_TIMEOUT_IN_MS=3000
      STORAGE_REDIS_FAILOVER_2_WRITE_TIMEOUT_IN_MS=3000
      STORAGE_REDIS_FAILOVER_2_POOL_TIMEOUT_IN_MS=3000
      STORAGE_REDIS_FAILOVER_2_POOL_SIZE=32
      STORAGE_REDIS_FAILOVER_2_CONN_MAX_IDLE_TIMEOUT_IN_MINUTES=15
      STORAGE_REDIS_FAILOVER_2_CONN_MAX_AGE_IN_MINUTES=30

      # Redis Failover Configuration 2 (ID: 4)
      STORAGE_REDIS_FAILOVER_4_SENTINEL_ADDRESSES=localhost:26379
      STORAGE_REDIS_FAILOVER_4_DB=0
      STORAGE_REDIS_FAILOVER_4_DISABLE_IDENTITY=true
      STORAGE_REDIS_FAILOVER_4_MASTER_NAME=mymaster
      STORAGE_REDIS_FAILOVER_4_MAX_IDLE_CONN=32
      STORAGE_REDIS_FAILOVER_4_MIN_IDLE_CONN=20
      STORAGE_REDIS_FAILOVER_4_MAX_ACTIVE_CONN=32
      STORAGE_REDIS_FAILOVER_4_MAX_RETRY=-1
      STORAGE_REDIS_FAILOVER_4_POOL_FIFO=false
      STORAGE_REDIS_FAILOVER_4_READ_TIMEOUT_IN_MS=3000
      STORAGE_REDIS_FAILOVER_4_WRITE_TIMEOUT_IN_MS=3000
      STORAGE_REDIS_FAILOVER_4_POOL_TIMEOUT_IN_MS=3000
      STORAGE_REDIS_FAILOVER_4_POOL_SIZE=32
      STORAGE_REDIS_FAILOVER_4_CONN_MAX_IDLE_TIMEOUT_IN_MINUTES=15
      STORAGE_REDIS_FAILOVER_4_CONN_MAX_AGE_IN_MINUTES=30

      # High-Performance Redis Configuration (ID: 6)
      STORAGE_REDIS_FAILOVER_6_CONN_MAX_AGE_IN_MINUTES=-1
      STORAGE_REDIS_FAILOVER_6_CONN_MAX_IDLE_TIMEOUT_IN_MINUTES=30
      STORAGE_REDIS_FAILOVER_6_DB=0
      STORAGE_REDIS_FAILOVER_6_DISABLE_IDENTITY=true
      STORAGE_REDIS_FAILOVER_6_MASTER_NAME=mymaster
      STORAGE_REDIS_FAILOVER_6_MAX_ACTIVE_CONN=202
      STORAGE_REDIS_FAILOVER_6_MAX_IDLE_CONN=157
      STORAGE_REDIS_FAILOVER_6_MAX_RETRY=-1
      STORAGE_REDIS_FAILOVER_6_MIN_IDLE_CONN=52
      STORAGE_REDIS_FAILOVER_6_PASSWORD=
      STORAGE_REDIS_FAILOVER_6_POOL_FIFO=false
      STORAGE_REDIS_FAILOVER_6_POOL_SIZE=202
      STORAGE_REDIS_FAILOVER_6_POOL_TIMEOUT_IN_MS=2
      STORAGE_REDIS_FAILOVER_6_READ_TIMEOUT_IN_MS=75
      STORAGE_REDIS_FAILOVER_6_ROUTE_RANDOM=true
      STORAGE_REDIS_FAILOVER_6_SENTINEL_ADDRESSES=localhost:26379
      STORAGE_REDIS_FAILOVER_6_WRITE_TIMEOUT_IN_MS=300

      # Active Redis configurations
      STORAGE_REDIS_FAILOVER_ACTIVE_CONFIG_IDS=2,4,6
      +
      # Redis Failover Configuration 1 (ID: 2)
      STORAGE_REDIS_FAILOVER_2_SENTINEL_ADDRESSES=localhost:26379
      STORAGE_REDIS_FAILOVER_2_DB=0
      STORAGE_REDIS_FAILOVER_2_DISABLE_IDENTITY=true
      STORAGE_REDIS_FAILOVER_2_MASTER_NAME=mymaster
      STORAGE_REDIS_FAILOVER_2_MAX_IDLE_CONN=32
      STORAGE_REDIS_FAILOVER_2_MIN_IDLE_CONN=20
      STORAGE_REDIS_FAILOVER_2_MAX_ACTIVE_CONN=32
      STORAGE_REDIS_FAILOVER_2_MAX_RETRY=-1
      STORAGE_REDIS_FAILOVER_2_POOL_FIFO=false
      STORAGE_REDIS_FAILOVER_2_READ_TIMEOUT_IN_MS=3000
      STORAGE_REDIS_FAILOVER_2_WRITE_TIMEOUT_IN_MS=3000
      STORAGE_REDIS_FAILOVER_2_POOL_TIMEOUT_IN_MS=3000
      STORAGE_REDIS_FAILOVER_2_POOL_SIZE=32
      STORAGE_REDIS_FAILOVER_2_CONN_MAX_IDLE_TIMEOUT_IN_MINUTES=15
      STORAGE_REDIS_FAILOVER_2_CONN_MAX_AGE_IN_MINUTES=30

      # Redis Failover Configuration 2 (ID: 4)
      STORAGE_REDIS_FAILOVER_4_SENTINEL_ADDRESSES=localhost:26379
      STORAGE_REDIS_FAILOVER_4_DB=0
      STORAGE_REDIS_FAILOVER_4_DISABLE_IDENTITY=true
      STORAGE_REDIS_FAILOVER_4_MASTER_NAME=mymaster
      STORAGE_REDIS_FAILOVER_4_MAX_IDLE_CONN=32
      STORAGE_REDIS_FAILOVER_4_MIN_IDLE_CONN=20
      STORAGE_REDIS_FAILOVER_4_MAX_ACTIVE_CONN=32
      STORAGE_REDIS_FAILOVER_4_MAX_RETRY=-1
      STORAGE_REDIS_FAILOVER_4_POOL_FIFO=false
      STORAGE_REDIS_FAILOVER_4_READ_TIMEOUT_IN_MS=3000
      STORAGE_REDIS_FAILOVER_4_WRITE_TIMEOUT_IN_MS=3000
      STORAGE_REDIS_FAILOVER_4_POOL_TIMEOUT_IN_MS=3000
      STORAGE_REDIS_FAILOVER_4_POOL_SIZE=32
      STORAGE_REDIS_FAILOVER_4_CONN_MAX_IDLE_TIMEOUT_IN_MINUTES=15
      STORAGE_REDIS_FAILOVER_4_CONN_MAX_AGE_IN_MINUTES=30

      # High-Performance Redis Configuration (ID: 6)
      STORAGE_REDIS_FAILOVER_6_CONN_MAX_AGE_IN_MINUTES=-1
      STORAGE_REDIS_FAILOVER_6_CONN_MAX_IDLE_TIMEOUT_IN_MINUTES=30
      STORAGE_REDIS_FAILOVER_6_DB=0
      STORAGE_REDIS_FAILOVER_6_DISABLE_IDENTITY=true
      STORAGE_REDIS_FAILOVER_6_MASTER_NAME=mymaster
      STORAGE_REDIS_FAILOVER_6_MAX_ACTIVE_CONN=202
      STORAGE_REDIS_FAILOVER_6_MAX_IDLE_CONN=157
      STORAGE_REDIS_FAILOVER_6_MAX_RETRY=-1
      STORAGE_REDIS_FAILOVER_6_MIN_IDLE_CONN=52
      STORAGE_REDIS_FAILOVER_6_PASSWORD=
      STORAGE_REDIS_FAILOVER_6_POOL_FIFO=false
      STORAGE_REDIS_FAILOVER_6_POOL_SIZE=202
      STORAGE_REDIS_FAILOVER_6_POOL_TIMEOUT_IN_MS=2
      STORAGE_REDIS_FAILOVER_6_READ_TIMEOUT_IN_MS=75
      STORAGE_REDIS_FAILOVER_6_ROUTE_RANDOM=true
      STORAGE_REDIS_FAILOVER_6_SENTINEL_ADDRESSES=localhost:26379
      STORAGE_REDIS_FAILOVER_6_WRITE_TIMEOUT_IN_MS=300

      # Active Redis configurations
      STORAGE_REDIS_FAILOVER_ACTIVE_CONFIG_IDS=2,4,6

      Caching Configuration

      -
      # In-Memory Cache
      IN_MEM_CACHE_3_ENABLED=true
      IN_MEM_CACHE_3_NAME=onfs
      IN_MEM_CACHE_3_SIZE_IN_BYTES=10000000
      IN_MEM_CACHE_ACTIVE_CONFIG_IDS=3

      # Distributed Cache (uses Redis configurations)
      # Redis configurations (IDs: 2,4,6) can be used for distributed caching
      DISTRIBUTED_CACHE_CONF_IDS=2
      +
      # In-Memory Cache
      IN_MEM_CACHE_3_ENABLED=true
      IN_MEM_CACHE_3_NAME=onfs
      IN_MEM_CACHE_3_SIZE_IN_BYTES=10000000
      IN_MEM_CACHE_ACTIVE_CONFIG_IDS=3

      # Distributed Cache (uses Redis configurations)
      # Redis configurations (IDs: 2,4,6) can be used for distributed caching
      DISTRIBUTED_CACHE_CONF_IDS=2

      Service Discovery and Configuration

      -
      # ETCD Configuration for service discovery
      ETCD_SERVER=0.0.0.0:2379
      ETCD_WATCHER_ENABLED=true
      +
      # ETCD Configuration for service discovery
      ETCD_SERVER=0.0.0.0:2379
      ETCD_WATCHER_ENABLED=true

      Horizon Configuration

      Horizon manages the metadata and configuration for the Online Feature Store system.

      Core Application Settings

      -
      APP_NAME=horizon
      APP_ENVIRONMENT=PROD
      APP_ENV=production
      APP_PORT=8082
      APP_LOG_LEVEL=DEBUG
      APP_METRIC_SAMPLING_RATE=1
      APP_GC_PERCENTAGE=1
      +
      APP_NAME=horizon
      APP_ENVIRONMENT=PROD
      APP_ENV=production
      APP_PORT=8082
      APP_LOG_LEVEL=DEBUG
      APP_METRIC_SAMPLING_RATE=1
      APP_GC_PERCENTAGE=1

      Database Configuration

      -
      # MySQL Master Configuration
      MYSQL_MASTER_MAX_POOL_SIZE=5
      MYSQL_MASTER_MIN_POOL_SIZE=2
      MYSQL_MASTER_PASSWORD=
      MYSQL_MASTER_HOST=127.0.0.1
      MYSQL_MASTER_PORT=3306
      MYSQL_DB_NAME=ml_config
      MYSQL_MASTER_USERNAME=root

      # MySQL Slave Configuration
      MYSQL_SLAVE_MAX_POOL_SIZE=5
      MYSQL_SLAVE_MIN_POOL_SIZE=2
      MYSQL_SLAVE_PASSWORD=
      MYSQL_SLAVE_HOST=127.0.0.1
      MYSQL_SLAVE_USERNAME=root
      MYSQL_SLAVE_PORT=3306
      +
      # MySQL Master Configuration
      MYSQL_MASTER_MAX_POOL_SIZE=5
      MYSQL_MASTER_MIN_POOL_SIZE=2
      MYSQL_MASTER_PASSWORD=
      MYSQL_MASTER_HOST=127.0.0.1
      MYSQL_MASTER_PORT=3306
      MYSQL_DB_NAME=ml_config
      MYSQL_MASTER_USERNAME=root

      # MySQL Slave Configuration
      MYSQL_SLAVE_MAX_POOL_SIZE=5
      MYSQL_SLAVE_MIN_POOL_SIZE=2
      MYSQL_SLAVE_PASSWORD=
      MYSQL_SLAVE_HOST=127.0.0.1
      MYSQL_SLAVE_USERNAME=root
      MYSQL_SLAVE_PORT=3306

      ScyllaDB Configuration

      -
      # ScyllaDB for Horizon
      SCYLLA_1_CONTACT_POINTS=localhost
      SCYLLA_1_KEYSPACE=onfs
      SCYLLA_1_NUM_CONNS=1
      SCYLLA_1_PORT=9042
      SCYLLA_1_TIMEOUT_IN_MS=300000
      SCYLLA_1_PASSWORD=
      SCYLLA_1_USERNAME=
      SCYLLA_ACTIVE_CONFIG_IDS=1
      +
      # ScyllaDB for Horizon
      SCYLLA_1_CONTACT_POINTS=localhost
      SCYLLA_1_KEYSPACE=onfs
      SCYLLA_1_NUM_CONNS=1
      SCYLLA_1_PORT=9042
      SCYLLA_1_TIMEOUT_IN_MS=300000
      SCYLLA_1_PASSWORD=
      SCYLLA_1_USERNAME=
      SCYLLA_ACTIVE_CONFIG_IDS=1

      Service Integration

      -
      # ETCD Configuration
      ETCD_WATCHER_ENABLED=true
      ETCD_SERVER=localhost:2379

      # Integration with Online Feature Store
      ONLINE_FEATURE_STORE_APP_NAME=online-feature-store
      +
      # ETCD Configuration
      ETCD_WATCHER_ENABLED=true
      ETCD_SERVER=localhost:2379

      # Integration with Online Feature Store
      ONLINE_FEATURE_STORE_APP_NAME=online-feature-store

      Key Constructs

      Understanding these key constructs is essential for effectively using the Online Feature Store:

      @@ -132,7 +132,7 @@

      Job

      Configuration Hierarchy

      The system uses a hierarchical configuration approach:

      -
      Store → Entity → Feature Group → Feature
      ↓ ↓ ↓ ↓
      Config Identity Collection Individual
      Level Level Level Level
      +
      Store → Entity → Feature Group → Feature
      ↓ ↓ ↓ ↓
      Config Identity Collection Individual
      Level Level Level Level

      This hierarchy allows for:

      • Inheritance: Lower levels inherit settings from higher levels
      • @@ -316,6 +316,6 @@

        LicenseBharatMLStack Business Source License 1.1.


        Built with ❤️ for the ML community from Meesho
        -
        If you find this useful, ⭐️ the repo — your support means the world to us!

    +
    If you find this useful, ⭐️ the repo — your support means the world to us!
    \ No newline at end of file diff --git a/experiments/episodic-memory-prototype/agent/__init__.py b/experiments/episodic-memory-prototype/agent/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/experiments/episodic-memory-prototype/agent/agent.py b/experiments/episodic-memory-prototype/agent/agent.py new file mode 100644 index 00000000..58e29dfe --- /dev/null +++ b/experiments/episodic-memory-prototype/agent/agent.py @@ -0,0 +1,313 @@ +"""Episodic-memory agent. + +Demonstrates the full loop: perceive -> recall -> decide -> reinforce. + +When ANTHROPIC_API_KEY is set, investigation decisions are made by Claude +using structured episodic context (episodes with outcomes/corrections, +generalized facts with support counts). Falls back to keyword heuristics +when no key is available. +""" + +from __future__ import annotations + +import os +from typing import Any + +from memory.encoder import Encoder +from memory.episodes import EpisodeBoundaryDetector +from memory.facts import FactExtractor +from memory.graph import EpisodeGraph +from memory.models import ( + OUTCOME_FAILURE, + OUTCOME_SUCCESS, + DecisionResult, + RetrievalResult, + ROLE_AGENT, + ROLE_USER, +) +from memory.reinforcer import Reinforcer +from memory.retriever import Retriever +from memory.timeline import Timeline + + +_POOL_PATTERNS = [ + "connection pool", "pool exhaust", "pool saturat", + "pool starvation", "pool contention", +] + +_INVESTIGATION_INSTRUCTION = ( + "Based on your past experience shown above, what should be investigated " + "FIRST? Be specific. Explain your reasoning citing the past episodes " + "and facts.\n\n" + "Respond in this exact format:\n" + "REASONING: <2-4 sentences analyzing the pattern from past episodes>\n" + "DECISION: Investigate: " +) + + +class EpisodicAgent: + """Agent backed by the full episodic memory stack.""" + + def __init__( + self, + encoder: Encoder | None = None, + anthropic_client: Any = None, + model: str = "claude-sonnet-4-20250514", + ) -> None: + self.encoder = encoder or Encoder() + self.timeline = Timeline() + self.episode_builder = EpisodeBoundaryDetector(self.encoder) + self.graph = EpisodeGraph() + self.fact_extractor = FactExtractor(self.encoder) + self.retriever = Retriever( + encoder=self.encoder, + graph=self.graph, + fact_extractor=self.fact_extractor, + ) + self.reinforcer = Reinforcer( + encoder=self.encoder, + graph=self.graph, + fact_extractor=self.fact_extractor, + ) + self._client = anthropic_client + self._model = model + if self._client is None: + self._client = _try_init_anthropic() + + # -- public API -------------------------------------------------------- + + def step(self, user_message: str) -> str: + """Process one user turn: record -> retrieve -> respond -> reinforce.""" + self.timeline.append(ROLE_USER, user_message) + self._rebuild_episodes() + + context = self.retriever.retrieve(user_message) + response = self._generate_response(user_message, context) + self.timeline.append(ROLE_AGENT, response) + + outcome, details = self._evaluate_outcome(user_message, response) + self.reinforcer.reinforce(context, outcome, outcome_details=details) + return response + + def decide( + self, bug_report: str, result: RetrievalResult, + ) -> DecisionResult: + """Given a bug report and retrieved episodic context, produce a + structured investigation decision (with reasoning).""" + context_text = self._format_episodic_context(result) + if self._client: + return self._decide_claude(bug_report, context_text) + return self._decide_heuristic(bug_report, context_text) + + # -- context formatting ------------------------------------------------ + + @staticmethod + def _format_episodic_context(result: RetrievalResult) -> str: + parts: list[str] = [] + + if result.episodes: + parts.append("## Past Episodes (from episodic memory)") + for i, ep in enumerate(result.episodes[:5], 1): + outcome = ep.outcome.upper() if ep.outcome else "UNKNOWN" + parts.append(f"Episode {i}: {ep.summary_text}") + parts.append(f" [OUTCOME: {outcome}]") + if ep.assumptions: + parts.append( + f" Initial assumption: {'; '.join(ep.assumptions)}" + ) + if ep.corrections: + parts.append( + f" Correction: {'; '.join(ep.corrections)}" + ) + if ep.outcome_details: + parts.append(f" Root cause: {ep.outcome_details}") + parts.append("") + + if result.facts: + parts.append("## Generalized Facts (learned from multiple episodes)") + for f in result.facts[:5]: + parts.append( + f"- {f.fact_text} " + f"(supported by {f.support_count} episodes, " + f"contradicted by {f.contradiction_count})" + ) + parts.append("") + + if result.causal_narrative: + parts.append(f"## Causal Chain\n{result.causal_narrative}\n") + + return "\n".join(parts) or "(no relevant past experience)" + + # -- Claude decision --------------------------------------------------- + + def _decide_claude( + self, bug_report: str, context_text: str, + ) -> DecisionResult: + prompt = ( + f"## Current Bug Report\n{bug_report}\n\n" + f"{context_text}\n" + f"## Instruction\n{_INVESTIGATION_INSTRUCTION}" + ) + resp = self._client.messages.create( + model=self._model, + max_tokens=400, + system=( + "You are a senior SRE debugging a production incident. " + "Use your episodic memory of past incidents to inform your " + "investigation. Be specific about which past episodes " + "support your hypothesis." + ), + messages=[{"role": "user", "content": prompt}], + ) + full_text = resp.content[0].text.strip() + reasoning, decision = _parse_reasoning_decision(full_text) + return DecisionResult( + decision=decision, + reasoning=reasoning, + context_used=context_text, + ) + + # -- heuristic fallback ------------------------------------------------ + + @staticmethod + def _decide_heuristic( + bug_report: str, context_text: str, + ) -> DecisionResult: + ctx = context_text.lower() + bug = bug_report.lower() + + deploy_signals = [ + "after deployment", "after deploy", "since deploy", + "after release", "after rollout", + ] + pool_correction_signals = [ + "not connection pool", "not pool exhaustion", + "correlated with deployment", "not load", + ] + pool_hits = sum(1 for p in _POOL_PATTERNS if p in ctx) + deploy_hits = sum(1 for s in deploy_signals if s in bug) + has_pool_correction = any(s in ctx for s in pool_correction_signals) + + if deploy_hits and has_pool_correction: + decision = ( + "Investigate: recent deployment/config change " + "-- check migration, schema changes, and rollback" + ) + reasoning = ( + "Bug report signals deployment timing. Past corrections " + "explicitly warn against the recurring pattern. " + "Checking deployment artifacts and migrations first." + ) + elif deploy_hits and pool_hits == 0: + decision = ( + "Investigate: recent deployment/config change " + "-- verify and rollback" + ) + reasoning = ( + "Bug report mentions a deployment. No connection pool " + "signals in past experience. Prioritizing deployment check." + ) + elif pool_hits: + decision = "Investigate: connection pool exhaustion under load" + reasoning = ( + f"Past episodes mention connection pool issues " + f"({pool_hits} references). Pattern suggests pool " + f"exhaustion as recurring root cause under load." + ) + elif deploy_hits: + decision = ( + "Investigate: recent deployment/config change " + "-- verify and rollback" + ) + reasoning = "Deployment signal in bug report." + elif "database" in ctx or "db connection" in ctx: + decision = "Investigate: database connections and query performance" + reasoning = "Past context mentions database issues." + elif "network" in ctx or "latency" in ctx or "timeout" in ctx: + decision = "Investigate: network connectivity and latency" + reasoning = "Past context mentions network/latency issues." + else: + decision = ( + "Investigate: general service health " + "(no prior pattern match)" + ) + reasoning = "No matching patterns found in past experience." + + return DecisionResult( + decision=decision, + reasoning=reasoning, + context_used=context_text, + ) + + # -- internals --------------------------------------------------------- + + def _rebuild_episodes(self) -> None: + episodes = self.episode_builder.build_episodes(self.timeline.entries) + for ep in episodes: + if ep.episode_id not in self.graph.episode_ids: + self.graph.add_episode(ep) + self.fact_extractor.extract(ep) + self.graph.auto_link(episodes) + + def _generate_response(self, query: str, context: RetrievalResult) -> str: + episode_summaries = [ep.summary_text for ep in context.episodes[:3]] + fact_statements = [f.fact_text for f in context.facts[:2]] + parts = ["Based on memory:"] + if episode_summaries: + parts.append(f" Episodes: {'; '.join(episode_summaries)}") + if fact_statements: + parts.append(f" Facts: {'; '.join(fact_statements)}") + parts.append(f" Responding to: {query}") + return "\n".join(parts) + + @staticmethod + def _evaluate_outcome(query: str, response: str) -> tuple[str, str]: + if "error" in response.lower(): + return OUTCOME_FAILURE, "Response contained an error" + return OUTCOME_SUCCESS, "Response generated successfully" + + +# -- shared helpers -------------------------------------------------------- + +def _try_init_anthropic() -> Any: + api_key = os.environ.get("ANTHROPIC_API_KEY") + if not api_key: + return None + try: + import anthropic + return anthropic.Anthropic(api_key=api_key) + except Exception: + return None + + +def _parse_reasoning_decision(text: str) -> tuple[str, str]: + """Extract REASONING and DECISION sections from Claude's response. + Returns (reasoning, decision). Falls back to full text if parsing fails. + """ + reasoning = text + decision = text + + lines = text.split("\n") + r_parts: list[str] = [] + d_parts: list[str] = [] + section = None + + for line in lines: + upper = line.strip().upper() + if upper.startswith("REASONING:"): + section = "r" + r_parts.append(line.strip()[len("REASONING:"):].strip()) + elif upper.startswith("DECISION:"): + section = "d" + d_parts.append(line.strip()[len("DECISION:"):].strip()) + elif section == "r": + r_parts.append(line.strip()) + elif section == "d": + d_parts.append(line.strip()) + + if r_parts: + reasoning = " ".join(p for p in r_parts if p) + if d_parts: + decision = " ".join(p for p in d_parts if p) + + return reasoning, decision diff --git a/experiments/episodic-memory-prototype/baseline/__init__.py b/experiments/episodic-memory-prototype/baseline/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/experiments/episodic-memory-prototype/baseline/vector_agent.py b/experiments/episodic-memory-prototype/baseline/vector_agent.py new file mode 100644 index 00000000..d4f93fc4 --- /dev/null +++ b/experiments/episodic-memory-prototype/baseline/vector_agent.py @@ -0,0 +1,262 @@ +"""Flat vector RAG agent -- baseline for comparison. + +Stores every entry embedding in a flat list and retrieves the top-k most +similar items at query time. No episodes, no graph, no facts, no +reinforcement. This is the "standard RAG" baseline. + +When ANTHROPIC_API_KEY is set, investigation decisions are made by Claude +using the raw text chunks. Falls back to keyword heuristics when no key +is available. +""" + +from __future__ import annotations + +import os +from typing import Any + +from memory.encoder import Encoder +from memory.models import ( + DecisionResult, + ROLE_AGENT, + ROLE_USER, + TimelineEntry, + cosine_similarity, +) + +_POOL_PATTERNS = [ + "connection pool", "pool exhaust", "pool saturat", + "pool starvation", "pool contention", +] + +_INVESTIGATION_INSTRUCTION = ( + "Based on your past experience shown above, what should be investigated " + "FIRST? Be specific. Explain your reasoning citing the past episodes " + "and facts.\n\n" + "Respond in this exact format:\n" + "REASONING: <2-4 sentences analyzing the pattern from past entries>\n" + "DECISION: Investigate: " +) + + +class VectorAgent: + """Agent using a simple flat vector store (no episodic structure).""" + + def __init__( + self, + encoder: Encoder | None = None, + top_k: int = 5, + anthropic_client: Any = None, + model: str = "claude-sonnet-4-20250514", + ) -> None: + self.encoder = encoder or Encoder() + self.top_k = top_k + self._entries: list[TimelineEntry] = [] + self._client = anthropic_client + self._model = model + if self._client is None: + self._client = _try_init_anthropic() + + # -- public API -------------------------------------------------------- + + def step(self, user_message: str) -> str: + entry = TimelineEntry(role=ROLE_USER, raw_content=user_message) + entry.semantic_embedding = self.encoder.encode_entry(entry) + self._entries.append(entry) + + context = self._retrieve(user_message) + response = self._generate_response(user_message, context) + + resp_entry = TimelineEntry(role=ROLE_AGENT, raw_content=response) + resp_entry.semantic_embedding = self.encoder.encode_entry(resp_entry) + self._entries.append(resp_entry) + return response + + def decide( + self, bug_report: str, retrieved_chunks: list[str], + ) -> DecisionResult: + """Given a bug report and retrieved text chunks, produce a + structured investigation decision (with reasoning).""" + context_text = self._format_baseline_context(retrieved_chunks) + if self._client: + return self._decide_claude(bug_report, context_text) + return self._decide_heuristic(bug_report, context_text) + + # -- context formatting ------------------------------------------------ + + @staticmethod + def _format_baseline_context(chunks: list[str]) -> str: + if not chunks: + return "(no relevant past entries)" + parts = ["## Retrieved Past Entries (from vector memory)"] + for i, chunk in enumerate(chunks[:5], 1): + parts.append(f"{i}. {chunk}") + return "\n".join(parts) + + # -- Claude decision --------------------------------------------------- + + def _decide_claude( + self, bug_report: str, context_text: str, + ) -> DecisionResult: + prompt = ( + f"## Current Bug Report\n{bug_report}\n\n" + f"{context_text}\n\n" + f"## Instruction\n{_INVESTIGATION_INSTRUCTION}" + ) + resp = self._client.messages.create( + model=self._model, + max_tokens=400, + system=( + "You are a senior SRE debugging a production incident. " + "Use the retrieved past entries to inform your investigation. " + "The entries are raw text from previous incidents with no " + "structure or outcome labels." + ), + messages=[{"role": "user", "content": prompt}], + ) + full_text = resp.content[0].text.strip() + reasoning, decision = _parse_reasoning_decision(full_text) + return DecisionResult( + decision=decision, + reasoning=reasoning, + context_used=context_text, + ) + + # -- heuristic fallback ------------------------------------------------ + + @staticmethod + def _decide_heuristic( + bug_report: str, context_text: str, + ) -> DecisionResult: + ctx = context_text.lower() + bug = bug_report.lower() + + deploy_signals = [ + "after deployment", "after deploy", "since deploy", + "after release", "after rollout", + ] + pool_correction_signals = [ + "not connection pool", "not pool exhaustion", + "correlated with deployment", "not load", + ] + pool_hits = sum(1 for p in _POOL_PATTERNS if p in ctx) + deploy_hits = sum(1 for s in deploy_signals if s in bug) + has_pool_correction = any(s in ctx for s in pool_correction_signals) + + if deploy_hits and has_pool_correction: + decision = ( + "Investigate: recent deployment/config change " + "-- check migration, schema changes, and rollback" + ) + reasoning = ( + "Bug report signals deployment timing. Past corrections " + "warn against the recurring pattern. " + "Checking deployment artifacts first." + ) + elif deploy_hits and pool_hits == 0: + decision = ( + "Investigate: recent deployment/config change " + "-- verify and rollback" + ) + reasoning = ( + "Bug report mentions a deployment. No connection pool " + "signals in retrieved entries. Checking deployment first." + ) + elif pool_hits: + decision = "Investigate: connection pool exhaustion under load" + reasoning = ( + f"Retrieved entries mention connection pool issues " + f"({pool_hits} references). Suggesting pool exhaustion." + ) + elif deploy_hits: + decision = ( + "Investigate: recent deployment/config change " + "-- verify and rollback" + ) + reasoning = "Deployment signal in bug report." + elif "database" in ctx or "db connection" in ctx: + decision = "Investigate: database connections and query performance" + reasoning = "Retrieved entries mention database issues." + elif "network" in ctx or "latency" in ctx or "timeout" in ctx: + decision = "Investigate: network connectivity and latency" + reasoning = "Retrieved entries mention network/latency issues." + else: + decision = ( + "Investigate: general service health " + "(no prior pattern match)" + ) + reasoning = "No matching patterns found in retrieved entries." + + return DecisionResult( + decision=decision, + reasoning=reasoning, + context_used=context_text, + ) + + # -- internals --------------------------------------------------------- + + def _retrieve(self, query: str) -> list[TimelineEntry]: + if not self._entries: + return [] + q_emb = self.encoder.encode_text(query) + scored = [] + for entry in self._entries: + if entry.semantic_embedding is None: + continue + sim = cosine_similarity(q_emb, entry.semantic_embedding) + scored.append((sim, entry)) + scored.sort(key=lambda x: x[0], reverse=True) + return [e for _, e in scored[: self.top_k]] + + def _generate_response( + self, query: str, context: list[TimelineEntry], + ) -> str: + snippets = [e.raw_content[:60] for e in context[:3]] + parts = ["Based on vector memory:"] + if snippets: + parts.append(f" Retrieved: {'; '.join(snippets)}") + parts.append(f" Responding to: {query}") + return "\n".join(parts) + + +# -- shared helpers -------------------------------------------------------- + +def _try_init_anthropic() -> Any: + api_key = os.environ.get("ANTHROPIC_API_KEY") + if not api_key: + return None + try: + import anthropic + return anthropic.Anthropic(api_key=api_key) + except Exception: + return None + + +def _parse_reasoning_decision(text: str) -> tuple[str, str]: + """Extract REASONING and DECISION sections from Claude's response.""" + reasoning = text + decision = text + + lines = text.split("\n") + r_parts: list[str] = [] + d_parts: list[str] = [] + section = None + + for line in lines: + upper = line.strip().upper() + if upper.startswith("REASONING:"): + section = "r" + r_parts.append(line.strip()[len("REASONING:"):].strip()) + elif upper.startswith("DECISION:"): + section = "d" + d_parts.append(line.strip()[len("DECISION:"):].strip()) + elif section == "r": + r_parts.append(line.strip()) + elif section == "d": + d_parts.append(line.strip()) + + if r_parts: + reasoning = " ".join(p for p in r_parts if p) + if d_parts: + decision = " ".join(p for p in d_parts if p) + + return reasoning, decision diff --git a/experiments/episodic-memory-prototype/episodic_memory_prototype.egg-info/PKG-INFO b/experiments/episodic-memory-prototype/episodic_memory_prototype.egg-info/PKG-INFO new file mode 100644 index 00000000..d6cb6005 --- /dev/null +++ b/experiments/episodic-memory-prototype/episodic_memory_prototype.egg-info/PKG-INFO @@ -0,0 +1,11 @@ +Metadata-Version: 2.4 +Name: episodic-memory-prototype +Version: 0.1.0 +Summary: Episodic memory system for LLM agents — prototype & evaluation +Requires-Python: >=3.10 +Requires-Dist: numpy>=1.24 +Requires-Dist: sentence-transformers>=2.2 +Requires-Dist: anthropic>=0.30 +Provides-Extra: dev +Requires-Dist: pytest>=7.0; extra == "dev" +Requires-Dist: ruff>=0.4; extra == "dev" diff --git a/experiments/episodic-memory-prototype/episodic_memory_prototype.egg-info/SOURCES.txt b/experiments/episodic-memory-prototype/episodic_memory_prototype.egg-info/SOURCES.txt new file mode 100644 index 00000000..0e96d8fc --- /dev/null +++ b/experiments/episodic-memory-prototype/episodic_memory_prototype.egg-info/SOURCES.txt @@ -0,0 +1,24 @@ +pyproject.toml +agent/__init__.py +agent/agent.py +baseline/__init__.py +baseline/vector_agent.py +episodic_memory_prototype.egg-info/PKG-INFO +episodic_memory_prototype.egg-info/SOURCES.txt +episodic_memory_prototype.egg-info/dependency_links.txt +episodic_memory_prototype.egg-info/requires.txt +episodic_memory_prototype.egg-info/top_level.txt +eval/__init__.py +eval/compare.py +memory/__init__.py +memory/encoder.py +memory/episodes.py +memory/facts.py +memory/graph.py +memory/models.py +memory/reinforcer.py +memory/retriever.py +memory/timeline.py +simulator/__init__.py +simulator/scenarios.py +tests/test_memory.py \ No newline at end of file diff --git a/experiments/episodic-memory-prototype/episodic_memory_prototype.egg-info/dependency_links.txt b/experiments/episodic-memory-prototype/episodic_memory_prototype.egg-info/dependency_links.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/experiments/episodic-memory-prototype/episodic_memory_prototype.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/experiments/episodic-memory-prototype/episodic_memory_prototype.egg-info/requires.txt b/experiments/episodic-memory-prototype/episodic_memory_prototype.egg-info/requires.txt new file mode 100644 index 00000000..0febb0a0 --- /dev/null +++ b/experiments/episodic-memory-prototype/episodic_memory_prototype.egg-info/requires.txt @@ -0,0 +1,7 @@ +numpy>=1.24 +sentence-transformers>=2.2 +anthropic>=0.30 + +[dev] +pytest>=7.0 +ruff>=0.4 diff --git a/experiments/episodic-memory-prototype/episodic_memory_prototype.egg-info/top_level.txt b/experiments/episodic-memory-prototype/episodic_memory_prototype.egg-info/top_level.txt new file mode 100644 index 00000000..9006220a --- /dev/null +++ b/experiments/episodic-memory-prototype/episodic_memory_prototype.egg-info/top_level.txt @@ -0,0 +1,5 @@ +agent +baseline +eval +memory +simulator diff --git a/experiments/episodic-memory-prototype/eval/__init__.py b/experiments/episodic-memory-prototype/eval/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/experiments/episodic-memory-prototype/eval/compare.py b/experiments/episodic-memory-prototype/eval/compare.py new file mode 100644 index 00000000..87c9de4b --- /dev/null +++ b/experiments/episodic-memory-prototype/eval/compare.py @@ -0,0 +1,674 @@ +"""Head-to-head comparison: episodic memory vs flat vector RAG. + +Runs BOTH agents through ALL 9 rounds of the debugging scenario and prints +a rich table for every round showing: + + - What the episodic agent retrieved (episodes with outcomes, facts) + - What the baseline agent retrieved (flat text chunks) + - Each agent's full reasoning and short decision + - Whether that decision was correct + +When ANTHROPIC_API_KEY is set, both agents call Claude (claude-sonnet-4-20250514) +with different context formats. Falls back to keyword heuristics otherwise. + +Usage: + python -m eval.compare +""" + +from __future__ import annotations + +import logging +import os +import textwrap +from dataclasses import dataclass, field +from typing import Any + +from agent.agent import EpisodicAgent +from baseline.vector_agent import VectorAgent +from memory.encoder import Encoder +from memory.episodes import EpisodeBoundaryDetector +from memory.facts import FactExtractor +from memory.graph import EpisodeGraph +from memory.models import ( + OUTCOME_FAILURE, + OUTCOME_SUCCESS, + OUTCOME_UNKNOWN, + ROLE_AGENT, + ROLE_SYSTEM, + ROLE_USER, + RetrievalResult, + cosine_similarity, +) +from memory.reinforcer import Reinforcer +from memory.retriever import Retriever +from memory.timeline import Timeline +from simulator.scenarios import DebugRound, debugging_scenario + +log = logging.getLogger(__name__) + +W = 80 +BOX_H = "=" * W +COL_W = 38 + +_POOL_PATTERNS = [ + "connection pool", "pool exhaust", "pool saturat", + "pool starvation", "pool contention", +] + + +# -- stacks ---------------------------------------------------------------- + +class EpisodicStack: + """Memory management wrapper -- delegates decisions to EpisodicAgent.""" + + def __init__(self, encoder: Encoder, anthropic_client: Any = None) -> None: + self.encoder = encoder + self.timeline = Timeline() + self.detector = EpisodeBoundaryDetector(encoder) + self.graph = EpisodeGraph() + self.facts = FactExtractor(encoder, min_episodes_for_extraction=3) + self.retriever = Retriever( + encoder=encoder, graph=self.graph, fact_extractor=self.facts, + ) + self.reinforcer = Reinforcer( + encoder=encoder, graph=self.graph, fact_extractor=self.facts, + ) + self.agent = EpisodicAgent( + encoder=encoder, anthropic_client=anthropic_client, + ) + + def ingest_round(self, rd: DebugRound) -> None: + self.timeline.append(ROLE_USER, rd.bug_report, state_label=rd.state_label) + for note in rd.investigation_notes: + self.timeline.append(ROLE_AGENT, note, state_label=rd.state_label) + if rd.attempted_fix: + self.timeline.append( + ROLE_AGENT, f"Fix attempted: {rd.attempted_fix}", + state_label=rd.state_label, + ) + self.timeline.append( + ROLE_SYSTEM, + f"Outcome: {rd.outcome}. Root cause: {rd.actual_root_cause}", + state_label=rd.state_label, + ) + if rd.correction: + self.timeline.append( + ROLE_SYSTEM, + f"Correction: {rd.correction}", + state_label=rd.state_label, + ) + episodes = self.detector.build_episodes(self.timeline.entries) + for ep in episodes: + if ep.episode_id not in self.graph.episode_ids: + self.graph.add_episode(ep) + self.facts.extract(ep) + self.graph.auto_link(episodes) + if episodes: + latest = episodes[-1] + latest.outcome = rd.outcome + latest.outcome_details = rd.actual_root_cause + if rd.assumption: + latest.assumptions = [rd.assumption] + if rd.correction: + latest.corrections = [rd.correction] + result = RetrievalResult(episodes=[latest], facts=[], scores=[1.0]) + outcome_ep = self.reinforcer.reinforce( + result, rd.outcome, outcome_details=rd.actual_root_cause, + ) + if outcome_ep is not None: + if rd.assumption: + outcome_ep.assumptions = [rd.assumption] + if rd.correction: + outcome_ep.corrections = [rd.correction] + + def retrieve(self, query: str) -> RetrievalResult: + return self.retriever.retrieve(query, current_state="debugging") + + def trigger_fact_extraction(self) -> None: + real = [ + self.graph.get_episode(eid) + for eid in self.graph.episode_ids + ] + self.facts.extract_patterns( + [ep for ep in real if ep is not None and ep.summary_text] + ) + + +class BaselineStack: + """Memory management wrapper -- delegates decisions to VectorAgent.""" + + def __init__(self, encoder: Encoder, anthropic_client: Any = None) -> None: + self.encoder = encoder + self._store: list[tuple[list[float], str]] = [] + self.agent = VectorAgent( + encoder=encoder, anthropic_client=anthropic_client, + ) + + def ingest_round(self, rd: DebugRound) -> None: + texts = ( + [rd.bug_report] + + rd.investigation_notes + + ([f"Fix attempted: {rd.attempted_fix}"] if rd.attempted_fix else []) + + [f"Outcome: {rd.outcome}. Root cause: {rd.actual_root_cause}"] + + ([f"Correction: {rd.correction}"] if rd.correction else []) + ) + for t in texts: + self._store.append((self.encoder.encode_text(t), t)) + + def retrieve(self, query: str, top_k: int = 5) -> list[str]: + if not self._store: + return [] + q_emb = self.encoder.encode_text(query) + scored = [ + (cosine_similarity(q_emb, emb), text) + for emb, text in self._store + ] + scored.sort(key=lambda x: x[0], reverse=True) + return [text for _, text in scored[:top_k]] + + +# -- per-round result ------------------------------------------------------ + +@dataclass +class RoundResult: + round_num: int + service: str + bug_report: str + ground_truth: str + round_label: str = "" + + ep_episodes: list[str] = field(default_factory=list) + ep_facts: list[str] = field(default_factory=list) + ep_decision: str = "" + ep_reasoning: str = "" + ep_correct: bool = False + + bl_chunks: list[str] = field(default_factory=list) + bl_decision: str = "" + bl_reasoning: str = "" + bl_correct: bool = False + + ep_keyword_hits: int = 0 + bl_keyword_hits: int = 0 + total_keywords: int = 0 + + ep_outcome_labeled: int = 0 + ep_total_retrieved: int = 0 + bl_outcome_chunks: int = 0 + bl_total_retrieved: int = 0 + + +# -- correctness ----------------------------------------------------------- + +def _is_correct(decision: str, rd: DebugRound) -> bool: + low = decision.lower() + if rd.incorrect_keywords: + if any(kw.lower() in low for kw in rd.incorrect_keywords): + return False + if rd.correct_keywords: + return any(kw.lower() in low for kw in rd.correct_keywords) + return any(p in low for p in _POOL_PATTERNS) + + +# -- formatting helpers ---------------------------------------------------- + +def _wrap_block(text: str, width: int = COL_W) -> list[str]: + lines: list[str] = [] + for paragraph in text.splitlines(): + if not paragraph.strip(): + lines.append("") + else: + lines.extend(textwrap.wrap(paragraph, width=width)) + return lines or [""] + + +def _side_by_side(label: str, left: str, right: str) -> str: + l_lines = _wrap_block(left) + r_lines = _wrap_block(right) + n = max(len(l_lines), len(r_lines)) + l_lines += [""] * (n - len(l_lines)) + r_lines += [""] * (n - len(r_lines)) + out = [f" {label}"] + for ll, rl in zip(l_lines, r_lines): + out.append(f" {ll:<{COL_W}} | {rl:<{COL_W}}") + return "\n".join(out) + + +def _count_hits(text: str, keywords: list[str]) -> int: + low = text.lower() + return sum(1 for kw in keywords if kw.lower() in low) + + +# -- main runner ----------------------------------------------------------- + +def run_comparison() -> list[RoundResult]: + encoder = Encoder() + anthropic_client = _try_init_anthropic() + mode = "Claude" if anthropic_client else "heuristic" + + episodic = EpisodicStack(encoder, anthropic_client=anthropic_client) + baseline = BaselineStack(encoder, anthropic_client=anthropic_client) + rounds = debugging_scenario() + results: list[RoundResult] = [] + + _print_header(mode) + + for rd in rounds: + ep_result = episodic.retrieve(rd.bug_report) + bl_entries = baseline.retrieve(rd.bug_report) + + ep_dr = episodic.agent.decide(rd.bug_report, ep_result) + bl_dr = baseline.agent.decide(rd.bug_report, bl_entries) + + ep_correct = _is_correct(ep_dr.decision, rd) + bl_correct = _is_correct(bl_dr.decision, rd) + + combined_ep = ep_dr.context_used + " " + ep_dr.decision + combined_bl = bl_dr.context_used + " " + bl_dr.decision + ep_kw = _count_hits(combined_ep, rd.expected_keywords) + bl_kw = _count_hits(combined_bl, rd.expected_keywords) + + ep_top = ep_result.episodes[:5] + ep_outcome_labeled = sum( + 1 for ep in ep_top + if ep.outcome in (OUTCOME_SUCCESS, OUTCOME_FAILURE) + ) + bl_top = bl_entries[:5] + bl_outcome_chunks = sum( + 1 for c in bl_top if "outcome:" in c.lower() + ) + + rr = RoundResult( + round_num=rd.round_num, + service=rd.service, + bug_report=rd.bug_report, + ground_truth=rd.actual_root_cause, + round_label=rd.round_label, + ep_episodes=[ + f"[{ep.outcome.upper()}] {ep.summary_text[:60]}" + + (f" => {'; '.join(ep.corrections)}" if ep.corrections else "") + for ep in ep_top + ], + ep_facts=[ + f"{f.fact_text[:70]} [sup={f.support_count}]" + for f in ep_result.facts[:3] + ], + ep_decision=ep_dr.decision, + ep_reasoning=ep_dr.reasoning, + ep_correct=ep_correct, + bl_chunks=[e[:75] for e in bl_top], + bl_decision=bl_dr.decision, + bl_reasoning=bl_dr.reasoning, + bl_correct=bl_correct, + ep_keyword_hits=ep_kw, + bl_keyword_hits=bl_kw, + total_keywords=len(rd.expected_keywords), + ep_outcome_labeled=ep_outcome_labeled, + ep_total_retrieved=len(ep_top), + bl_outcome_chunks=bl_outcome_chunks, + bl_total_retrieved=len(bl_top), + ) + results.append(rr) + _print_round(rr) + + episodic.ingest_round(rd) + baseline.ingest_round(rd) + + if rd.trigger_fact_extraction: + print( + f"\n >>> TRIGGERING FACT EXTRACTION " + f"AFTER ROUND {rd.round_num} <<<" + ) + episodic.trigger_fact_extraction() + for f in episodic.facts.facts: + print(f" * {f.fact_text[:80]}") + print() + + _print_summary_table(results) + _print_memory_state(episodic) + return results + + +# -- printing -------------------------------------------------------------- + +def _print_header(mode: str) -> None: + print(f"\n{BOX_H}") + print(" EPISODIC MEMORY vs FLAT VECTOR RAG") + print(" Scenario: 9-Round Debugging") + print(f" Decision mode: {mode}") + print(BOX_H) + print(" Round types:") + print(" LEARN -- agent builds memory from failure") + print(" RED HERRING -- root cause is NOT connection pool") + print(" TEST -- root cause IS connection pool") + print(" SUBTLE -- connection pool but different symptoms") + print(" CORRECTION -- agent gets corrected, then tested on similar case") + print(BOX_H) + + +def _print_round(rr: RoundResult) -> None: + label = f" [{rr.round_label}]" if rr.round_label else "" + print(f"\n+{'-' * (W - 2)}+") + print(f"|{' ' * (W - 2)}|") + title = f"ROUND {rr.round_num}{label} | {rr.service}" + print(f"| {title:<{W - 4}}|") + bug = rr.bug_report + if len(bug) > W - 4: + bug = bug[: W - 7] + "..." + print(f"| {bug:<{W - 4}}|") + print(f"|{' ' * (W - 2)}|") + print(f"+{'-' * (COL_W + 2)}+{'-' * (W - COL_W - 5)}+") + + header = ( + f"| {'EPISODIC AGENT':<{COL_W}}" + f"| {'BASELINE AGENT':<{W - COL_W - 5}}|" + ) + print(header) + print(f"+{'-' * (COL_W + 2)}+{'-' * (W - COL_W - 5)}+") + + ep_ep_text = ( + "\n".join(rr.ep_episodes) if rr.ep_episodes else "(no prior episodes)" + ) + bl_ch_text = ( + "\n".join(f"* {c}" for c in rr.bl_chunks) if rr.bl_chunks + else "(no prior entries)" + ) + print(_side_by_side("Retrieved memory:", ep_ep_text, bl_ch_text)) + + if rr.ep_facts: + ep_facts_text = "\n".join(rr.ep_facts) + print(_side_by_side( + "Generalized facts:", + ep_facts_text, + "(N/A -- no fact extraction)", + )) + + print(f" {'-' * COL_W}--+--{'-' * (W - COL_W - 6)}") + print(_side_by_side("Reasoning:", rr.ep_reasoning, rr.bl_reasoning)) + + print(f" {'-' * COL_W}--+--{'-' * (W - COL_W - 6)}") + + ep_mark = "CORRECT" if rr.ep_correct else "WRONG" + bl_mark = "CORRECT" if rr.bl_correct else "WRONG" + ep_dec = textwrap.shorten(rr.ep_decision, width=COL_W, placeholder="...") + bl_dec = textwrap.shorten( + rr.bl_decision, width=W - COL_W - 6, placeholder="...", + ) + print(f" {ep_dec:<{COL_W}} | {bl_dec}") + print(f" {ep_mark:<{COL_W}} | {bl_mark}") + + if rr.total_keywords: + ep_kw = f"Keywords: {rr.ep_keyword_hits}/{rr.total_keywords}" + bl_kw = f"Keywords: {rr.bl_keyword_hits}/{rr.total_keywords}" + print(f" {ep_kw:<{COL_W}} | {bl_kw}") + + print(f"+{'-' * (W - 2)}+") + gt = rr.ground_truth + if len(gt) > W - 18: + gt = gt[: W - 21] + "..." + print(f"| Ground truth: {gt:<{W - 18}}|") + print(f"+{'-' * (W - 2)}+") + + +def _has_pool_pattern(decision: str) -> bool: + low = decision.lower() + return any(p in low for p in _POOL_PATTERNS) + + +def _print_summary_table(results: list[RoundResult]) -> None: + # -- 1. Decision correctness table (existing) ------------------------- + print(f"\n{BOX_H}") + print(" SUMMARY -- Decision Correctness Across All Rounds") + print(BOX_H) + print( + f" {'Round':<8}{'Type':<14}{'Service':<22}" + f"{'Episodic':^10}{'Baseline':^10}{'Keywords (E/B)':>16}" + ) + print(f" {'-' * 78}") + + ep_wins, bl_wins = 0, 0 + for rr in results: + label = rr.round_label or "LEARN" + ep_mark = " OK " if rr.ep_correct else " X " + bl_mark = " OK " if rr.bl_correct else " X " + kw = ( + f"{rr.ep_keyword_hits}/{rr.total_keywords}" + f" {rr.bl_keyword_hits}/{rr.total_keywords}" + if rr.total_keywords else " --" + ) + print( + f" {rr.round_num:<8}{label:<14}{rr.service:<22}" + f"{ep_mark:^10}{bl_mark:^10}{kw:>16}" + ) + ep_wins += rr.ep_correct + bl_wins += rr.bl_correct + + print(f" {'-' * 78}") + print(f" {'TOTAL':<44}{ep_wins:^10}{bl_wins:^10}") + print() + + # -- 2. Structured retrieval score ------------------------------------ + print(BOX_H) + print(" STRUCTURED RETRIEVAL SCORE") + print(" (items with explicit outcome labels vs raw outcome mentions)") + print(BOX_H) + print( + f" {'Round':<8}{'Type':<14}" + f"{'Episodic (labeled/total)':>26}" + f"{'Baseline (outcome/total)':>28}" + ) + print(f" {'-' * 74}") + + ep_labeled_total, ep_ret_total = 0, 0 + bl_out_total, bl_ret_total = 0, 0 + for rr in results: + label = rr.round_label or "LEARN" + if rr.ep_total_retrieved: + ep_ratio = f"{rr.ep_outcome_labeled}/{rr.ep_total_retrieved}" + else: + ep_ratio = " --" + if rr.bl_total_retrieved: + bl_ratio = f"{rr.bl_outcome_chunks}/{rr.bl_total_retrieved}" + else: + bl_ratio = " --" + print( + f" {rr.round_num:<8}{label:<14}" + f"{ep_ratio:>26}{bl_ratio:>28}" + ) + ep_labeled_total += rr.ep_outcome_labeled + ep_ret_total += rr.ep_total_retrieved + bl_out_total += rr.bl_outcome_chunks + bl_ret_total += rr.bl_total_retrieved + + print(f" {'-' * 74}") + ep_pct = ( + f"{ep_labeled_total}/{ep_ret_total} " + f"({ep_labeled_total * 100 // ep_ret_total}%)" + if ep_ret_total else "--" + ) + bl_pct = ( + f"{bl_out_total}/{bl_ret_total} " + f"({bl_out_total * 100 // bl_ret_total}%)" + if bl_ret_total else "--" + ) + print(f" {'TOTAL':<22}{ep_pct:>26}{bl_pct:>28}") + print() + + # -- 3. Pattern application (rounds 4-9) ------------------------------ + print(BOX_H) + print(" PATTERN APPLICATION (Rounds 4-9)") + print(" Connection pool pattern: correct when IS root cause, FP when NOT") + print(BOX_H) + print( + f" {'Round':<8}{'Type':<14}{'Pool correct?':<16}" + f"{'Episodic':^16}{'Baseline':^16}" + ) + print(f" {'-' * 68}") + + ep_fp, bl_fp = 0, 0 + ep_correct_applies, bl_correct_applies = 0, 0 + pattern_rounds = [rr for rr in results if rr.round_num >= 4] + for rr in pattern_rounds: + label = rr.round_label or "LEARN" + pool_is_correct = label not in ("RED HERRING", "CORRECTION") + + ep_applied = _has_pool_pattern(rr.ep_decision) + bl_applied = _has_pool_pattern(rr.bl_decision) + + if pool_is_correct: + ep_tag = "correct" if ep_applied else "missed" + bl_tag = "correct" if bl_applied else "missed" + ep_correct_applies += ep_applied + bl_correct_applies += bl_applied + else: + ep_tag = "FALSE POS" if ep_applied else "avoided" + bl_tag = "FALSE POS" if bl_applied else "avoided" + ep_fp += ep_applied + bl_fp += bl_applied + + print( + f" {rr.round_num:<8}{label:<14}" + f"{'yes' if pool_is_correct else 'NO':<16}" + f"{ep_tag:^16}{bl_tag:^16}" + ) + + print(f" {'-' * 68}") + print( + f" {'Correct applies:':<38}" + f"{ep_correct_applies:^16}{bl_correct_applies:^16}" + ) + print( + f" {'False positives:':<38}" + f"{ep_fp:^16}{bl_fp:^16}" + ) + print() + + # -- 4. Adaptation score (Round 8 → 9) -------------------------------- + rr8 = next((r for r in results if r.round_num == 8), None) + rr9 = next((r for r in results if r.round_num == 9), None) + + print(BOX_H) + print(" ADAPTATION SCORE (Round 8 → 9: did the agent learn from correction?)") + print(BOX_H) + + ep_adapted, bl_adapted = False, False + if rr8 and rr9: + ep_pool_r8 = _has_pool_pattern(rr8.ep_decision) + ep_pool_r9 = _has_pool_pattern(rr9.ep_decision) + bl_pool_r8 = _has_pool_pattern(rr8.bl_decision) + bl_pool_r9 = _has_pool_pattern(rr9.bl_decision) + + ep_adapted = ep_pool_r8 and not ep_pool_r9 + bl_adapted = bl_pool_r8 and not bl_pool_r9 + + def _adapt_verdict(applied_r8: bool, applied_r9: bool) -> str: + if applied_r8 and applied_r9: + return "repeated mistake" + if applied_r8 and not applied_r9: + return "ADAPTED" + return "correctly avoided" + + ep_adapt_tag = _adapt_verdict(ep_pool_r8, ep_pool_r9) + bl_adapt_tag = _adapt_verdict(bl_pool_r8, bl_pool_r9) + + print(f" {'':38}{'Episodic':^16}{'Baseline':^16}") + print(f" {'-' * 68}") + print( + f" {'Round 8 applied pool pattern:':<38}" + f"{'yes' if ep_pool_r8 else 'no':^16}" + f"{'yes' if bl_pool_r8 else 'no':^16}" + ) + print( + f" {'Round 9 applied pool pattern:':<38}" + f"{'yes' if ep_pool_r9 else 'no':^16}" + f"{'yes' if bl_pool_r9 else 'no':^16}" + ) + print( + f" {'Verdict:':<38}" + f"{ep_adapt_tag:^16}{bl_adapt_tag:^16}" + ) + else: + print(" (Rounds 8-9 not found — skipping adaptation check)") + print() + + # -- 5. Weighted total score ------------------------------------------ + ep_avoid_fp = len([ + rr for rr in pattern_rounds + if rr.round_label in ("RED HERRING", "CORRECTION") + and not _has_pool_pattern(rr.ep_decision) + ]) + bl_avoid_fp = len([ + rr for rr in pattern_rounds + if rr.round_label in ("RED HERRING", "CORRECTION") + and not _has_pool_pattern(rr.bl_decision) + ]) + ep_adapt_pts = 2 if ep_adapted else 0 + bl_adapt_pts = 2 if bl_adapted else 0 + + ep_total = ep_wins + ep_avoid_fp + ep_adapt_pts + bl_total = bl_wins + bl_avoid_fp + bl_adapt_pts + + print(BOX_H) + print(" WEIGHTED SCORE") + print(" Correct decision = 1pt | Avoided false positive = 1pt | Adaptation = 2pt") + print(BOX_H) + print(f" {'':38}{'Episodic':^16}{'Baseline':^16}") + print(f" {'-' * 68}") + print(f" {'Correct decisions (×1):':<38}{ep_wins:^16}{bl_wins:^16}") + print( + f" {'Avoided false positives (×1):':<38}" + f"{ep_avoid_fp:^16}{bl_avoid_fp:^16}" + ) + print( + f" {'Adaptation bonus (×2):':<38}" + f"{ep_adapt_pts:^16}{bl_adapt_pts:^16}" + ) + print(f" {'-' * 68}") + print(f" {'WEIGHTED TOTAL:':<38}{ep_total:^16}{bl_total:^16}") + print() + + if ep_total > bl_total: + print(" ==> Episodic memory outperforms flat vector RAG.") + elif ep_total == bl_total: + print( + " ==> Tie -- try with ANTHROPIC_API_KEY set " + "for Claude-based decisions." + ) + else: + print(" ==> Baseline wins (unexpected -- check scenario data).") + print() + + +def _print_memory_state(episodic: EpisodicStack) -> None: + print(BOX_H) + print(" FINAL EPISODIC MEMORY STATE") + print(BOX_H) + print(f" Episodes: {len(episodic.graph.episode_ids)}") + print(f" Links: {len(episodic.graph.links)}") + print(f" Facts: {len(episodic.facts.facts)}") + for f in episodic.facts.facts: + print( + f" * {f.fact_text[:72]} " + f"[sup={f.support_count} con={f.contradiction_count}]" + ) + print() + + +# -- helpers --------------------------------------------------------------- + +def _try_init_anthropic() -> Any: + api_key = os.environ.get("ANTHROPIC_API_KEY") + if not api_key: + return None + try: + import anthropic + return anthropic.Anthropic(api_key=api_key) + except Exception: + return None + + +# -- entry point ----------------------------------------------------------- + +if __name__ == "__main__": + logging.basicConfig(level=logging.WARNING) + run_comparison() diff --git a/experiments/episodic-memory-prototype/memory/__init__.py b/experiments/episodic-memory-prototype/memory/__init__.py new file mode 100644 index 00000000..aeec135f --- /dev/null +++ b/experiments/episodic-memory-prototype/memory/__init__.py @@ -0,0 +1,17 @@ +from memory.timeline import Timeline +from memory.encoder import Encoder +from memory.episodes import EpisodeBoundaryDetector +from memory.graph import EpisodeGraph +from memory.facts import FactExtractor +from memory.retriever import Retriever +from memory.reinforcer import Reinforcer + +__all__ = [ + "Timeline", + "Encoder", + "EpisodeBoundaryDetector", + "EpisodeGraph", + "FactExtractor", + "Retriever", + "Reinforcer", +] diff --git a/experiments/episodic-memory-prototype/memory/encoder.py b/experiments/episodic-memory-prototype/memory/encoder.py new file mode 100644 index 00000000..f6779045 --- /dev/null +++ b/experiments/episodic-memory-prototype/memory/encoder.py @@ -0,0 +1,110 @@ +"""Embedding and state encoding. + +Three kinds of encoding: + 1. Semantic — sentence-transformers (all-MiniLM-L6-v2) for content meaning. + Falls back to a deterministic hash projection when the model is + unavailable (keeps tests fast and dependency-free). + 2. State — fixed dictionary that maps state-label strings to + random-but-consistent vectors (seeded from the label hash). + 3. Time — exponential decay scalar: exp(-λ · Δhours). Not a full + positional encoding; just a recency weight between 0 and 1. +""" + +from __future__ import annotations + +import hashlib +import math +from datetime import datetime, timezone +from typing import Any + +import numpy as np + +from memory.models import TimelineEntry + + +class Encoder: + """Produces semantic embeddings, state embeddings, and time-decay scalars.""" + + def __init__( + self, + model_name: str = "all-MiniLM-L6-v2", + dim: int = 384, + time_half_life_hours: float = 24.0, + ) -> None: + self.dim = dim + self._model_name = model_name + self._model: Any = None + self._model_load_attempted = False + self._state_cache: dict[str, list[float]] = {} + self._time_half_life_hours = time_half_life_hours + self._time_lambda = math.log(2) / max(time_half_life_hours, 1e-9) + + # -- model loading --------------------------------------------------------- + + def _load_model(self) -> Any: + if not self._model_load_attempted: + self._model_load_attempted = True + try: + from sentence_transformers import SentenceTransformer + self._model = SentenceTransformer(self._model_name) + except Exception: + self._model = None + return self._model + + # -- semantic embeddings --------------------------------------------------- + + def encode_text(self, text: str) -> list[float]: + """Embed arbitrary text via sentence-transformers (or hash fallback).""" + model = self._load_model() + if model is not None: + vec = model.encode(text, normalize_embeddings=True) + return vec.tolist() + return self._deterministic_hash_embed(text) + + def encode_entry(self, entry: TimelineEntry) -> list[float]: + """Semantic embedding for a timeline entry's content.""" + return self.encode_text(f"[{entry.role}] {entry.raw_content}") + + # -- state embeddings ------------------------------------------------------ + + def encode_state(self, state_label: str) -> list[float]: + """Deterministic random vector for a state label. + + Same label always produces the same vector; different labels produce + near-orthogonal vectors. Cached after first computation. + """ + if not state_label: + state_label = "__empty__" + + if state_label in self._state_cache: + return self._state_cache[state_label] + + vec = self._deterministic_hash_embed(f"__state__:{state_label}") + self._state_cache[state_label] = vec + return vec + + # -- time decay ------------------------------------------------------------ + + def time_decay( + self, + entry_ts: datetime, + reference: datetime | None = None, + ) -> float: + """Exponential decay scalar in (0, 1]. + + Returns 1.0 for *reference* itself and decays toward 0 as *entry_ts* + gets older. Half-life is configurable at init (default 24 h). + """ + ref = reference or datetime.now(timezone.utc) + delta_hours = max((ref - entry_ts).total_seconds() / 3600.0, 0.0) + return math.exp(-self._time_lambda * delta_hours) + + # -- hash fallback (no ML model needed) ------------------------------------ + + def _deterministic_hash_embed(self, text: str) -> list[float]: + """Reproducible pseudo-embedding derived from SHA-256.""" + digest = hashlib.sha256(text.encode()).digest() + rng = np.random.Generator(np.random.PCG64(int.from_bytes(digest[:8], "big"))) + vec = rng.standard_normal(self.dim).astype(np.float32) + vec /= np.linalg.norm(vec) + 1e-9 + return vec.tolist() diff --git a/experiments/episodic-memory-prototype/memory/episodes.py b/experiments/episodic-memory-prototype/memory/episodes.py new file mode 100644 index 00000000..dd613f0a --- /dev/null +++ b/experiments/episodic-memory-prototype/memory/episodes.py @@ -0,0 +1,179 @@ +"""Episode boundary detection and creation. + +Boundary signal is a weighted combination of two features: + - state change (weight 0.6): binary — did the state_label flip? + - semantic shift (weight 0.4): 1 − cosine_sim between consecutive entries. + +When the combined score crosses the threshold (default 0.5), a new episode +starts. Episode summaries are generated via the Claude API when available, +with a plain-text fallback for tests / offline use. +""" + +from __future__ import annotations + +import logging +import os +from typing import Any + +from memory.encoder import Encoder +from memory.models import ( + Episode, + OUTCOME_UNKNOWN, + TimelineEntry, + cosine_similarity, +) + +log = logging.getLogger(__name__) + + +class EpisodeBoundaryDetector: + """Detects episode boundaries and builds Episode objects.""" + + def __init__( + self, + encoder: Encoder, + threshold: float = 0.5, + state_change_weight: float = 0.6, + semantic_shift_weight: float = 0.4, + anthropic_client: Any = None, + model: str = "claude-sonnet-4-20250514", + ) -> None: + self.encoder = encoder + self.threshold = threshold + self.state_change_weight = state_change_weight + self.semantic_shift_weight = semantic_shift_weight + self._model = model + self._anthropic = anthropic_client or self._try_init_anthropic() + + # -- public API ------------------------------------------------------------ + + def build_episodes(self, entries: list[TimelineEntry]) -> list[Episode]: + """Segment entries into episodes and return them.""" + if not entries: + return [] + + self._ensure_embeddings(entries) + + boundaries = self.detect_boundaries(entries) + segments = self._split_at_boundaries(entries, boundaries) + return [self._segment_to_episode(seg) for seg in segments] + + def detect_boundaries(self, entries: list[TimelineEntry]) -> list[int]: + """Return indices (1-based into *entries*) where a new episode starts. + + Index *i* in the result means a boundary exists *before* entries[i]. + """ + self._ensure_embeddings(entries) + + boundaries: list[int] = [] + for i in range(1, len(entries)): + score = self._boundary_score(entries[i - 1], entries[i]) + if score >= self.threshold: + boundaries.append(i) + return boundaries + + # -- boundary scoring ------------------------------------------------------ + + def _boundary_score(self, prev: TimelineEntry, curr: TimelineEntry) -> float: + state_change = 1.0 if ( + prev.state_label != curr.state_label + and prev.state_label != "" + and curr.state_label != "" + ) else 0.0 + + if prev.semantic_embedding and curr.semantic_embedding: + sim = cosine_similarity(prev.semantic_embedding, curr.semantic_embedding) + semantic_shift = 1.0 - max(sim, 0.0) + else: + semantic_shift = 0.0 + + return ( + self.state_change_weight * state_change + + self.semantic_shift_weight * semantic_shift + ) + + # -- segment → episode ----------------------------------------------------- + + @staticmethod + def _split_at_boundaries( + entries: list[TimelineEntry], boundaries: list[int] + ) -> list[list[TimelineEntry]]: + segments: list[list[TimelineEntry]] = [] + prev = 0 + for b in boundaries: + if prev < b: + segments.append(entries[prev:b]) + prev = b + if prev < len(entries): + segments.append(entries[prev:]) + return segments + + def _segment_to_episode(self, segment: list[TimelineEntry]) -> Episode: + summary = self._summarize_entries(segment) + return Episode( + start_time=segment[0].timestamp, + end_time=segment[-1].timestamp, + timeline_start=segment[0].sequence_id, + timeline_end=segment[-1].sequence_id, + entry_ids=[e.entry_id for e in segment], + entry_count=len(segment), + state=segment[0].state_label, + summary_text=summary, + summary_embedding=self.encoder.encode_text(summary), + outcome=OUTCOME_UNKNOWN, + ) + + # -- summary generation ---------------------------------------------------- + + def _summarize_entries(self, entries: list[TimelineEntry]) -> str: + content = "\n".join( + f"[{e.role}] {e.raw_content}" for e in entries + ) + + if self._anthropic is not None: + try: + return self._call_claude(content) + except Exception: + log.debug("Claude API call failed, using fallback summary", exc_info=True) + + return self._fallback_summary(entries) + + def _call_claude(self, content: str) -> str: + response = self._anthropic.messages.create( + model=self._model, + max_tokens=150, + messages=[ + { + "role": "user", + "content": ( + "Summarize this agent interaction in exactly 2 sentences. " + "Focus on what was discussed and what was decided or resolved.\n\n" + f"{content}" + ), + } + ], + ) + return response.content[0].text.strip() + + @staticmethod + def _fallback_summary(entries: list[TimelineEntry]) -> str: + first = entries[0].raw_content[:80] + return f"{len(entries)} entries: {first}…" + + # -- helpers --------------------------------------------------------------- + + def _ensure_embeddings(self, entries: list[TimelineEntry]) -> None: + for e in entries: + if e.semantic_embedding is None: + e.semantic_embedding = self.encoder.encode_entry(e) + + @staticmethod + def _try_init_anthropic() -> Any: + api_key = os.environ.get("ANTHROPIC_API_KEY") + if not api_key: + return None + try: + import anthropic + return anthropic.Anthropic(api_key=api_key) + except Exception: + return None diff --git a/experiments/episodic-memory-prototype/memory/facts.py b/experiments/episodic-memory-prototype/memory/facts.py new file mode 100644 index 00000000..8aa6cede --- /dev/null +++ b/experiments/episodic-memory-prototype/memory/facts.py @@ -0,0 +1,165 @@ +"""Generalized fact extraction. + +Single-episode calls accumulate in a buffer. When 3+ episodes have been +seen, Claude is called to find cross-episode patterns. Falls back to +embedding-merge when no API key / package is available. +""" + +from __future__ import annotations + +import logging +import os +from typing import Any + +from memory.encoder import Encoder +from memory.models import Episode, GeneralizedFact, cosine_similarity, _now + +log = logging.getLogger(__name__) + + +class FactExtractor: + """Extracts and maintains a corpus of generalized facts.""" + + def __init__( + self, + encoder: Encoder, + merge_threshold: float = 0.80, + min_episodes_for_extraction: int = 3, + anthropic_client: Any = None, + model: str = "claude-sonnet-4-20250514", + ) -> None: + self.encoder = encoder + self.merge_threshold = merge_threshold + self.min_episodes_for_extraction = min_episodes_for_extraction + self._model = model + self._anthropic = anthropic_client or _try_init_anthropic() + self._facts: list[GeneralizedFact] = [] + self._episode_buffer: list[Episode] = [] + + @property + def facts(self) -> list[GeneralizedFact]: + return list(self._facts) + + # -- single-episode entry point (backward compat) -------------------------- + + def extract(self, episode: Episode) -> list[GeneralizedFact]: + """Accumulate one episode. Triggers pattern extraction at threshold.""" + self._merge_or_add_basic(episode) + self._episode_buffer.append(episode) + + if len(self._episode_buffer) >= self.min_episodes_for_extraction: + self._extract_patterns(list(self._episode_buffer)) + self._episode_buffer.clear() + + return self._facts + + # -- explicit batch entry point -------------------------------------------- + + def extract_patterns(self, episodes: list[Episode]) -> list[GeneralizedFact]: + """Call Claude to find patterns across 3+ episodes.""" + if len(episodes) < self.min_episodes_for_extraction: + for ep in episodes: + self._merge_or_add_basic(ep) + return self._facts + + self._extract_patterns(episodes) + return self._facts + + # -- query ----------------------------------------------------------------- + + def query(self, text: str, top_k: int = 5) -> list[GeneralizedFact]: + if not self._facts: + return [] + q_emb = self.encoder.encode_text(text) + scored = [] + for f in self._facts: + f_emb = self._fact_embedding(f) + scored.append((cosine_similarity(q_emb, f_emb), f)) + scored.sort(key=lambda x: x[0], reverse=True) + return [f for _, f in scored[:top_k]] + + # -- internals ------------------------------------------------------------- + + def _extract_patterns(self, episodes: list[Episode]) -> None: + summaries = [ + ep.summary_text for ep in episodes if ep.summary_text + ] + if not summaries: + return + + episode_ids = [ep.episode_id for ep in episodes] + + if self._anthropic is not None: + try: + patterns = self._call_claude_patterns(summaries) + for text in patterns: + self._merge_or_add(text.strip(), episode_ids) + return + except Exception: + log.debug("Claude pattern extraction failed, using fallback", exc_info=True) + + for ep in episodes: + self._merge_or_add_basic(ep) + + def _call_claude_patterns(self, summaries: list[str]) -> list[str]: + numbered = "\n".join(f"{i+1}. {s}" for i, s in enumerate(summaries)) + response = self._anthropic.messages.create( + model=self._model, + max_tokens=300, + messages=[{ + "role": "user", + "content": ( + "Given these episode summaries from an agent's experience:\n\n" + f"{numbered}\n\n" + "Identify 1-3 generalized patterns, heuristics, or lessons " + "that emerge. Each should be a single declarative sentence " + "that could guide future decisions.\n\n" + "Return ONLY the patterns, one per line, no numbering or bullets." + ), + }], + ) + raw = response.content[0].text.strip() + return [line for line in raw.splitlines() if line.strip()] + + def _merge_or_add_basic(self, episode: Episode) -> None: + """Merge a single episode's summary into facts (no LLM).""" + if not episode.summary_text: + return + self._merge_or_add(episode.summary_text, [episode.episode_id]) + + def _merge_or_add(self, fact_text: str, episode_ids: list[str]) -> None: + embedding = self.encoder.encode_text(fact_text) + + for existing in self._facts: + existing_emb = self._fact_embedding(existing) + if cosine_similarity(embedding, existing_emb) >= self.merge_threshold: + existing.support_count += 1 + existing.last_updated = _now() + for eid in episode_ids: + if eid not in existing.supporting_episodes: + existing.supporting_episodes.append(eid) + return + + self._facts.append(GeneralizedFact( + fact_text=fact_text, + supporting_episodes=list(episode_ids), + fact_embedding=embedding, + )) + + def _fact_embedding(self, fact: GeneralizedFact) -> list[float]: + if fact.fact_embedding is not None: + return fact.fact_embedding + emb = self.encoder.encode_text(fact.fact_text) + fact.fact_embedding = emb + return emb + + +def _try_init_anthropic() -> Any: + api_key = os.environ.get("ANTHROPIC_API_KEY") + if not api_key: + return None + try: + import anthropic + return anthropic.Anthropic(api_key=api_key) + except Exception: + return None diff --git a/experiments/episodic-memory-prototype/memory/graph.py b/experiments/episodic-memory-prototype/memory/graph.py new file mode 100644 index 00000000..3c8f3c0e --- /dev/null +++ b/experiments/episodic-memory-prototype/memory/graph.py @@ -0,0 +1,176 @@ +"""Episode graph — links and traversal. + +Episodes stored as a dict, links as a list. No Neo4j. + +Auto-link inference produces two relationship types: + - CONTINUATION: same state, close in time (chronologically adjacent). + - RETRY_OF: similar embedding + same state + the earlier episode failed. +""" + +from __future__ import annotations + +from collections import defaultdict +from typing import Sequence + +from memory.models import ( + Episode, + EpisodeLink, + LINK_CONTINUATION, + LINK_RETRY_OF, + LINK_SOURCE_INFERRED, + OUTCOME_FAILURE, + cosine_similarity, +) + + +class EpisodeGraph: + """Directed graph over episodes. Dict + list, nothing fancy.""" + + def __init__(self) -> None: + self._episodes: dict[str, Episode] = {} + self._links: list[EpisodeLink] = [] + self._out_index: dict[str, list[int]] = defaultdict(list) + + # -- mutations ------------------------------------------------------------- + + def add_episode(self, episode: Episode) -> None: + self._episodes[episode.episode_id] = episode + + def add_link(self, link: EpisodeLink) -> None: + idx = len(self._links) + self._links.append(link) + self._out_index[link.source_episode_id].append(idx) + + def auto_link( + self, + episodes: list[Episode], + sim_threshold: float = 0.55, + max_gap_seconds: float = 3600, + ) -> list[EpisodeLink]: + """Infer CONTINUATION and RETRY_OF links from a batch of episodes.""" + new_links: list[EpisodeLink] = [] + + for ep in episodes: + self.add_episode(ep) + + by_time = sorted(episodes, key=lambda e: e.start_time) + + for prev, curr in zip(by_time[:-1], by_time[1:]): + if not prev.state or not curr.state: + continue + if prev.state != curr.state: + continue + gap = (curr.start_time - prev.end_time).total_seconds() + if gap > max_gap_seconds: + continue + link = EpisodeLink( + source_episode_id=prev.episode_id, + target_episode_id=curr.episode_id, + link_type=LINK_CONTINUATION, + source=LINK_SOURCE_INFERRED, + evidence=f"Same state '{prev.state}', {gap:.0f}s apart", + ) + self.add_link(link) + new_links.append(link) + + for i, candidate in enumerate(by_time): + if candidate.summary_embedding is None: + continue + for j in range(i): + failed = by_time[j] + if failed.outcome != OUTCOME_FAILURE: + continue + if not candidate.state or failed.state != candidate.state: + continue + if failed.summary_embedding is None: + continue + sim = cosine_similarity( + candidate.summary_embedding, failed.summary_embedding + ) + if sim >= sim_threshold: + link = EpisodeLink( + source_episode_id=candidate.episode_id, + target_episode_id=failed.episode_id, + link_type=LINK_RETRY_OF, + strength=sim, + source=LINK_SOURCE_INFERRED, + evidence=f"Retrying failed episode (sim={sim:.2f})", + ) + self.add_link(link) + new_links.append(link) + + return new_links + + # -- queries --------------------------------------------------------------- + + def get_links_from(self, episode_id: str) -> list[EpisodeLink]: + """All outgoing links from an episode.""" + return [self._links[i] for i in self._out_index.get(episode_id, [])] + + def traverse( + self, + start_id: str, + depth: int = 1, + link_types: Sequence[str] | None = None, + ) -> list[Episode]: + """BFS from *start_id* up to *depth* hops. Returns visited episodes + (excluding the start node). + """ + visited: set[str] = set() + frontier = {start_id} + + for _ in range(depth): + next_frontier: set[str] = set() + for nid in frontier: + for link in self.get_links_from(nid): + target = link.target_episode_id + if target in visited: + continue + if link_types and link.link_type not in link_types: + continue + next_frontier.add(target) + visited |= frontier + frontier = next_frontier - visited + + visited |= frontier + visited.discard(start_id) + + return [self._episodes[eid] for eid in visited if eid in self._episodes] + + def neighbors( + self, + episode_id: str, + link_types: Sequence[str] | None = None, + depth: int = 1, + ) -> list[Episode]: + """Alias kept for backward compatibility with retriever.""" + return self.traverse(episode_id, depth=depth, link_types=link_types) + + def find_similar_by_state( + self, + state: str, + embedding: list[float], + top_k: int = 5, + ) -> list[tuple[float, Episode]]: + """Find episodes sharing *state*, ranked by embedding similarity.""" + scored: list[tuple[float, Episode]] = [] + for ep in self._episodes.values(): + if ep.state != state: + continue + if ep.summary_embedding is None: + continue + sim = cosine_similarity(embedding, ep.summary_embedding) + scored.append((sim, ep)) + scored.sort(key=lambda x: x[0], reverse=True) + return scored[:top_k] + + def get_episode(self, episode_id: str) -> Episode | None: + return self._episodes.get(episode_id) + + @property + def episode_ids(self) -> list[str]: + return list(self._episodes) + + @property + def links(self) -> list[EpisodeLink]: + return list(self._links) diff --git a/experiments/episodic-memory-prototype/memory/models.py b/experiments/episodic-memory-prototype/memory/models.py new file mode 100644 index 00000000..79477785 --- /dev/null +++ b/experiments/episodic-memory-prototype/memory/models.py @@ -0,0 +1,237 @@ +"""All data classes for the episodic memory system. + +Plain dataclasses, no ORMs. Designed for SQLite persistence + dict-based +in-memory graph. Embeddings are list[float]; enum-like values are plain +strings grouped as module-level constants. +""" + +from __future__ import annotations + +import uuid +from dataclasses import dataclass, field +from datetime import datetime, timezone + +import numpy as np + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +def _uid() -> str: + return uuid.uuid4().hex[:12] + + +def _now() -> datetime: + return datetime.now(timezone.utc) + + +def cosine_similarity(a: list[float], b: list[float]) -> float: + va, vb = np.asarray(a), np.asarray(b) + denom = np.linalg.norm(va) * np.linalg.norm(vb) + if denom == 0: + return 0.0 + return float(np.dot(va, vb) / denom) + + +# --------------------------------------------------------------------------- +# String enums +# --------------------------------------------------------------------------- + +# Roles +ROLE_USER = "user" +ROLE_AGENT = "agent" +ROLE_SYSTEM = "system" + +# Content types +CONTENT_TEXT = "text" +CONTENT_TOOL_CALL = "tool_call" +CONTENT_TOOL_RESULT = "tool_result" +CONTENT_ERROR = "error" +CONTENT_FEEDBACK = "feedback" +CONTENT_STATE_CHANGE = "state_change" +CONTENT_DECISION = "decision" +CONTENT_CODE_DIFF = "code_diff" + +# Episode types +EPISODE_TASK = "task" +EPISODE_CONVERSATION = "conversation" +EPISODE_INCIDENT = "incident" +EPISODE_PLANNING = "planning" +EPISODE_DEBUGGING = "debugging" +EPISODE_REVIEW = "review" +EPISODE_EXPLORATION = "exploration" + +# Outcomes +OUTCOME_SUCCESS = "success" +OUTCOME_FAILURE = "failure" +OUTCOME_PARTIAL = "partial" +OUTCOME_ABANDONED = "abandoned" +OUTCOME_UNKNOWN = "unknown" + +# Link types +LINK_CAUSED_BY = "caused_by" +LINK_LED_TO = "led_to" +LINK_REFINED = "refined" +LINK_CONTRADICTED = "contradicted" +LINK_SIMILAR_SITUATION = "similar_situation" +LINK_RETRY_OF = "retry_of" +LINK_ESCALATION_OF = "escalation_of" +LINK_CONTINUATION = "continuation" +LINK_ROLLBACK_OF = "rollback_of" +LINK_LEARNED_FROM = "learned_from" +LINK_TEMPORAL = "temporal" +LINK_SIMILARITY = "similarity" +LINK_CAUSAL = "causal" +LINK_CORRECTION = "correction" + +# Link source +LINK_SOURCE_EXPLICIT = "explicit" +LINK_SOURCE_INFERRED = "inferred" +LINK_SOURCE_REINFORCED = "reinforced" + +# Fact types +FACT_HEURISTIC = "heuristic" +FACT_CAUSAL_PATTERN = "causal_pattern" +FACT_ANTI_PATTERN = "anti_pattern" +FACT_PREFERENCE = "preference" +FACT_INVARIANT = "invariant" +FACT_CORRELATION = "correlation" + + +# --------------------------------------------------------------------------- +# Data classes +# --------------------------------------------------------------------------- + +@dataclass +class TimelineEntry: + """Immutable. Append-only. Never modified after creation.""" + + entry_id: str = field(default_factory=_uid) + timestamp: datetime = field(default_factory=_now) + sequence_id: int = 0 + + role: str = ROLE_USER + content_type: str = CONTENT_TEXT + raw_content: str = "" + + semantic_embedding: list[float] | None = None + state_embedding: list[float] | None = None + + agent_id: str = "" + session_id: str = "" + state_label: str = "" + tags: list[str] = field(default_factory=list) + + +@dataclass +class Episode: + """A coherent slice of experience. References timeline entries by ID.""" + + episode_id: str = field(default_factory=_uid) + + timeline_start: int = 0 + timeline_end: int = 0 + start_time: datetime = field(default_factory=_now) + end_time: datetime = field(default_factory=_now) + + state: str = "" + episode_type: str = EPISODE_TASK + + summary_embedding: list[float] | None = None + summary_text: str = "" + + outcome: str = OUTCOME_UNKNOWN + outcome_signal: str = "" + outcome_details: str = "" + outcome_score: float = 0.0 + + decisions_made: list[str] = field(default_factory=list) + assumptions: list[str] = field(default_factory=list) + corrections: list[str] = field(default_factory=list) + + parent_episode_id: str = "" + triggered_by: str = "" + + entry_count: int = 0 + entry_ids: list[str] = field(default_factory=list) + + +@dataclass +class EpisodeLink: + """Typed, weighted, directional relationship between episodes.""" + + link_id: str = field(default_factory=_uid) + source_episode_id: str = "" + target_episode_id: str = "" + + link_type: str = LINK_TEMPORAL + strength: float = 1.0 + source: str = LINK_SOURCE_INFERRED + evidence: str = "" + + created_at: datetime = field(default_factory=_now) + last_reinforced: datetime = field(default_factory=_now) + reinforcement_count: int = 0 + decay_eligible: bool = True + + +@dataclass +class GeneralizedFact: + """A pattern extracted from multiple episodes.""" + + fact_id: str = field(default_factory=_uid) + + fact_text: str = "" + fact_type: str = FACT_HEURISTIC + + domain: str = "" + applicable_states: list[str] = field(default_factory=list) + + supporting_episodes: list[str] = field(default_factory=list) + contradicting_episodes: list[str] = field(default_factory=list) + support_count: int = 1 + contradiction_count: int = 0 + + formed_at: datetime = field(default_factory=_now) + last_updated: datetime = field(default_factory=_now) + version: int = 1 + + fact_embedding: list[float] | None = None + + +@dataclass +class MemoryQuery: + """A memory cue — richer than a search string.""" + + query_text: str = "" + query_embedding: list[float] | None = None + + current_state: str = "" + current_task: str = "" + + max_episodes: int = 5 + max_facts: int = 3 + recency_weight: float = 0.3 + traverse_links: bool = True + link_depth: int = 1 + + +@dataclass +class RetrievalResult: + """Organized experience returned to the agent.""" + + episodes: list[Episode] = field(default_factory=list) + facts: list[GeneralizedFact] = field(default_factory=list) + scores: list[float] = field(default_factory=list) + causal_narrative: str = "" + conflicts: list[str] = field(default_factory=list) + + +@dataclass +class DecisionResult: + """Agent investigation decision — returned by decide().""" + + decision: str = "" + reasoning: str = "" + context_used: str = "" diff --git a/experiments/episodic-memory-prototype/memory/reinforcer.py b/experiments/episodic-memory-prototype/memory/reinforcer.py new file mode 100644 index 00000000..ddd002ed --- /dev/null +++ b/experiments/episodic-memory-prototype/memory/reinforcer.py @@ -0,0 +1,229 @@ +"""Outcome → memory feedback loop. + +After every agent decision with a known outcome: + 1. Create a new outcome Episode capturing what happened. + 2. Link it to the retrieved episodes via LINK_LEARNED_FROM. + 3. Call Claude to check whether each relevant fact is supported or + contradicted by the outcome. + 4. Update support_count / contradiction_count on each fact. + +Falls back to a simple heuristic (positive → support, negative → +contradict) when Claude is unavailable. +""" + +from __future__ import annotations + +import logging +import os +from typing import Any + +from memory.encoder import Encoder +from memory.facts import FactExtractor +from memory.graph import EpisodeGraph +from memory.models import ( + Episode, + EpisodeLink, + GeneralizedFact, + LINK_LEARNED_FROM, + LINK_SOURCE_INFERRED, + OUTCOME_FAILURE, + OUTCOME_SUCCESS, + OUTCOME_UNKNOWN, + RetrievalResult, + _now, +) + +log = logging.getLogger(__name__) + +_VERDICT_SUPPORTS = "supports" +_VERDICT_CONTRADICTS = "contradicts" +_VERDICT_NEUTRAL = "neutral" + + +class Reinforcer: + """Feeds outcome signals back into episodic memory.""" + + def __init__( + self, + encoder: Encoder, + graph: EpisodeGraph, + fact_extractor: FactExtractor, + decay: float = 0.9, + anthropic_client: Any = None, + model: str = "claude-sonnet-4-20250514", + ) -> None: + self.encoder = encoder + self.graph = graph + self.fact_extractor = fact_extractor + self.decay = decay + self._model = model + self._anthropic = anthropic_client or _try_init_anthropic() + + def reinforce( + self, + result: RetrievalResult, + outcome: str, + outcome_details: str = "", + ) -> Episode | None: + """Full feedback loop: episode → links → fact-check → count update.""" + if not result.episodes: + return None + + outcome_ep = self._create_outcome_episode(result, outcome, outcome_details) + self.graph.add_episode(outcome_ep) + self._link_to_retrieved(outcome_ep, result) + + self._update_episode_scores(result, outcome) + self._check_and_update_facts(result.facts, outcome, outcome_details) + + return outcome_ep + + # -- outcome episode ------------------------------------------------------- + + def _create_outcome_episode( + self, + result: RetrievalResult, + outcome: str, + outcome_details: str, + ) -> Episode: + summary = f"Outcome: {outcome}" + if outcome_details: + summary += f" — {outcome_details}" + + ref_state = result.episodes[0].state if result.episodes else "" + + return Episode( + summary_text=summary, + summary_embedding=self.encoder.encode_text(summary), + outcome=outcome, + outcome_details=outcome_details, + state=ref_state, + start_time=_now(), + end_time=_now(), + ) + + # -- linking --------------------------------------------------------------- + + def _link_to_retrieved( + self, outcome_ep: Episode, result: RetrievalResult + ) -> None: + for ep in result.episodes: + link = EpisodeLink( + source_episode_id=outcome_ep.episode_id, + target_episode_id=ep.episode_id, + link_type=LINK_LEARNED_FROM, + source=LINK_SOURCE_INFERRED, + evidence=f"Outcome '{outcome_ep.outcome}' after retrieving this episode", + ) + self.graph.add_link(link) + + # -- episode score update -------------------------------------------------- + + def _update_episode_scores( + self, result: RetrievalResult, outcome: str + ) -> None: + score = self._outcome_to_score(outcome) + for i, episode in enumerate(result.episodes): + weight = self.decay ** i + ep = self.graph.get_episode(episode.episode_id) + if ep is not None: + ep.outcome_score = ( + ep.outcome_score * 0.7 + score * weight * 0.3 + ) + + # -- fact checking --------------------------------------------------------- + + def _check_and_update_facts( + self, + facts: list[GeneralizedFact], + outcome: str, + outcome_details: str, + ) -> None: + for fact in facts: + stored = self._find_stored_fact(fact.fact_id) + if stored is None: + continue + + verdict = self._check_fact(stored.fact_text, outcome, outcome_details) + + if verdict == _VERDICT_SUPPORTS: + stored.support_count += 1 + stored.last_updated = _now() + elif verdict == _VERDICT_CONTRADICTS: + stored.contradiction_count += 1 + stored.last_updated = _now() + + def _check_fact( + self, fact_text: str, outcome: str, outcome_details: str + ) -> str: + if self._anthropic is not None: + try: + return self._call_claude_fact_check( + fact_text, outcome, outcome_details + ) + except Exception: + log.debug("Claude fact-check failed, using heuristic", exc_info=True) + + return self._heuristic_verdict(outcome) + + def _call_claude_fact_check( + self, fact_text: str, outcome: str, outcome_details: str + ) -> str: + outcome_desc = outcome + if outcome_details: + outcome_desc += f" — {outcome_details}" + + response = self._anthropic.messages.create( + model=self._model, + max_tokens=20, + messages=[{ + "role": "user", + "content": ( + "An agent made a decision and got this outcome:\n" + f"Outcome: {outcome_desc}\n\n" + "Does this outcome support or contradict the following " + "learned fact?\n" + f'Fact: "{fact_text}"\n\n' + "Answer with exactly one word: SUPPORTS or CONTRADICTS or NEUTRAL" + ), + }], + ) + raw = response.content[0].text.strip().lower() + if "support" in raw: + return _VERDICT_SUPPORTS + if "contradict" in raw: + return _VERDICT_CONTRADICTS + return _VERDICT_NEUTRAL + + @staticmethod + def _heuristic_verdict(outcome: str) -> str: + if outcome in (OUTCOME_SUCCESS,): + return _VERDICT_SUPPORTS + if outcome in (OUTCOME_FAILURE,): + return _VERDICT_CONTRADICTS + return _VERDICT_NEUTRAL + + @staticmethod + def _outcome_to_score(outcome: str) -> float: + if outcome == OUTCOME_SUCCESS: + return 1.0 + if outcome == OUTCOME_FAILURE: + return -1.0 + return 0.0 + + def _find_stored_fact(self, fact_id: str) -> GeneralizedFact | None: + for f in self.fact_extractor.facts: + if f.fact_id == fact_id: + return f + return None + + +def _try_init_anthropic() -> Any: + api_key = os.environ.get("ANTHROPIC_API_KEY") + if not api_key: + return None + try: + import anthropic + return anthropic.Anthropic(api_key=api_key) + except Exception: + return None diff --git a/experiments/episodic-memory-prototype/memory/retriever.py b/experiments/episodic-memory-prototype/memory/retriever.py new file mode 100644 index 00000000..6c6c8d13 --- /dev/null +++ b/experiments/episodic-memory-prototype/memory/retriever.py @@ -0,0 +1,237 @@ +"""Query-time retrieval. + +Retrieval strategy: + 1. Score all episodes by cosine similarity to the query embedding. + 2. Boost episodes that match the current state (×1.5). + 3. Boost episodes whose outcome is FAILURE (×1.25 — failures teach more). + 4. Optionally traverse graph 1 hop to pull in connected episodes. + 5. Select top-K with MMR: penalize candidates similar to already-selected. + 6. Ensure at least one SUCCESS (counter-example) episode if any exists. + 7. Fetch generalized facts, preferring ones applicable to the current state. + 8. Return a RetrievalResult with episodes, facts, scores, and a narrative. +""" + +from __future__ import annotations + +from memory.encoder import Encoder +from memory.facts import FactExtractor +from memory.graph import EpisodeGraph +from memory.models import ( + Episode, + OUTCOME_FAILURE, + OUTCOME_SUCCESS, + RetrievalResult, + cosine_similarity, +) + + +class Retriever: + """Assembles a ranked context window from episodic memory.""" + + def __init__( + self, + encoder: Encoder, + graph: EpisodeGraph, + fact_extractor: FactExtractor, + top_k_episodes: int = 5, + top_k_facts: int = 3, + graph_depth: int = 1, + failure_boost: float = 1.25, + state_match_boost: float = 1.5, + ) -> None: + self.encoder = encoder + self.graph = graph + self.fact_extractor = fact_extractor + self.top_k_episodes = top_k_episodes + self.top_k_facts = top_k_facts + self.graph_depth = graph_depth + self.failure_boost = failure_boost + self.state_match_boost = state_match_boost + + def retrieve( + self, + query: str, + current_state: str = "", + traverse: bool = True, + ) -> RetrievalResult: + q_emb = self.encoder.encode_text(query) + + scored = self._score_all_episodes(q_emb, current_state) + + if traverse and self.graph_depth > 0: + scored = self._expand_via_graph(scored, q_emb, current_state) + + top = self._select_with_mmr(scored, self.top_k_episodes) + episodes, scores = self._ensure_counter_example(scored, top) + facts = self._fetch_facts(query, current_state) + narrative = self._build_narrative(episodes) + + return RetrievalResult( + episodes=episodes, + facts=facts, + scores=scores, + causal_narrative=narrative, + ) + + # -- scoring --------------------------------------------------------------- + + def _score_all_episodes( + self, q_emb: list[float], current_state: str + ) -> list[tuple[float, Episode]]: + results: list[tuple[float, Episode]] = [] + for eid in self.graph.episode_ids: + ep = self.graph.get_episode(eid) + if ep is None: + continue + score = self._episode_score(q_emb, ep, current_state) + results.append((score, ep)) + results.sort(key=lambda x: x[0], reverse=True) + return results + + def _episode_score( + self, q_emb: list[float], episode: Episode, current_state: str = "" + ) -> float: + if not episode.summary_embedding: + return 0.0 + sim = cosine_similarity(q_emb, episode.summary_embedding) + if episode.outcome == OUTCOME_FAILURE: + sim *= self.failure_boost + if current_state and episode.state == current_state: + sim *= self.state_match_boost + return sim + + # -- graph expansion ------------------------------------------------------- + + def _expand_via_graph( + self, + scored: list[tuple[float, Episode]], + q_emb: list[float], + current_state: str, + ) -> list[tuple[float, Episode]]: + seen: dict[str, tuple[float, Episode]] = { + ep.episode_id: (s, ep) for s, ep in scored + } + for _, ep in list(scored[: self.top_k_episodes]): + for neighbor in self.graph.traverse( + ep.episode_id, depth=self.graph_depth + ): + if neighbor.episode_id in seen: + continue + n_score = ( + self._episode_score(q_emb, neighbor, current_state) * 0.8 + ) + seen[neighbor.episode_id] = (n_score, neighbor) + + result = list(seen.values()) + result.sort(key=lambda x: x[0], reverse=True) + return result + + # -- MMR and counter-example ------------------------------------------------ + + def _select_with_mmr( + self, + scored: list[tuple[float, Episode]], + top_k: int, + mmr_penalty: float = 0.5, + ) -> list[tuple[float, Episode]]: + """Select top_k episodes using maximal marginal relevance. + For each slot after the first, penalize score by similarity to already + selected: new_score = score * (1 - mmr_penalty * max_sim_to_selected). + """ + if not scored or top_k <= 0: + return [] + selected: list[tuple[float, Episode]] = [] + remaining = list(scored) + + # Top-1: take highest-scoring + remaining.sort(key=lambda x: x[0], reverse=True) + best = remaining.pop(0) + selected.append(best) + + for _ in range(top_k - 1): + if not remaining: + break + best_idx = -1 + best_mmr_score = -1.0 + for i, (score, ep) in enumerate(remaining): + if not ep.summary_embedding: + mmr_score = score + else: + max_sim = 0.0 + for _, sel_ep in selected: + if sel_ep.summary_embedding: + sim = cosine_similarity( + ep.summary_embedding, + sel_ep.summary_embedding, + ) + max_sim = max(max_sim, sim) + mmr_score = score * (1.0 - mmr_penalty * max_sim) + if mmr_score > best_mmr_score: + best_mmr_score = mmr_score + best_idx = i + if best_idx < 0: + break + chosen = remaining.pop(best_idx) + selected.append(chosen) + + return selected + + def _ensure_counter_example( + self, + scored: list[tuple[float, Episode]], + selected: list[tuple[float, Episode]], + ) -> tuple[list[Episode], list[float]]: + """If any episode has outcome=SUCCESS and none is in selected, + add one as counter-example. Return (episodes, scores) in order. + """ + selected_eps = [ep for _, ep in selected] + selected_ids = {ep.episode_id for ep in selected_eps} + has_success = any(ep.outcome == OUTCOME_SUCCESS for ep in selected_eps) + + if has_success: + episodes = selected_eps + scores = [s for s, _ in selected] + return (episodes, scores) + + # Find best SUCCESS episode not already selected (by original score) + success_candidates = [ + (s, ep) for s, ep in scored + if ep.outcome == OUTCOME_SUCCESS and ep.episode_id not in selected_ids + ] + if not success_candidates: + episodes = selected_eps + scores = [s for s, _ in selected] + return (episodes, scores) + + success_candidates.sort(key=lambda x: x[0], reverse=True) + add_score, add_ep = success_candidates[0] + episodes = selected_eps + [add_ep] + scores = [s for s, _ in selected] + [add_score] + return (episodes, scores) + + # -- facts ----------------------------------------------------------------- + + def _fetch_facts(self, query: str, state: str) -> list: + all_facts = self.fact_extractor.query(query, top_k=self.top_k_facts * 2) + if not state: + return all_facts[: self.top_k_facts] + + state_matched = [ + f for f in all_facts + if not f.applicable_states or state in f.applicable_states + ] + return state_matched[: self.top_k_facts] or all_facts[: self.top_k_facts] + + # -- narrative ------------------------------------------------------------- + + @staticmethod + def _build_narrative(episodes: list[Episode]) -> str: + if not episodes: + return "" + parts: list[str] = [] + for ep in episodes: + label = ep.summary_text[:60] if ep.summary_text else ep.episode_id + outcome_tag = f", outcome={ep.outcome}" if ep.outcome else "" + state_tag = f" [{ep.state}]" if ep.state else "" + parts.append(f"{label}{state_tag}{outcome_tag}") + return " → ".join(parts) diff --git a/experiments/episodic-memory-prototype/memory/timeline.py b/experiments/episodic-memory-prototype/memory/timeline.py new file mode 100644 index 00000000..47e58b82 --- /dev/null +++ b/experiments/episodic-memory-prototype/memory/timeline.py @@ -0,0 +1,224 @@ +"""Append-only timeline store backed by SQLite. + +One table: timeline_entries. Entries are written once and never mutated — +no UPDATE, no DELETE, ever. The timeline is the source-of-truth log from +which episodes, facts, and embeddings are derived. +""" + +from __future__ import annotations + +import json +import sqlite3 +from datetime import datetime, timezone +from typing import Sequence + +from memory.models import CONTENT_TEXT, ROLE_USER, TimelineEntry, _uid, _now + +_SCHEMA = """\ +CREATE TABLE IF NOT EXISTS timeline_entries ( + entry_id TEXT PRIMARY KEY, + sequence_id INTEGER UNIQUE NOT NULL, + timestamp TEXT NOT NULL, + role TEXT NOT NULL DEFAULT 'user', + content_type TEXT NOT NULL DEFAULT 'text', + raw_content TEXT NOT NULL DEFAULT '', + semantic_embedding TEXT, + state_embedding TEXT, + agent_id TEXT DEFAULT '', + session_id TEXT DEFAULT '', + state_label TEXT DEFAULT '', + tags TEXT DEFAULT '[]' +)""" + +_INSERT = """\ +INSERT INTO timeline_entries ( + entry_id, sequence_id, timestamp, role, content_type, raw_content, + semantic_embedding, state_embedding, + agent_id, session_id, state_label, tags +) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""" + +_SELECT_ALL = """\ +SELECT entry_id, sequence_id, timestamp, role, content_type, raw_content, + semantic_embedding, state_embedding, + agent_id, session_id, state_label, tags +FROM timeline_entries ORDER BY sequence_id""" + + +class Timeline: + """Ordered, append-only timeline backed by a single SQLite table.""" + + def __init__(self, db_path: str = ":memory:") -> None: + self._conn = sqlite3.connect(db_path, check_same_thread=False) + self._conn.execute("PRAGMA journal_mode=WAL") + self._conn.execute(_SCHEMA) + self._conn.commit() + + # -- internal helpers ------------------------------------------------------ + + def _next_seq(self) -> int: + row = self._conn.execute( + "SELECT MAX(sequence_id) FROM timeline_entries" + ).fetchone() + return 0 if row[0] is None else row[0] + 1 + + @staticmethod + def _encode_embedding(emb: list[float] | None) -> str | None: + return json.dumps(emb) if emb is not None else None + + @staticmethod + def _decode_embedding(val: str | None) -> list[float] | None: + return json.loads(val) if val else None + + def _entry_to_row(self, e: TimelineEntry) -> tuple: + return ( + e.entry_id, + e.sequence_id, + e.timestamp.isoformat(), + e.role, + e.content_type, + e.raw_content, + self._encode_embedding(e.semantic_embedding), + self._encode_embedding(e.state_embedding), + e.agent_id, + e.session_id, + e.state_label, + json.dumps(e.tags), + ) + + def _row_to_entry(self, row: tuple) -> TimelineEntry: + return TimelineEntry( + entry_id=row[0], + sequence_id=row[1], + timestamp=datetime.fromisoformat(row[2]), + role=row[3], + content_type=row[4], + raw_content=row[5], + semantic_embedding=self._decode_embedding(row[6]), + state_embedding=self._decode_embedding(row[7]), + agent_id=row[8], + session_id=row[9], + state_label=row[10], + tags=json.loads(row[11]) if row[11] else [], + ) + + # -- writes (append-only, never update / delete) --------------------------- + + def append( + self, + role: str, + content: str, + content_type: str = CONTENT_TEXT, + state_label: str = "", + tags: list[str] | None = None, + ) -> TimelineEntry: + """Create a TimelineEntry from arguments and persist it.""" + entry = TimelineEntry( + role=role, + raw_content=content, + content_type=content_type, + state_label=state_label, + sequence_id=self._next_seq(), + tags=tags or [], + ) + self._conn.execute(_INSERT, self._entry_to_row(entry)) + self._conn.commit() + return entry + + def append_entry(self, entry: TimelineEntry) -> TimelineEntry: + """Persist a pre-built TimelineEntry, assigning sequence_id.""" + entry.sequence_id = self._next_seq() + self._conn.execute(_INSERT, self._entry_to_row(entry)) + self._conn.commit() + return entry + + # -- reads ----------------------------------------------------------------- + + def get_range(self, start_seq: int, end_seq: int) -> list[TimelineEntry]: + """Return entries whose sequence_id is in [start_seq, end_seq].""" + rows = self._conn.execute( + "SELECT entry_id, sequence_id, timestamp, role, content_type, " + "raw_content, semantic_embedding, state_embedding, " + "agent_id, session_id, state_label, tags " + "FROM timeline_entries " + "WHERE sequence_id >= ? AND sequence_id <= ? " + "ORDER BY sequence_id", + (start_seq, end_seq), + ).fetchall() + return [self._row_to_entry(r) for r in rows] + + def get_recent(self, n: int) -> list[TimelineEntry]: + """Return the last *n* entries by sequence order.""" + rows = self._conn.execute( + "SELECT entry_id, sequence_id, timestamp, role, content_type, " + "raw_content, semantic_embedding, state_embedding, " + "agent_id, session_id, state_label, tags " + "FROM timeline_entries ORDER BY sequence_id DESC LIMIT ?", + (n,), + ).fetchall() + return [self._row_to_entry(r) for r in reversed(rows)] + + @property + def entries(self) -> list[TimelineEntry]: + """All entries, ordered by sequence_id.""" + rows = self._conn.execute(_SELECT_ALL).fetchall() + return [self._row_to_entry(r) for r in rows] + + def last(self, n: int = 1) -> list[TimelineEntry]: + return self.get_recent(n) + + def by_ids(self, ids: Sequence[str]) -> list[TimelineEntry]: + if not ids: + return [] + rows = self._conn.execute( + "SELECT entry_id, sequence_id, timestamp, role, content_type, " + "raw_content, semantic_embedding, state_embedding, " + "agent_id, session_id, state_label, tags " + "FROM timeline_entries " + "WHERE entry_id IN (SELECT value FROM json_each(?)) " + "ORDER BY sequence_id", + (json.dumps(list(ids)),), + ).fetchall() + return [self._row_to_entry(r) for r in rows] + + def slice( + self, + start: datetime | None = None, + end: datetime | None = None, + ) -> list[TimelineEntry]: + if start and end: + rows = self._conn.execute( + "SELECT entry_id, sequence_id, timestamp, role, content_type, " + "raw_content, semantic_embedding, state_embedding, " + "agent_id, session_id, state_label, tags " + "FROM timeline_entries " + "WHERE timestamp >= ? AND timestamp <= ? " + "ORDER BY sequence_id", + (start.isoformat(), end.isoformat()), + ).fetchall() + elif start: + rows = self._conn.execute( + "SELECT entry_id, sequence_id, timestamp, role, content_type, " + "raw_content, semantic_embedding, state_embedding, " + "agent_id, session_id, state_label, tags " + "FROM timeline_entries WHERE timestamp >= ? " + "ORDER BY sequence_id", + (start.isoformat(),), + ).fetchall() + elif end: + rows = self._conn.execute( + "SELECT entry_id, sequence_id, timestamp, role, content_type, " + "raw_content, semantic_embedding, state_embedding, " + "agent_id, session_id, state_label, tags " + "FROM timeline_entries WHERE timestamp <= ? " + "ORDER BY sequence_id", + (end.isoformat(),), + ).fetchall() + else: + return self.entries + return [self._row_to_entry(r) for r in rows] + + def __len__(self) -> int: + row = self._conn.execute( + "SELECT COUNT(*) FROM timeline_entries" + ).fetchone() + return row[0] diff --git a/experiments/episodic-memory-prototype/pyproject.toml b/experiments/episodic-memory-prototype/pyproject.toml new file mode 100644 index 00000000..31e44d6d --- /dev/null +++ b/experiments/episodic-memory-prototype/pyproject.toml @@ -0,0 +1,24 @@ +[project] +name = "episodic-memory-prototype" +version = "0.1.0" +description = "Episodic memory system for LLM agents — prototype & evaluation" +requires-python = ">=3.10" +dependencies = [ + "numpy>=1.24", + "sentence-transformers>=2.2", + "anthropic>=0.30", +] + +[project.optional-dependencies] +dev = ["pytest>=7.0", "ruff>=0.4"] + +[build-system] +requires = ["setuptools>=68"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.packages.find] +include = ["memory*", "agent*", "baseline*", "simulator*", "eval*"] + +[tool.ruff] +line-length = 100 +target-version = "py310" diff --git a/experiments/episodic-memory-prototype/simulator/__init__.py b/experiments/episodic-memory-prototype/simulator/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/experiments/episodic-memory-prototype/simulator/scenarios.py b/experiments/episodic-memory-prototype/simulator/scenarios.py new file mode 100644 index 00000000..2f1d661d --- /dev/null +++ b/experiments/episodic-memory-prototype/simulator/scenarios.py @@ -0,0 +1,281 @@ +"""Synthetic scenario generator. + +Contains a scripted 9-round debugging scenario designed to test whether +episodic memory can: + 1. Extract and surface a recurring pattern (connection pool exhaustion) + 2. NOT blindly apply that pattern to unrelated failures (red herring) + 3. Generalize the pattern to novel symptoms (subtle) + 4. Learn from corrections and update its hypothesis (correction test) +""" + +from __future__ import annotations + +from dataclasses import dataclass, field + + +# --------------------------------------------------------------------------- +# Data structures +# --------------------------------------------------------------------------- + +@dataclass +class DebugRound: + round_num: int + service: str + bug_report: str + investigation_notes: list[str] + attempted_fix: str + actual_root_cause: str + outcome: str # "success" | "failure" + assumption: str = "" + correction: str = "" + state_label: str = "debugging" + is_test_round: bool = False + expected_keywords: list[str] = field(default_factory=list) + + correct_keywords: list[str] = field(default_factory=list) + incorrect_keywords: list[str] = field(default_factory=list) + round_label: str = "" + trigger_fact_extraction: bool = False + + +# --------------------------------------------------------------------------- +# Keyword groups reused across rounds +# --------------------------------------------------------------------------- + +_POOL_KW = ["connection pool", "pool exhaust", "pool saturat", "pool starvation"] +_DEPLOY_KW = ["deployment", "migration", "config", "deploy", "release"] + + +# --------------------------------------------------------------------------- +# The 9-round debugging scenario +# --------------------------------------------------------------------------- + +def debugging_scenario() -> list[DebugRound]: + return [ + # -- Round 1: API service, initial encounter ----------------------- + DebugRound( + round_num=1, + service="api-gateway", + bug_report="API returning 500 errors under load.", + investigation_notes=[ + "Checked application logs: seeing 'connection refused' errors.", + "Database CPU looks normal, queries are fine.", + "Noticed Redis client throwing 'pool exhausted' exceptions.", + ], + attempted_fix="Increased database connection pool size.", + actual_root_cause="Redis connection pool exhaustion under high QPS.", + outcome="failure", + assumption="database connections", + correction="Was actually Redis connection pool exhaustion, not DB.", + correct_keywords=_POOL_KW, + ), + + # -- Round 2: Payment service, different domain, same pattern ------ + DebugRound( + round_num=2, + service="payment-service", + bug_report="Timeouts in payment processing during peak hours.", + investigation_notes=[ + "Payment queue depth spiking during peak traffic.", + "Downstream calls to fraud-check timing out.", + "Connection pool metrics show 100% utilization on cache layer.", + ], + attempted_fix="Added retry logic with exponential backoff.", + actual_root_cause="Cache connection pool saturated under peak load.", + outcome="failure", + assumption="network latency to fraud-check service", + correction="Connection pool to cache was the bottleneck, not network.", + correct_keywords=_POOL_KW, + ), + + # -- Round 3: RED HERRING — looks similar but is NOT pool ---------- + DebugRound( + round_num=3, + service="payment-service", + bug_report=( + "Payment service returning 504 errors after deployment." + ), + investigation_notes=[ + "504s started exactly when v2.14.3 was deployed.", + "Upstream dependency health checks all passing.", + "Connection pool metrics look normal — no exhaustion.", + "Found misconfigured timeout value in new config: " + "1ms instead of 1000ms.", + ], + attempted_fix="Rolled back deployment to v2.14.2.", + actual_root_cause=( + "Bad config change in v2.14.3 — upstream timeout set to " + "1ms instead of 1000ms." + ), + outcome="success", + assumption="", + correction="", + is_test_round=True, + round_label="RED HERRING", + expected_keywords=[ + "config", "deployment", "rollback", "timeout setting", + ], + correct_keywords=[ + "config", "deployment", "rollback", "misconfigur", + "bad deploy", "timeout setting", + ], + incorrect_keywords=["connection pool", "pool exhaust"], + ), + + # -- Round 4: Search service, pattern solidifies ------------------- + DebugRound( + round_num=4, + service="search-service", + bug_report="Search service 503s during batch indexing.", + investigation_notes=[ + "Batch indexing triggers 10x normal query volume.", + "Elasticsearch is healthy, but the service can't reach it.", + "Thread dump shows all threads blocked waiting for connections.", + ], + attempted_fix="Scaled up search service replicas.", + actual_root_cause=( + "Connection pool to Elasticsearch exhausted during " + "indexing spike." + ), + outcome="failure", + assumption="insufficient compute capacity", + correction=( + "Connection pool starvation, not compute — need bigger pool." + ), + correct_keywords=_POOL_KW, + trigger_fact_extraction=True, + ), + + # -- Round 5: Test — does agent recall the pattern? ---------------- + DebugRound( + round_num=5, + service="notification-service", + bug_report="Notification service dropping messages under load.", + investigation_notes=[ + "Message broker consumer lag increasing rapidly.", + "Service health checks passing but throughput collapsed.", + ], + attempted_fix="", + actual_root_cause="Connection pool to message broker exhausted.", + outcome="failure", + is_test_round=True, + round_label="TEST", + expected_keywords=[ + "connection pool", "exhaustion", "under load", "pool", + ], + correct_keywords=_POOL_KW, + ), + + # -- Round 6: Test — does generalized fact surface? ---------------- + DebugRound( + round_num=6, + service="auth-service", + bug_report="Auth service slow during login spike.", + investigation_notes=[ + "Login latency p99 jumped from 200ms to 8s.", + "Token validation calls to Redis backing up.", + ], + attempted_fix="", + actual_root_cause=( + "Redis connection pool exhaustion during auth spike." + ), + outcome="failure", + is_test_round=True, + round_label="TEST", + expected_keywords=[ + "connection pool", "Redis", "pool exhaustion", "under load", + ], + correct_keywords=_POOL_KW, + ), + + # -- Round 7: SUBTLE — same root cause, totally different symptoms - + DebugRound( + round_num=7, + service="auth-service", + bug_report=( + "Auth service latency P99 increased from 50ms to 200ms, " + "no errors." + ), + investigation_notes=[ + "No 5xx errors, no timeouts — just gradual latency increase.", + "CPU and memory utilization steady at 40%.", + "Thread pool looks healthy, no blocked threads.", + ], + attempted_fix="", + actual_root_cause=( + "Connection pool contention — pool sized for average load " + "but insufficient during traffic bursts, causing queueing " + "delays without hard failures." + ), + outcome="failure", + is_test_round=True, + round_label="SUBTLE", + expected_keywords=[ + "connection pool", "pool contention", "pool", "queueing", + ], + correct_keywords=_POOL_KW + ["pool contention"], + ), + + # -- Round 8: CORRECTION — agent gets it wrong, learns from it ---- + DebugRound( + round_num=8, + service="order-service", + bug_report=( + "Order service returning 500 errors after deploying v2.4.1. " + "Errors started exactly at deploy time, not during a " + "traffic spike." + ), + investigation_notes=[ + "Errors started exactly at deploy time, not during load.", + "Connection pool metrics look healthy — no saturation.", + "Database query latency spiked: full table scans detected.", + "Found that v2.4.1 migration dropped an index on the " + "orders table.", + ], + attempted_fix="Investigated connection pool metrics — all healthy.", + actual_root_cause=( + "Missing database index from v2.4.1 migration caused " + "full table scans and 500 errors." + ), + outcome="failure", + assumption="connection pool exhaustion", + correction=( + "Not connection pool — errors correlated with deployment " + "time, not load. Root cause was missing DB index from " + "migration." + ), + round_label="CORRECTION", + expected_keywords=["deployment", "migration", "index", "deploy", + "config"], + correct_keywords=_DEPLOY_KW + ["index", "database migration"], + incorrect_keywords=["connection pool", "pool exhaust"], + trigger_fact_extraction=True, + ), + + # -- Round 9: CORRECTION TEST — did the agent learn? -------------- + DebugRound( + round_num=9, + service="inventory-service", + bug_report=( + "Inventory service errors spiking after v3.1.0 release. " + "Started immediately after deployment, no change in traffic." + ), + investigation_notes=[ + "Error rate jumped from 0 to 15% at exactly deploy time.", + "Traffic volume unchanged — this is not a load issue.", + "Rollback to v3.0.9 resolved errors immediately.", + ], + attempted_fix="", + actual_root_cause=( + "Bad deployment — v3.1.0 introduced a schema mismatch " + "that caused query failures." + ), + outcome="failure", + is_test_round=True, + round_label="CORRECTION", + expected_keywords=["deployment", "migration", "config", + "release", "deploy"], + correct_keywords=_DEPLOY_KW, + incorrect_keywords=["connection pool", "pool exhaust"], + ), + ] diff --git a/experiments/episodic-memory-prototype/success_test1.txt b/experiments/episodic-memory-prototype/success_test1.txt new file mode 100644 index 00000000..add14f3c --- /dev/null +++ b/experiments/episodic-memory-prototype/success_test1.txt @@ -0,0 +1,650 @@ +python -m eval.compare ok | to py | 20:41:12 + +================================================================================ + EPISODIC MEMORY vs FLAT VECTOR RAG + Scenario: 9-Round Debugging + Decision mode: Claude +================================================================================ + Round types: + LEARN -- agent builds memory from failure + RED HERRING -- root cause is NOT connection pool + TEST -- root cause IS connection pool + SUBTLE -- connection pool but different symptoms + CORRECTION -- agent gets corrected, then tested on similar case +================================================================================ +Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads. +WARNING:huggingface_hub.utils._http:Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads. +Loading weights: 100%|███████████████████████████████████████████████████████████| 103/103 [00:00<00:00, 7150.41it/s, Materializing param=pooler.dense.weight] +BertModel LOAD REPORT from: sentence-transformers/all-MiniLM-L6-v2 +Key | Status | | +------------------------+------------+--+- +embeddings.position_ids | UNEXPECTED | | + +Notes: +- UNEXPECTED :can be ignored when loading from different task/architecture; not ok if you expect identical arch. + ++------------------------------------------------------------------------------+ +| | +| ROUND 1 | api-gateway | +| API returning 500 errors under load. | +| | ++----------------------------------------+-------------------------------------+ +| EPISODIC AGENT | BASELINE AGENT | ++----------------------------------------+-------------------------------------+ + Retrieved memory: + (no prior episodes) | (no prior entries) + ----------------------------------------+-------------------------------------- + Reasoning: + Since no relevant past experience is | Without any past entries to reference, + provided, I must rely on standard SRE | I must rely on standard SRE practices + best practices for 500 error | for 500 errors under load. HTTP 500 + investigations under load. The most | errors indicate server-side issues, + common root causes for load-related | and when they occur specifically + 500 errors are resource exhaustion | "under load," this suggests resource + (CPU, memory, database connections) or | exhaustion, database connection + downstream service failures that | limits, or application-level + cascade upward. These issues typically | bottlenecks rather than code bugs that + manifest first in resource utilization | would manifest consistently. + metrics and service dependencies. | + ----------------------------------------+-------------------------------------- + Investigate: Check current CPU and... | Investigate: Check database... + CORRECT | CORRECT ++------------------------------------------------------------------------------+ +| Ground truth: Redis connection pool exhaustion under high QPS. | ++------------------------------------------------------------------------------+ + ++------------------------------------------------------------------------------+ +| | +| ROUND 2 | payment-service | +| Timeouts in payment processing during peak hours. | +| | ++----------------------------------------+-------------------------------------+ +| EPISODIC AGENT | BASELINE AGENT | ++----------------------------------------+-------------------------------------+ + Retrieved memory: + [FAILURE] Outcome: failure — Redis | * Outcome: failure. Root cause: Redis + connection pool exhaustion under hi | connection pool exhaustion under high + => Was actually Redis connection pool | Q + exhaustion, not DB. | * Noticed Redis client throwing 'pool + [FAILURE] The user reported API 500 | exhausted' exceptions. + errors under load, and the agent i => | * Database CPU looks normal, queries + Was actually Redis connection pool | are fine. + exhaustion, not DB. | * Correction: Was actually Redis + | connection pool exhaustion, not DB. + | * Checked application logs: seeing + | 'connection refused' errors. + Generalized facts: + The user reported API 500 errors under | (N/A -- no fact extraction) + load, and the agent investigate | + [sup=1] | + ----------------------------------------+-------------------------------------- + Reasoning: + Both Episode 1 and Episode 2 show a | The past entries show a clear pattern + clear pattern where I initially | where payment timeouts were previously + assumed database connection issues but | caused by Redis connection pool + the actual root cause was Redis | exhaustion under high load, with + connection pool exhaustion under high | symptoms including "pool exhausted" + QPS. The generalized fact confirms | exceptions and "connection refused" + this pattern occurred consistently. | errors. Multiple entries confirm this + Payment processing timeouts during | was the actual root cause after + peak hours strongly resembles the | initial misdiagnosis pointed to + "high QPS" scenario from my past | database issues. Since we're + episodes where Redis was the | experiencing timeouts during peak + bottleneck, not the database. | hours again, this suggests the same + | Redis bottleneck is likely recurring. + ----------------------------------------+-------------------------------------- + Investigate: Check Redis connection... | Investigate: Check Redis... + CORRECT | CORRECT ++------------------------------------------------------------------------------+ +| Ground truth: Cache connection pool saturated under peak load. | ++------------------------------------------------------------------------------+ + ++------------------------------------------------------------------------------+ +| | +| ROUND 3 [RED HERRING] | payment-service | +| Payment service returning 504 errors after deployment. | +| | ++----------------------------------------+-------------------------------------+ +| EPISODIC AGENT | BASELINE AGENT | ++----------------------------------------+-------------------------------------+ + Retrieved memory: + [FAILURE] The user reported API 500 | * API returning 500 errors under load. + errors under load, and the agent i => | * Timeouts in payment processing + Was actually Redis connection pool | during peak hours. + exhaustion, not DB. | * Fix attempted: Added retry logic + [FAILURE] The user reported two | with exponential backoff. + separate performance issues: API 500 e | * Fix attempted: Increased database + => Connection pool to cache was the | connection pool size. + bottleneck, not network. | * Checked application logs: seeing + [FAILURE] Outcome: failure — Cache | 'connection refused' errors. + connection pool saturated under pea | + => Connection pool to cache was the | + bottleneck, not network. | + [FAILURE] Outcome: failure — Redis | + connection pool exhaustion under hi | + => Was actually Redis connection pool | + exhaustion, not DB. | + Generalized facts: + The user reported API 500 errors under | (N/A -- no fact extraction) + load, and the agent investigate | + [sup=2] | + ----------------------------------------+-------------------------------------- + Reasoning: + Looking at my episodic memory, I see a | The past entries show a clear pattern + clear pattern across Episodes 1, 2, | of connectivity issues, with entry #5 + and 4 where 504/500 errors after | specifically mentioning 'connection + deployments were consistently caused | refused' errors in logs, and entry #2 + by connection pool exhaustion - | indicating timeout problems during + specifically Redis and cache | high load periods. The 504 error + connection pools, not database issues | (Gateway Timeout) combined with the + as initially suspected. The | historical pattern of connection + generalized fact confirms this pattern | refusal suggests the payment service + with 2 supporting episodes showing | cannot establish or maintain + Redis connection pool exhaustion under | connections to its dependencies. + high QPS was the actual root cause, | Previous fixes focused on retry logic + despite initial assumptions about | and database connection pools, + database connections. Since we're | indicating database connectivity has + dealing with a payment service | been a recurring bottleneck. + returning 504 errors after deployment, | + this matches the exact scenario from | + my past failures. | + ----------------------------------------+-------------------------------------- + Investigate: Check Redis and cache... | Investigate: Check the database... + WRONG | WRONG + Keywords: 0/4 | Keywords: 0/4 ++------------------------------------------------------------------------------+ +| Ground truth: Bad config change in v2.14.3 — upstream timeout set to 1ms ...| ++------------------------------------------------------------------------------+ + ++------------------------------------------------------------------------------+ +| | +| ROUND 4 | search-service | +| Search service 503s during batch indexing. | +| | ++----------------------------------------+-------------------------------------+ +| EPISODIC AGENT | BASELINE AGENT | ++----------------------------------------+-------------------------------------+ + Retrieved memory: + [FAILURE] The user reported API 500 | * 504s started exactly when v2.14.3 + errors under load, and the agent i => | was deployed. + Was actually Redis connection pool | * Payment service returning 504 errors + exhaustion, not DB. | after deployment. + [FAILURE] Outcome: failure — Cache | * Database CPU looks normal, queries + connection pool saturated under pea | are fine. + => Connection pool to cache was the | * Fix attempted: Increased database + bottleneck, not network. | connection pool size. + [FAILURE] The user reported two | * Outcome: failure. Root cause: Cache + separate performance issues: API 500 e | connection pool saturated under peak + => Connection pool to cache was the | lo + bottleneck, not network. | + [SUCCESS] The interaction covered | + three separate incidents: API 500 er | + [SUCCESS] Outcome: success — Bad | + config change in v2.14.3 — upstream t | + Generalized facts: + Deployment-related errors with | (N/A -- no fact extraction) + specific HTTP status codes like 504 | + sho [sup=1] | + When encountering 500 errors or | + timeouts under load, investigate conne | + [sup=1] | + The user reported API 500 errors under | + load, and the agent investigate | + [sup=2] | + ----------------------------------------+-------------------------------------- + Reasoning: + Looking at the pattern from past | The past entries show a clear pattern + episodes, I see a clear trend where | where 504/503 errors occurred after + connection pool exhaustion has been | deployments and were initially + the root cause in Episodes 1, 2, and | misdiagnosed as database issues. The + 3, all resulting in 500/503 errors | key insight from entry 5 is that the + under load conditions. Episodes 4 and | root cause was actually cache + 5 show that when the issue occurs | connection pool saturation, not + after deployment, configuration | database problems, despite normal + changes (especially timeout values) | database metrics. This suggests + are often the culprit. Since this is a | connection pool exhaustion is a common + 503 error during batch indexing (high | failure mode during high-load + load scenario), my experience strongly | operations like batch indexing. + suggests connection pool issues should | + be investigated first, as supported by | + multiple failed episodes where I | + initially misdiagnosed similar | + symptoms. | + ----------------------------------------+-------------------------------------- + Investigate: Check connection pool... | Investigate: Check the cache... + CORRECT | CORRECT ++------------------------------------------------------------------------------+ +| Ground truth: Connection pool to Elasticsearch exhausted during indexing ...| ++------------------------------------------------------------------------------+ + + >>> TRIGGERING FACT EXTRACTION AFTER ROUND 4 <<< + * The user reported API 500 errors under load, and the agent investigated finding + * The interaction covered three separate incidents: API 500 errors caused by Redis + * When encountering 500 errors or timeouts under load, investigate connection pool + * Connection pool saturation across different services (Redis, cache, database) of + * Deployment-related errors with specific HTTP status codes like 504 should prompt + * The agent investigated multiple service outages caused by various issues includi + * Connection pool exhaustion is a common root cause of service failures under load + * Misdiagnosis of connection pool issues as database problems or other component f + * Configuration changes, especially timeout values, should be verified carefully a + + ++------------------------------------------------------------------------------+ +| | +| ROUND 5 [TEST] | notification-service | +| Notification service dropping messages under load. | +| | ++----------------------------------------+-------------------------------------+ +| EPISODIC AGENT | BASELINE AGENT | ++----------------------------------------+-------------------------------------+ + Retrieved memory: + [FAILURE] Outcome: failure — Cache | * Outcome: failure. Root cause: Cache + connection pool saturated under pea | connection pool saturated under peak + => Connection pool to cache was the | lo + bottleneck, not network. | * Found misconfigured timeout value in + [FAILURE] The user reported API 500 | new config: 1ms instead of 1000ms. + errors under load, and the agent i => | * Outcome: success. Root cause: Bad + Was actually Redis connection pool | config change in v2.14.3 — upstream + exhaustion, not DB. | timeo + [FAILURE] The agent investigated | * API returning 500 errors under load. + multiple service outages caused by va | * Fix attempted: Increased database + => Connection pool starvation, not | connection pool size. + compute — need bigger pool. | + [SUCCESS] Outcome: success — Bad | + config change in v2.14.3 — upstream t | + [FAILURE] The user reported two | + separate performance issues: API 500 e | + => Connection pool to cache was the | + bottleneck, not network. | + Generalized facts: + Connection pool exhaustion is a common | (N/A -- no fact extraction) + root cause of service failures | + [sup=1] | + When encountering 500 errors or | + timeouts under load, investigate conne | + [sup=1] | + Connection pool saturation across | + different services (Redis, cache, da | + [sup=1] | + ----------------------------------------+-------------------------------------- + Reasoning: + Looking at my past episodes, there's a | The past entries show a clear pattern + clear pattern where connection pool | of configuration-related issues + exhaustion is the underlying cause of | causing problems under load, + service failures under load, even when | particularly timeout + initial symptoms suggest other issues. | misconfigurations. Entries #2 and #3 + Episodes 1, 2, 3, and 5 all involved | specifically mention a recurring issue + connection pool saturation (cache, | where timeout values were set to 1ms + Redis, Elasticsearch) that was | instead of 1000ms, which would cause + initially misdiagnosed as network | services to fail rapidly under any + latency, database issues, or compute | significant load. This type of + capacity problems. The generalized | configuration error appears to be a + facts confirm that connection pool | common root cause in this system's + exhaustion should be investigated | history. + before attempting other fixes like | + scaling or retry logic. | + ----------------------------------------+-------------------------------------- + Investigate: Check the connection... | Investigate: Check all timeout... + CORRECT | WRONG + Keywords: 4/4 | Keywords: 3/4 ++------------------------------------------------------------------------------+ +| Ground truth: Connection pool to message broker exhausted. | ++------------------------------------------------------------------------------+ + ++------------------------------------------------------------------------------+ +| | +| ROUND 6 [TEST] | auth-service | +| Auth service slow during login spike. | +| | ++----------------------------------------+-------------------------------------+ +| EPISODIC AGENT | BASELINE AGENT | ++----------------------------------------+-------------------------------------+ + Retrieved memory: + [FAILURE] The user reported two | * Found misconfigured timeout value in + separate performance issues: API 500 e | new config: 1ms instead of 1000ms. + => Connection pool to cache was the | * Outcome: success. Root cause: Bad + bottleneck, not network. | config change in v2.14.3 — upstream + [FAILURE] The conversation covered | timeo + five separate incidents involving A | * Notification service dropping + [FAILURE] The user reported API 500 | messages under load. + errors under load, and the agent i => | * Message broker consumer lag + Was actually Redis connection pool | increasing rapidly. + exhaustion, not DB. | * Fix attempted: Scaled up search + [SUCCESS] Outcome: success — Bad | service replicas. + config change in v2.14.3 — upstream t | + [FAILURE] The agent investigated | + multiple service outages caused by va | + => Connection pool starvation, not | + compute — need bigger pool. | + Generalized facts: + Connection pool exhaustion is a common | (N/A -- no fact extraction) + root cause of service failures | + [sup=1] | + The conversation covered five separate | + incidents involving API 500 err | + [sup=1] | + When encountering 500 errors or | + timeouts under load, investigate conne | + [sup=1] | + ----------------------------------------+-------------------------------------- + Reasoning: + Looking at my past episodes, there's a | The past entries show a clear pattern + clear pattern where connection pool | where configuration issues, + exhaustion is the root cause in 4 out | particularly timeout values, were the + of 5 incidents involving service | root cause of service slowdowns. Entry + slowdowns under load (Episodes 1, 2, | #1 and #2 specifically document a + 3, and 5). My episodic memory shows I | successful resolution where a + repeatedly misdiagnosed these issues | misconfigured timeout value (1ms + initially, attempting fixes like | instead of 1000ms) in version 2.14.3 + scaling replicas or adjusting | caused performance issues. This type + application logic, when the actual | of configuration error is both common + bottleneck was saturated connection | and has high impact during load spikes + pools to downstream services like | when services need adequate time to + Redis, cache layers, message brokers, | process requests. + or databases. The one successful | + resolution (Episode 4) was a | + configuration issue, but given we're | + dealing with load-related auth service | + slowness, this matches the connection | + pool exhaustion pattern perfectly. | + ----------------------------------------+-------------------------------------- + Investigate: Check connection pool... | Investigate: Check recent... + CORRECT | WRONG + Keywords: 4/4 | Keywords: 1/4 ++------------------------------------------------------------------------------+ +| Ground truth: Redis connection pool exhaustion during auth spike. | ++------------------------------------------------------------------------------+ + ++------------------------------------------------------------------------------+ +| | +| ROUND 7 [SUBTLE] | auth-service | +| Auth service latency P99 increased from 50ms to 200ms, no errors. | +| | ++----------------------------------------+-------------------------------------+ +| EPISODIC AGENT | BASELINE AGENT | ++----------------------------------------+-------------------------------------+ + Retrieved memory: + [FAILURE] Outcome: failure — Redis | * Login latency p99 jumped from 200ms + connection pool exhaustion during a | to 8s. + [FAILURE] The agent investigated | * Auth service slow during login + multiple service issues including API | spike. + [FAILURE] The conversation covered | * Found misconfigured timeout value in + five separate incidents involving A | new config: 1ms instead of 1000ms. + [FAILURE] The user reported two | * Outcome: failure. Root cause: Redis + separate performance issues: API 500 e | connection pool exhaustion during auth + => Connection pool to cache was the | * Outcome: success. Root cause: Bad + bottleneck, not network. | config change in v2.14.3 — upstream + [SUCCESS] Outcome: success — Bad | timeo + config change in v2.14.3 — upstream t | + Generalized facts: + Configuration changes, especially | (N/A -- no fact extraction) + timeout values, should be verified c | + [sup=1] | + Extremely low timeout values like 1ms | + in production deployments are al | + [sup=1] | + The conversation covered five separate | + incidents involving API 500 err | + [sup=2] | + ----------------------------------------+-------------------------------------- + Reasoning: + Looking at the pattern from past | The past entries show two clear + episodes, I see two main categories of | patterns for auth service latency + auth service issues: connection pool | spikes: configuration errors + exhaustion (Episodes 1-4, all | (specifically timeout + failures) and configuration problems | misconfigurations like "1ms instead of + (Episode 5, successful resolution). | 1000ms") and resource exhaustion + However, Episode 5 stands out because | (Redis connection pool issues during + it was the only successful resolution | traffic spikes). Entry #5 shows a + and involved a specific symptom match | successful resolution from fixing a + - latency increase without errors due | bad config change in v2.14.3 with an + to a misconfigured timeout value (1ms | upstream timeout misconfiguration, + instead of 1000ms). The generalized | while entry #4 shows connection pool + facts emphasize that extremely low | exhaustion as another common cause. + timeout values are almost always | + misconfigurations requiring immediate | + rollback, and configuration changes | + should be verified first as they can | + cause immediate service failures. | + ----------------------------------------+-------------------------------------- + Investigate: Check recent... | Investigate: Check recent... + WRONG | WRONG + Keywords: 2/4 | Keywords: 2/4 ++------------------------------------------------------------------------------+ +| Ground truth: Connection pool contention — pool sized for average load bu...| ++------------------------------------------------------------------------------+ + ++------------------------------------------------------------------------------+ +| | +| ROUND 8 [CORRECTION] | order-service | +| Order service returning 500 errors after deploying v2.4.1. Errors started...| +| | ++----------------------------------------+-------------------------------------+ +| EPISODIC AGENT | BASELINE AGENT | ++----------------------------------------+-------------------------------------+ + Retrieved memory: + [FAILURE] The conversation covered | * Payment service returning 504 errors + five separate incidents involving A | after deployment. + [FAILURE] The agent investigated | * API returning 500 errors under load. + multiple service issues including API | * Found misconfigured timeout value in + [FAILURE] The agent investigated | new config: 1ms instead of 1000ms. + multiple service issues including API | * Outcome: success. Root cause: Bad + [FAILURE] The user reported API 500 | config change in v2.14.3 — upstream + errors under load, and the agent i => | timeo + Was actually Redis connection pool | * Outcome: failure. Root cause: + exhaustion, not DB. | Connection pool to message broker + [FAILURE] The agent investigated | exhausted. + multiple service outages caused by va | + => Connection pool starvation, not | + compute — need bigger pool. | + Generalized facts: + The conversation covered five separate | (N/A -- no fact extraction) + incidents involving API 500 err | + [sup=3] | + Deployment-related errors with | + specific HTTP status codes like 504 | + sho [sup=1] | + Extremely low timeout values like 1ms | + in production deployments are al | + [sup=1] | + ----------------------------------------+-------------------------------------- + Reasoning: + Looking at the past episodes, there's | The past entries show a clear pattern + a clear pattern where deployment- | of deployment-related 500/504 errors + related 500 errors have two main | being caused by configuration issues, + causes: connection pool exhaustion | specifically timeout + (Episodes 1-5) or deployment | misconfigurations. Entry #3 and #4 + configuration issues like timeout | directly demonstrate a successful + misconfigurations (Episodes 2, 3, 5). | resolution where a timeout was + However, since this incident started | accidentally set to 1ms instead of + "exactly at deploy time, not during a | 1000ms after deployment. Since the + traffic spike," this strongly suggests | current incident started exactly at + a deployment configuration issue | deploy time (not during traffic + rather than load-related connection | spikes), this strongly suggests a + pool problems. Episode 5 specifically | similar configuration regression + shows a successful resolution of a | rather than a capacity issue. + deployment-related issue by | + identifying and rolling back a | + misconfigured 1ms timeout value. | + ----------------------------------------+-------------------------------------- + Investigate: Check the v2.4.1... | Investigate: Check all timeout... + CORRECT | CORRECT + Keywords: 4/5 | Keywords: 3/5 ++------------------------------------------------------------------------------+ +| Ground truth: Missing database index from v2.4.1 migration caused full ta...| ++------------------------------------------------------------------------------+ + + >>> TRIGGERING FACT EXTRACTION AFTER ROUND 8 <<< + * The user reported API 500 errors under load, and the agent investigated finding + * The interaction covered three separate incidents: API 500 errors caused by Redis + * When encountering 500 errors or timeouts under load, investigate connection pool + * Connection pool saturation across different services (Redis, cache, database) of + * Deployment-related errors with specific HTTP status codes like 504 should prompt + * The agent investigated multiple service outages caused by various issues includi + * Connection pool exhaustion is a common root cause of service failures under load + * Misdiagnosis of connection pool issues as database problems or other component f + * Configuration changes, especially timeout values, should be verified carefully a + * The conversation covered five separate incidents involving API 500 errors, payme + * Extremely low timeout values like 1ms in production deployments are almost alway + * The agent interaction shows a troubleshooting session where the user reported th + * Misdiagnosing connection pool saturation as application-layer problems leads to + * Deployment-related issues with immediate onset after version changes are more li + + ++------------------------------------------------------------------------------+ +| | +| ROUND 9 [CORRECTION] | inventory-service | +| Inventory service errors spiking after v3.1.0 release. Started immediatel...| +| | ++----------------------------------------+-------------------------------------+ +| EPISODIC AGENT | BASELINE AGENT | ++----------------------------------------+-------------------------------------+ + Retrieved memory: + [FAILURE] The agent interaction shows | * Order service returning 500 errors + a troubleshooting session where => | after deploying v2.4.1. Errors started + Not connection pool — errors | e + correlated with deployment time, not | * Errors started exactly at deploy + load. Root cause was missing DB index | time, not during load. + from migration. | * Fix attempted: Rolled back + [FAILURE] The agent investigated | deployment to v2.14.2. + multiple service issues including API | * Outcome: success. Root cause: Bad + [FAILURE] The conversation covered | config change in v2.14.3 — upstream + five separate incidents involving A | timeo + [FAILURE] The agent investigated | * Found misconfigured timeout value in + multiple service issues including API | new config: 1ms instead of 1000ms. + [FAILURE] The agent investigated | + multiple service outages caused by va | + => Connection pool starvation, not | + compute — need bigger pool. | + Generalized facts: + Deployment-related issues with | (N/A -- no fact extraction) + immediate onset after version changes | + a [sup=1] | + The agent interaction shows a | + troubleshooting session where the user | + r [sup=1] | + Extremely low timeout values like 1ms | + in production deployments are al | + [sup=1] | + ----------------------------------------+-------------------------------------- + Reasoning: + Episode 1 directly parallels this | The past entries show a clear pattern + situation - errors spiking immediately | where deployment-time errors were + after a deployment (v2.4.1 then, | caused by a configuration issue - + v3.1.0 now) with no traffic change. In | specifically a timeout value that was + that case, the root cause was a | incorrectly set to 1ms instead of + database migration that dropped an | 1000ms. Since the current inventory + index, causing full table scans and | service errors also started + 500 errors. The generalized fact | "immediately after deployment" with + confirms that "deployment-related | "no change in traffic," this matches + issues with immediate onset after | the exact same symptom pattern from + version changes are more likely caused | the previous incidents. The successful + by configuration errors or missing | rollback and root cause identification + dependencies than by connection pool | in the past entries strongly suggest + problems." | configuration changes are the most + | likely culprit. + ----------------------------------------+-------------------------------------- + Investigate: Check if the v3.1.0... | Investigate: Check the v3.1.0... + CORRECT | CORRECT + Keywords: 4/5 | Keywords: 3/5 ++------------------------------------------------------------------------------+ +| Ground truth: Bad deployment — v3.1.0 introduced a schema mismatch that c...| ++------------------------------------------------------------------------------+ + +================================================================================ + SUMMARY -- Decision Correctness Across All Rounds +================================================================================ + Round Type Service Episodic Baseline Keywords (E/B) + ------------------------------------------------------------------------------ + 1 LEARN api-gateway OK OK -- + 2 LEARN payment-service OK OK -- + 3 RED HERRING payment-service X X 0/4 0/4 + 4 LEARN search-service OK OK -- + 5 TEST notification-service OK X 4/4 3/4 + 6 TEST auth-service OK X 4/4 1/4 + 7 SUBTLE auth-service X X 2/4 2/4 + 8 CORRECTION order-service OK OK 4/5 3/5 + 9 CORRECTION inventory-service OK OK 4/5 3/5 + ------------------------------------------------------------------------------ + TOTAL 7 5 + +================================================================================ + STRUCTURED RETRIEVAL SCORE + (items with explicit outcome labels vs raw outcome mentions) +================================================================================ + Round Type Episodic (labeled/total) Baseline (outcome/total) + -------------------------------------------------------------------------- + 1 LEARN -- -- + 2 LEARN 2/2 1/5 + 3 RED HERRING 4/4 0/5 + 4 LEARN 5/5 1/5 + 5 TEST 5/5 2/5 + 6 TEST 5/5 1/5 + 7 SUBTLE 5/5 2/5 + 8 CORRECTION 5/5 2/5 + 9 CORRECTION 5/5 1/5 + -------------------------------------------------------------------------- + TOTAL 36/36 (100%) 10/40 (25%) + +================================================================================ + PATTERN APPLICATION (Rounds 4-9) + Connection pool pattern: correct when IS root cause, FP when NOT +================================================================================ + Round Type Pool correct? Episodic Baseline + -------------------------------------------------------------------- + 4 LEARN yes correct correct + 5 TEST yes correct missed + 6 TEST yes correct missed + 7 SUBTLE yes missed missed + 8 CORRECTION NO avoided avoided + 9 CORRECTION NO avoided avoided + -------------------------------------------------------------------- + Correct applies: 3 1 + False positives: 0 0 + +================================================================================ + ADAPTATION SCORE (Round 8 → 9: did the agent learn from correction?) +================================================================================ + Episodic Baseline + -------------------------------------------------------------------- + Round 8 applied pool pattern: no no + Round 9 applied pool pattern: no no + Verdict: repeated mistakerepeated mistake + +================================================================================ + WEIGHTED SCORE + Correct decision = 1pt | Avoided false positive = 1pt | Adaptation = 2pt +================================================================================ + Episodic Baseline + -------------------------------------------------------------------- + Correct decisions (×1): 7 5 + Avoided false positives (×1): 2 2 + Adaptation bonus (×2): 0 0 + -------------------------------------------------------------------- + WEIGHTED TOTAL: 9 7 + + ==> Episodic memory outperforms flat vector RAG. + +================================================================================ + FINAL EPISODIC MEMORY STATE +================================================================================ + Episodes: 18 + Links: 9 + Facts: 17 + * The user reported API 500 errors under load, and the agent investigated [sup=2 con=0] + * The interaction covered three separate incidents: API 500 errors caused [sup=1 con=0] + * When encountering 500 errors or timeouts under load, investigate connect [sup=1 con=0] + * Connection pool saturation across different services (Redis, cache, data [sup=2 con=0] + * Deployment-related errors with specific HTTP status codes like 504 shoul [sup=1 con=0] + * The agent investigated multiple service outages caused by various issues [sup=1 con=0] + * Connection pool exhaustion is a common root cause of service failures un [sup=4 con=0] + * Misdiagnosis of connection pool issues as database problems or other com [sup=1 con=0] + * Configuration changes, especially timeout values, should be verified car [sup=1 con=0] + * The conversation covered five separate incidents involving API 500 error [sup=3 con=0] + * Extremely low timeout values like 1ms in production deployments are almo [sup=1 con=0] + * The agent interaction shows a troubleshooting session where the user rep [sup=1 con=0] + * Misdiagnosing connection pool saturation as application-layer problems l [sup=1 con=0] + * Deployment-related issues with immediate onset after version changes are [sup=1 con=0] + * The agent investigated multiple service incidents including API 500 erro [sup=1 con=0] + * When service errors immediately follow a deployment, investigate configu [sup=1 con=0] + * Correlation between error onset and traffic spikes versus deployment tim [sup=1 con=0] \ No newline at end of file diff --git a/experiments/episodic-memory-prototype/success_test2.txt b/experiments/episodic-memory-prototype/success_test2.txt new file mode 100644 index 00000000..94181444 --- /dev/null +++ b/experiments/episodic-memory-prototype/success_test2.txt @@ -0,0 +1,636 @@ +================================================================================ + EPISODIC MEMORY vs FLAT VECTOR RAG + Scenario: 9-Round Debugging + Decision mode: Claude +================================================================================ + Round types: + LEARN -- agent builds memory from failure + RED HERRING -- root cause is NOT connection pool + TEST -- root cause IS connection pool + SUBTLE -- connection pool but different symptoms + CORRECTION -- agent gets corrected, then tested on similar case +================================================================================ +Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads. +WARNING:huggingface_hub.utils._http:Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads. +Loading weights: 100%|████████████████████████████| 103/103 [00:00<00:00, 5847.34it/s, Materializing param=pooler.dense.weight] +BertModel LOAD REPORT from: sentence-transformers/all-MiniLM-L6-v2 +Key | Status | | +------------------------+------------+--+- +embeddings.position_ids | UNEXPECTED | | + +Notes: +- UNEXPECTED :can be ignored when loading from different task/architecture; not ok if you expect identical arch. + ++------------------------------------------------------------------------------+ +| | +| ROUND 1 | api-gateway | +| API returning 500 errors under load. | +| | ++----------------------------------------+-------------------------------------+ +| EPISODIC AGENT | BASELINE AGENT | ++----------------------------------------+-------------------------------------+ + Retrieved memory: + (no prior episodes) | (no prior entries) + ----------------------------------------+-------------------------------------- + Reasoning: + Without any past episodic memory to | Without any relevant past entries to + draw from, I need to apply standard | reference, I must rely on fundamental + SRE principles for 500 error | SRE principles for 500 errors under + investigations. The most critical | load. The most common root cause of + first step is to establish the | 500 errors appearing specifically + baseline - understanding the current | under load (rather than consistently) + error rate, patterns, and system | is resource exhaustion - either the + health metrics before diving into | application running out of memory, + specific components. This systematic | database connections, file + approach prevents tunnel vision and | descriptors, or CPU capacity causing + ensures we're addressing the actual | timeouts and failures that only + problem scope. | manifest when traffic increases. + ----------------------------------------+-------------------------------------- + Investigate: Check current error... | Investigate: Check application... + WRONG | CORRECT ++------------------------------------------------------------------------------+ +| Ground truth: Redis connection pool exhaustion under high QPS. | ++------------------------------------------------------------------------------+ + ++------------------------------------------------------------------------------+ +| | +| ROUND 2 | payment-service | +| Timeouts in payment processing during peak hours. | +| | ++----------------------------------------+-------------------------------------+ +| EPISODIC AGENT | BASELINE AGENT | ++----------------------------------------+-------------------------------------+ + Retrieved memory: + [FAILURE] Outcome: failure — Redis | * Outcome: failure. Root cause: Redis + connection pool exhaustion under hi | connection pool exhaustion under high + => Was actually Redis connection pool | Q + exhaustion, not DB. | * Noticed Redis client throwing 'pool + [FAILURE] The user reported API 500 | exhausted' exceptions. + errors under load, and the agent i => | * Database CPU looks normal, queries + Was actually Redis connection pool | are fine. + exhaustion, not DB. | * Correction: Was actually Redis + | connection pool exhaustion, not DB. + | * Checked application logs: seeing + | 'connection refused' errors. + Generalized facts: + The user reported API 500 errors under | (N/A -- no fact extraction) + load, and the agent investigate | + [sup=1] | + ----------------------------------------+-------------------------------------- + Reasoning: + Both Episode 1 and Episode 2 show a | The past entries show a clear pattern + clear pattern where I initially | of Redis connection pool exhaustion + assumed database connection issues but | being the root cause of timeouts + the actual root cause was Redis | during high load scenarios. Multiple + connection pool exhaustion under high | entries (1, 2, 4, 5) point to Redis + QPS. The generalized fact confirms | connectivity issues manifesting as + this pattern - API errors under load | "pool exhausted" exceptions and + that appear to be database-related are | "connection refused" errors, while + actually Redis connection pool | database performance remained normal + problems. Given that payment | (entry 3). This suggests Redis + processing timeouts during peak hours | connection limits are hit before + follow the same load-based pattern, my | database bottlenecks occur during peak + past experience strongly suggests | traffic. + avoiding the database assumption trap. | + ----------------------------------------+-------------------------------------- + Investigate: Redis connection pool... | Investigate: Check Redis... + CORRECT | CORRECT ++------------------------------------------------------------------------------+ +| Ground truth: Cache connection pool saturated under peak load. | ++------------------------------------------------------------------------------+ + ++------------------------------------------------------------------------------+ +| | +| ROUND 3 [RED HERRING] | payment-service | +| Payment service returning 504 errors after deployment. | +| | ++----------------------------------------+-------------------------------------+ +| EPISODIC AGENT | BASELINE AGENT | ++----------------------------------------+-------------------------------------+ + Retrieved memory: + [FAILURE] The agent investigated two | * API returning 500 errors under load. + performance issues: API 500 error => | * Timeouts in payment processing + Connection pool to cache was the | during peak hours. + bottleneck, not network. | * Fix attempted: Added retry logic + [FAILURE] The user reported API 500 | with exponential backoff. + errors under load, and the agent i => | * Fix attempted: Increased database + Was actually Redis connection pool | connection pool size. + exhaustion, not DB. | * Checked application logs: seeing + [FAILURE] Outcome: failure — Cache | 'connection refused' errors. + connection pool saturated under pea | + => Connection pool to cache was the | + bottleneck, not network. | + [FAILURE] Outcome: failure — Redis | + connection pool exhaustion under hi | + => Was actually Redis connection pool | + exhaustion, not DB. | + Generalized facts: + The agent investigated two performance | (N/A -- no fact extraction) + issues: API 500 errors under lo | + [sup=1] | + The user reported API 500 errors under | + load, and the agent investigate | + [sup=1] | + ----------------------------------------+-------------------------------------- + Reasoning: + My episodic memory shows a clear | The past entries show a clear pattern + pattern of repeated failures where I | of connection-related issues during + initially misdiagnosed connection pool | high load scenarios, with entry #5 + exhaustion issues. In Episodes 1-4, I | specifically mentioning "connection + consistently assumed database or | refused" errors in logs. The + network problems when the actual root | progression from 500 errors to + cause was Redis/cache connection pool | timeouts to connection issues suggests + saturation. The generalized facts | infrastructure-level problems rather + confirm this pattern - connection pool | than application logic failures. Since + exhaustion (specifically Redis) was | 504 errors specifically indicate + the true culprit in multiple incidents | gateway timeouts, this aligns with the + involving 500 errors and timeouts. | historical pattern of connection and + | timeout issues seen in entries #2 and + | #5. + ----------------------------------------+-------------------------------------- + Investigate: Check Redis connection... | Investigate: Check if the payment... + WRONG | WRONG + Keywords: 1/4 | Keywords: 2/4 ++------------------------------------------------------------------------------+ +| Ground truth: Bad config change in v2.14.3 — upstream timeout set to 1ms ...| ++------------------------------------------------------------------------------+ + ++------------------------------------------------------------------------------+ +| | +| ROUND 4 | search-service | +| Search service 503s during batch indexing. | +| | ++----------------------------------------+-------------------------------------+ +| EPISODIC AGENT | BASELINE AGENT | ++----------------------------------------+-------------------------------------+ + Retrieved memory: + [FAILURE] The user reported API 500 | * 504s started exactly when v2.14.3 + errors under load, and the agent i => | was deployed. + Was actually Redis connection pool | * Payment service returning 504 errors + exhaustion, not DB. | after deployment. + [FAILURE] Outcome: failure — Cache | * Database CPU looks normal, queries + connection pool saturated under pea | are fine. + => Connection pool to cache was the | * Fix attempted: Increased database + bottleneck, not network. | connection pool size. + [FAILURE] The agent investigated two | * Outcome: failure. Root cause: Cache + performance issues: API 500 error => | connection pool saturated under peak + Connection pool to cache was the | lo + bottleneck, not network. | + [SUCCESS] Outcome: success — Bad | + config change in v2.14.3 — upstream t | + [SUCCESS] The agent investigated three | + separate issues: API 500 errors | + Generalized facts: + The user reported API 500 errors under | (N/A -- no fact extraction) + load, and the agent investigate | + [sup=1] | + The agent investigated two performance | + issues: API 500 errors under lo | + [sup=2] | + Connection pool exhaustion is a common | + root cause of service degradati | + [sup=1] | + ----------------------------------------+-------------------------------------- + Reasoning: + Looking at my past episodes, there's a | The past entries show a clear pattern + clear pattern of misdiagnosis around | where 504/503 errors occurred + connection pool exhaustion. Episodes | immediately after deployments, and the + 1, 2, and 3 all show that I initially | initial investigation focused on + blamed database or network issues when | database issues which proved to be a + the actual root cause was connection | red herring. The actual root cause was + pool saturation (Redis in Episodes | connection pool saturation in a + 1&3, cache in Episodes 2&3). The | different component (cache), not the + generalized facts confirm that | database. This suggests that + "connection pool exhaustion is a | connection pool exhaustion in non- + common root cause of service | obvious services is a common failure + degradation under load" and that I | mode during high-load operations like + have a tendency to misdiagnose these | batch indexing. + as other infrastructure problems. | + ----------------------------------------+-------------------------------------- + Investigate: Check connection pool... | Investigate: Check the search... + CORRECT | CORRECT ++------------------------------------------------------------------------------+ +| Ground truth: Connection pool to Elasticsearch exhausted during indexing ...| ++------------------------------------------------------------------------------+ + + >>> TRIGGERING FACT EXTRACTION AFTER ROUND 4 <<< + * The user reported API 500 errors under load, and the agent investigated finding + * The agent investigated two performance issues: API 500 errors under load and pay + * Connection pool exhaustion is a common root cause of service degradation under l + * Database and network connectivity issues are frequently misdiagnosed when the ac + * Configuration changes in deployments should be immediately suspected when servic + * The agent investigated four separate service outage incidents, all involving con + * When multiple infrastructure components exhibit similar symptoms simultaneously, + + ++------------------------------------------------------------------------------+ +| | +| ROUND 5 [TEST] | notification-service | +| Notification service dropping messages under load. | +| | ++----------------------------------------+-------------------------------------+ +| EPISODIC AGENT | BASELINE AGENT | ++----------------------------------------+-------------------------------------+ + Retrieved memory: + [FAILURE] The agent investigated four | * Outcome: failure. Root cause: Cache + separate service outage incident => | connection pool saturated under peak + Connection pool starvation, not | lo + compute — need bigger pool. | * Found misconfigured timeout value in + [FAILURE] Outcome: failure — Cache | new config: 1ms instead of 1000ms. + connection pool saturated under pea | * Outcome: success. Root cause: Bad + => Connection pool to cache was the | config change in v2.14.3 — upstream + bottleneck, not network. | timeo + [SUCCESS] Outcome: success — Bad | * API returning 500 errors under load. + config change in v2.14.3 — upstream t | * Fix attempted: Increased database + [FAILURE] The user reported API 500 | connection pool size. + errors under load, and the agent i => | + Was actually Redis connection pool | + exhaustion, not DB. | + [FAILURE] The agent investigated two | + performance issues: API 500 error => | + Connection pool to cache was the | + bottleneck, not network. | + Generalized facts: + Connection pool exhaustion is a common | (N/A -- no fact extraction) + root cause of service degradati | + [sup=2] | + The agent investigated four separate | + service outage incidents, all inv | + [sup=1] | + Configuration changes in deployments | + should be immediately suspected w | + [sup=2] | + ----------------------------------------+-------------------------------------- + Reasoning: + Looking at my past episodes, I see a | The retrieved entries show a clear + clear pattern where I repeatedly | pattern of configuration issues + misdiagnosed load-related issues by | causing production failures, + focusing on compute/network problems | specifically timeout misconfigurations + when the actual root cause was | (entries 2 and 3) where values were + connection pool exhaustion (Episodes | set to 1ms instead of 1000ms. Both + 1, 2, 4, and 5). However, Episode 3 | cache connection pool saturation + stands out as my only successful | (entry 1) and general load-related API + resolution, where I correctly | errors (entry 4) appear in the + identified a recent deployment with | historical data, but the timeout + misconfigured timeout values. The | misconfiguration pattern has a + generalized facts confirm that both | documented successful resolution. + connection pool exhaustion and | Given that notification services + configuration changes in deployments | typically depend on downstream systems + are common causes of service | and connection timeouts, this + degradation under load. | represents the highest probability + | root cause. + ----------------------------------------+-------------------------------------- + Investigate: Check for any recent... | Investigate: Check all timeout... + CORRECT | WRONG + Keywords: 4/4 | Keywords: 3/4 ++------------------------------------------------------------------------------+ +| Ground truth: Connection pool to message broker exhausted. | ++------------------------------------------------------------------------------+ + ++------------------------------------------------------------------------------+ +| | +| ROUND 6 [TEST] | auth-service | +| Auth service slow during login spike. | +| | ++----------------------------------------+-------------------------------------+ +| EPISODIC AGENT | BASELINE AGENT | ++----------------------------------------+-------------------------------------+ + Retrieved memory: + [FAILURE] The agent investigated two | * Found misconfigured timeout value in + performance issues: API 500 error => | new config: 1ms instead of 1000ms. + Connection pool to cache was the | * Outcome: success. Root cause: Bad + bottleneck, not network. | config change in v2.14.3 — upstream + [SUCCESS] Outcome: success — Bad | timeo + config change in v2.14.3 — upstream t | * Notification service dropping + [FAILURE] The conversation involved | messages under load. + troubleshooting various production | * Message broker consumer lag + [FAILURE] The agent investigated four | increasing rapidly. + separate service outage incident => | * Fix attempted: Scaled up search + Connection pool starvation, not | service replicas. + compute — need bigger pool. | + [FAILURE] Outcome: failure — Cache | + connection pool saturated under pea | + => Connection pool to cache was the | + bottleneck, not network. | + Generalized facts: + The agent investigated two performance | (N/A -- no fact extraction) + issues: API 500 errors under lo | + [sup=2] | + Connection pool exhaustion is a common | + root cause of service degradati | + [sup=2] | + The conversation involved | + troubleshooting various production | + issues in [sup=1] | + ----------------------------------------+-------------------------------------- + Reasoning: + Looking at my past episodes, there's a | The past entries show a clear pattern + clear pattern where connection pool | of configuration-related issues + exhaustion is the most frequent root | causing performance problems, + cause of service slowdowns under load | specifically entry #1 and #2 which + spikes. Episodes 1, 3, 4, and 5 all | document a timeout misconfiguration + involved connection pool saturation | (1ms instead of 1000ms) that was + (Redis, cache, Elasticsearch, message | successfully resolved. This type of + broker) being misdiagnosed as network | configuration error directly matches + or compute issues. The only successful | our current symptom of auth service + resolution (Episode 2) was a | slowness during load spikes, as + configuration error, but the | inadequate timeouts would cause + overwhelming pattern shows connection | cascading failures under increased + pools failing first during traffic | traffic. + spikes. | + ----------------------------------------+-------------------------------------- + Investigate: Check the connection... | Investigate: Check all recent... + CORRECT | WRONG + Keywords: 4/4 | Keywords: 1/4 ++------------------------------------------------------------------------------+ +| Ground truth: Redis connection pool exhaustion during auth spike. | ++------------------------------------------------------------------------------+ + ++------------------------------------------------------------------------------+ +| | +| ROUND 7 [SUBTLE] | auth-service | +| Auth service latency P99 increased from 50ms to 200ms, no errors. | +| | ++----------------------------------------+-------------------------------------+ +| EPISODIC AGENT | BASELINE AGENT | ++----------------------------------------+-------------------------------------+ + Retrieved memory: + [FAILURE] Outcome: failure — Redis | * Login latency p99 jumped from 200ms + connection pool exhaustion during a | to 8s. + [FAILURE] The agent investigated | * Auth service slow during login + multiple service performance issues i | spike. + [SUCCESS] Outcome: success — Bad | * Found misconfigured timeout value in + config change in v2.14.3 — upstream t | new config: 1ms instead of 1000ms. + [FAILURE] The agent investigated two | * Outcome: failure. Root cause: Redis + performance issues: API 500 error => | connection pool exhaustion during auth + Connection pool to cache was the | * Outcome: success. Root cause: Bad + bottleneck, not network. | config change in v2.14.3 — upstream + [FAILURE] Outcome: failure — | timeo + Connection pool to message broker | + exhaust | + Generalized facts: + The agent investigated two performance | (N/A -- no fact extraction) + issues: API 500 errors under lo | + [sup=3] | + The conversation involved | + troubleshooting various production | + issues in [sup=1] | + Connection pool exhaustion is a common | + root cause of service degradati | + [sup=3] | + ----------------------------------------+-------------------------------------- + Reasoning: + Based on my episodic memory, | The past entries show a clear pattern + connection pool exhaustion is the most | where auth service latency issues were + frequent root cause of auth service | caused by configuration problems, + latency issues, appearing in 4 out of | specifically timeout misconfigurations + 5 past episodes (Episodes 1, 2, 4, and | (1ms instead of 1000ms in entry 5) and + 5). The pattern shows that latency | connection pool exhaustion (entry 4). + increases without errors are commonly | Entry 5 shows a successful resolution + caused by connection pools hitting | where a bad config change in version + limits, forcing requests to queue and | 2.14.3 set upstream timeouts + wait. Episode 3 shows configuration | incorrectly. Since the current + changes can also cause latency, but | incident shows no errors but increased + the overwhelming pattern points to | latency, this points to a + connection pool saturation as the | configuration issue rather than a hard + primary culprit. | failure. + ----------------------------------------+-------------------------------------- + Investigate: Check Redis connection... | Investigate: Check recent... + CORRECT | WRONG + Keywords: 2/4 | Keywords: 2/4 ++------------------------------------------------------------------------------+ +| Ground truth: Connection pool contention — pool sized for average load bu...| ++------------------------------------------------------------------------------+ + ++------------------------------------------------------------------------------+ +| | +| ROUND 8 [CORRECTION] | order-service | +| Order service returning 500 errors after deploying v2.4.1. Errors started...| +| | ++----------------------------------------+-------------------------------------+ +| EPISODIC AGENT | BASELINE AGENT | ++----------------------------------------+-------------------------------------+ + Retrieved memory: + [FAILURE] The conversation involved | * Payment service returning 504 errors + troubleshooting various production | after deployment. + [FAILURE] The user reported API 500 | * API returning 500 errors under load. + errors under load, and the agent i => | * Found misconfigured timeout value in + Was actually Redis connection pool | new config: 1ms instead of 1000ms. + exhaustion, not DB. | * Outcome: success. Root cause: Bad + [FAILURE] The agent investigated | config change in v2.14.3 — upstream + multiple service performance issues i | timeo + [FAILURE] The agent investigated four | * Outcome: failure. Root cause: + separate service outage incident => | Connection pool to message broker + Connection pool starvation, not | exhausted. + compute — need bigger pool. | + [FAILURE] The agent interaction | + documented multiple production inciden | + Generalized facts: + The conversation involved | (N/A -- no fact extraction) + troubleshooting various production | + issues in [sup=1] | + Configuration changes in deployments | + should be immediately suspected w | + [sup=3] | + The agent investigated four separate | + service outage incidents, all inv | + [sup=1] | + ----------------------------------------+-------------------------------------- + Reasoning: + Episodes 1, 4, and 5 show a clear | The past entries show a clear pattern + pattern where deployment-related 500 | where deployment-related 500 errors + errors that start exactly at deploy | were caused by configuration issues, + time are often caused by configuration | specifically timeout misconfigurations + changes, particularly timeout values. | (entries 3 and 4 show a successful + Episode 4 specifically mentions a | resolution where a timeout was set to + successful resolution by "rolling back | 1ms instead of 1000ms). Since the + a deployment that contained a | current incident started exactly at + misconfigured timeout value," and the | deploy time with v2.4.1 and not during + generalized facts emphasize that | traffic spikes, this strongly suggests + "configuration changes in deployments | a configuration change rather than a + should be immediately suspected when | capacity issue. The successful + services fail after releases, | resolution pattern from the past + especially timeout-related | indicates configuration validation + parameters." While connection pool | should be the first step. + exhaustion was the root cause in many | + episodes, those typically occurred | + during traffic spikes, not immediately | + at deploy time like this incident. | + ----------------------------------------+-------------------------------------- + Investigate: Check the v2.4.1... | Investigate: Check all timeout... + CORRECT | CORRECT + Keywords: 4/5 | Keywords: 3/5 ++------------------------------------------------------------------------------+ +| Ground truth: Missing database index from v2.4.1 migration caused full ta...| ++------------------------------------------------------------------------------+ + + >>> TRIGGERING FACT EXTRACTION AFTER ROUND 8 <<< + * The user reported API 500 errors under load, and the agent investigated finding + * The agent investigated two performance issues: API 500 errors under load and pay + * Connection pool exhaustion is a common root cause of service degradation under l + * Database and network connectivity issues are frequently misdiagnosed when the ac + * Configuration changes in deployments should be immediately suspected when servic + * The agent investigated four separate service outage incidents, all involving con + * When multiple infrastructure components exhibit similar symptoms simultaneously, + * The conversation involved troubleshooting various production issues including AP + * Service outages during peak traffic periods typically stem from resource content + * The agent interaction documented multiple production incidents where services ex + * Deployment-related issues such as configuration errors and database migration pr + * Initial symptom-based diagnosis often leads to misidentifying the root cause, re + + ++------------------------------------------------------------------------------+ +| | +| ROUND 9 [CORRECTION] | inventory-service | +| Inventory service errors spiking after v3.1.0 release. Started immediatel...| +| | ++----------------------------------------+-------------------------------------+ +| EPISODIC AGENT | BASELINE AGENT | ++----------------------------------------+-------------------------------------+ + Retrieved memory: + [FAILURE] The conversation involved | * Order service returning 500 errors + troubleshooting various production | after deploying v2.4.1. Errors started + [FAILURE] Outcome: failure — Cache | e + connection pool saturated under pea | * Errors started exactly at deploy + => Connection pool to cache was the | time, not during load. + bottleneck, not network. | * Fix attempted: Rolled back + [FAILURE] The interaction discussed | deployment to v2.14.2. + multiple service outages where the => | * Outcome: success. Root cause: Bad + Not connection pool — errors | config change in v2.14.3 — upstream + correlated with deployment time, not | timeo + load. Root cause was missing DB index | * Found misconfigured timeout value in + from migration. | new config: 1ms instead of 1000ms. + [FAILURE] The agent investigated | + multiple service performance issues i | + [FAILURE] Outcome: failure — | + Connection pool to message broker | + exhaust | + Generalized facts: + The conversation involved | (N/A -- no fact extraction) + troubleshooting various production | + issues in [sup=1] | + Service outages during peak traffic | + periods typically stem from resour | + [sup=1] | + Configuration changes in deployments | + should be immediately suspected w | + [sup=3] | + ----------------------------------------+-------------------------------------- + Reasoning: + Looking at my past episodes, I see a | The past entries show a clear pattern + clear pattern where deployment-related | of deployment-related incidents caused + issues have different root causes than | by configuration errors, specifically + load-based failures. Episode 3 | timeout misconfigurations. In the + specifically shows that when errors | previous case, a bad config change set + spike "immediately after deployment, | an upstream timeout to 1ms instead of + no change in traffic," the issue was | 1000ms, causing immediate 500 errors + NOT connection pool exhaustion but | right at deploy time. The current + rather a missing database index from | incident follows the identical pattern + the migration. The generalized facts | - errors spiking immediately after + confirm that "configuration changes in | deployment with no traffic change. + deployments should be immediately | + suspected when services fail after | + releases." | + ----------------------------------------+-------------------------------------- + Investigate: Check if the v3.1.0... | Investigate: Check the v3.1.0... + CORRECT | CORRECT + Keywords: 5/5 | Keywords: 3/5 ++------------------------------------------------------------------------------+ +| Ground truth: Bad deployment — v3.1.0 introduced a schema mismatch that c...| ++------------------------------------------------------------------------------+ + +================================================================================ + SUMMARY -- Decision Correctness Across All Rounds +================================================================================ + Round Type Service Episodic Baseline Keywords (E/B) + ------------------------------------------------------------------------------ + 1 LEARN api-gateway X OK -- + 2 LEARN payment-service OK OK -- + 3 RED HERRING payment-service X X 1/4 2/4 + 4 LEARN search-service OK OK -- + 5 TEST notification-service OK X 4/4 3/4 + 6 TEST auth-service OK X 4/4 1/4 + 7 SUBTLE auth-service OK X 2/4 2/4 + 8 CORRECTION order-service OK OK 4/5 3/5 + 9 CORRECTION inventory-service OK OK 5/5 3/5 + ------------------------------------------------------------------------------ + TOTAL 7 5 + +================================================================================ + STRUCTURED RETRIEVAL SCORE + (items with explicit outcome labels vs raw outcome mentions) +================================================================================ + Round Type Episodic (labeled/total) Baseline (outcome/total) + -------------------------------------------------------------------------- + 1 LEARN -- -- + 2 LEARN 2/2 1/5 + 3 RED HERRING 4/4 0/5 + 4 LEARN 5/5 1/5 + 5 TEST 5/5 2/5 + 6 TEST 5/5 1/5 + 7 SUBTLE 5/5 2/5 + 8 CORRECTION 5/5 2/5 + 9 CORRECTION 5/5 1/5 + -------------------------------------------------------------------------- + TOTAL 36/36 (100%) 10/40 (25%) + +================================================================================ + PATTERN APPLICATION (Rounds 4-9) + Connection pool pattern: correct when IS root cause, FP when NOT +================================================================================ + Round Type Pool correct? Episodic Baseline + -------------------------------------------------------------------- + 4 LEARN yes correct correct + 5 TEST yes correct missed + 6 TEST yes correct missed + 7 SUBTLE yes correct missed + 8 CORRECTION NO avoided avoided + 9 CORRECTION NO avoided avoided + -------------------------------------------------------------------- + Correct applies: 4 1 + False positives: 0 0 + +================================================================================ + ADAPTATION SCORE (Round 8 → 9: did the agent learn from correction?) +================================================================================ + Episodic Baseline + -------------------------------------------------------------------- + Round 8 applied pool pattern: no no + Round 9 applied pool pattern: no no + Verdict: correctly avoidedcorrectly avoided + +================================================================================ + WEIGHTED SCORE + Correct decision = 1pt | Avoided false positive = 1pt | Adaptation = 2pt +================================================================================ + Episodic Baseline + -------------------------------------------------------------------- + Correct decisions (×1): 7 5 + Avoided false positives (×1): 2 2 + Adaptation bonus (×2): 0 0 + -------------------------------------------------------------------- + WEIGHTED TOTAL: 9 7 + + ==> Episodic memory outperforms flat vector RAG. + +================================================================================ + FINAL EPISODIC MEMORY STATE +================================================================================ + Episodes: 18 + Links: 9 + Facts: 15 + * The user reported API 500 errors under load, and the agent investigated [sup=1 con=0] + * The agent investigated two performance issues: API 500 errors under load [sup=3 con=0] + * Connection pool exhaustion is a common root cause of service degradation [sup=4 con=0] + * Database and network connectivity issues are frequently misdiagnosed whe [sup=1 con=0] + * Configuration changes in deployments should be immediately suspected whe [sup=3 con=0] + * The agent investigated four separate service outage incidents, all invol [sup=2 con=0] + * When multiple infrastructure components exhibit similar symptoms simulta [sup=1 con=0] + * The conversation involved troubleshooting various production issues incl [sup=1 con=0] + * Service outages during peak traffic periods typically stem from resource [sup=1 con=0] + * The agent interaction documented multiple production incidents where ser [sup=2 con=0] + * Deployment-related issues such as configuration errors and database migr [sup=1 con=0] + * Initial symptom-based diagnosis often leads to misidentifying the root c [sup=1 con=0] + * When investigating service performance issues under high load, systemati [sup=1 con=0] + * Connection pool issues are common symptoms during high load scenarios, b [sup=1 con=0] + * Verify recent deployments and configuration changes first when troublesh [sup=1 con=0] \ No newline at end of file diff --git a/experiments/episodic-memory-prototype/tests/__init__.py b/experiments/episodic-memory-prototype/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/experiments/episodic-memory-prototype/tests/test_memory.py b/experiments/episodic-memory-prototype/tests/test_memory.py new file mode 100644 index 00000000..2b3196ac --- /dev/null +++ b/experiments/episodic-memory-prototype/tests/test_memory.py @@ -0,0 +1,729 @@ +"""Tests for the episodic memory system. + +Uses the deterministic hash encoder (no ML model required) so tests are +fast and reproducible. +""" + +from __future__ import annotations + +from datetime import datetime, timedelta, timezone + +import pytest + +from memory.encoder import Encoder +from memory.episodes import EpisodeBoundaryDetector +from memory.facts import FactExtractor +from memory.graph import EpisodeGraph +from memory.models import ( + Episode, + LINK_CONTINUATION, + LINK_LEARNED_FROM, + LINK_RETRY_OF, + OUTCOME_FAILURE, + OUTCOME_SUCCESS, + OUTCOME_UNKNOWN, + ROLE_AGENT, + ROLE_USER, + RetrievalResult, + TimelineEntry, + cosine_similarity, +) +from memory.reinforcer import Reinforcer +from memory.retriever import Retriever +from memory.timeline import Timeline + + +@pytest.fixture +def encoder() -> Encoder: + return Encoder() + + +@pytest.fixture +def timeline() -> Timeline: + tl = Timeline() + tl.append(ROLE_USER, "Hello, I need help with my order.") + tl.append(ROLE_AGENT, "Sure! What's your order number?") + tl.append(ROLE_USER, "It's 12345.") + tl.append(ROLE_AGENT, "I found order 12345. It ships tomorrow.") + tl.append(ROLE_USER, "Can I change the shipping address?") + tl.append(ROLE_AGENT, "Yes, what's the new address?") + tl.append(ROLE_USER, "123 Main St, Springfield.") + tl.append(ROLE_AGENT, "Address updated!") + return tl + + +@pytest.fixture +def state_change_timeline() -> Timeline: + """Timeline with explicit state_label changes to trigger boundaries.""" + tl = Timeline() + tl.append(ROLE_USER, "Let's plan the architecture.", state_label="planning") + tl.append(ROLE_AGENT, "I suggest a microservices approach.", state_label="planning") + tl.append(ROLE_USER, "Now let's debug the auth service.", state_label="debugging") + tl.append(ROLE_AGENT, "I see a null pointer in line 42.", state_label="debugging") + tl.append(ROLE_USER, "Fixed it. Let's review the PR.", state_label="review") + tl.append(ROLE_AGENT, "LGTM, merging now.", state_label="review") + return tl + + +# -- Timeline ------------------------------------------------------------------ + +class TestTimeline: + def test_append_and_length(self): + tl = Timeline() + tl.append(ROLE_USER, "hello") + tl.append(ROLE_AGENT, "hi") + assert len(tl) == 2 + + def test_last(self, timeline: Timeline): + last = timeline.last(2) + assert len(last) == 2 + assert last[-1].raw_content == "Address updated!" + + def test_by_ids(self, timeline: Timeline): + entries = timeline.entries + ids = [entries[0].entry_id, entries[2].entry_id] + result = timeline.by_ids(ids) + assert len(result) == 2 + + def test_sequence_ids_monotonic(self, timeline: Timeline): + seq_ids = [e.sequence_id for e in timeline.entries] + assert seq_ids == list(range(len(seq_ids))) + + def test_get_range(self, timeline: Timeline): + result = timeline.get_range(1, 3) + assert len(result) == 3 + assert result[0].sequence_id == 1 + assert result[-1].sequence_id == 3 + + def test_get_recent(self, timeline: Timeline): + recent = timeline.get_recent(3) + assert len(recent) == 3 + assert recent[-1].raw_content == "Address updated!" + assert recent[0].sequence_id < recent[-1].sequence_id + + def test_append_entry(self): + tl = Timeline() + entry = TimelineEntry(role=ROLE_USER, raw_content="pre-built entry") + tl.append_entry(entry) + assert len(tl) == 1 + assert tl.entries[0].raw_content == "pre-built entry" + assert tl.entries[0].sequence_id == 0 + + def test_sqlite_persistence(self, tmp_path): + db = str(tmp_path / "test.db") + tl1 = Timeline(db_path=db) + tl1.append(ROLE_USER, "persist me") + + tl2 = Timeline(db_path=db) + assert len(tl2) == 1 + assert tl2.entries[0].raw_content == "persist me" + + def test_embedding_roundtrip(self): + tl = Timeline() + entry = TimelineEntry( + role=ROLE_USER, + raw_content="with embedding", + semantic_embedding=[0.1, 0.2, 0.3], + ) + tl.append_entry(entry) + loaded = tl.entries[0] + assert loaded.semantic_embedding == [0.1, 0.2, 0.3] + + +# -- Encoder ------------------------------------------------------------------- + +class TestEncoder: + def test_deterministic(self, encoder: Encoder): + a = encoder.encode_text("hello world") + b = encoder.encode_text("hello world") + assert a == b + + def test_different_texts(self, encoder: Encoder): + a = encoder.encode_text("the cat sat on the mat") + b = encoder.encode_text("quantum mechanics lecture notes") + sim = cosine_similarity(a, b) + assert sim < 0.9 + + def test_embedding_dimension(self, encoder: Encoder): + vec = encoder.encode_text("test") + assert len(vec) == encoder.dim + + def test_state_embedding_deterministic(self, encoder: Encoder): + a = encoder.encode_state("debugging") + b = encoder.encode_state("debugging") + assert a == b + + def test_state_embedding_different_labels(self, encoder: Encoder): + a = encoder.encode_state("debugging") + b = encoder.encode_state("planning") + sim = cosine_similarity(a, b) + assert sim < 0.9 + + def test_state_embedding_cached(self, encoder: Encoder): + encoder.encode_state("debugging") + assert "debugging" in encoder._state_cache + + def test_time_decay_now_is_one(self, encoder: Encoder): + now = datetime.now(timezone.utc) + assert encoder.time_decay(now, reference=now) == pytest.approx(1.0) + + def test_time_decay_decreases(self, encoder: Encoder): + ref = datetime.now(timezone.utc) + old = ref - timedelta(hours=48) + recent = ref - timedelta(hours=1) + assert encoder.time_decay(old, ref) < encoder.time_decay(recent, ref) + + def test_time_decay_half_life(self): + enc = Encoder(time_half_life_hours=24.0) + ref = datetime.now(timezone.utc) + one_day_ago = ref - timedelta(hours=24) + assert enc.time_decay(one_day_ago, ref) == pytest.approx(0.5, abs=0.01) + + +# -- Episode Boundary Detection ------------------------------------------------ + +class TestEpisodeBoundaryDetector: + def test_builds_at_least_one_episode(self, encoder: Encoder, timeline: Timeline): + detector = EpisodeBoundaryDetector(encoder) + episodes = detector.build_episodes(timeline.entries) + assert len(episodes) >= 1 + + def test_episodes_cover_all_entries(self, encoder: Encoder, timeline: Timeline): + detector = EpisodeBoundaryDetector(encoder) + episodes = detector.build_episodes(timeline.entries) + all_ids = set() + for ep in episodes: + all_ids.update(ep.entry_ids) + assert all_ids == {e.entry_id for e in timeline.entries} + + def test_empty_timeline(self, encoder: Encoder): + detector = EpisodeBoundaryDetector(encoder) + assert detector.build_episodes([]) == [] + + def test_episode_has_summary(self, encoder: Encoder, timeline: Timeline): + detector = EpisodeBoundaryDetector(encoder) + episodes = detector.build_episodes(timeline.entries) + for ep in episodes: + assert ep.summary_text + + def test_state_change_triggers_boundary( + self, encoder: Encoder, state_change_timeline: Timeline + ): + detector = EpisodeBoundaryDetector(encoder) + entries = state_change_timeline.entries + boundaries = detector.detect_boundaries(entries) + assert len(boundaries) >= 2, ( + f"Expected >=2 boundaries for 3 state phases, got {boundaries}" + ) + + def test_state_change_produces_multiple_episodes( + self, encoder: Encoder, state_change_timeline: Timeline + ): + detector = EpisodeBoundaryDetector(encoder) + episodes = detector.build_episodes(state_change_timeline.entries) + assert len(episodes) >= 3, ( + f"Expected >=3 episodes for 3 state phases, got {len(episodes)}" + ) + + def test_episode_references_sequence_ids( + self, encoder: Encoder, state_change_timeline: Timeline + ): + detector = EpisodeBoundaryDetector(encoder) + episodes = detector.build_episodes(state_change_timeline.entries) + for ep in episodes: + assert ep.timeline_start <= ep.timeline_end + assert ep.entry_count == len(ep.entry_ids) + + def test_episode_outcome_starts_unknown( + self, encoder: Encoder, timeline: Timeline + ): + detector = EpisodeBoundaryDetector(encoder) + episodes = detector.build_episodes(timeline.entries) + for ep in episodes: + assert ep.outcome == OUTCOME_UNKNOWN + + def test_episode_has_embedding(self, encoder: Encoder, timeline: Timeline): + detector = EpisodeBoundaryDetector(encoder) + episodes = detector.build_episodes(timeline.entries) + for ep in episodes: + assert ep.summary_embedding is not None + assert len(ep.summary_embedding) == encoder.dim + + def test_no_boundary_when_same_state_similar_content(self, encoder: Encoder): + tl = Timeline() + tl.append(ROLE_USER, "Tell me about cats.", state_label="chatting") + tl.append(ROLE_AGENT, "Cats are great pets.", state_label="chatting") + tl.append(ROLE_USER, "What breeds are popular?", state_label="chatting") + + detector = EpisodeBoundaryDetector(encoder) + episodes = detector.build_episodes(tl.entries) + assert len(episodes) == 1 + + def test_boundary_score_weights(self, encoder: Encoder): + detector = EpisodeBoundaryDetector( + encoder, + state_change_weight=0.6, + semantic_shift_weight=0.4, + threshold=0.5, + ) + prev = TimelineEntry(role=ROLE_USER, raw_content="x", state_label="a") + curr = TimelineEntry(role=ROLE_USER, raw_content="x", state_label="b") + prev.semantic_embedding = encoder.encode_text("x") + curr.semantic_embedding = encoder.encode_text("x") + score = detector._boundary_score(prev, curr) + assert score == pytest.approx(0.6, abs=0.01) + + def test_fallback_summary_without_claude(self, encoder: Encoder, timeline: Timeline): + detector = EpisodeBoundaryDetector(encoder, anthropic_client=None) + detector._anthropic = None + episodes = detector.build_episodes(timeline.entries) + for ep in episodes: + assert "entries:" in ep.summary_text + + +# -- Graph --------------------------------------------------------------------- + +class TestGraph: + def test_add_and_retrieve(self, encoder: Encoder, timeline: Timeline): + detector = EpisodeBoundaryDetector(encoder) + episodes = detector.build_episodes(timeline.entries) + graph = EpisodeGraph() + for ep in episodes: + graph.add_episode(ep) + assert len(graph.episode_ids) == len(episodes) + + def test_continuation_links_same_state(self, encoder: Encoder): + """Consecutive episodes with the same state get CONTINUATION links.""" + now = datetime.now(timezone.utc) + ep_a = Episode( + episode_id="a", state="debugging", + start_time=now, end_time=now + timedelta(minutes=5), + summary_embedding=encoder.encode_text("fix bug A"), + ) + ep_b = Episode( + episode_id="b", state="debugging", + start_time=now + timedelta(minutes=6), + end_time=now + timedelta(minutes=10), + summary_embedding=encoder.encode_text("fix bug B"), + ) + graph = EpisodeGraph() + links = graph.auto_link([ep_a, ep_b]) + cont = [l for l in links if l.link_type == LINK_CONTINUATION] + assert len(cont) == 1 + assert cont[0].source_episode_id == "a" + assert cont[0].target_episode_id == "b" + + def test_no_continuation_across_states(self, encoder: Encoder): + now = datetime.now(timezone.utc) + ep_a = Episode( + episode_id="a", state="planning", + start_time=now, end_time=now + timedelta(minutes=5), + summary_embedding=encoder.encode_text("plan"), + ) + ep_b = Episode( + episode_id="b", state="debugging", + start_time=now + timedelta(minutes=6), + end_time=now + timedelta(minutes=10), + summary_embedding=encoder.encode_text("debug"), + ) + graph = EpisodeGraph() + links = graph.auto_link([ep_a, ep_b]) + assert len(links) == 0 + + def test_retry_of_link(self, encoder: Encoder): + """RETRY_OF links a new episode to a prior failed one with same state + and similar embedding.""" + emb = encoder.encode_text("deploy service") + now = datetime.now(timezone.utc) + failed = Episode( + episode_id="fail", state="deploying", + start_time=now, end_time=now + timedelta(minutes=5), + outcome=OUTCOME_FAILURE, + summary_embedding=emb, + ) + retry = Episode( + episode_id="retry", state="deploying", + start_time=now + timedelta(hours=2), + end_time=now + timedelta(hours=2, minutes=5), + summary_embedding=emb, + ) + graph = EpisodeGraph() + links = graph.auto_link([failed, retry]) + retry_links = [l for l in links if l.link_type == LINK_RETRY_OF] + assert len(retry_links) == 1 + assert retry_links[0].source_episode_id == "retry" + assert retry_links[0].target_episode_id == "fail" + + def test_get_links_from(self, encoder: Encoder): + now = datetime.now(timezone.utc) + ep_a = Episode( + episode_id="a", state="debugging", + start_time=now, end_time=now + timedelta(minutes=5), + summary_embedding=encoder.encode_text("x"), + ) + ep_b = Episode( + episode_id="b", state="debugging", + start_time=now + timedelta(minutes=6), + end_time=now + timedelta(minutes=10), + summary_embedding=encoder.encode_text("y"), + ) + graph = EpisodeGraph() + graph.auto_link([ep_a, ep_b]) + from_a = graph.get_links_from("a") + assert len(from_a) >= 1 + assert all(l.source_episode_id == "a" for l in from_a) + assert graph.get_links_from("nonexistent") == [] + + def test_traverse_depth(self, encoder: Encoder): + now = datetime.now(timezone.utc) + eps = [] + for i in range(4): + eps.append(Episode( + episode_id=str(i), state="debugging", + start_time=now + timedelta(minutes=i * 10), + end_time=now + timedelta(minutes=i * 10 + 5), + summary_embedding=encoder.encode_text(f"step {i}"), + )) + graph = EpisodeGraph() + graph.auto_link(eps) + + depth_1 = graph.traverse("0", depth=1) + depth_2 = graph.traverse("0", depth=2) + assert len(depth_1) >= 1 + assert len(depth_2) >= len(depth_1) + + def test_traverse_with_link_type_filter(self, encoder: Encoder): + emb = encoder.encode_text("deploy service") + now = datetime.now(timezone.utc) + failed = Episode( + episode_id="fail", state="deploying", + start_time=now, end_time=now + timedelta(minutes=5), + outcome=OUTCOME_FAILURE, summary_embedding=emb, + ) + cont = Episode( + episode_id="cont", state="deploying", + start_time=now + timedelta(minutes=6), + end_time=now + timedelta(minutes=10), + summary_embedding=emb, + ) + graph = EpisodeGraph() + graph.auto_link([failed, cont]) + only_retry = graph.traverse( + "cont", depth=1, link_types=[LINK_RETRY_OF] + ) + only_cont = graph.traverse( + "fail", depth=1, link_types=[LINK_CONTINUATION] + ) + assert all( + graph.get_episode(ep.episode_id) is not None + for ep in only_retry + only_cont + ) + + def test_find_similar_by_state(self, encoder: Encoder): + emb_target = encoder.encode_text("fix authentication bug") + now = datetime.now(timezone.utc) + ep_match = Episode( + episode_id="match", state="debugging", + start_time=now, end_time=now + timedelta(minutes=5), + summary_embedding=encoder.encode_text("debug auth issue"), + ) + ep_diff_state = Episode( + episode_id="wrong_state", state="planning", + start_time=now, end_time=now + timedelta(minutes=5), + summary_embedding=encoder.encode_text("debug auth issue"), + ) + ep_no_emb = Episode( + episode_id="no_emb", state="debugging", + start_time=now, end_time=now + timedelta(minutes=5), + ) + graph = EpisodeGraph() + for ep in [ep_match, ep_diff_state, ep_no_emb]: + graph.add_episode(ep) + + results = graph.find_similar_by_state("debugging", emb_target, top_k=5) + result_ids = [ep.episode_id for _, ep in results] + assert "match" in result_ids + assert "wrong_state" not in result_ids + assert "no_emb" not in result_ids + + +# -- Facts --------------------------------------------------------------------- + +class TestFacts: + def test_extract_creates_fact(self, encoder: Encoder, timeline: Timeline): + detector = EpisodeBoundaryDetector(encoder) + episodes = detector.build_episodes(timeline.entries) + extractor = FactExtractor(encoder) + for ep in episodes: + extractor.extract(ep) + assert len(extractor.facts) >= 1 + + def test_query_returns_results(self, encoder: Encoder, timeline: Timeline): + detector = EpisodeBoundaryDetector(encoder) + episodes = detector.build_episodes(timeline.entries) + extractor = FactExtractor(encoder) + for ep in episodes: + extractor.extract(ep) + results = extractor.query("order shipping") + assert len(results) >= 1 + + def test_extract_patterns_batch(self, encoder: Encoder): + episodes = [ + Episode(episode_id=f"e{i}", summary_text=f"Summary of episode {i}") + for i in range(4) + ] + extractor = FactExtractor(encoder, min_episodes_for_extraction=3) + extractor.extract_patterns(episodes) + assert len(extractor.facts) >= 1 + + def test_auto_trigger_at_threshold(self, encoder: Encoder): + extractor = FactExtractor(encoder, min_episodes_for_extraction=3) + for i in range(3): + extractor.extract( + Episode(episode_id=f"e{i}", summary_text=f"Event {i} happened") + ) + assert len(extractor.facts) >= 1 + + def test_below_threshold_no_crash(self, encoder: Encoder): + extractor = FactExtractor(encoder, min_episodes_for_extraction=5) + extractor.extract(Episode(episode_id="e0", summary_text="Only one")) + assert len(extractor.facts) >= 1 + + def test_merge_similar_facts(self, encoder: Encoder): + extractor = FactExtractor(encoder, merge_threshold=0.80) + ep1 = Episode(episode_id="a", summary_text="exact same text") + ep2 = Episode(episode_id="b", summary_text="exact same text") + extractor.extract(ep1) + extractor.extract(ep2) + assert len(extractor.facts) == 1 + assert extractor.facts[0].support_count >= 2 + + def test_facts_have_embedding(self, encoder: Encoder): + extractor = FactExtractor(encoder) + extractor.extract(Episode(episode_id="x", summary_text="test fact")) + for f in extractor.facts: + assert f.fact_embedding is not None + assert len(f.fact_embedding) == encoder.dim + + +# -- Retriever ----------------------------------------------------------------- + +class TestRetriever: + def _build_retriever(self, encoder, episodes): + """Helper: graph + fact extractor + retriever from a list of episodes.""" + graph = EpisodeGraph() + graph.auto_link(episodes) + extractor = FactExtractor(encoder) + for ep in episodes: + extractor.extract(ep) + retriever = Retriever( + encoder=encoder, graph=graph, fact_extractor=extractor + ) + return retriever, graph, extractor + + def test_basic_retrieval(self, encoder: Encoder, timeline: Timeline): + detector = EpisodeBoundaryDetector(encoder) + episodes = detector.build_episodes(timeline.entries) + retriever, _, _ = self._build_retriever(encoder, episodes) + + result = retriever.retrieve("What was the order number?") + assert len(result.episodes) >= 1 + assert len(result.scores) == len(result.episodes) + + def test_state_filter_prefers_matching_state(self, encoder: Encoder): + now = datetime.now(timezone.utc) + emb = encoder.encode_text("auth issue") + ep_debug = Episode( + episode_id="dbg", state="debugging", + start_time=now, end_time=now + timedelta(minutes=5), + summary_text="fix null pointer", + summary_embedding=emb, + ) + ep_plan = Episode( + episode_id="pln", state="planning", + start_time=now + timedelta(minutes=10), + end_time=now + timedelta(minutes=15), + summary_text="plan auth module", + summary_embedding=emb, + ) + retriever, _, _ = self._build_retriever(encoder, [ep_debug, ep_plan]) + result = retriever.retrieve("auth issue", current_state="debugging") + assert result.episodes[0].episode_id == "dbg" + + def test_failure_boost(self, encoder: Encoder): + now = datetime.now(timezone.utc) + emb = encoder.encode_text("deploy the service") + ep_ok = Episode( + episode_id="ok", state="deploying", + start_time=now, end_time=now + timedelta(minutes=5), + summary_text="deploy service", + summary_embedding=emb, + outcome="success", + ) + ep_fail = Episode( + episode_id="fail", state="deploying", + start_time=now + timedelta(minutes=10), + end_time=now + timedelta(minutes=15), + summary_text="deploy service", + summary_embedding=emb, + outcome=OUTCOME_FAILURE, + ) + retriever, _, _ = self._build_retriever(encoder, [ep_ok, ep_fail]) + result = retriever.retrieve("deploy the service") + assert result.episodes[0].episode_id == "fail" + + def test_graph_traversal_expands_results(self, encoder: Encoder): + now = datetime.now(timezone.utc) + ep_a = Episode( + episode_id="a", state="debugging", + start_time=now, end_time=now + timedelta(minutes=5), + summary_text="fix bug A", + summary_embedding=encoder.encode_text("fix bug A"), + ) + ep_b = Episode( + episode_id="b", state="debugging", + start_time=now + timedelta(minutes=6), + end_time=now + timedelta(minutes=10), + summary_text="fix bug B (unrelated text)", + summary_embedding=encoder.encode_text("completely different topic xyz"), + ) + retriever_trav, _, _ = self._build_retriever(encoder, [ep_a, ep_b]) + result_with = retriever_trav.retrieve("fix bug A", traverse=True) + + graph_no = EpisodeGraph() + for ep in [ep_a, ep_b]: + graph_no.add_episode(ep) + ext_no = FactExtractor(encoder) + retriever_no = Retriever( + encoder=encoder, graph=graph_no, fact_extractor=ext_no, + top_k_episodes=1, + ) + result_without = retriever_no.retrieve("fix bug A", traverse=False) + + with_ids = {ep.episode_id for ep in result_with.episodes} + without_ids = {ep.episode_id for ep in result_without.episodes} + assert with_ids >= without_ids + + def test_narrative_is_populated(self, encoder: Encoder, timeline: Timeline): + detector = EpisodeBoundaryDetector(encoder) + episodes = detector.build_episodes(timeline.entries) + retriever, _, _ = self._build_retriever(encoder, episodes) + + result = retriever.retrieve("order number") + assert result.causal_narrative + assert "→" in result.causal_narrative or len(result.episodes) == 1 + + def test_retrieve_without_state(self, encoder: Encoder, timeline: Timeline): + """Backward-compat: retrieve(query) still works with no state.""" + detector = EpisodeBoundaryDetector(encoder) + episodes = detector.build_episodes(timeline.entries) + retriever, _, _ = self._build_retriever(encoder, episodes) + + result = retriever.retrieve("order") + assert len(result.episodes) >= 1 + + +# -- Reinforcer ---------------------------------------------------------------- + +class TestReinforcer: + def _build_stack(self, encoder, episodes): + graph = EpisodeGraph() + graph.auto_link(episodes) + extractor = FactExtractor(encoder) + for ep in episodes: + extractor.extract(ep) + retriever = Retriever( + encoder=encoder, graph=graph, fact_extractor=extractor + ) + reinforcer = Reinforcer( + encoder=encoder, graph=graph, fact_extractor=extractor + ) + return retriever, reinforcer, graph, extractor + + def test_reinforce_creates_outcome_episode(self, encoder: Encoder, timeline: Timeline): + detector = EpisodeBoundaryDetector(encoder) + episodes = detector.build_episodes(timeline.entries) + retriever, reinforcer, graph, _ = self._build_stack(encoder, episodes) + + result = retriever.retrieve("shipping address") + before_count = len(graph.episode_ids) + outcome_ep = reinforcer.reinforce(result, OUTCOME_SUCCESS, "All good") + assert outcome_ep is not None + assert len(graph.episode_ids) == before_count + 1 + assert outcome_ep.outcome == OUTCOME_SUCCESS + + def test_reinforce_links_to_retrieved(self, encoder: Encoder, timeline: Timeline): + detector = EpisodeBoundaryDetector(encoder) + episodes = detector.build_episodes(timeline.entries) + retriever, reinforcer, graph, _ = self._build_stack(encoder, episodes) + + result = retriever.retrieve("shipping address") + outcome_ep = reinforcer.reinforce(result, OUTCOME_SUCCESS) + links = graph.get_links_from(outcome_ep.episode_id) + assert len(links) == len(result.episodes) + assert all(l.link_type == LINK_LEARNED_FROM for l in links) + + def test_reinforce_updates_episode_scores(self, encoder: Encoder, timeline: Timeline): + detector = EpisodeBoundaryDetector(encoder) + episodes = detector.build_episodes(timeline.entries) + retriever, reinforcer, graph, _ = self._build_stack(encoder, episodes) + + result = retriever.retrieve("shipping address") + reinforcer.reinforce(result, OUTCOME_SUCCESS) + for ep in result.episodes: + stored = graph.get_episode(ep.episode_id) + if stored: + assert stored.outcome_score != 0.0 + + def test_reinforce_failure_contradicts_facts(self, encoder: Encoder): + ep = Episode( + episode_id="ep1", summary_text="Always restart before deploying", + summary_embedding=encoder.encode_text("Always restart before deploying"), + state="deploying", + ) + _, reinforcer, graph, extractor = self._build_stack(encoder, [ep]) + + facts_before = extractor.facts + initial_contradiction = ( + facts_before[0].contradiction_count if facts_before else 0 + ) + + result = RetrievalResult( + episodes=[ep], + facts=list(extractor.facts), + scores=[1.0], + ) + reinforcer.reinforce(result, OUTCOME_FAILURE, "Deploy crashed") + + for f in extractor.facts: + assert f.contradiction_count > initial_contradiction + + def test_reinforce_success_supports_facts(self, encoder: Encoder): + ep = Episode( + episode_id="ep1", summary_text="Check logs before restarting", + summary_embedding=encoder.encode_text("Check logs before restarting"), + state="debugging", + ) + _, reinforcer, graph, extractor = self._build_stack(encoder, [ep]) + + facts_before = extractor.facts + initial_support = facts_before[0].support_count if facts_before else 0 + + result = RetrievalResult( + episodes=[ep], + facts=list(extractor.facts), + scores=[1.0], + ) + reinforcer.reinforce(result, OUTCOME_SUCCESS, "Issue resolved") + + for f in extractor.facts: + assert f.support_count > initial_support + + def test_reinforce_empty_result(self, encoder: Encoder): + graph = EpisodeGraph() + extractor = FactExtractor(encoder) + reinforcer = Reinforcer( + encoder=encoder, graph=graph, fact_extractor=extractor + ) + result = RetrievalResult() + assert reinforcer.reinforce(result, OUTCOME_SUCCESS) is None diff --git a/flashring/.cursor/rules/global/golang/code-quality.mdc b/flashring/.cursor/rules/global/golang/code-quality.mdc new file mode 100644 index 00000000..9a48e63e --- /dev/null +++ b/flashring/.cursor/rules/global/golang/code-quality.mdc @@ -0,0 +1,17 @@ +--- +description: "Guidelines for maintaining code quality, clarity, and consistency across imports, function usage, and documentation" +globs: +alwaysApply: false +--- + +### Import statements quality +1. Do not change the import order of existing import files and packages, just add the new ones +2. Avoid wild card imports + +### Functions usage +1. Do not change the private functions to public unless we want other packages to use it +2. If the same package is using the function then keep it private + +### Code comments and documentation +1. Add comments only when necessary, do not add comments when the code itself gives the understanding +2. Add comments for architectural decision diff --git a/flashring/.cursor/rules/global/golang/compliance.mdc b/flashring/.cursor/rules/global/golang/compliance.mdc new file mode 100644 index 00000000..2b802036 --- /dev/null +++ b/flashring/.cursor/rules/global/golang/compliance.mdc @@ -0,0 +1,36 @@ +--- +description: "Provide the list of rules which are obeyed in every response" +globs: +alwaysApply: true +--- + +# Rule Compliance Tracking + +## Response Format Requirement +When responding to user queries about code patterns, architecture, or implementation examples: + +1. **Always List Obeyed Rules**: At the end of your response, include a section titled "**Rules Followed:**" that lists the specific rules from the codebase that are being obeyed or demonstrated in your response. + +2. **Rule Identification**: Identify rules from the following files: + - `.cursor/rules/unit-testing.mdc` + - `.cursor/rules/requirement-planning.mdc` + - `.cursor/rules/code-quality.mdc` + - `.cursor/rules/compliance.mdc` + +3. **Example Format**: + ``` + **Rules Followed:** + - Dependency Injection Pattern + - Configuration Management + - Interface Design + ``` + +4. **Context Awareness**: Only list rules that are actually relevant to the specific response or examples provided. + +5. **Rule Verification**: When analyzing code examples, verify which rule categories are being followed and explicitly mention the rule headings. + +## Implementation Guidelines +- Scan the codebase for examples that demonstrate rule compliance +- Provide concrete code examples when possible +- Explain how the examples follow the specific rule categories +- Use the rule headings/categories from the source files when listing them diff --git a/flashring/.cursor/rules/global/golang/data-interface-concurrency-errors.mdc b/flashring/.cursor/rules/global/golang/data-interface-concurrency-errors.mdc new file mode 100644 index 00000000..f359b79e --- /dev/null +++ b/flashring/.cursor/rules/global/golang/data-interface-concurrency-errors.mdc @@ -0,0 +1,61 @@ +--- +description: These rules cover data shapes, allocation, interface design, and safe concurrency/error handling—areas where AI can subtly violate idioms or introduce leaks and races. +alwaysApply: false +globs: *.go +--- + +### Effective Go Rules: Data, Allocation, Interfaces, Concurrency, Errors + +These rules cover data shapes, allocation, interface design, and safe concurrency/error handling—areas where AI can subtly violate idioms or introduce leaks and races. + +## Arrays, Slices, Maps +- Reassign the result of `append`; capacity may change. +- Preallocate capacity when known; use `copy` for duplication. +- If copying a slice or map, the pointers nested in these will not be copied. +- Maps: missing keys yield zero values; use comma-ok to test presence. + +Example: +```go +v, ok := m[key] +if !ok { /* handle missing */ } +``` + +## Variadics & Append +- Use `...T` for flexible APIs; forward with `f(v...)`. +- Concatenate slices with `append(dst, src...)`. + + +## Methods: Pointer vs Value +- Pointer receivers for mutation/big copies. + +## Interfaces +- Define small, behavior-driven interfaces near use sites. +- Constructors should return the narrowest interface needed. +- Convert types to reuse method sets (e.g., `sort.IntSlice(s).Sort()`). + + +## Concurrency Basics +- Start goroutines only when beneficial; ensure they can terminate. +- Use channels for synchronization/communication; avoid shared memory without coordination. + +Use Worker pool at appropriate places: +```go +jobs := make(chan Job) +for i := 0; i < N; i++ { go func() { for j := range jobs { handle(j) } }() } +``` + +Channel ownership: +- Close channels only from the sender/owner. + +## Context & Cancellation +- Accept `context.Context` and honor `Done()` for long-lived operations. +- Avoid `time.Sleep` polling; use timers/tickers with `select`. + +## Panic, Recover, Errors +- Prefer `error` returns; reserve `panic` for unrecoverable programmer errors. +- At boundaries, `defer` + `recover` to convert internal panics to errors; type-assert expected panic values. +- Structure error strings without caps/punctuation; prefix with operation or package when helpful. +- Avoid double-reporting (log and return); choose a single owner. +- Use rich error types (ex: `*os.PathError`) and `%w` wrapping. +- Always try to return a meaningfull error code and message, client should be able to find out the failure reason without exposing internal details. +- Either log or return - not both. If you're returning an API response, then log it as well. diff --git a/flashring/.cursor/rules/global/golang/formatting-control.mdc b/flashring/.cursor/rules/global/golang/formatting-control.mdc new file mode 100644 index 00000000..e9f6d7ec --- /dev/null +++ b/flashring/.cursor/rules/global/golang/formatting-control.mdc @@ -0,0 +1,34 @@ +--- +description: These rules focus on the places where AI often drifts from idiomatic Go,, naming that avoids stutter, brace placement, early-returns, and minimalistic control flow. Follow these to produce idiomatic, readable Go. +alwaysApply: false +globs: *.go +--- + +### Effective Go Rules: Formatting, Comments, Naming, Semicolons, Control Flow + +These rules focus on the places where AI often drifts from idiomatic Go: formatting handled by tools, naming that avoids stutter, brace placement, early-returns, and minimalistic control flow. Follow these to produce idiomatic, readable Go. + + +## Naming +- Do not prefix getters with `Get`; prefer `Owner()` over `GetOwner()`; setters as `SetOwner(x)`. + +## Control Flow Essentials +- Prefer early returns for errors; let the success path flow downward. +- Omit `else` when the `if` body ends in `return/break/continue`. +- Use `if`/`switch` init statements to scope locals. +- Prefer `switch` over long `if-else` chains; avoid implicit fallthrough. + +## `defer` and Resources +- Place `defer` immediately after acquiring a resource; args evaluated at `defer` time. +- Deferred calls run LIFO. Avoid deferring inside hot loops. + +## Printing and Diagnostics +- Prefer `%v` for values, `%+v` to include field names, `%#v` for Go syntax, `%T` for type. + +## Shadowing and Short Decls +- Reuse `err` with `:=` when at least one new variable exists; avoid accidental shadowing across scopes. +- Declare variables at first use; avoid predeclaring far from usage. + + +## Extra +- Prefer `const` for stable magic numbers; otherwise localize and comment literals. diff --git a/flashring/.cursor/rules/global/golang/requirement-planning.mdc b/flashring/.cursor/rules/global/golang/requirement-planning.mdc new file mode 100644 index 00000000..868ffa59 --- /dev/null +++ b/flashring/.cursor/rules/global/golang/requirement-planning.mdc @@ -0,0 +1,12 @@ +--- +description: "Promotes thorough requirement analysis, careful planning, and implementation of robust, general-purpose solutions." +alwaysApply: false +--- +### Requirement Understanding + +Before creating a plan you MUST ask questions from the user to get clarity on the problem statement + +1. Break down a problem statement into smaller problem statements +2. Please write a high quality, general purpose solution. Implement a solution that works correctly for all valid inputs, not just the test cases. Do not hard-code values or create solutions that only work for specific test inputs. Instead, implement the actual logic that solves the problem generally. +3. Focus on understanding the problem requirements and implementing the correct algorithm. Tests are there to verify correctness, not to define the solution. Provide a principled implementation that follows best practices and software design principles. +4. If the task is unreasonable or infeasible, or if any of the tests are incorrect, please tell me. The solution should be robust, maintainable, and extendable. diff --git a/flashring/.cursor/rules/global/golang/unit-testing.mdc b/flashring/.cursor/rules/global/golang/unit-testing.mdc new file mode 100644 index 00000000..c159d66f --- /dev/null +++ b/flashring/.cursor/rules/global/golang/unit-testing.mdc @@ -0,0 +1,108 @@ +--- +description: "Go testing patterns including test structure, mocking, test data, assertions and more" +globs: *.go +alwaysApply: false +--- + +# Go Testing Patterns + +## Instructions + +### Test Plan Creation + +When writing unit tests, get clarity on the system under test and come up with a list of what all cases can be possible within that system. Generate the unit test for all those cases + +### Coverage Guidelines +- MUST run `go test -cover` to measure coverage, instead of calculating yourself and aim for 80%+ code coverage for critical business logic +- Focus on testing behavior, not just coverage percentage +- Test error paths and edge cases + +## Test Structure and Organization + +### Test File Organization +- Group related tests using subtests with `t.Run()` +- If there is a single test, then don't use `t.Run()` +- Generate Go tests using testify's suite package for structure wherever applicable + +### Test Naming Conventions +Use descriptive test names that clearly indicate the scenario: + +```go +// ✅ Good - Clear scenario description +func TestConfigService_GetConfig_ReturnsConfigWhenFound(t *testing.T) {} +func TestConfigService_GetConfig_ReturnsErrorWhenNotFound(t *testing.T) {} +func TestConfigService_ValidateConfig_ReturnsErrorOnInvalidData(t *testing.T) {} + +// ❌ Avoid - Vague test names +func TestGetConfig(t *testing.T) {} +func TestValidation(t *testing.T) {} +``` + +## Mocking and Dependencies + +### Interface-Based Mocking +Use interfaces for dependencies to enable easy mocking + +### Mock Setup Patterns +mockRepo := mocks.NewMockRepository(ctrl) +mockRepo.EXPECT(). + GetConfig("test-id"). + Return(&Config{ID: "test-id"}, nil). + Times(1) + +## Test Data and Fixtures + +### Test Data Creation +Create helper functions for building test data where the test data is common + +### Table-Driven Tests +Use table-driven tests for testing multiple scenarios + +## Assertion Patterns + +### Using testify/assert +Prefer testify/assert for better error messages + +```go +// ✅ Good - Clear assertions with testify +assert.NoError(t, err) +assert.Equal(t, expectedValue, actualValue) +assert.Contains(t, slice, element) +assert.Len(t, collection, expectedLength) + +// ❌ Avoid - Basic Go testing with poor error messages +if err != nil { + t.Errorf("expected no error, got %v", err) +} +``` + +### Error Testing +```go +// Testing specific error types +assert.ErrorIs(t, err, ErrConfigNotFound) +assert.ErrorAs(t, err, &validationErr) +assert.NoError(t, err) + +// Testing error messages +assert.EqualError(t, err, "expected error message") +assert.Contains(t, err.Error(), "partial error message") +``` + +## HTTP Handler Testing + +### Testing HTTP Handlers +Use `httptest` for testing HTTP handlers + +## Test Quality + +### Test Organization +- Use setup/teardown functions for complex test scenarios +- Keep tests independent - one test should not depend on another +- Use parallel tests where appropriate: `t.Parallel()` + +### Common Pitfalls to Avoid +1. **Testing Implementation Details**: Focus on behavior, not internal implementation +2. **Ignoring Error Cases**: Always test both success and error scenarios +3. **Flaky Tests**: Avoid time-dependent tests, use deterministic test data +4. **Over-Mocking**: Don't mock everything, test real integrations where valuable +5. **Poor Test Data**: Use realistic test data that reflects production scenarios diff --git a/flashring/.cursor/rules/global/security-golang/secure-golang-rules.mdc b/flashring/.cursor/rules/global/security-golang/secure-golang-rules.mdc new file mode 100644 index 00000000..49cffe70 --- /dev/null +++ b/flashring/.cursor/rules/global/security-golang/secure-golang-rules.mdc @@ -0,0 +1,211 @@ +--- +description: Rules to ensure secure coding in Golang +globs: **/*.go +alwaysApply: true +--- +These rules apply to all Go code in the repository and aim to prevent common security risks through disciplined input handling, safe APIs, and secure defaults. + +All violations must include a clear explanation of which rule was triggered and why, so developers can fix issues quickly.\ +Generated code must not violate these rules. If a rule is violated, add a code comment that explains the problem and proposes a correction. + +## 1. Decode Untrusted Data Safely + +- Do not deserialize untrusted data with unsafe or permissive decoders. Prefer strict JSON or protobuf with size limits. Reject unknown fields. Avoid `encoding/gob` for untrusted input. Use strict YAML decoding only if required. +- + ```go + // Accepts arbitrarily large input and unknown fields + var in any + _ = json.NewDecoder(r.Body).Decode(&in) + ``` +- + ```go + type CreateUser struct { + Name string `json:"name"` + Email string `json:"email"` + } + + dec := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20)) // 1 MB cap + dec.DisallowUnknownFields() + dec.UseNumber() + + var in CreateUser + if err := dec.Decode(&in); err != nil { /* handle */ } + ``` +- **YAML (only if needed):** + ```go + dec := yaml.NewDecoder(bytes.NewReader(b)) + dec.KnownFields(true) // yaml.v3 + if err := dec.Decode(&cfg); err != nil { /* handle */ } + ``` +- **Protobuf JSON:** + ```go + opts := protojson.UnmarshalOptions{DiscardUnknown: false} + if err := opts.Unmarshal(b, msg); err != nil { /* handle */ } + ``` + +## 2. Use Parameterized Queries for Database Access + +- Never format SQL or NoSQL queries with user input. Use placeholders and arguments. Use context with timeouts. +- + ```go + query := fmt.Sprintf("SELECT * FROM users WHERE name = '%s'", name) + rows, _ := db.Query(query) + ``` +- + ```go + ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second) + defer cancel() + + row := db.QueryRowContext(ctx, "SELECT id FROM users WHERE name = $1", name) + ``` + +## 3. Prevent Command Injection + +- Do not pass untrusted input to shells. Use `exec.CommandContext` with fixed program and separate args. Validate inputs against allow lists. +- + ```go + exec.Command("sh", "-c", "ls "+userArg).Run() + ``` +- + ```go + // validatedArg must pass strict allow list or regex + cmd := exec.CommandContext(ctx, "ls", validatedArg) + cmd.Stdout = w + cmd.Stderr = w + _ = cmd.Run() + ``` + +## 4. Prevent Path Traversal Vulnerabilities + +- : Do not use untrusted input directly in filesystem APIs (os.Open, os.ReadFile, os.Create). + Always sanitize path input to remove traversal elements and enforce a strict allow-list for filenames. +- : If the filename doesn't need to be user-controlled, use a UUID or another randomly generated string instead. This is the most secure method. +- : + ```go + import "path/filepath" + + fileName := r.URL.Query().Get("filename") // Attacker can provide "../../etc/passwd" as a filename + path := filepath.Join("/var/data/", fileName) + data, err := os.ReadFile(path) + ``` +- (Never use user input directly. Sanitize it with filepath.Base and validate with an allow-list): + ```go + import ( + "path/filepath" + "regexp" + ) + + userInput := r.URL.Query().Get("filename") + + // 1. Sanitize the input to get only the final path component. + // removes and turns "../../etc/passwd" into "passwd". + filename := filepath.Base(userInput) + + // 2. Validate the sanitized filename against a strict allow-list. + isValid, _ := regexp.MatchString(`^[A-Za-z0-9_-]{1,200}\.png$`, filename) + + if !isValid { + return + } + ``` + +// 3. Now it's safe to join with the base directory. +safePath := filepath.Join("/var/data/", filename) + ``` + +## 5. Template Safety + +- Use `html/template` for HTML to get auto-escaping. Never use `text/template` for HTML. +- + ```go + t := template.Must(template.New("x").Parse("
    {{.UserInput}}
    ")) + ``` +- + ```go + t := template.Must(htmltemplate.New("x").Parse("
    {{.UserInput}}
    ")) + ``` + +## 6. Log and Error Hygiene + +- Do not log secrets, tokens, personal data, or full request bodies. Redact sensitive fields. Return generic error messages to clients. Use a recover middleware to hide panics. +- + ```go + // Example redaction + logger.Info("login", "user", in.Email, "token", "[redacted]") + + // Recover middleware + func Recover(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + defer func() { + if rec := recover(); rec != nil { + http.Error(w, "internal error", http.StatusInternalServerError) + } + }() + next.ServeHTTP(w, r) + }) + } + ``` + +## 7. Concurrency and Race Safety + +- Avoid TOCTOU on files and permissions. Guard shared state. +- + ```go + f, err := os.OpenFile(p, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600) + ``` + +## 8. Cookies and Sessions + +- Set `Secure`, `HttpOnly`, and an appropriate `SameSite`. Do not store secrets in client cookies unless encrypted and signed with a server key. +- + ```go + http.SetCookie(w, &http.Cookie{ + Name: "sid", + Value: token, + Secure: true, + HttpOnly: true, + SameSite: http.SameSiteLaxMode, + Path: "/", + }) + ``` + +## 9. CSRF and CORS + +- Use CSRF protections for state-changing requests in browser-based apps. For CORS, allow list origins and avoid `Access-Control-Allow-Origin: *` with credentials. +- : + ```go + // Pseudocode: only allow https://app.example.com + w.Header().Set("Access-Control-Allow-Origin", "https://app.example.com") + w.Header().Set("Vary", "Origin") + ``` + +## 10. Avoid Reflection, `unsafe`, and Cgo for Untrusted Data + +- Do not use `reflect`, `unsafe`, or Cgo to parse or transform untrusted inputs. Keep type boundaries strict. + +## 11. Operational Hardening + +- Run with least privilege. In containers, avoid root, drop capabilities, mount only what you need. Never expose debug endpoints publicly. +- + ```go + // Exposing pprof on 0.0.0.0 in prod + http.ListenAndServe(":6060", http.DefaultServeMux) + ``` +- + ```go + // Bind pprof to localhost only, or protect with auth and network policy + go func() { _ = http.ListenAndServe("127.0.0.1:6060", nil) }() + ``` +## 12. Parsing XML Securly: +- Use encoding/xml for parsing XML. It does not support DTDs or external entities, so it’s safe against XXE by default. Always limit input size and nesting depth to prevent denial-of-service. +- Never use third-party XML libraries that enable DTDs or external entities. Wrap input with io.LimitReader to cap size. Track nesting depth in custom decoders to prevent stack exhaustion. Validate parsed data against business rules (schemas, required fields, etc.). + +--- + +### Additional Guidance + +- Check inputs as early as possible: validate type, length, format, and restrict to safe values. +- Impose strict size limits on untrusted data such as HTTP requests, file uploads, or archives. +- Avoid reflecting user-controlled content in error messages, HTML, or logs. +- Use `context.Context` to manage timeouts and cancellations consistently across I/O operations. +- Clearly explain any deviations from these rules in the code, along with a follow-up task to remove them. diff --git a/flashring/.cursor/rules/global/security-golang/ssrf-prevention-rules.mdc b/flashring/.cursor/rules/global/security-golang/ssrf-prevention-rules.mdc new file mode 100644 index 00000000..fd8906b7 --- /dev/null +++ b/flashring/.cursor/rules/global/security-golang/ssrf-prevention-rules.mdc @@ -0,0 +1,30 @@ +--- +description: "Rules to prevent Server-Side Request Forgery (SSRF) vulnerabilities" +globs: +alwaysApply: true +--- +# SSRF Prevention + +These rules apply to all code that performs outbound network requests, regardless of language or framework, including generated code. + +All violations must include a clear explanation of which rule was triggered and why, to help developers understand and fix the issue effectively. +Generated code must not violate these rules. If a rule is violated, a comment must be added explaining the issue and suggesting a correction. + +## 1. Do Not Allow User Input to Control Target URLs +- **Rule:** Never use raw or unchecked user input as the destination for outbound HTTP requests. +- **Example (unsafe):** + ```python + requests.get(request.args["url"]) + ``` + +## 2. Block Access to Private/Internal IP Ranges +- **Rule:** Outbound requests must not be allowed to reach `localhost`, `127.0.0.1`, `169.254.0.0/16`, `192.168.0.0/16`, `10.0.0.0/8`, or other internal/reserved ranges. + +## 3. Resolve and Validate Hostnames Before Use +- **Rule:** Perform DNS resolution and validate that the resolved IP is in an allowed range before initiating a request. + +## 4. Restrict Allowed Protocols and Ports +- **Rule:** Only allow HTTP/HTTPS protocols and known-safe ports (e.g., 80, 443). Block access to file URLs, gopher, FTP, or custom handlers. + +## 5. Do Not Forward Authorization Headers Automatically +- **Rule:** Never pass internal tokens, cookies, or auth headers when proxying or forwarding outbound requests unless explicitly scoped and audited. diff --git a/flashring/.vscode/launch.json b/flashring/.vscode/launch.json deleted file mode 100644 index 6ae01079..00000000 --- a/flashring/.vscode/launch.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - - { - "name": "Flashring", - "type": "go", - "request": "launch", - "mode": "debug", - "program": "/home/a0d00kc/Desktop/BharatMLStack/flashring/cmd/flashringtest/main.go", - "env": { - "GODEBUG": "asyncpreemptoff=1" - } - }, - ] -} \ No newline at end of file diff --git a/flashring/cmd/flashringtest/__debug_bin2081587258 b/flashring/cmd/flashringtest/__debug_bin2081587258 deleted file mode 100755 index c90caa97..00000000 Binary files a/flashring/cmd/flashringtest/__debug_bin2081587258 and /dev/null differ diff --git a/flashring/cmd/flashringtest/main.go b/flashring/cmd/flashringtest/main.go index 57051662..39379189 100644 --- a/flashring/cmd/flashringtest/main.go +++ b/flashring/cmd/flashringtest/main.go @@ -1,25 +1,33 @@ package main import ( + "flag" + "fmt" + "math/bits" "math/rand" + "net/http" "os" + "runtime" + "runtime/pprof" + "sync/atomic" + "time" _ "net/http/pprof" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" ) // normalDistInt returns an integer in [0, max) following a normal distribution -// centered at max/2 with standard deviation = max/6 (so ~99.7% values are in range) +// centered at max/2 with standard deviation = max/8 func normalDistInt(max int) int { if max <= 0 { return 0 } - mean := float64(max) / 2.0 stdDev := float64(max) / 8.0 - for { val := rand.NormFloat64()*stdDev + mean - if val >= 0 && val < float64(max) { return int(val) } @@ -27,79 +35,243 @@ func normalDistInt(max int) int { } // normalDistIntPartitioned returns an integer following a normal distribution -// centered at the middle of the total key space, but constrained to a specific -// worker's partition. Workers assigned to ranges near the center will naturally -// get more load, while workers at the edges get less load. -// workerID: the ID of the worker (0-indexed) -// numWorkers: total number of workers -// totalKeys: total number of keys across all partitions +// constrained to a specific worker's partition of the total key space. func normalDistIntPartitioned(workerID, numWorkers, totalKeys int) int { if totalKeys <= 0 || numWorkers <= 0 { return 0 } - - // Calculate partition boundaries for this worker partitionSize := totalKeys / numWorkers partitionStart := workerID * partitionSize partitionEnd := partitionStart + partitionSize - - // Last worker takes any remaining keys if workerID == numWorkers-1 { partitionEnd = totalKeys } - - // All workers sample from the same distribution centered at the middle mean := float64(totalKeys) / 2.0 stdDev := float64(totalKeys) / 8.0 - - // Keep sampling until we get a value in this worker's partition for { val := rand.NormFloat64()*stdDev + mean - if val >= float64(partitionStart) && val < float64(partitionEnd) { return int(val) } } } +// ---- Shared metrics & profiling infrastructure ---- + +const histBuckets = 32 + +type opMetrics struct { + count atomic.Int64 + totalNs atomic.Int64 + minNs atomic.Int64 + maxNs atomic.Int64 + hist [histBuckets]atomic.Int64 +} + +func (m *opMetrics) record(d time.Duration) { + ns := d.Nanoseconds() + if ns <= 0 { + ns = 1 + } + m.count.Add(1) + m.totalNs.Add(ns) + + bucket := bits.Len64(uint64(ns)) - 1 + if bucket >= histBuckets { + bucket = histBuckets - 1 + } + m.hist[bucket].Add(1) + + for { + cur := m.minNs.Load() + if cur != 0 && cur <= ns { + break + } + if m.minNs.CompareAndSwap(cur, ns) { + break + } + } + for { + cur := m.maxNs.Load() + if cur >= ns { + break + } + if m.maxNs.CompareAndSwap(cur, ns) { + break + } + } +} + +func (m *opMetrics) percentile(p float64) time.Duration { + total := m.count.Load() + if total == 0 { + return 0 + } + threshold := int64(float64(total)*p/100.0 + 0.5) + var cumulative int64 + for i := 0; i < histBuckets; i++ { + cumulative += m.hist[i].Load() + if cumulative >= threshold { + return time.Duration(int64(1) << i) + } + } + return time.Duration(m.maxNs.Load()) +} + +func (m *opMetrics) snapshot() (count int64, avg, min, max, p50, p99 time.Duration) { + count = m.count.Load() + if count == 0 { + return + } + avg = time.Duration(m.totalNs.Load() / count) + min = time.Duration(m.minNs.Load()) + max = time.Duration(m.maxNs.Load()) + p50 = m.percentile(50) + p99 = m.percentile(99) + return +} + +type loadMetrics struct { + getMetrics opMetrics + putMetrics opMetrics + prepopulatePutMetrics opMetrics + getHits atomic.Int64 + getMisses atomic.Int64 + getExpired atomic.Int64 +} + +func printOpLine(name string, m *opMetrics) { + count, avg, min, max, p50, p99 := m.snapshot() + fmt.Printf("%-5s count=%-12d\n", name, count) + if count > 0 { + fmt.Printf(" avg=%-14s min=%-14s max=%-14s p50=%-14s p99=%-14s\n", avg, min, max, p50, p99) + } +} + +func (lm *loadMetrics) printStats(label string) { + gc, _, _, _, _, _ := lm.getMetrics.snapshot() + fmt.Printf("\n===== %s =====\n", label) + fmt.Printf("GET count=%-12d hits=%-12d misses=%-12d expired=%-12d\n", + gc, lm.getHits.Load(), lm.getMisses.Load(), lm.getExpired.Load()) + if gc > 0 { + printOpLine("GET", &lm.getMetrics) + } + printOpLine("PUT", &lm.putMetrics) + printOpLine("PREPOP", &lm.prepopulatePutMetrics) + fmt.Println() +} + +// commonFlags holds the shared flags across all plans. +type commonFlags struct { + mountPoint string + numShards int + keysPerShard int + memtableMB int + fileSizeMultiplier float64 + readWorkers int + writeWorkers int + sampleSecs int + iterations int64 + logStats bool + memProfile string + cpuProfile string +} + +func (f *commonFlags) register(fs *flag.FlagSet, defaults commonFlags) { + fs.StringVar(&f.mountPoint, "mount", defaults.mountPoint, "data directory for shard files") + fs.IntVar(&f.numShards, "shards", defaults.numShards, "number of shards") + fs.IntVar(&f.keysPerShard, "keys-per-shard", defaults.keysPerShard, "keys per shard") + fs.IntVar(&f.memtableMB, "memtable-mb", defaults.memtableMB, "memtable size in MiB") + fs.Float64Var(&f.fileSizeMultiplier, "file-size-multiplier", defaults.fileSizeMultiplier, "file size in GiB per shard") + fs.IntVar(&f.readWorkers, "readers", defaults.readWorkers, "number of read workers") + fs.IntVar(&f.writeWorkers, "writers", defaults.writeWorkers, "number of write workers") + fs.IntVar(&f.sampleSecs, "sample-secs", defaults.sampleSecs, "predictor sampling window in seconds") + fs.Int64Var(&f.iterations, "iterations", defaults.iterations, "number of iterations") + fs.BoolVar(&f.logStats, "log-stats", defaults.logStats, "periodically log cache stats") + fs.StringVar(&f.memProfile, "memprofile", defaults.memProfile, "write memory profile to this file") + fs.StringVar(&f.cpuProfile, "cpuprofile", defaults.cpuProfile, "write cpu profile to this file") +} + +func (f *commonFlags) memtableSizeBytes() int32 { + return int32(f.memtableMB) * 1024 * 1024 +} + +func (f *commonFlags) fileSizeBytes() int64 { + return int64(f.fileSizeMultiplier * 1024 * 1024 * 1024) +} + +// setupProfiling starts pprof, CPU profiling and returns a teardown function +// that writes the memory profile. +func setupProfiling(flags commonFlags) func() { + zerolog.SetGlobalLevel(zerolog.InfoLevel) + + go func() { + log.Info().Msg("Starting pprof server on :8080") + if err := http.ListenAndServe(":8080", nil); err != nil { + log.Error().Err(err).Msg("pprof server failed") + } + }() + + if flags.cpuProfile != "" { + f, err := os.Create(flags.cpuProfile) + if err != nil { + log.Fatal().Err(err).Msg("could not create CPU profile") + } + if err := pprof.StartCPUProfile(f); err != nil { + f.Close() + log.Fatal().Err(err).Msg("could not start CPU profile") + } + } + + return func() { + pprof.StopCPUProfile() + + if flags.memProfile != "" { + runtime.GC() + f, err := os.Create(flags.memProfile) + if err != nil { + log.Fatal().Err(err).Msg("could not create memory profile") + } + defer f.Close() + if err := pprof.WriteHeapProfile(f); err != nil { + log.Fatal().Err(err).Msg("could not write memory profile") + } + log.Info().Msgf("Memory profile written to %s", flags.memProfile) + } + + var m runtime.MemStats + runtime.ReadMemStats(&m) + log.Info(). + Str("alloc", fmt.Sprintf("%.2f MB", float64(m.Alloc)/1024/1024)). + Str("total_alloc", fmt.Sprintf("%.2f MB", float64(m.TotalAlloc)/1024/1024)). + Str("sys", fmt.Sprintf("%.2f MB", float64(m.Sys)/1024/1024)). + Uint32("num_gc", m.NumGC). + Msg("Memory statistics") + } +} + +// ---- Plan registry ---- + +type plan func() + +var plans = map[string]plan{ + "freecache": planFreecache, + "readthrough": planReadthroughGaussian, + "random": planRandomGaussian, + "readthrough-batched": planReadthroughGaussianBatched, + "badger": planBadger, +} + func main() { - // Flags to parameterize load tests - //pick plan from the environment variable - plan := os.Getenv("PLAN") - if plan == "freecache" { - planFreecache() - } else if plan == "readthrough" { - planReadthroughGaussian() - } else if plan == "random" { - planRandomGaussian() - } else if plan == "readthrough-batched" { - planReadthroughGaussianBatched() - } else if plan == "lockless" { - planLockless() - } else if plan == "badger" { - planBadger() - } else { - panic("invalid plan") - } -} - -// func BucketsByWidth(a float64, n int) []float64 { -// if n <= 0 { -// return []float64{0} -// } -// b := make([]float64, n+1) -// b[0] = 0 -// if math.Abs(a) < 1e-12 { -// // a ~ 0 => uniform -// for i := 1; i <= n; i++ { -// b[i] = float64(i) / float64(n) -// } -// return b -// } -// s := math.Expm1(a) / float64(n) // (e^a - 1)/n (stable) -// ia := 1.0 / a -// for i := 0; i <= n; i++ { -// b[i] = ia * math.Log1p(s*float64(i)) // ln(1 + s*i) -// } -// return b -// } + name := os.Getenv("PLAN") + p, ok := plans[name] + if !ok { + fmt.Fprintf(os.Stderr, "unknown plan %q, available: ", name) + for k := range plans { + fmt.Fprintf(os.Stderr, "%s ", k) + } + fmt.Fprintln(os.Stderr) + os.Exit(1) + } + p() +} diff --git a/flashring/cmd/flashringtest/plan_badger.go b/flashring/cmd/flashringtest/plan_badger.go index 4ba266d4..2c988e39 100644 --- a/flashring/cmd/flashringtest/plan_badger.go +++ b/flashring/cmd/flashringtest/plan_badger.go @@ -4,105 +4,75 @@ import ( "flag" "fmt" "math/rand" - "os" - "runtime" - "runtime/pprof" "strings" "sync" + "time" - cachepkg "github.com/Meesho/BharatMLStack/flashring/internal/cache" - "github.com/rs/zerolog" + cachepkg "github.com/Meesho/BharatMLStack/flashring/pkg/cache" "github.com/rs/zerolog/log" ) func planBadger() { - - var ( - mountPoint string - numShards int - keysPerShard int - memtableMB int - fileSizeMultiplier int - readWorkers int - writeWorkers int - sampleSecs int - iterations int64 - aVal float64 - logStats bool - memProfile string - cpuProfile string - ) - - flag.StringVar(&mountPoint, "mount", "/media/a0d00kc/trishul/badger", "data directory for shard files") - flag.IntVar(&numShards, "shards", 1, "number of shards") - flag.IntVar(&keysPerShard, "keys-per-shard", 20_000_000, "keys per shard") - flag.IntVar(&memtableMB, "memtable-mb", 16, "memtable size in MiB") - flag.IntVar(&fileSizeMultiplier, "file-size-multiplier", 1, "file size in GiB per shard") - flag.IntVar(&readWorkers, "readers", 4, "number of read workers") - flag.IntVar(&writeWorkers, "writers", 4, "number of write workers") - flag.IntVar(&sampleSecs, "sample-secs", 30, "predictor sampling window in seconds") - flag.Int64Var(&iterations, "iterations", 100_000_000, "number of iterations") - flag.Float64Var(&aVal, "a", 0.4, "a value for the predictor") - flag.BoolVar(&logStats, "log-stats", true, "periodically log cache stats") - flag.StringVar(&memProfile, "memprofile", "mem.prof", "write memory profile to this file") - flag.StringVar(&cpuProfile, "cpuprofile", "", "write cpu profile to this file") + var flags commonFlags + flags.register(flag.CommandLine, commonFlags{ + mountPoint: "/mnt/disks/nvme/badger", + numShards: 1, + keysPerShard: 20_000_000, + memtableMB: 16, + fileSizeMultiplier: 1, + readWorkers: 4, + writeWorkers: 4, + sampleSecs: 30, + iterations: 100_000_000, + logStats: true, + memProfile: "mem.prof", + }) flag.Parse() + teardown := setupProfiling(flags) + defer teardown() - zerolog.SetGlobalLevel(zerolog.InfoLevel) - - cfg := cachepkg.WrapCacheConfig{ - MountPoint: mountPoint, - } - - cache, err := cachepkg.NewBadger(cfg, logStats) + cache, err := cachepkg.NewBadger(cachepkg.Config{}, flags.mountPoint) if err != nil { panic(err) } + defer cache.Close() - MULTIPLIER := 300 + const multiplier = 300 + totalKeys := flags.keysPerShard * flags.numShards + str1kb := "%d" + strings.Repeat("a", 1024) - missedKeyChanList := make([]chan int, writeWorkers) - for i := 0; i < writeWorkers; i++ { + missedKeyChanList := make([]chan int, flags.writeWorkers) + for i := range missedKeyChanList { missedKeyChanList[i] = make(chan int) } - totalKeys := keysPerShard * numShards - str1kb := strings.Repeat("a", 1024) - str1kb = "%d" + str1kb - - var wg sync.WaitGroup - var writeWg sync.WaitGroup - - //prepopulate 70% keys - fmt.Printf("----------------------------------------------prepopulating keys\n") - for k := 0; k < int(totalKeys); k++ { - + fmt.Println("----------------------------------------------prepopulating keys") + for k := 0; k < totalKeys; k++ { if rand.Intn(100) < 30 { continue } - key := fmt.Sprintf("key%d", k) val := []byte(fmt.Sprintf(str1kb, k)) - if err := cache.Put(key, val, 60*60); err != nil { + if err := cache.Put(key, val, time.Hour); err != nil { panic(err) } - if k%5000000 == 0 { + if k%5_000_000 == 0 { fmt.Printf("----------------------------------------------prepopulated %d keys\n", k) } } - if writeWorkers > 0 { - fmt.Printf("----------------------------------------------starting write workers\n") - writeWg.Add(writeWorkers) + var wg, writeWg sync.WaitGroup - for w := 0; w < writeWorkers; w++ { + if flags.writeWorkers > 0 { + fmt.Println("----------------------------------------------starting write workers") + writeWg.Add(flags.writeWorkers) + for w := 0; w < flags.writeWorkers; w++ { go func(workerID int) { defer writeWg.Done() - for mk := range missedKeyChanList[workerID] { key := fmt.Sprintf("key%d", mk) val := []byte(fmt.Sprintf(str1kb, mk)) - if err := cache.Put(key, val, 60*60); err != nil { + if err := cache.Put(key, val, time.Hour); err != nil { panic(err) } } @@ -110,27 +80,24 @@ func planBadger() { } } - if readWorkers > 0 { - fmt.Printf("----------------------------------------------reading keys\n") - wg.Add(readWorkers) - - for r := 0; r < readWorkers; r++ { + if flags.readWorkers > 0 { + fmt.Println("----------------------------------------------reading keys") + wg.Add(flags.readWorkers) + for r := 0; r < flags.readWorkers; r++ { go func(workerID int) { defer wg.Done() - for k := 0; k < totalKeys*MULTIPLIER; k += 1 { + for k := 0; k < totalKeys*multiplier; k++ { randomval := normalDistInt(totalKeys) key := fmt.Sprintf("key%d", randomval) _, found, expired := cache.Get(key) if !found { - writeWorkerid := randomval % writeWorkers - missedKeyChanList[writeWorkerid] <- randomval + missedKeyChanList[randomval%flags.writeWorkers] <- randomval } - if expired { panic("key expired") } - if k%5000000 == 0 { + if k%5_000_000 == 0 { fmt.Printf("----------------------------------------------read %d keys %d readerid\n", k, workerID) } } @@ -138,32 +105,6 @@ func planBadger() { } } - // Start pprof HTTP server for runtime profiling - wg.Wait() - log.Info().Msgf("done putting") - - // Memory profiling - if memProfile != "" { - runtime.GC() // get up-to-date statistics - f, err := os.Create(memProfile) - if err != nil { - log.Fatal().Err(err).Msg("could not create memory profile") - } - defer f.Close() - if err := pprof.WriteHeapProfile(f); err != nil { - log.Fatal().Err(err).Msg("could not write memory profile") - } - log.Info().Msgf("Memory profile written to %s", memProfile) - } - - // Print memory stats - var m runtime.MemStats - runtime.ReadMemStats(&m) - log.Info(). - Str("alloc", fmt.Sprintf("%.2f MB", float64(m.Alloc)/1024/1024)). - Str("total_alloc", fmt.Sprintf("%.2f MB", float64(m.TotalAlloc)/1024/1024)). - Str("sys", fmt.Sprintf("%.2f MB", float64(m.Sys)/1024/1024)). - Uint32("num_gc", m.NumGC). - Msg("Memory statistics") + log.Info().Msg("done") } diff --git a/flashring/cmd/flashringtest/plan_freecache.go b/flashring/cmd/flashringtest/plan_freecache.go index 0fe6a297..8e417abb 100644 --- a/flashring/cmd/flashringtest/plan_freecache.go +++ b/flashring/cmd/flashringtest/plan_freecache.go @@ -4,108 +4,75 @@ import ( "flag" "fmt" "math/rand" - "os" - "runtime" - "runtime/debug" - "runtime/pprof" "strings" "sync" + "time" - cachepkg "github.com/Meesho/BharatMLStack/flashring/internal/cache" - "github.com/rs/zerolog" + cachepkg "github.com/Meesho/BharatMLStack/flashring/pkg/cache" "github.com/rs/zerolog/log" ) func planFreecache() { - - var ( - mountPoint string - numShards int - keysPerShard int - memtableMB int - fileSizeMultiplier int - readWorkers int - writeWorkers int - sampleSecs int - iterations int64 - aVal float64 - logStats bool - memProfile string - cpuProfile string - ) - - flag.StringVar(&mountPoint, "mount", "/media/a0d00kc/trishul/", "data directory for shard files") - flag.IntVar(&numShards, "shards", 1, "number of shards") - flag.IntVar(&keysPerShard, "keys-per-shard", 20_000_000, "keys per shard") - flag.IntVar(&memtableMB, "memtable-mb", 16, "memtable size in MiB") - flag.IntVar(&fileSizeMultiplier, "file-size-multiplier", 1, "file size in GiB per shard") - flag.IntVar(&readWorkers, "readers", 4, "number of read workers") - flag.IntVar(&writeWorkers, "writers", 4, "number of write workers") - flag.IntVar(&sampleSecs, "sample-secs", 30, "predictor sampling window in seconds") - flag.Int64Var(&iterations, "iterations", 100_000_000, "number of iterations") - flag.Float64Var(&aVal, "a", 0.4, "a value for the predictor") - flag.BoolVar(&logStats, "log-stats", true, "periodically log cache stats") - flag.StringVar(&memProfile, "memprofile", "mem.prof", "write memory profile to this file") - flag.StringVar(&cpuProfile, "cpuprofile", "", "write cpu profile to this file") + var flags commonFlags + flags.register(flag.CommandLine, commonFlags{ + mountPoint: "/mnt/disks/nvme/", + numShards: 1, + keysPerShard: 20_000_000, + memtableMB: 16, + fileSizeMultiplier: 4, + readWorkers: 4, + writeWorkers: 4, + sampleSecs: 30, + iterations: 100_000_000, + logStats: true, + memProfile: "mem.prof", + }) flag.Parse() + teardown := setupProfiling(flags) + defer teardown() - zerolog.SetGlobalLevel(zerolog.InfoLevel) - - cfg := cachepkg.WrapCacheConfig{ - KeysPerShard: keysPerShard, - FileSize: 4 * 1024 * 1024 * 1024, - } - - cache, err := cachepkg.NewFreecache(cfg, logStats) + cache, err := cachepkg.NewFreecache(int(flags.fileSizeBytes())) if err != nil { panic(err) } - debug.SetGCPercent(20) + defer cache.Close() - MULTIPLIER := 300 + const multiplier = 300 + totalKeys := flags.keysPerShard * flags.numShards + str1kb := "%d" + strings.Repeat("a", 1024) - missedKeyChanList := make([]chan int, writeWorkers) - for i := 0; i < writeWorkers; i++ { + missedKeyChanList := make([]chan int, flags.writeWorkers) + for i := range missedKeyChanList { missedKeyChanList[i] = make(chan int) } - totalKeys := keysPerShard * numShards - str1kb := strings.Repeat("a", 1024) - str1kb = "%d" + str1kb - - var wg sync.WaitGroup - var writeWg sync.WaitGroup - - //prepopulate 70% keys - fmt.Printf("----------------------------------------------prepopulating keys\n") - for k := 0; k < int(totalKeys); k++ { - + fmt.Println("----------------------------------------------prepopulating keys") + for k := 0; k < totalKeys; k++ { if rand.Intn(100) < 30 { continue } - key := fmt.Sprintf("key%d", k) val := []byte(fmt.Sprintf(str1kb, k)) - if err := cache.Put(key, val, 60*60); err != nil { + if err := cache.Put(key, val, time.Hour); err != nil { panic(err) } - if k%5000000 == 0 { + if k%5_000_000 == 0 { fmt.Printf("----------------------------------------------prepopulated %d keys\n", k) } } - if writeWorkers > 0 { - fmt.Printf("----------------------------------------------starting write workers\n") - writeWg.Add(writeWorkers) + var wg, writeWg sync.WaitGroup - for w := 0; w < writeWorkers; w++ { + if flags.writeWorkers > 0 { + fmt.Println("----------------------------------------------starting write workers") + writeWg.Add(flags.writeWorkers) + for w := 0; w < flags.writeWorkers; w++ { go func(workerID int) { defer writeWg.Done() - for mk := range missedKeyChanList[workerID] { key := fmt.Sprintf("key%d", mk) val := []byte(fmt.Sprintf(str1kb, mk)) - if err := cache.Put(key, val, 60*60); err != nil { + if err := cache.Put(key, val, time.Hour); err != nil { panic(err) } } @@ -113,27 +80,24 @@ func planFreecache() { } } - if readWorkers > 0 { - fmt.Printf("----------------------------------------------reading keys\n") - wg.Add(readWorkers) - - for r := 0; r < readWorkers; r++ { + if flags.readWorkers > 0 { + fmt.Println("----------------------------------------------reading keys") + wg.Add(flags.readWorkers) + for r := 0; r < flags.readWorkers; r++ { go func(workerID int) { defer wg.Done() - for k := 0; k < totalKeys*MULTIPLIER; k += 1 { + for k := 0; k < totalKeys*multiplier; k++ { randomval := normalDistInt(totalKeys) key := fmt.Sprintf("key%d", randomval) _, found, expired := cache.Get(key) if !found { - writeWorkerid := randomval % writeWorkers - missedKeyChanList[writeWorkerid] <- randomval + missedKeyChanList[randomval%flags.writeWorkers] <- randomval } - if expired { panic("key expired") } - if k%5000000 == 0 { + if k%5_000_000 == 0 { fmt.Printf("----------------------------------------------read %d keys %d readerid\n", k, workerID) } } @@ -141,32 +105,6 @@ func planFreecache() { } } - // Start pprof HTTP server for runtime profiling - wg.Wait() - log.Info().Msgf("done putting") - - // Memory profiling - if memProfile != "" { - runtime.GC() // get up-to-date statistics - f, err := os.Create(memProfile) - if err != nil { - log.Fatal().Err(err).Msg("could not create memory profile") - } - defer f.Close() - if err := pprof.WriteHeapProfile(f); err != nil { - log.Fatal().Err(err).Msg("could not write memory profile") - } - log.Info().Msgf("Memory profile written to %s", memProfile) - } - - // Print memory stats - var m runtime.MemStats - runtime.ReadMemStats(&m) - log.Info(). - Str("alloc", fmt.Sprintf("%.2f MB", float64(m.Alloc)/1024/1024)). - Str("total_alloc", fmt.Sprintf("%.2f MB", float64(m.TotalAlloc)/1024/1024)). - Str("sys", fmt.Sprintf("%.2f MB", float64(m.Sys)/1024/1024)). - Uint32("num_gc", m.NumGC). - Msg("Memory statistics") + log.Info().Msg("done") } diff --git a/flashring/cmd/flashringtest/plan_lockless.go b/flashring/cmd/flashringtest/plan_lockless.go deleted file mode 100644 index e946c9af..00000000 --- a/flashring/cmd/flashringtest/plan_lockless.go +++ /dev/null @@ -1,228 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "math/rand" - "net/http" - "os" - "path/filepath" - "runtime" - "runtime/pprof" - "strings" - "sync" - "time" - - cachepkg "github.com/Meesho/BharatMLStack/flashring/internal/cache" - "github.com/rs/zerolog" - "github.com/rs/zerolog/log" -) - -func planLockless() { - var ( - mountPoint string - numShards int - keysPerShard int - memtableMB int - fileSizeMultiplier int - readWorkers int - writeWorkers int - sampleSecs int - iterations int64 - aVal float64 - logStats bool - memProfile string - cpuProfile string - ) - - flag.StringVar(&mountPoint, "mount", "/media/a0d00kc/trishul/", "data directory for shard files") - flag.IntVar(&numShards, "shards", 500, "number of shards") - flag.IntVar(&keysPerShard, "keys-per-shard", 10_00_00, "keys per shard") - flag.IntVar(&memtableMB, "memtable-mb", 16, "memtable size in MiB") - flag.IntVar(&fileSizeMultiplier, "file-size-multiplier", 2, "file size in GiB per shard") - flag.IntVar(&readWorkers, "readers", 8, "number of read workers") - flag.IntVar(&writeWorkers, "writers", 8, "number of write workers") - flag.IntVar(&sampleSecs, "sample-secs", 30, "predictor sampling window in seconds") - flag.Int64Var(&iterations, "iterations", 100_000_000, "number of iterations") - flag.Float64Var(&aVal, "a", 0.4, "a value for the predictor") - flag.BoolVar(&logStats, "log-stats", true, "periodically log cache stats") - flag.StringVar(&memProfile, "memprofile", "mem.prof", "write memory profile to this file") - flag.StringVar(&cpuProfile, "cpuprofile", "", "write cpu profile to this file") - flag.Parse() - - zerolog.SetGlobalLevel(zerolog.InfoLevel) - go func() { - log.Info().Msg("Starting pprof server on :8080") - log.Info().Msg("Access profiles at: http://localhost:8080/debug/pprof/") - log.Info().Msg("Memory profile: http://localhost:8080/debug/pprof/heap") - log.Info().Msg("Goroutine profile: http://localhost:8080/debug/pprof/goroutine") - if err := http.ListenAndServe(":8080", nil); err != nil { - log.Error().Err(err).Msg("pprof server failed") - } - }() - - // CPU profiling - if cpuProfile != "" { - f, err := os.Create(cpuProfile) - if err != nil { - log.Fatal().Err(err).Msg("could not create CPU profile") - } - defer f.Close() - if err := pprof.StartCPUProfile(f); err != nil { - log.Fatal().Err(err).Msg("could not start CPU profile") - } - defer pprof.StopCPUProfile() - } - - //remove all files inside the mount point - files, err := os.ReadDir(mountPoint) - if err != nil { - panic(err) - } - for _, file := range files { - os.Remove(filepath.Join(mountPoint, file.Name())) - } - - memtableSizeInBytes := int32(memtableMB) * 1024 * 1024 - fileSizeInBytes := int64(fileSizeMultiplier) * int64(memtableSizeInBytes) - - cfg := cachepkg.WrapCacheConfig{ - NumShards: numShards, - KeysPerShard: keysPerShard, - FileSize: fileSizeInBytes, - MemtableSize: memtableSizeInBytes, - ReWriteScoreThreshold: 0.8, - GridSearchEpsilon: 0.0001, - SampleDuration: time.Duration(sampleSecs) * time.Second, - - // Pass the metrics collector to record cache metrics - MetricsRecorder: InitMetricsCollector(), - } - - // Set additional input parameters that the cache doesn't know about - metricsCollector.SetShards(numShards) - metricsCollector.SetKeysPerShard(keysPerShard) - metricsCollector.SetReadWorkers(readWorkers) - metricsCollector.SetWriteWorkers(writeWorkers) - metricsCollector.SetPlan("lockless") - - // Start background goroutine to wait for shutdown signal and export CSV - go RunmetricsWaitForShutdown() - - pc, err := cachepkg.NewWrapCache(cfg, mountPoint, logStats) - if err != nil { - panic(err) - } - - MULTIPLIER := 300 - - missedKeyChanList := make([]chan int, writeWorkers) - for i := 0; i < writeWorkers; i++ { - missedKeyChanList[i] = make(chan int) - } - - totalKeys := keysPerShard * numShards - str1kb := strings.Repeat("a", 1024) - str1kb = "%d" + str1kb - - var wg sync.WaitGroup - var writeWg sync.WaitGroup - - //prepopulate 70% keys - fmt.Printf("----------------------------------------------prepopulating keys\n") - for k := 0; k < int(totalKeys); k++ { - - if rand.Intn(100) < 30 { - continue - } - - key := fmt.Sprintf("key%d", k) - val := []byte(fmt.Sprintf(str1kb, k)) - if err := pc.PutLL(key, val, 60); err != nil { - panic(err) - } - if k%5000000 == 0 { - fmt.Printf("----------------------------------------------prepopulated %d keys\n", k) - } - } - - if writeWorkers > 0 { - fmt.Printf("----------------------------------------------starting write workers\n") - writeWg.Add(writeWorkers) - - for w := 0; w < writeWorkers; w++ { - go func(workerID int) { - defer writeWg.Done() - - for mk := range missedKeyChanList[workerID] { - key := fmt.Sprintf("key%d", mk) - val := []byte(fmt.Sprintf(str1kb, mk)) - if err := pc.PutLL(key, val, 60); err != nil { - panic(err) - } - } - }(w) - } - } - - if readWorkers > 0 { - fmt.Printf("----------------------------------------------reading keys\n") - wg.Add(readWorkers) - - for r := 0; r < readWorkers; r++ { - go func(workerID int) { - defer wg.Done() - for k := 0; k < totalKeys*MULTIPLIER; k += 1 { - randomval := normalDistIntPartitioned(workerID, readWorkers, totalKeys) - key := fmt.Sprintf("key%d", randomval) - val, found, expired := pc.GetLL(key) - - if !found { - writeWorkerid := randomval % writeWorkers - missedKeyChanList[writeWorkerid] <- randomval - } - - if expired { - panic("key expired") - - } - if found && string(val) != fmt.Sprintf(str1kb, randomval) { - panic("value mismatch") - } - if k%5000000 == 0 { - fmt.Printf("----------------------------------------------read %d keys %d readerid\n", k, workerID) - } - } - }(r) - } - } - - // Start pprof HTTP server for runtime profiling - - wg.Wait() - log.Info().Msgf("done putting") - - // Memory profiling - if memProfile != "" { - runtime.GC() // get up-to-date statistics - f, err := os.Create(memProfile) - if err != nil { - log.Fatal().Err(err).Msg("could not create memory profile") - } - defer f.Close() - if err := pprof.WriteHeapProfile(f); err != nil { - log.Fatal().Err(err).Msg("could not write memory profile") - } - log.Info().Msgf("Memory profile written to %s", memProfile) - } - - // Print memory stats - var m runtime.MemStats - runtime.ReadMemStats(&m) - log.Info(). - Str("alloc", fmt.Sprintf("%.2f MB", float64(m.Alloc)/1024/1024)). - Str("total_alloc", fmt.Sprintf("%.2f MB", float64(m.TotalAlloc)/1024/1024)). - Str("sys", fmt.Sprintf("%.2f MB", float64(m.Sys)/1024/1024)). - Uint32("num_gc", m.NumGC). - Msg("Memory statistics") -} diff --git a/flashring/cmd/flashringtest/plan_random_gausian.go b/flashring/cmd/flashringtest/plan_random_gausian.go index 3fbaf849..88ddf26e 100644 --- a/flashring/cmd/flashringtest/plan_random_gausian.go +++ b/flashring/cmd/flashringtest/plan_random_gausian.go @@ -3,128 +3,79 @@ package main import ( "flag" "fmt" - "net/http" "os" "path/filepath" - "runtime" - "runtime/pprof" "strings" "sync" "time" - cachepkg "github.com/Meesho/BharatMLStack/flashring/internal/cache" - "github.com/rs/zerolog" + cachepkg "github.com/Meesho/BharatMLStack/flashring/pkg/cache" "github.com/rs/zerolog/log" ) func planRandomGaussian() { - var ( - mountPoint string - numShards int - keysPerShard int - memtableMB int - fileSizeMultiplier int - readWorkers int - writeWorkers int - sampleSecs int - iterations int64 - aVal float64 - logStats bool - memProfile string - cpuProfile string - ) - - flag.StringVar(&mountPoint, "mount", "/media/a0d00kc/trishul/", "data directory for shard files") - flag.IntVar(&numShards, "shards", 1, "number of shards") - flag.IntVar(&keysPerShard, "keys-per-shard", 20_000_000, "keys per shard") - flag.IntVar(&memtableMB, "memtable-mb", 16, "memtable size in MiB") - flag.IntVar(&fileSizeMultiplier, "file-size-multiplier", 40, "file size in GiB per shard") - flag.IntVar(&readWorkers, "readers", 1, "number of read workers") - flag.IntVar(&writeWorkers, "writers", 1, "number of write workers") - flag.IntVar(&sampleSecs, "sample-secs", 30, "predictor sampling window in seconds") - flag.Int64Var(&iterations, "iterations", 100_000_000, "number of iterations") - flag.Float64Var(&aVal, "a", 0.4, "a value for the predictor") - flag.BoolVar(&logStats, "log-stats", true, "periodically log cache stats") - flag.StringVar(&memProfile, "memprofile", "mem.prof", "write memory profile to this file") - flag.StringVar(&cpuProfile, "cpuprofile", "", "write cpu profile to this file") + var flags commonFlags + flags.register(flag.CommandLine, commonFlags{ + mountPoint: "/mnt/disks/nvme/", + numShards: 1, + keysPerShard: 20_000_000, + memtableMB: 16, + fileSizeMultiplier: 40, + readWorkers: 1, + writeWorkers: 1, + sampleSecs: 30, + iterations: 100_000_000, + logStats: true, + memProfile: "mem.prof", + }) flag.Parse() + teardown := setupProfiling(flags) + defer teardown() - zerolog.SetGlobalLevel(zerolog.InfoLevel) - go func() { - log.Info().Msg("Starting pprof server on :8080") - log.Info().Msg("Access profiles at: http://localhost:8080/debug/pprof/") - log.Info().Msg("Memory profile: http://localhost:8080/debug/pprof/heap") - log.Info().Msg("Goroutine profile: http://localhost:8080/debug/pprof/goroutine") - if err := http.ListenAndServe(":8080", nil); err != nil { - log.Error().Err(err).Msg("pprof server failed") - } - }() - - // CPU profiling - if cpuProfile != "" { - f, err := os.Create(cpuProfile) - if err != nil { - log.Fatal().Err(err).Msg("could not create CPU profile") - } - defer f.Close() - if err := pprof.StartCPUProfile(f); err != nil { - log.Fatal().Err(err).Msg("could not start CPU profile") - } - defer pprof.StopCPUProfile() - } - - //remove all files inside the mount point - files, err := os.ReadDir(mountPoint) + files, err := os.ReadDir(flags.mountPoint) if err != nil { panic(err) } for _, file := range files { - os.Remove(filepath.Join(mountPoint, file.Name())) + os.Remove(filepath.Join(flags.mountPoint, file.Name())) } - memtableSizeInBytes := int32(memtableMB) * 1024 * 1024 - fileSizeInBytes := int64(fileSizeMultiplier) * int64(memtableSizeInBytes) - - cfg := cachepkg.WrapCacheConfig{ - NumShards: numShards, - KeysPerShard: keysPerShard, - FileSize: fileSizeInBytes, - MemtableSize: memtableSizeInBytes, + cfg := cachepkg.Config{ + NumShards: flags.numShards, + KeysPerShard: flags.keysPerShard, + FileSize: flags.fileSizeBytes(), + MemtableSize: flags.memtableSizeBytes(), ReWriteScoreThreshold: 0.8, GridSearchEpsilon: 0.0001, - SampleDuration: time.Duration(sampleSecs) * time.Second, + SampleDuration: time.Duration(flags.sampleSecs) * time.Second, } - pc, err := cachepkg.NewWrapCache(cfg, mountPoint, logStats) + pc, err := cachepkg.NewWrapCache(cfg, flags.mountPoint) if err != nil { panic(err) } + defer pc.Close() - MULTIPLIER := 300 - - totalKeys := keysPerShard * numShards - str1kb := strings.Repeat("a", 1024) - str1kb = "%d" + str1kb + const multiplier = 300 + totalKeys := flags.keysPerShard * flags.numShards + str1kb := "%d" + strings.Repeat("a", 1024) var wg sync.WaitGroup - if writeWorkers > 0 { - fmt.Printf("----------------------------------------------writing keys\n") - wg.Add(writeWorkers) - - for w := 0; w < writeWorkers; w++ { + if flags.writeWorkers > 0 { + fmt.Println("----------------------------------------------writing keys") + wg.Add(flags.writeWorkers) + for w := 0; w < flags.writeWorkers; w++ { go func(workerID int) { defer wg.Done() - for k := 0; k < totalKeys*MULTIPLIER; k += 1 { + for k := 0; k < totalKeys*multiplier; k++ { randomval := normalDistInt(totalKeys) key := fmt.Sprintf("key%d", randomval) - val := []byte(fmt.Sprintf(str1kb, randomval)) - if err := pc.Put(key, val, 60); err != nil { + if err := pc.Put(key, val, 60*time.Minute); err != nil { panic(err) } - - if k%5000000 == 0 { + if k%5_000_000 == 0 { fmt.Printf("----------------------------------------------wrote %d keys %d writerid\n", k, workerID) } } @@ -132,25 +83,23 @@ func planRandomGaussian() { } } - if readWorkers > 0 { - fmt.Printf("----------------------------------------------reading keys\n") - wg.Add(readWorkers) - - for r := 0; r < readWorkers; r++ { + if flags.readWorkers > 0 { + fmt.Println("----------------------------------------------reading keys") + wg.Add(flags.readWorkers) + for r := 0; r < flags.readWorkers; r++ { go func(workerID int) { defer wg.Done() - for k := 0; k < totalKeys*MULTIPLIER; k += 1 { + for k := 0; k < totalKeys*multiplier; k++ { randomval := normalDistInt(totalKeys) key := fmt.Sprintf("key%d", randomval) val, found, expired := pc.Get(key) - if expired { panic("key expired") } if found && string(val) != fmt.Sprintf(str1kb, randomval) { panic("value mismatch") } - if k%5000000 == 0 { + if k%5_000_000 == 0 { fmt.Printf("----------------------------------------------read %d keys %d readerid\n", k, workerID) } } @@ -158,32 +107,6 @@ func planRandomGaussian() { } } - // Start pprof HTTP server for runtime profiling - wg.Wait() - log.Info().Msgf("done putting") - - // Memory profiling - if memProfile != "" { - runtime.GC() // get up-to-date statistics - f, err := os.Create(memProfile) - if err != nil { - log.Fatal().Err(err).Msg("could not create memory profile") - } - defer f.Close() - if err := pprof.WriteHeapProfile(f); err != nil { - log.Fatal().Err(err).Msg("could not write memory profile") - } - log.Info().Msgf("Memory profile written to %s", memProfile) - } - - // Print memory stats - var m runtime.MemStats - runtime.ReadMemStats(&m) - log.Info(). - Str("alloc", fmt.Sprintf("%.2f MB", float64(m.Alloc)/1024/1024)). - Str("total_alloc", fmt.Sprintf("%.2f MB", float64(m.TotalAlloc)/1024/1024)). - Str("sys", fmt.Sprintf("%.2f MB", float64(m.Sys)/1024/1024)). - Uint32("num_gc", m.NumGC). - Msg("Memory statistics") + log.Info().Msg("done") } diff --git a/flashring/cmd/flashringtest/plan_readthrough_gausian.go b/flashring/cmd/flashringtest/plan_readthrough_gausian.go index 56c6da3d..3e71edb9 100644 --- a/flashring/cmd/flashringtest/plan_readthrough_gausian.go +++ b/flashring/cmd/flashringtest/plan_readthrough_gausian.go @@ -4,192 +4,148 @@ import ( "flag" "fmt" "math/rand" - "net/http" "os" "path/filepath" - "runtime" - "runtime/pprof" "strings" "sync" "time" - cachepkg "github.com/Meesho/BharatMLStack/flashring/internal/cache" - "github.com/rs/zerolog" + cachepkg "github.com/Meesho/BharatMLStack/flashring/pkg/cache" "github.com/rs/zerolog/log" ) func planReadthroughGaussian() { - var ( - mountPoint string - numShards int - keysPerShard int - memtableMB int - fileSizeMultiplier int - readWorkers int - writeWorkers int - sampleSecs int - iterations int64 - aVal float64 - logStats bool - memProfile string - cpuProfile string - ) - - flag.StringVar(&mountPoint, "mount", "/media/a0d00kc/trishul/", "data directory for shard files") - flag.IntVar(&numShards, "shards", 500, "number of shards") - flag.IntVar(&keysPerShard, "keys-per-shard", 4_00_00, "keys per shard") - flag.IntVar(&memtableMB, "memtable-mb", 16, "memtable size in MiB") - flag.IntVar(&fileSizeMultiplier, "file-size-multiplier", 2, "file size in GiB per shard") - flag.IntVar(&readWorkers, "readers", 8, "number of read workers") - flag.IntVar(&writeWorkers, "writers", 8, "number of write workers") - flag.IntVar(&sampleSecs, "sample-secs", 30, "predictor sampling window in seconds") - flag.Int64Var(&iterations, "iterations", 100_000_000, "number of iterations") - flag.Float64Var(&aVal, "a", 0.4, "a value for the predictor") - flag.BoolVar(&logStats, "log-stats", true, "periodically log cache stats") - flag.StringVar(&memProfile, "memprofile", "mem.prof", "write memory profile to this file") - flag.StringVar(&cpuProfile, "cpuprofile", "", "write cpu profile to this file") + var flags commonFlags + flags.register(flag.CommandLine, commonFlags{ + mountPoint: "/mnt/disks/nvme/", + numShards: 50, + keysPerShard: 6_00_000, + memtableMB: 2, + fileSizeMultiplier: 0.25, + readWorkers: 16, + writeWorkers: 16, + sampleSecs: 30, + iterations: 100_000_000, + logStats: true, + memProfile: "mem.prof", + }) flag.Parse() + teardown := setupProfiling(flags) + defer teardown() - zerolog.SetGlobalLevel(zerolog.InfoLevel) - go func() { - log.Info().Msg("Starting pprof server on :8080") - log.Info().Msg("Access profiles at: http://localhost:8080/debug/pprof/") - log.Info().Msg("Memory profile: http://localhost:8080/debug/pprof/heap") - log.Info().Msg("Goroutine profile: http://localhost:8080/debug/pprof/goroutine") - if err := http.ListenAndServe(":8080", nil); err != nil { - log.Error().Err(err).Msg("pprof server failed") - } - }() - - // CPU profiling - if cpuProfile != "" { - f, err := os.Create(cpuProfile) - if err != nil { - log.Fatal().Err(err).Msg("could not create CPU profile") - } - defer f.Close() - if err := pprof.StartCPUProfile(f); err != nil { - log.Fatal().Err(err).Msg("could not start CPU profile") - } - defer pprof.StopCPUProfile() - } - - //remove all files inside the mount point - files, err := os.ReadDir(mountPoint) + files, err := os.ReadDir(flags.mountPoint) if err != nil { panic(err) } for _, file := range files { - os.Remove(filepath.Join(mountPoint, file.Name())) + os.Remove(filepath.Join(flags.mountPoint, file.Name())) } - memtableSizeInBytes := int32(memtableMB) * 1024 * 1024 - fileSizeInBytes := int64(fileSizeMultiplier) * int64(memtableSizeInBytes) - - cfg := cachepkg.WrapCacheConfig{ - NumShards: numShards, - KeysPerShard: keysPerShard, - FileSize: fileSizeInBytes, - MemtableSize: memtableSizeInBytes, + cfg := cachepkg.Config{ + NumShards: flags.numShards, + KeysPerShard: flags.keysPerShard, + FileSize: flags.fileSizeBytes(), + MemtableSize: flags.memtableSizeBytes(), ReWriteScoreThreshold: 0.8, GridSearchEpsilon: 0.0001, - SampleDuration: time.Duration(sampleSecs) * time.Second, - - // Pass the metrics collector to record cache metrics - MetricsRecorder: InitMetricsCollector(), + SampleDuration: time.Duration(flags.sampleSecs) * time.Second, } - // Set additional input parameters that the cache doesn't know about - metricsCollector.SetShards(numShards) - metricsCollector.SetKeysPerShard(keysPerShard) - metricsCollector.SetReadWorkers(readWorkers) - metricsCollector.SetWriteWorkers(writeWorkers) - metricsCollector.SetPlan("readthrough") - - // Start background goroutine to wait for shutdown signal and export CSV - go RunmetricsWaitForShutdown() - - pc, err := cachepkg.NewWrapCache(cfg, mountPoint, logStats) + pc, err := cachepkg.NewWrapCache(cfg, flags.mountPoint) if err != nil { panic(err) } + defer pc.Close() - MULTIPLIER := 300 + const multiplier = 300 + totalKeys := 10_000_000 + str1kb := "%d" + strings.Repeat("a", 1024) - missedKeyChanList := make([]chan int, writeWorkers) - for i := 0; i < writeWorkers; i++ { + var metrics loadMetrics + stopReporter := make(chan struct{}) + go func() { + ticker := time.NewTicker(10 * time.Second) + defer ticker.Stop() + for { + select { + case <-ticker.C: + metrics.printStats("PERIODIC") + case <-stopReporter: + return + } + } + }() + + missedKeyChanList := make([]chan int, flags.writeWorkers) + for i := range missedKeyChanList { missedKeyChanList[i] = make(chan int) } - totalKeys := keysPerShard * numShards - str1kb := strings.Repeat("a", 1024) - str1kb = "%d" + str1kb - - var wg sync.WaitGroup - var writeWg sync.WaitGroup - - //prepopulate 70% keys - fmt.Printf("----------------------------------------------prepopulating keys\n") - for k := 0; k < int(totalKeys); k++ { - + fmt.Println("----------------------------------------------prepopulating keys") + for k := 0; k < totalKeys; k++ { if rand.Intn(100) < 30 { continue } - key := fmt.Sprintf("key%d", k) val := []byte(fmt.Sprintf(str1kb, k)) - if err := pc.Put(key, val, 60); err != nil { - panic(err) + start := time.Now() + if err := pc.Put(key, val, 60*time.Minute); err != nil { + log.Error().Err(err).Msgf("error putting key %s", key) } - if k%5000000 == 0 { + metrics.prepopulatePutMetrics.record(time.Since(start)) + if k%5_000_000 == 0 { fmt.Printf("----------------------------------------------prepopulated %d keys\n", k) } } - if writeWorkers > 0 { - fmt.Printf("----------------------------------------------starting write workers\n") - writeWg.Add(writeWorkers) + var wg, writeWg sync.WaitGroup - for w := 0; w < writeWorkers; w++ { + if flags.writeWorkers > 0 { + fmt.Println("----------------------------------------------starting write workers") + writeWg.Add(flags.writeWorkers) + for w := 0; w < flags.writeWorkers; w++ { go func(workerID int) { defer writeWg.Done() - for mk := range missedKeyChanList[workerID] { key := fmt.Sprintf("key%d", mk) val := []byte(fmt.Sprintf(str1kb, mk)) - if err := pc.Put(key, val, 60); err != nil { - panic(err) + start := time.Now() + if err := pc.Put(key, val, 60*time.Minute); err != nil { + log.Error().Err(err).Msgf("error putting key %s", key) } + metrics.putMetrics.record(time.Since(start)) } }(w) } } - if readWorkers > 0 { - fmt.Printf("----------------------------------------------reading keys\n") - wg.Add(readWorkers) - - for r := 0; r < readWorkers; r++ { + if flags.readWorkers > 0 { + fmt.Println("----------------------------------------------reading keys") + wg.Add(flags.readWorkers) + for r := 0; r < flags.readWorkers; r++ { go func(workerID int) { defer wg.Done() - for k := 0; k < totalKeys*MULTIPLIER; k += 1 { - randomval := normalDistIntPartitioned(workerID, readWorkers, totalKeys) + for k := 0; k < totalKeys*multiplier; k++ { + randomval := normalDistIntPartitioned(workerID, flags.readWorkers, totalKeys) key := fmt.Sprintf("key%d", randomval) + start := time.Now() val, found, expired := pc.Get(key) + metrics.getMetrics.record(time.Since(start)) if !found { - writeWorkerid := randomval % writeWorkers - missedKeyChanList[writeWorkerid] <- randomval + metrics.getMisses.Add(1) + missedKeyChanList[randomval%flags.writeWorkers] <- randomval + } else { + metrics.getHits.Add(1) } - if expired { - panic("key expired") - + metrics.getExpired.Add(1) + log.Error().Msgf("key %s expired", key) } if found && string(val) != fmt.Sprintf(str1kb, randomval) { panic("value mismatch") } - if k%5000000 == 0 { + if k%50000 == 0 { fmt.Printf("----------------------------------------------read %d keys %d readerid\n", k, workerID) } } @@ -197,32 +153,8 @@ func planReadthroughGaussian() { } } - // Start pprof HTTP server for runtime profiling - wg.Wait() - log.Info().Msgf("done putting") - - // Memory profiling - if memProfile != "" { - runtime.GC() // get up-to-date statistics - f, err := os.Create(memProfile) - if err != nil { - log.Fatal().Err(err).Msg("could not create memory profile") - } - defer f.Close() - if err := pprof.WriteHeapProfile(f); err != nil { - log.Fatal().Err(err).Msg("could not write memory profile") - } - log.Info().Msgf("Memory profile written to %s", memProfile) - } - - // Print memory stats - var m runtime.MemStats - runtime.ReadMemStats(&m) - log.Info(). - Str("alloc", fmt.Sprintf("%.2f MB", float64(m.Alloc)/1024/1024)). - Str("total_alloc", fmt.Sprintf("%.2f MB", float64(m.TotalAlloc)/1024/1024)). - Str("sys", fmt.Sprintf("%.2f MB", float64(m.Sys)/1024/1024)). - Uint32("num_gc", m.NumGC). - Msg("Memory statistics") + close(stopReporter) + metrics.printStats("FINAL") + log.Info().Msg("done") } diff --git a/flashring/cmd/flashringtest/plan_readthrough_gausian_batched.go b/flashring/cmd/flashringtest/plan_readthrough_gausian_batched.go index fd33e06a..786eef36 100644 --- a/flashring/cmd/flashringtest/plan_readthrough_gausian_batched.go +++ b/flashring/cmd/flashringtest/plan_readthrough_gausian_batched.go @@ -4,174 +4,95 @@ import ( "flag" "fmt" "math/rand" - "net/http" "os" "path/filepath" - "runtime" - "runtime/pprof" "strings" "sync" "time" - cachepkg "github.com/Meesho/BharatMLStack/flashring/internal/cache" - "github.com/rs/zerolog" + cachepkg "github.com/Meesho/BharatMLStack/flashring/pkg/cache" "github.com/rs/zerolog/log" ) func planReadthroughGaussianBatched() { - var ( - mountPoint string - numShards int - keysPerShard int - memtableMB int - fileSizeMultiplier int - readWorkers int - writeWorkers int - sampleSecs int - iterations int64 - aVal float64 - logStats bool - memProfile string - cpuProfile string - - //batching reads - enableBatching bool - batchWindowMicros int // in microseconds - maxBatchSize int - ) - - flag.StringVar(&mountPoint, "mount", "/media/a0d00kc/trishul/", "data directory for shard files") - flag.IntVar(&numShards, "shards", 200, "number of shards") - flag.IntVar(&keysPerShard, "keys-per-shard", 10_00_00, "keys per shard") - flag.IntVar(&memtableMB, "memtable-mb", 16, "memtable size in MiB") - flag.IntVar(&fileSizeMultiplier, "file-size-multiplier", 10, "file size in GiB per shard") - flag.IntVar(&readWorkers, "readers", 8, "number of read workers") - flag.IntVar(&writeWorkers, "writers", 8, "number of write workers") - flag.IntVar(&sampleSecs, "sample-secs", 30, "predictor sampling window in seconds") - flag.Int64Var(&iterations, "iterations", 100_000_000, "number of iterations") - flag.Float64Var(&aVal, "a", 0.4, "a value for the predictor") - flag.BoolVar(&logStats, "log-stats", true, "periodically log cache stats") - flag.StringVar(&memProfile, "memprofile", "mem.prof", "write memory profile to this file") - flag.StringVar(&cpuProfile, "cpuprofile", "", "write cpu profile to this file") - - flag.BoolVar(&enableBatching, "enable-batching", true, "enable read batching") - flag.IntVar(&batchWindowMicros, "batch-window-us", 1, "batch window in microseconds") - flag.IntVar(&maxBatchSize, "max-batch", 200, "max batch size") + var flags commonFlags + flags.register(flag.CommandLine, commonFlags{ + mountPoint: "/mnt/disks/nvme/", + numShards: 200, + keysPerShard: 10_00_00, + memtableMB: 16, + fileSizeMultiplier: 10, + readWorkers: 8, + writeWorkers: 8, + sampleSecs: 30, + iterations: 100_000_000, + logStats: true, + memProfile: "mem.prof", + }) flag.Parse() + teardown := setupProfiling(flags) + defer teardown() - zerolog.SetGlobalLevel(zerolog.InfoLevel) - go func() { - log.Info().Msg("Starting pprof server on :8080") - log.Info().Msg("Access profiles at: http://localhost:8080/debug/pprof/") - log.Info().Msg("Memory profile: http://localhost:8080/debug/pprof/heap") - log.Info().Msg("Goroutine profile: http://localhost:8080/debug/pprof/goroutine") - if err := http.ListenAndServe(":8080", nil); err != nil { - log.Error().Err(err).Msg("pprof server failed") - } - }() - - // CPU profiling - if cpuProfile != "" { - f, err := os.Create(cpuProfile) - if err != nil { - log.Fatal().Err(err).Msg("could not create CPU profile") - } - defer f.Close() - if err := pprof.StartCPUProfile(f); err != nil { - log.Fatal().Err(err).Msg("could not start CPU profile") - } - defer pprof.StopCPUProfile() - } - - //remove all files inside the mount point - files, err := os.ReadDir(mountPoint) + files, err := os.ReadDir(flags.mountPoint) if err != nil { panic(err) } for _, file := range files { - os.Remove(filepath.Join(mountPoint, file.Name())) + os.Remove(filepath.Join(flags.mountPoint, file.Name())) } - memtableSizeInBytes := int32(memtableMB) * 1024 * 1024 - fileSizeInBytes := int64(fileSizeMultiplier) * int64(memtableSizeInBytes) - - cfg := cachepkg.WrapCacheConfig{ - NumShards: numShards, - KeysPerShard: keysPerShard, - FileSize: fileSizeInBytes, - MemtableSize: memtableSizeInBytes, + cfg := cachepkg.Config{ + NumShards: flags.numShards, + KeysPerShard: flags.keysPerShard, + FileSize: flags.fileSizeBytes(), + MemtableSize: flags.memtableSizeBytes(), ReWriteScoreThreshold: 0.8, GridSearchEpsilon: 0.0001, - SampleDuration: time.Duration(sampleSecs) * time.Second, - - //batching reads - EnableBatching: enableBatching, - BatchWindowMicros: batchWindowMicros, - MaxBatchSize: maxBatchSize, - - // Pass the metrics collector to record cache metrics - MetricsRecorder: InitMetricsCollector(), + SampleDuration: time.Duration(flags.sampleSecs) * time.Second, } - // Set additional input parameters that the cache doesn't know about - metricsCollector.SetShards(numShards) - metricsCollector.SetKeysPerShard(keysPerShard) - metricsCollector.SetReadWorkers(readWorkers) - metricsCollector.SetWriteWorkers(writeWorkers) - metricsCollector.SetPlan("readthrough-batched") - - // Start background goroutine to wait for shutdown signal and export CSV - go RunmetricsWaitForShutdown() - - pc, err := cachepkg.NewWrapCache(cfg, mountPoint, logStats) + pc, err := cachepkg.NewWrapCache(cfg, flags.mountPoint) if err != nil { panic(err) } + defer pc.Close() - MULTIPLIER := 300 + const multiplier = 300 + totalKeys := flags.keysPerShard * flags.numShards + str1kb := "%d" + strings.Repeat("a", 1024) - missedKeyChanList := make([]chan int, writeWorkers) - for i := 0; i < writeWorkers; i++ { + missedKeyChanList := make([]chan int, flags.writeWorkers) + for i := range missedKeyChanList { missedKeyChanList[i] = make(chan int) } - totalKeys := keysPerShard * numShards - str1kb := strings.Repeat("a", 1024) - str1kb = "%d" + str1kb - - var wg sync.WaitGroup - var writeWg sync.WaitGroup - - //prepopulate 70% keys - fmt.Printf("----------------------------------------------prepopulating keys\n") - for k := 0; k < int(totalKeys); k++ { - + fmt.Println("----------------------------------------------prepopulating keys") + for k := 0; k < totalKeys; k++ { if rand.Intn(100) < 30 { continue } - key := fmt.Sprintf("key%d", k) val := []byte(fmt.Sprintf(str1kb, k)) - if err := pc.Put(key, val, 60); err != nil { + if err := pc.Put(key, val, 60*time.Minute); err != nil { panic(err) } - if k%5000000 == 0 { + if k%5_000_000 == 0 { fmt.Printf("----------------------------------------------prepopulated %d keys\n", k) } } - if writeWorkers > 0 { - fmt.Printf("----------------------------------------------starting write workers\n") - writeWg.Add(writeWorkers) + var wg, writeWg sync.WaitGroup - for w := 0; w < writeWorkers; w++ { + if flags.writeWorkers > 0 { + fmt.Println("----------------------------------------------starting write workers") + writeWg.Add(flags.writeWorkers) + for w := 0; w < flags.writeWorkers; w++ { go func(workerID int) { defer writeWg.Done() - for mk := range missedKeyChanList[workerID] { key := fmt.Sprintf("key%d", mk) val := []byte(fmt.Sprintf(str1kb, mk)) - if err := pc.Put(key, val, 60); err != nil { + if err := pc.Put(key, val, 60*time.Minute); err != nil { panic(err) } } @@ -179,32 +100,27 @@ func planReadthroughGaussianBatched() { } } - if readWorkers > 0 { - fmt.Printf("----------------------------------------------reading keys\n") - wg.Add(readWorkers) - - for r := 0; r < readWorkers; r++ { + if flags.readWorkers > 0 { + fmt.Println("----------------------------------------------reading keys") + wg.Add(flags.readWorkers) + for r := 0; r < flags.readWorkers; r++ { go func(workerID int) { defer wg.Done() - for k := 0; k < totalKeys*MULTIPLIER; k += 1 { - // Each worker samples from its own partition of the key space - randomval := normalDistIntPartitioned(workerID, readWorkers, totalKeys) + for k := 0; k < totalKeys*multiplier; k++ { + randomval := normalDistIntPartitioned(workerID, flags.readWorkers, totalKeys) key := fmt.Sprintf("key%d", randomval) val, found, expired := pc.Get(key) if !found { - writeWorkerid := randomval % writeWorkers - missedKeyChanList[writeWorkerid] <- randomval + missedKeyChanList[randomval%flags.writeWorkers] <- randomval } - if expired { panic("key expired") - } if found && string(val) != fmt.Sprintf(str1kb, randomval) { panic("value mismatch") } - if k%5000000 == 0 { + if k%5_000_000 == 0 { fmt.Printf("----------------------------------------------read %d keys %d readerid\n", k, workerID) } } @@ -212,32 +128,6 @@ func planReadthroughGaussianBatched() { } } - // Start pprof HTTP server for runtime profiling - wg.Wait() - log.Info().Msgf("done putting") - - // Memory profiling - if memProfile != "" { - runtime.GC() // get up-to-date statistics - f, err := os.Create(memProfile) - if err != nil { - log.Fatal().Err(err).Msg("could not create memory profile") - } - defer f.Close() - if err := pprof.WriteHeapProfile(f); err != nil { - log.Fatal().Err(err).Msg("could not write memory profile") - } - log.Info().Msgf("Memory profile written to %s", memProfile) - } - - // Print memory stats - var m runtime.MemStats - runtime.ReadMemStats(&m) - log.Info(). - Str("alloc", fmt.Sprintf("%.2f MB", float64(m.Alloc)/1024/1024)). - Str("total_alloc", fmt.Sprintf("%.2f MB", float64(m.TotalAlloc)/1024/1024)). - Str("sys", fmt.Sprintf("%.2f MB", float64(m.Sys)/1024/1024)). - Uint32("num_gc", m.NumGC). - Msg("Memory statistics") + log.Info().Msg("done") } diff --git a/flashring/cmd/flashringtest/runmetrics.go b/flashring/cmd/flashringtest/runmetrics.go deleted file mode 100644 index 5e1aabec..00000000 --- a/flashring/cmd/flashringtest/runmetrics.go +++ /dev/null @@ -1,515 +0,0 @@ -package main - -import ( - "bufio" - "encoding/csv" - "fmt" - "log" - "os" - "os/signal" - "runtime" - "strconv" - "strings" - "sync" - "syscall" - "time" -) - -// Define your parameter structure -type RunMetrics struct { - // Input Parameters - Shards int - KeysPerShard int - ReadWorkers int - WriteWorkers int - Plan string - - // Observation Parameters - RP99 time.Duration - RP50 time.Duration - RP25 time.Duration - WP99 time.Duration - WP50 time.Duration - WP25 time.Duration - RThroughput float64 - WThroughput float64 - HitRate float64 - CPUUsage float64 - MemoryUsage float64 -} - -// MetricChannels holds separate channels for each metric type -type MetricChannels struct { - RP99 chan time.Duration - RP50 chan time.Duration - RP25 chan time.Duration - WP99 chan time.Duration - WP50 chan time.Duration - WP25 chan time.Duration - RThroughput chan float64 - WThroughput chan float64 - HitRate chan float64 - CPUUsage chan float64 - MemoryUsage chan float64 -} - -// MetricAverager maintains running averages for a metric -type MetricAverager struct { - mu sync.RWMutex - sum float64 - count int64 - lastValue float64 -} - -func (ma *MetricAverager) Add(value float64) { - if value == 0 { - return // Ignore zero values - } - ma.mu.Lock() - defer ma.mu.Unlock() - ma.sum += value - ma.count++ - ma.lastValue = value -} - -func (ma *MetricAverager) AddDuration(value time.Duration) { - if value == 0 { - return // Ignore zero values - } - ma.mu.Lock() - defer ma.mu.Unlock() - ma.sum += float64(value) - ma.count++ -} - -func (ma *MetricAverager) Average() float64 { - ma.mu.RLock() - defer ma.mu.RUnlock() - if ma.count == 0 { - return 0 - } - return ma.sum / float64(ma.count) -} - -func (ma *MetricAverager) Latest() float64 { - ma.mu.RLock() - defer ma.mu.RUnlock() - return ma.lastValue -} - -func (ma *MetricAverager) Reset() { - ma.mu.Lock() - defer ma.mu.Unlock() - ma.sum = 0 - ma.count = 0 -} - -// MetricsCollector collects and averages all metrics -type MetricsCollector struct { - channels MetricChannels - averagers map[string]*MetricAverager - stopCh chan struct{} - wg sync.WaitGroup - - // Input parameters (set once) - Shards int - KeysPerShard int - ReadWorkers int - WriteWorkers int - Plan string -} - -// NewMetricsCollector creates a new metrics collector with channels -func NewMetricsCollector(bufferSize int) *MetricsCollector { - mc := &MetricsCollector{ - channels: MetricChannels{ - RP99: make(chan time.Duration, bufferSize), - RP50: make(chan time.Duration, bufferSize), - RP25: make(chan time.Duration, bufferSize), - WP99: make(chan time.Duration, bufferSize), - WP50: make(chan time.Duration, bufferSize), - WP25: make(chan time.Duration, bufferSize), - RThroughput: make(chan float64, bufferSize), - WThroughput: make(chan float64, bufferSize), - HitRate: make(chan float64, bufferSize), - CPUUsage: make(chan float64, bufferSize), - MemoryUsage: make(chan float64, bufferSize), - }, - averagers: make(map[string]*MetricAverager), - stopCh: make(chan struct{}), - } - - // Initialize averagers for each metric - metricNames := []string{"RThroughput", "RP99", "RP50", "RP25", "WThroughput", "WP99", "WP50", "WP25", "HitRate", "CPUUsage", "MemoryUsage"} - for _, name := range metricNames { - mc.averagers[name] = &MetricAverager{} - } - - return mc -} - -// Start begins collecting metrics from all channels -func (mc *MetricsCollector) Start() { - // Start a goroutine for each metric channel - mc.wg.Add(11) - - go mc.collectMetricDuration(mc.channels.RP99, "RP99") - go mc.collectMetricDuration(mc.channels.RP50, "RP50") - go mc.collectMetricDuration(mc.channels.RP25, "RP25") - go mc.collectMetricDuration(mc.channels.WP99, "WP99") - go mc.collectMetricDuration(mc.channels.WP50, "WP50") - go mc.collectMetricDuration(mc.channels.WP25, "WP25") - go mc.collectMetric(mc.channels.RThroughput, "RThroughput") - go mc.collectMetric(mc.channels.WThroughput, "WThroughput") - go mc.collectMetric(mc.channels.HitRate, "HitRate") - go mc.collectMetric(mc.channels.CPUUsage, "CPUUsage") - go mc.collectMetric(mc.channels.MemoryUsage, "MemoryUsage") -} - -func (mc *MetricsCollector) collectMetric(ch chan float64, name string) { - defer mc.wg.Done() - for { - select { - case <-mc.stopCh: - return - case value, ok := <-ch: - if !ok { - return - } - mc.averagers[name].Add(value) - } - } -} - -func (mc *MetricsCollector) collectMetricDuration(ch chan time.Duration, name string) { - defer mc.wg.Done() - for { - select { - case <-mc.stopCh: - return - case value, ok := <-ch: - if !ok { - return - } - mc.averagers[name].AddDuration(value) - } - } -} - -// RecordRP99 sends a value to the RP99 channel -func (mc *MetricsCollector) RecordRP99(value time.Duration) { - select { - case mc.channels.RP99 <- value: - default: // Don't block if channel is full - } -} - -// RecordRP50 sends a value to the RP50 channel -func (mc *MetricsCollector) RecordRP50(value time.Duration) { - select { - case mc.channels.RP50 <- value: - default: - } -} - -// RecordRP25 sends a value to the RP25 channel -func (mc *MetricsCollector) RecordRP25(value time.Duration) { - select { - case mc.channels.RP25 <- value: - default: - } -} - -// RecordWP99 sends a value to the WP99 channel -func (mc *MetricsCollector) RecordWP99(value time.Duration) { - select { - case mc.channels.WP99 <- value: - default: - } -} - -// RecordWP50 sends a value to the WP50 channel -func (mc *MetricsCollector) RecordWP50(value time.Duration) { - select { - case mc.channels.WP50 <- value: - default: - } -} - -// RecordWP25 sends a value to the WP25 channel -func (mc *MetricsCollector) RecordWP25(value time.Duration) { - select { - case mc.channels.WP25 <- value: - default: - } -} - -// RecordRThroughput sends a value to the RThroughput channel -func (mc *MetricsCollector) RecordRThroughput(value float64) { - select { - case mc.channels.RThroughput <- value: - default: - } -} - -// RecordWThroughput sends a value to the WThroughput channel -func (mc *MetricsCollector) RecordWThroughput(value float64) { - select { - case mc.channels.WThroughput <- value: - default: - } -} - -// RecordHitRate sends a value to the HitRate channel -func (mc *MetricsCollector) RecordHitRate(value float64) { - select { - case mc.channels.HitRate <- value: - default: - } -} - -// GetAveragedMetrics returns the current averaged metrics -func (mc *MetricsCollector) GetAveragedMetrics() RunMetrics { - return RunMetrics{ - Shards: mc.Shards, - KeysPerShard: mc.KeysPerShard, - ReadWorkers: mc.ReadWorkers, - WriteWorkers: mc.WriteWorkers, - Plan: mc.Plan, - RP99: time.Duration(mc.averagers["RP99"].Average()), - RP50: time.Duration(mc.averagers["RP50"].Average()), - RP25: time.Duration(mc.averagers["RP25"].Average()), - WP99: time.Duration(mc.averagers["WP99"].Average()), - WP50: time.Duration(mc.averagers["WP50"].Average()), - WP25: time.Duration(mc.averagers["WP25"].Average()), - RThroughput: mc.averagers["RThroughput"].Latest(), - WThroughput: mc.averagers["WThroughput"].Latest(), - HitRate: mc.averagers["HitRate"].Average(), - CPUUsage: mc.averagers["CPUUsage"].Average(), - MemoryUsage: mc.averagers["MemoryUsage"].Average(), - } -} - -// ResetAverages resets all averagers to start fresh -func (mc *MetricsCollector) ResetAverages() { - for _, avg := range mc.averagers { - avg.Reset() - } -} - -// Stop stops all collector goroutines -func (mc *MetricsCollector) Stop() { - close(mc.stopCh) - mc.wg.Wait() -} - -// SetShards sets the number of shards (input parameter) -func (mc *MetricsCollector) SetShards(value int) { - mc.Shards = value -} - -// SetKeysPerShard sets the keys per shard (input parameter) -func (mc *MetricsCollector) SetKeysPerShard(value int) { - mc.KeysPerShard = value -} - -// SetReadWorkers sets the number of read workers (input parameter) -func (mc *MetricsCollector) SetReadWorkers(value int) { - mc.ReadWorkers = value -} - -// SetWriteWorkers sets the number of write workers (input parameter) -func (mc *MetricsCollector) SetWriteWorkers(value int) { - mc.WriteWorkers = value -} - -// SetPlan sets the plan name (input parameter) -func (mc *MetricsCollector) SetPlan(value string) { - mc.Plan = value -} - -// Global variable to hold runtime data -var currentMetrics RunMetrics -var metricsCollector *MetricsCollector - -// --- CSV Configuration --- -const CSVFileName = "performance_results.csv" - -// InitMetricsCollector creates and starts the metrics collector, returning it -// so it can be passed to other components (e.g., cache config) -func InitMetricsCollector() *MetricsCollector { - metricsCollector = NewMetricsCollector(100) - metricsCollector.Start() - return metricsCollector -} - -// RunmetricsWaitForShutdown waits for shutdown signal and logs final metrics to CSV -func RunmetricsWaitForShutdown() { - // --- Set up Signal Handling --- - stopChan := make(chan os.Signal, 1) - signal.Notify(stopChan, syscall.SIGINT, syscall.SIGTERM) - - fmt.Println("Program running. Press Ctrl+C to stop and log results to CSV...") - - // --- Wait for Stop Signal --- - <-stopChan - fmt.Println("\nTermination signal received. Stopping work and logging results...") - - // Stop the metrics collector - if metricsCollector != nil { - metricsCollector.Stop() - - // Get final averaged metrics - currentMetrics = metricsCollector.GetAveragedMetrics() - } - - // Get memory usage and CPU usage at this instant - currentMetrics.MemoryUsage = getMemoryUsageMB() - currentMetrics.CPUUsage = getCPUUsagePercent() - - // --- Log Data to CSV --- - if err := logResultsToCSV(); err != nil { - log.Fatalf("FATAL: Failed to log results to CSV: %v", err) - } - - fmt.Printf("Successfully logged results to %s.\n", CSVFileName) - - // Exit the program since we're running in a goroutine - os.Exit(0) -} - -// RunmetricsInit initializes metrics and waits for shutdown (convenience function) -func RunmetricsInit() { - InitMetricsCollector() - RunmetricsWaitForShutdown() -} - -func logResultsToCSV() error { - // 1. Check if the file exists to determine if we need a header row. - file, err := os.OpenFile(CSVFileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - return fmt.Errorf("failed to open CSV file: %w", err) - } - defer file.Close() - - writer := csv.NewWriter(file) - defer writer.Flush() // Crucial to ensure data is written to the file before exiting. - - // The list of all your column headers - header := []string{ - "SHARDS", "KEYS_PER_SHARD", "READ_WORKERS", "WRITE_WORKERS", "PLAN", - "R_THROUGHPUT", "R_P99", "R_P50", "R_P25", "W_THROUGHPUT", "W_P99", "W_P50", "W_P25", - "HIT_RATE", "CPU", "MEMORY", "TIME", - } - - // Determine if the file is new (or empty) and needs the header - fileInfo, _ := file.Stat() - if fileInfo.Size() == 0 { - if err := writer.Write(header); err != nil { - return fmt.Errorf("error writing CSV header: %w", err) - } - } - - // Convert your struct fields into a slice of strings for the CSV writer - dataRow := []string{ - // Input Parameters - strconv.Itoa(currentMetrics.Shards), - strconv.Itoa(currentMetrics.KeysPerShard), - strconv.Itoa(currentMetrics.ReadWorkers), // Convert int to string - strconv.Itoa(currentMetrics.WriteWorkers), - currentMetrics.Plan, - - // Observation Parameters (convert floats to strings) - fmt.Sprintf("%v", currentMetrics.RThroughput), - fmt.Sprintf("%v", currentMetrics.RP99), - fmt.Sprintf("%v", currentMetrics.RP50), - fmt.Sprintf("%v", currentMetrics.RP25), - - fmt.Sprintf("%v", currentMetrics.WThroughput), - fmt.Sprintf("%v", currentMetrics.WP99), - fmt.Sprintf("%v", currentMetrics.WP50), - fmt.Sprintf("%v", currentMetrics.WP25), - - fmt.Sprintf("%v", currentMetrics.HitRate), - fmt.Sprintf("%v", currentMetrics.CPUUsage), - fmt.Sprintf("%v", currentMetrics.MemoryUsage), - fmt.Sprintf("%v", time.Now().In(time.FixedZone("IST", 5*60*60+30*60)).Format("2006-01-02 15:04:05")), - } - - if err := writer.Write(dataRow); err != nil { - return fmt.Errorf("error writing CSV data row: %w", err) - } - - return nil -} - -// getMemoryUsageMB returns the current memory usage of this process in MB -func getMemoryUsageMB() float64 { - var m runtime.MemStats - runtime.ReadMemStats(&m) - // Alloc is bytes of allocated heap objects - return float64(m.Alloc) / 1024 / 1024 -} - -// getSystemMemoryUsageMB returns the total system memory used by this process in MB -func getSystemMemoryUsageMB() float64 { - var m runtime.MemStats - runtime.ReadMemStats(&m) - // Sys is the total bytes of memory obtained from the OS - return float64(m.Sys) / 1024 / 1024 -} - -// getCPUUsagePercent returns the CPU usage percentage for this process -// It measures CPU usage over a short interval -func getCPUUsagePercent() float64 { - // Read initial CPU stats - idle1, total1 := getCPUStats() - time.Sleep(100 * time.Millisecond) - // Read CPU stats again - idle2, total2 := getCPUStats() - - idleDelta := float64(idle2 - idle1) - totalDelta := float64(total2 - total1) - - if totalDelta == 0 { - return 0 - } - - cpuUsage := (1.0 - idleDelta/totalDelta) * 100.0 - return cpuUsage -} - -// getCPUStats reads /proc/stat and returns idle and total CPU time -func getCPUStats() (idle, total uint64) { - file, err := os.Open("/proc/stat") - if err != nil { - return 0, 0 - } - defer file.Close() - - scanner := bufio.NewScanner(file) - for scanner.Scan() { - line := scanner.Text() - if strings.HasPrefix(line, "cpu ") { - fields := strings.Fields(line) - if len(fields) < 5 { - return 0, 0 - } - // fields: cpu user nice system idle iowait irq softirq steal guest guest_nice - var values []uint64 - for _, field := range fields[1:] { - val, err := strconv.ParseUint(field, 10, 64) - if err != nil { - continue - } - values = append(values, val) - total += val - } - if len(values) >= 4 { - idle = values[3] // idle is the 4th value - } - break - } - } - return idle, total -} diff --git a/flashring/go.mod b/flashring/go.mod index f02d9663..206adab3 100644 --- a/flashring/go.mod +++ b/flashring/go.mod @@ -13,7 +13,23 @@ require ( ) require ( - github.com/dgraph-io/badger/v4 v4.9.0 // indirect + github.com/Microsoft/go-winio v0.5.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/sagikazarmark/locafero v0.11.0 // indirect + github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect + github.com/spf13/afero v1.15.0 // indirect + github.com/spf13/cast v1.10.0 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/text v0.28.0 // indirect +) + +require ( + github.com/DataDog/datadog-go/v5 v5.8.2 + github.com/dgraph-io/badger/v4 v4.9.0 github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/go-logr/logr v1.4.3 // indirect @@ -23,6 +39,7 @@ require ( github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/spf13/viper v1.21.0 go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/otel v1.37.0 // indirect go.opentelemetry.io/otel/metric v1.37.0 // indirect diff --git a/flashring/go.sum b/flashring/go.sum index 6c22ab66..5d69f8d2 100644 --- a/flashring/go.sum +++ b/flashring/go.sum @@ -1,42 +1,92 @@ +github.com/DataDog/datadog-go/v5 v5.8.2 h1:9IEfH1Mw9AjWwhAMqCAkhbxjuJeMxm2ARX2VdgL+ols= +github.com/DataDog/datadog-go/v5 v5.8.2/go.mod h1:K9kcYBlxkcPP8tvvjZZKs/m1edNAUFzBbdpTUKfCsuw= +github.com/Microsoft/go-winio v0.5.0 h1:Elr9Wn+sGKPlkaBvwu4mTrxtmOp3F3yV9qhaHbXGjwU= +github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/coocood/freecache v1.2.4 h1:UdR6Yz/X1HW4fZOuH0Z94KwG851GWOSknua5VUbb/5M= github.com/coocood/freecache v1.2.4/go.mod h1:RBUWa/Cy+OHdfTGFEhEuE1pMCMX51Ncizj7rthiQ3vk= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgraph-io/badger/v4 v4.9.0 h1:tpqWb0NewSrCYqTvywbcXOhQdWcqephkVkbBmaaqHzc= github.com/dgraph-io/badger/v4 v4.9.0/go.mod h1:5/MEx97uzdPUHR4KtkNt8asfI2T4JiEiQlV7kWUo8c0= github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM= github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI= +github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38= +github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= -github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= +github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= +github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= +github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= +github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= +github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= @@ -49,14 +99,46 @@ go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/Wgbsd go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/flashring/internal/allocators/allocators.go b/flashring/internal/allocators/allocators.go index 3f4cf692..48d75b82 100644 --- a/flashring/internal/allocators/allocators.go +++ b/flashring/internal/allocators/allocators.go @@ -4,3 +4,8 @@ type SizeClass struct { Size int MinCount int } + +type Meta struct { + Size int + Name string +} diff --git a/flashring/internal/allocators/byte_slice_allocator.go b/flashring/internal/allocators/byte_slice_allocator.go deleted file mode 100644 index f2990924..00000000 --- a/flashring/internal/allocators/byte_slice_allocator.go +++ /dev/null @@ -1,55 +0,0 @@ -package allocators - -import ( - "fmt" - "sort" - - "github.com/Meesho/BharatMLStack/flashring/internal/pools" - "github.com/rs/zerolog/log" -) - -type ByteSliceAllocatorConfig struct { - SizeClasses []SizeClass -} - -type ByteSliceAllocator struct { - config ByteSliceAllocatorConfig - pools []*pools.LeakyPool -} - -func NewByteSliceAllocator(config ByteSliceAllocatorConfig) *ByteSliceAllocator { - poolList := make([]*pools.LeakyPool, len(config.SizeClasses)) - sort.Slice(config.SizeClasses, func(i, j int) bool { - return config.SizeClasses[i].Size < config.SizeClasses[j].Size - }) - for i, sizeClass := range config.SizeClasses { - poolConfig := pools.LeakyPoolConfig{ - Capacity: sizeClass.MinCount, - Meta: Meta{Size: sizeClass.Size, Name: fmt.Sprintf("ByteSlicePool-%dBytes", sizeClass.Size)}, - CreateFunc: func() interface{} { return make([]byte, sizeClass.Size) }, - } - poolList[i] = pools.NewLeakyPool(poolConfig) - log.Debug().Msgf("ByteSliceAllocator: size class - %d | min count - %d", sizeClass.Size, sizeClass.MinCount) - } - return &ByteSliceAllocator{config: config, pools: poolList} -} - -func (a *ByteSliceAllocator) Get(size int) []byte { - for _, pool := range a.pools { - if size <= pool.Meta.(Meta).Size { - slice := pool.Get() - return slice.([]byte) - } - } - return nil -} - -func (a *ByteSliceAllocator) Put(p []byte) { - for _, pool := range a.pools { - if len(p) <= pool.Meta.(Meta).Size { - pool.Put(p) - return - } - } - log.Error().Msgf("ByteSliceAllocator: Size class not found for size %d", len(p)) -} diff --git a/flashring/internal/allocators/byte_slice_allocator_test.go b/flashring/internal/allocators/byte_slice_allocator_test.go deleted file mode 100644 index a962dd06..00000000 --- a/flashring/internal/allocators/byte_slice_allocator_test.go +++ /dev/null @@ -1,447 +0,0 @@ -package allocators - -import ( - "testing" -) - -func TestNewByteSliceAllocator(t *testing.T) { - t.Run("creates allocator with single size class", func(t *testing.T) { - config := ByteSliceAllocatorConfig{ - SizeClasses: []SizeClass{ - {Size: 1024, MinCount: 10}, - }, - } - allocator := NewByteSliceAllocator(config) - - if allocator == nil { - t.Error("Expected allocator to be non-nil") - } - if allocator.config.SizeClasses[0].Size != config.SizeClasses[0].Size { - t.Errorf("Expected config to match, got %v", allocator.config) - } - if len(allocator.pools) != 1 { - t.Errorf("Expected 1 pool, got %d", len(allocator.pools)) - } - if allocator.pools[0].Meta.(Meta).Size != 1024 { - t.Errorf("Expected pool size 1024, got %d", allocator.pools[0].Meta.(Meta).Size) - } - if allocator.pools[0].Meta.(Meta).Name != "ByteSlicePool-1024Bytes" { - t.Errorf("Expected pool name 'ByteSlicePool-1024Bytes', got %s", allocator.pools[0].Meta.(Meta).Name) - } - }) - - t.Run("creates allocator with multiple size classes", func(t *testing.T) { - config := ByteSliceAllocatorConfig{ - SizeClasses: []SizeClass{ - {Size: 512, MinCount: 5}, - {Size: 1024, MinCount: 10}, - {Size: 256, MinCount: 15}, - }, - } - allocator := NewByteSliceAllocator(config) - - if allocator == nil { - t.Error("Expected allocator to be non-nil") - } - if len(allocator.pools) != 3 { - t.Errorf("Expected 3 pools, got %d", len(allocator.pools)) - } - - // Should be sorted by size - if allocator.pools[0].Meta.(Meta).Size != 256 { - t.Errorf("Expected first pool size 256, got %d", allocator.pools[0].Meta.(Meta).Size) - } - if allocator.pools[1].Meta.(Meta).Size != 512 { - t.Errorf("Expected second pool size 512, got %d", allocator.pools[1].Meta.(Meta).Size) - } - if allocator.pools[2].Meta.(Meta).Size != 1024 { - t.Errorf("Expected third pool size 1024, got %d", allocator.pools[2].Meta.(Meta).Size) - } - }) - - t.Run("creates allocator with empty size classes", func(t *testing.T) { - config := ByteSliceAllocatorConfig{ - SizeClasses: []SizeClass{}, - } - allocator := NewByteSliceAllocator(config) - - if allocator == nil { - t.Error("Expected allocator to be non-nil") - } - if len(allocator.pools) != 0 { - t.Errorf("Expected 0 pools, got %d", len(allocator.pools)) - } - }) -} - -func TestByteSliceAllocator_Get(t *testing.T) { - t.Run("returns byte slice for exact size match", func(t *testing.T) { - config := ByteSliceAllocatorConfig{ - SizeClasses: []SizeClass{ - {Size: 1024, MinCount: 10}, - }, - } - allocator := NewByteSliceAllocator(config) - - slice := allocator.Get(1024) - if slice == nil { - t.Error("Expected slice to be non-nil") - } - if cap(slice) != 1024 { - t.Errorf("Expected slice capacity 1024, got %d", cap(slice)) - } - if len(slice) != 1024 { - t.Errorf("Expected slice length 1024, got %d", len(slice)) - } - }) - - t.Run("returns byte slice for smaller size", func(t *testing.T) { - config := ByteSliceAllocatorConfig{ - SizeClasses: []SizeClass{ - {Size: 1024, MinCount: 10}, - }, - } - allocator := NewByteSliceAllocator(config) - - slice := allocator.Get(512) - if slice == nil { - t.Error("Expected slice to be non-nil") - } - if cap(slice) != 1024 { - t.Errorf("Expected slice capacity 1024, got %d", cap(slice)) - } - if len(slice) != 1024 { - t.Errorf("Expected slice length 1024, got %d", len(slice)) - } - }) - - t.Run("returns smallest suitable size class", func(t *testing.T) { - config := ByteSliceAllocatorConfig{ - SizeClasses: []SizeClass{ - {Size: 256, MinCount: 5}, - {Size: 512, MinCount: 10}, - {Size: 1024, MinCount: 15}, - }, - } - allocator := NewByteSliceAllocator(config) - - slice := allocator.Get(300) - if slice == nil { - t.Error("Expected slice to be non-nil") - } - if cap(slice) != 512 { - t.Errorf("Expected slice capacity 512, got %d", cap(slice)) - } - if len(slice) != 512 { - t.Errorf("Expected slice length 512, got %d", len(slice)) - } - }) - - t.Run("returns nil for size larger than all size classes", func(t *testing.T) { - config := ByteSliceAllocatorConfig{ - SizeClasses: []SizeClass{ - {Size: 1024, MinCount: 10}, - }, - } - allocator := NewByteSliceAllocator(config) - - slice := allocator.Get(2048) - if slice != nil { - t.Error("Expected slice to be nil for size larger than all size classes") - } - }) - - t.Run("returns nil for empty size classes", func(t *testing.T) { - config := ByteSliceAllocatorConfig{ - SizeClasses: []SizeClass{}, - } - allocator := NewByteSliceAllocator(config) - - slice := allocator.Get(1024) - if slice != nil { - t.Error("Expected slice to be nil for empty size classes") - } - }) - - t.Run("returns slice for zero size request", func(t *testing.T) { - config := ByteSliceAllocatorConfig{ - SizeClasses: []SizeClass{ - {Size: 1024, MinCount: 10}, - }, - } - allocator := NewByteSliceAllocator(config) - - slice := allocator.Get(0) - if slice == nil { - t.Error("Expected slice to be non-nil for zero size request") - } - if cap(slice) != 1024 { - t.Errorf("Expected slice capacity 1024, got %d", cap(slice)) - } - }) - - t.Run("returns slice for negative size request", func(t *testing.T) { - config := ByteSliceAllocatorConfig{ - SizeClasses: []SizeClass{ - {Size: 1024, MinCount: 10}, - }, - } - allocator := NewByteSliceAllocator(config) - - slice := allocator.Get(-1) - if slice == nil { - t.Error("Expected slice to be non-nil for negative size request") - } - if cap(slice) != 1024 { - t.Errorf("Expected slice capacity 1024, got %d", cap(slice)) - } - }) -} - -func TestByteSliceAllocator_Put(t *testing.T) { - t.Run("puts byte slice back to correct pool", func(t *testing.T) { - config := ByteSliceAllocatorConfig{ - SizeClasses: []SizeClass{ - {Size: 1024, MinCount: 10}, - }, - } - allocator := NewByteSliceAllocator(config) - - slice := allocator.Get(1024) - if slice == nil { - t.Fatal("Expected slice to be non-nil") - } - - // Put should not panic - allocator.Put(slice) - }) - - t.Run("puts byte slice to smallest suitable pool", func(t *testing.T) { - config := ByteSliceAllocatorConfig{ - SizeClasses: []SizeClass{ - {Size: 256, MinCount: 5}, - {Size: 512, MinCount: 10}, - {Size: 1024, MinCount: 15}, - }, - } - allocator := NewByteSliceAllocator(config) - - slice := make([]byte, 300) - allocator.Put(slice) - // Should not panic, even though slice wasn't from the pool - }) - - t.Run("handles slice larger than all size classes", func(t *testing.T) { - config := ByteSliceAllocatorConfig{ - SizeClasses: []SizeClass{ - {Size: 1024, MinCount: 10}, - }, - } - allocator := NewByteSliceAllocator(config) - - slice := make([]byte, 2048) - // Should not panic, but will log error - allocator.Put(slice) - }) - - t.Run("handles empty slice", func(t *testing.T) { - config := ByteSliceAllocatorConfig{ - SizeClasses: []SizeClass{ - {Size: 1024, MinCount: 10}, - }, - } - allocator := NewByteSliceAllocator(config) - - slice := make([]byte, 0) - allocator.Put(slice) - // Should not panic - }) - - t.Run("handles nil slice", func(t *testing.T) { - config := ByteSliceAllocatorConfig{ - SizeClasses: []SizeClass{ - {Size: 1024, MinCount: 10}, - }, - } - allocator := NewByteSliceAllocator(config) - - // Should not panic - allocator.Put(nil) - }) -} - -func TestByteSliceAllocator_GetAndPut_Integration(t *testing.T) { - t.Run("get and put multiple times", func(t *testing.T) { - config := ByteSliceAllocatorConfig{ - SizeClasses: []SizeClass{ - {Size: 256, MinCount: 2}, - {Size: 512, MinCount: 3}, - {Size: 1024, MinCount: 5}, - }, - } - allocator := NewByteSliceAllocator(config) - - // Get multiple slices - slices := make([][]byte, 5) - for i := 0; i < 5; i++ { - slices[i] = allocator.Get(200) - if slices[i] == nil { - t.Errorf("Expected slice %d to be non-nil", i) - } - if len(slices[i]) != 256 { - t.Errorf("Expected slice %d length 256, got %d", i, len(slices[i])) - } - } - - // Put them back - for _, slice := range slices { - allocator.Put(slice) - } - - // Get them again - for i := 0; i < 5; i++ { - slice := allocator.Get(200) - if slice == nil { - t.Errorf("Expected slice %d to be non-nil on second get", i) - } - if len(slice) != 256 { - t.Errorf("Expected slice %d length 256 on second get, got %d", i, len(slice)) - } - } - }) - - t.Run("get and put with different sizes", func(t *testing.T) { - config := ByteSliceAllocatorConfig{ - SizeClasses: []SizeClass{ - {Size: 256, MinCount: 2}, - {Size: 512, MinCount: 3}, - {Size: 1024, MinCount: 5}, - }, - } - allocator := NewByteSliceAllocator(config) - - // Get slices of different sizes - slice256 := allocator.Get(200) - slice512 := allocator.Get(400) - slice1024 := allocator.Get(800) - - if len(slice256) != 256 { - t.Errorf("Expected slice256 length 256, got %d", len(slice256)) - } - if len(slice512) != 512 { - t.Errorf("Expected slice512 length 512, got %d", len(slice512)) - } - if len(slice1024) != 1024 { - t.Errorf("Expected slice1024 length 1024, got %d", len(slice1024)) - } - - // Put them back - allocator.Put(slice256) - allocator.Put(slice512) - allocator.Put(slice1024) - - // Get them again - newSlice256 := allocator.Get(200) - newSlice512 := allocator.Get(400) - newSlice1024 := allocator.Get(800) - - if len(newSlice256) != 256 { - t.Errorf("Expected newSlice256 length 256, got %d", len(newSlice256)) - } - if len(newSlice512) != 512 { - t.Errorf("Expected newSlice512 length 512, got %d", len(newSlice512)) - } - if len(newSlice1024) != 1024 { - t.Errorf("Expected newSlice1024 length 1024, got %d", len(newSlice1024)) - } - }) -} - -func TestByteSliceAllocator_SizeClassSorting(t *testing.T) { - t.Run("size classes are sorted correctly", func(t *testing.T) { - config := ByteSliceAllocatorConfig{ - SizeClasses: []SizeClass{ - {Size: 1024, MinCount: 10}, - {Size: 256, MinCount: 5}, - {Size: 512, MinCount: 15}, - {Size: 128, MinCount: 20}, - }, - } - allocator := NewByteSliceAllocator(config) - - // Verify pools are sorted by size - if allocator.pools[0].Meta.(Meta).Size != 128 { - t.Errorf("Expected first pool size 128, got %d", allocator.pools[0].Meta.(Meta).Size) - } - if allocator.pools[1].Meta.(Meta).Size != 256 { - t.Errorf("Expected second pool size 256, got %d", allocator.pools[1].Meta.(Meta).Size) - } - if allocator.pools[2].Meta.(Meta).Size != 512 { - t.Errorf("Expected third pool size 512, got %d", allocator.pools[2].Meta.(Meta).Size) - } - if allocator.pools[3].Meta.(Meta).Size != 1024 { - t.Errorf("Expected fourth pool size 1024, got %d", allocator.pools[3].Meta.(Meta).Size) - } - - // Test that Get returns from the correct pool - slice := allocator.Get(200) - if slice == nil { - t.Error("Expected slice to be non-nil") - } - if len(slice) != 256 { - t.Errorf("Expected slice length 256 (should use 256 pool, not 128), got %d", len(slice)) - } - }) -} - -func TestByteSliceAllocator_EdgeCases(t *testing.T) { - t.Run("single size class with exact match", func(t *testing.T) { - config := ByteSliceAllocatorConfig{ - SizeClasses: []SizeClass{ - {Size: 512, MinCount: 1}, - }, - } - allocator := NewByteSliceAllocator(config) - - slice := allocator.Get(512) - if slice == nil { - t.Error("Expected slice to be non-nil") - } - if len(slice) != 512 { - t.Errorf("Expected slice length 512, got %d", len(slice)) - } - - allocator.Put(slice) - - // Get again after putting back - slice2 := allocator.Get(512) - if slice2 == nil { - t.Error("Expected slice2 to be non-nil") - } - if len(slice2) != 512 { - t.Errorf("Expected slice2 length 512, got %d", len(slice2)) - } - }) - - t.Run("duplicate size classes", func(t *testing.T) { - config := ByteSliceAllocatorConfig{ - SizeClasses: []SizeClass{ - {Size: 512, MinCount: 5}, - {Size: 512, MinCount: 10}, - }, - } - allocator := NewByteSliceAllocator(config) - - if len(allocator.pools) != 2 { - t.Errorf("Expected 2 pools, got %d", len(allocator.pools)) - } - - slice := allocator.Get(512) - if slice == nil { - t.Error("Expected slice to be non-nil") - } - if len(slice) != 512 { - t.Errorf("Expected slice length 512, got %d", len(slice)) - } - }) -} diff --git a/flashring/internal/allocators/slab_aligned_page_allocator.go b/flashring/internal/allocators/slab_aligned_page_allocator.go index 07a8d8ba..4d1c1ae4 100644 --- a/flashring/internal/allocators/slab_aligned_page_allocator.go +++ b/flashring/internal/allocators/slab_aligned_page_allocator.go @@ -18,53 +18,52 @@ type SlabAlignedPageAllocatorConfig struct { SizeClasses []SizeClass } -type Meta struct { - Size int - Name string -} - type SlabAlignedPageAllocator struct { config SlabAlignedPageAllocatorConfig - pools []*pools.LeakyPool + pools []*pools.LeakyPool[*fs.AlignedPage] + sizes []int } func NewSlabAlignedPageAllocator(config SlabAlignedPageAllocatorConfig) (*SlabAlignedPageAllocator, error) { - poolList := make([]*pools.LeakyPool, len(config.SizeClasses)) sort.Slice(config.SizeClasses, func(i, j int) bool { return config.SizeClasses[i].Size < config.SizeClasses[j].Size }) - for i, sizeClass := range config.SizeClasses { - if sizeClass.Size%fs.BLOCK_SIZE != 0 { + + poolList := make([]*pools.LeakyPool[*fs.AlignedPage], len(config.SizeClasses)) + sizes := make([]int, len(config.SizeClasses)) + + for i, sc := range config.SizeClasses { + if sc.Size%fs.BLOCK_SIZE != 0 { return nil, ErrSizeNotAligned } - poolConfig := pools.LeakyPoolConfig{ - Capacity: sizeClass.MinCount, - Meta: Meta{Size: sizeClass.Size, Name: fmt.Sprintf("SlabAlignedPagePool-%dBytes", sizeClass.Size)}, - CreateFunc: func() interface{} { return fs.NewAlignedPage(sizeClass.Size) }, - } - poolList[i] = pools.NewLeakyPool(poolConfig) - poolList[i].RegisterPreDrefHook(func(obj interface{}) { - fs.Unmap(obj.(*fs.AlignedPage)) + sizes[i] = sc.Size + size := sc.Size + poolList[i] = pools.NewLeakyPool(pools.LeakyPoolConfig[*fs.AlignedPage]{ + Capacity: sc.MinCount, + Meta: Meta{Size: sc.Size, Name: fmt.Sprintf("SlabAlignedPagePool-%dBytes", sc.Size)}, + CreateFunc: func() *fs.AlignedPage { return fs.NewAlignedPage(size) }, + }) + poolList[i].RegisterPreDrefHook(func(p *fs.AlignedPage) { + fs.Unmap(p) }) - log.Debug().Msgf("SlabAlignedPageAllocator: size class - %d | min count - %d", sizeClass.Size, sizeClass.MinCount) + log.Debug().Msgf("SlabAlignedPageAllocator: size class - %d | min count - %d", sc.Size, sc.MinCount) } - return &SlabAlignedPageAllocator{config: config, pools: poolList}, nil + return &SlabAlignedPageAllocator{config: config, pools: poolList, sizes: sizes}, nil } func (a *SlabAlignedPageAllocator) Get(size int) *fs.AlignedPage { - for _, pool := range a.pools { - if size <= pool.Meta.(Meta).Size { - page := pool.Get() - return page.(*fs.AlignedPage) + for i, s := range a.sizes { + if size <= s { + return a.pools[i].Get() } } return nil } func (a *SlabAlignedPageAllocator) Put(p *fs.AlignedPage) { - for _, pool := range a.pools { - if len(p.Buf) <= pool.Meta.(Meta).Size { - pool.Put(p) + for i, s := range a.sizes { + if len(p.Buf) <= s { + a.pools[i].Put(p) return } } diff --git a/flashring/internal/cache/badger.go b/flashring/internal/cache/badger.go deleted file mode 100644 index 7ff8c691..00000000 --- a/flashring/internal/cache/badger.go +++ /dev/null @@ -1,135 +0,0 @@ -package internal - -import ( - "sync/atomic" - "time" - - filecache "github.com/Meesho/BharatMLStack/flashring/internal/shard" - badger "github.com/dgraph-io/badger/v4" - "github.com/rs/zerolog/log" -) - -type Badger struct { - cache *badger.DB - stats *CacheStats -} - -func NewBadger(config WrapCacheConfig, logStats bool) (*Badger, error) { - options := badger.DefaultOptions(config.MountPoint) - options.MetricsEnabled = false - - // 1. PRIMARY CACHE (1GB) - // This caches the data blocks themselves. - options.BlockCacheSize = 1024 << 20 - - // 2. INDEX CACHE (512MB) - // This keeps the keys and the structure of the LSM tree in RAM. - // This is the most critical setting for read latency. - options.IndexCacheSize = 512 << 20 - - // 3. WRITE BUFFERS (Memtables) - // We use 3 tables of 64MB each. This allows Badger to handle - // write spikes without blocking. (~192MB total) - options.NumMemtables = 40 - options.MemTableSize = 1024 << 20 - - options.ValueThreshold = 1024 - options.SyncWrites = false - - cache, err := badger.Open(options) - if err != nil { - return nil, err - } - bc := &Badger{ - cache: cache, - stats: &CacheStats{ - Hits: atomic.Uint64{}, - TotalGets: atomic.Uint64{}, - TotalPuts: atomic.Uint64{}, - ReWrites: atomic.Uint64{}, - Expired: atomic.Uint64{}, - ShardWiseActiveEntries: atomic.Uint64{}, - LatencyTracker: filecache.NewLatencyTracker(), - }, - } - - if logStats { - go func() { - sleepDuration := 10 * time.Second - var prevTotalGets, prevTotalPuts uint64 - for { - time.Sleep(sleepDuration) - - totalGets := bc.stats.TotalGets.Load() - totalPuts := bc.stats.TotalPuts.Load() - getsPerSec := float64(totalGets-prevTotalGets) / sleepDuration.Seconds() - putsPerSec := float64(totalPuts-prevTotalPuts) / sleepDuration.Seconds() - - log.Info().Msgf("Shard %d HitRate: %v", 0, cache.BlockCacheMetrics().Hits()) - log.Info().Msgf("Shard %d Expired: %v", 0, cache.BlockCacheMetrics().Misses()) - log.Info().Msgf("Shard %d Total: %v", 0, cache.BlockCacheMetrics().KeysEvicted()) - log.Info().Msgf("Gets/sec: %v", getsPerSec) - log.Info().Msgf("Puts/sec: %v", putsPerSec) - - getP25, getP50, getP99 := bc.stats.LatencyTracker.GetLatencyPercentiles() - putP25, putP50, putP99 := bc.stats.LatencyTracker.PutLatencyPercentiles() - - log.Info().Msgf("Get Count: %v", totalGets) - log.Info().Msgf("Put Count: %v", totalPuts) - log.Info().Msgf("Get Latencies - P25: %v, P50: %v, P99: %v", getP25, getP50, getP99) - log.Info().Msgf("Put Latencies - P25: %v, P50: %v, P99: %v", putP25, putP50, putP99) - - prevTotalGets = totalGets - prevTotalPuts = totalPuts - } - }() - } - - return bc, nil -} - -func (b *Badger) Put(key string, value []byte, exptimeInMinutes uint16) error { - - start := time.Now() - defer func() { - b.stats.LatencyTracker.RecordPut(time.Since(start)) - }() - - b.stats.TotalPuts.Add(1) - err := b.cache.Update(func(txn *badger.Txn) error { - entry := badger.NewEntry([]byte(key), value).WithTTL(time.Duration(exptimeInMinutes) * time.Minute) - err := txn.SetEntry(entry) - return err - }) - return err -} - -func (b *Badger) Get(key string) ([]byte, bool, bool) { - - start := time.Now() - defer func() { - b.stats.LatencyTracker.RecordGet(time.Since(start)) - }() - - b.stats.TotalGets.Add(1) - - val := make([]byte, 0) - err := b.cache.View(func(txn *badger.Txn) error { - item, err := txn.Get([]byte(key)) - if err != nil { - return err - } - val, err = item.ValueCopy(val) - - if err != nil { - b.stats.Hits.Add(1) - } - - return err - }) - return val, err != badger.ErrKeyNotFound, false -} - -func (b *Badger) Close() error { - return b.cache.Close() -} diff --git a/flashring/internal/cache/cache.go b/flashring/internal/cache/cache.go deleted file mode 100644 index 74755251..00000000 --- a/flashring/internal/cache/cache.go +++ /dev/null @@ -1,457 +0,0 @@ -package internal - -import ( - "fmt" - "strconv" - "sync" - "sync/atomic" - "time" - - "github.com/Meesho/BharatMLStack/flashring/internal/maths" - filecache "github.com/Meesho/BharatMLStack/flashring/internal/shard" - "github.com/cespare/xxhash/v2" - "github.com/rs/zerolog/log" -) - -/* - Each shard can keep 67M keys - With Round = 1, expected collision (67M)^2/(2*2^62) = 4.87×10^-4 -*/ - -const ( - ROUNDS = 1 - KEYS_PER_SHARD = (1 << 26) - BLOCK_SIZE = 4096 -) - -var ( - ErrNumShardLessThan1 = fmt.Errorf("num shards must be greater than 0") - ErrKeysPerShardLessThan1 = fmt.Errorf("keys per shard must be greater than 0") - ErrKeysPerShardGreaterThan67M = fmt.Errorf("keys per shard must be less than 67M") - ErrMemtableSizeLessThan1 = fmt.Errorf("memtable size must be greater than 0") - ErrMemtableSizeGreaterThan1GB = fmt.Errorf("memtable size must be less than 1GB") - ErrMemtableSizeNotMultipleOf4KB = fmt.Errorf("memtable size must be a multiple of 4KB") - ErrFileSizeLessThan1 = fmt.Errorf("file size must be greater than 0") - ErrFileSizeNotMultipleOf4KB = fmt.Errorf("file size must be a multiple of 4KB") - Seed = xxhash.Sum64String(strconv.Itoa(int(time.Now().UnixNano()))) -) - -type WrapCache struct { - shards []*filecache.ShardCache - shardLocks []sync.RWMutex - predictor *maths.Predictor - stats []*CacheStats - metricsRecorder MetricsRecorder -} - -type CacheStats struct { - Hits atomic.Uint64 - TotalGets atomic.Uint64 - TotalPuts atomic.Uint64 - ReWrites atomic.Uint64 - Expired atomic.Uint64 - ShardWiseActiveEntries atomic.Uint64 - LatencyTracker *filecache.LatencyTracker - BatchTracker *filecache.BatchTracker -} - -// MetricsRecorder is an interface for recording metrics from the cache -// Implement this interface to receive metrics from the cache layer -type MetricsRecorder interface { - // Input parameters - SetShards(value int) - SetKeysPerShard(value int) - SetReadWorkers(value int) - SetWriteWorkers(value int) - SetPlan(value string) - - // Observation metrics - RecordRP99(value time.Duration) - RecordRP50(value time.Duration) - RecordRP25(value time.Duration) - RecordWP99(value time.Duration) - RecordWP50(value time.Duration) - RecordWP25(value time.Duration) - RecordRThroughput(value float64) - RecordWThroughput(value float64) - RecordHitRate(value float64) -} - -type WrapCacheConfig struct { - NumShards int - KeysPerShard int - FileSize int64 - MemtableSize int32 - ReWriteScoreThreshold float32 - GridSearchEpsilon float64 - SampleDuration time.Duration - - // Batching reads - EnableBatching bool - BatchWindowMicros int // in microseconds - MaxBatchSize int - - // Optional metrics recorder - MetricsRecorder MetricsRecorder - - //Badger - MountPoint string -} - -func NewWrapCache(config WrapCacheConfig, mountPoint string, logStats bool) (*WrapCache, error) { - if config.NumShards <= 0 { - return nil, ErrNumShardLessThan1 - } - if config.KeysPerShard <= 0 { - return nil, ErrKeysPerShardLessThan1 - } - if config.KeysPerShard > KEYS_PER_SHARD { - return nil, ErrKeysPerShardGreaterThan67M - } - if config.MemtableSize <= 0 { - return nil, ErrMemtableSizeLessThan1 - } - if config.MemtableSize > 1024*1024*1024 { - return nil, ErrMemtableSizeGreaterThan1GB - } - if config.MemtableSize%BLOCK_SIZE != 0 { - return nil, ErrMemtableSizeNotMultipleOf4KB - } - if config.FileSize <= 0 { - return nil, ErrFileSizeLessThan1 - } - if config.FileSize%BLOCK_SIZE != 0 { - return nil, ErrFileSizeNotMultipleOf4KB - } - weights := []maths.WeightTuple{ - { - WFreq: 0.1, - WLA: 0.1, - }, - { - WFreq: 0.45, - WLA: 0.1, - }, - { - WFreq: 0.9, - WLA: 0.1, - }, - { - WFreq: 0.1, - WLA: 0.45, - }, - { - WFreq: 0.45, - WLA: 0.45, - }, - { - WFreq: 0.9, - WLA: 0.45, - }, - { - WFreq: 0.1, - WLA: 0.9, - }, - { - WFreq: 0.45, - WLA: 0.9, - }, - { - WFreq: 0.9, - WLA: 0.9, - }, - } - MaxMemTableCount := config.FileSize / int64(config.MemtableSize) - predictor := maths.NewPredictor(maths.PredictorConfig{ - ReWriteScoreThreshold: config.ReWriteScoreThreshold, - Weights: weights, - SampleDuration: config.SampleDuration, - MaxMemTableCount: uint32(MaxMemTableCount), - GridSearchEpsilon: config.GridSearchEpsilon, - }) - - batchWindow := time.Duration(0) - if config.EnableBatching && config.BatchWindowMicros > 0 { - batchWindow = time.Duration(config.BatchWindowMicros) * time.Microsecond - } - shardLocks := make([]sync.RWMutex, config.NumShards) - shards := make([]*filecache.ShardCache, config.NumShards) - for i := 0; i < config.NumShards; i++ { - shards[i] = filecache.NewShardCache(filecache.ShardCacheConfig{ - MemtableSize: config.MemtableSize, - Rounds: ROUNDS, - RbInitial: config.KeysPerShard, - RbMax: config.KeysPerShard, - DeleteAmortizedStep: 10000, - MaxFileSize: int64(config.FileSize), - BlockSize: BLOCK_SIZE, - Directory: mountPoint, - Predictor: predictor, - - //batching reads - EnableBatching: config.EnableBatching, - BatchWindow: batchWindow, - MaxBatchSize: config.MaxBatchSize, - }, &shardLocks[i]) - } - - stats := make([]*CacheStats, config.NumShards) - for i := 0; i < config.NumShards; i++ { - stats[i] = &CacheStats{LatencyTracker: filecache.NewLatencyTracker(), BatchTracker: filecache.NewBatchTracker()} - } - wc := &WrapCache{ - shards: shards, - shardLocks: shardLocks, - predictor: predictor, - stats: stats, - metricsRecorder: config.MetricsRecorder, - } - if logStats { - - go func() { - sleepDuration := 10 * time.Second - // perShardPrevTotalGets := make([]uint64, config.NumShards) - // perShardPrevTotalPuts := make([]uint64, config.NumShards) - combinedPrevTotalGets := uint64(0) - combinedPrevTotalPuts := uint64(0) - for { - time.Sleep(sleepDuration) - - combinedTotalGets := uint64(0) - combinedTotalPuts := uint64(0) - combinedHits := uint64(0) - combinedReWrites := uint64(0) - combinedExpired := uint64(0) - combinedShardWiseActiveEntries := uint64(0) - for i := 0; i < config.NumShards; i++ { - combinedTotalGets += wc.stats[i].TotalGets.Load() - combinedTotalPuts += wc.stats[i].TotalPuts.Load() - combinedHits += wc.stats[i].Hits.Load() - combinedReWrites += wc.stats[i].ReWrites.Load() - combinedExpired += wc.stats[i].Expired.Load() - combinedShardWiseActiveEntries += wc.stats[i].ShardWiseActiveEntries.Load() - } - - combinedHitRate := float64(0) - if combinedTotalGets > 0 { - combinedHitRate = float64(combinedHits) / float64(combinedTotalGets) - } - - log.Info().Msgf("Combined HitRate: %v", combinedHitRate) - log.Info().Msgf("Combined ReWrites: %v", combinedReWrites) - log.Info().Msgf("Combined Expired: %v", combinedExpired) - log.Info().Msgf("Combined Total: %v", combinedTotalGets) - log.Info().Msgf("Combined Puts/sec: %v", float64(combinedTotalPuts-combinedPrevTotalPuts)/float64(sleepDuration.Seconds())) - log.Info().Msgf("Combined Gets/sec: %v", float64(combinedTotalGets-combinedPrevTotalGets)/float64(sleepDuration.Seconds())) - log.Info().Msgf("Combined ShardWiseActiveEntries: %v", combinedShardWiseActiveEntries) - - combinedGetP25, combinedGetP50, combinedGetP99 := wc.stats[0].LatencyTracker.GetLatencyPercentiles() - combinedPutP25, combinedPutP50, combinedPutP99 := wc.stats[0].LatencyTracker.PutLatencyPercentiles() - - log.Info().Msgf("Combined Get Count: %v", combinedTotalGets) - log.Info().Msgf("Combined Put Count: %v", combinedTotalPuts) - log.Info().Msgf("Combined Get Latencies - P25: %v, P50: %v, P99: %v", combinedGetP25, combinedGetP50, combinedGetP99) - log.Info().Msgf("Combined Put Latencies - P25: %v, P50: %v, P99: %v", combinedPutP25, combinedPutP50, combinedPutP99) - - combinedGetBatchP25, combinedGetBatchP50, combinedGetBatchP99 := wc.shards[0].Stats.BatchTracker.GetBatchSizePercentiles() - log.Info().Msgf("Combined Get Batch Sizes - P25: %v, P50: %v, P99: %v", combinedGetBatchP25, combinedGetBatchP50, combinedGetBatchP99) - - // Send metrics to the recorder if configured - if wc.metricsRecorder != nil { - rThroughput := float64(combinedTotalGets-combinedPrevTotalGets) / sleepDuration.Seconds() - wThroughput := float64(combinedTotalPuts-combinedPrevTotalPuts) / sleepDuration.Seconds() - - wc.metricsRecorder.RecordRP25(combinedGetP25) - wc.metricsRecorder.RecordRP50(combinedGetP50) - wc.metricsRecorder.RecordRP99(combinedGetP99) - wc.metricsRecorder.RecordWP25(combinedPutP25) - wc.metricsRecorder.RecordWP50(combinedPutP50) - wc.metricsRecorder.RecordWP99(combinedPutP99) - wc.metricsRecorder.RecordRThroughput(rThroughput) - wc.metricsRecorder.RecordWThroughput(wThroughput) - wc.metricsRecorder.RecordHitRate(combinedHitRate) - } - - combinedPrevTotalGets = combinedTotalGets - combinedPrevTotalPuts = combinedTotalPuts - - /* disabling per shard stats for now - for i := 0; i < config.NumShards; i++ { - log.Info().Msgf("Shard %d has %d active entries", i, wc.stats[i].ShardWiseActiveEntries.Load()) - total := wc.stats[i].TotalGets.Load() - hits := wc.stats[i].Hits.Load() - hitRate := float64(0) - if total > 0 { - hitRate = float64(hits) / float64(total) - } - log.Info().Msgf("Shard %d HitRate: %v", i, hitRate) - log.Info().Msgf("Shard %d ReWrites: %v", i, wc.stats[i].ReWrites.Load()) - log.Info().Msgf("Shard %d Expired: %v", i, wc.stats[i].Expired.Load()) - log.Info().Msgf("Shard %d Total: %v", i, total) - log.Info().Msgf("Gets/sec: %v", float64(total-perShardPrevTotalGets[i])/float64(sleepDuration.Seconds())) - log.Info().Msgf("Puts/sec: %v", float64(wc.stats[i].TotalPuts.Load()-perShardPrevTotalPuts[i])/float64(sleepDuration.Seconds())) - perShardPrevTotalGets[i] = total - perShardPrevTotalPuts[i] = wc.stats[i].TotalPuts.Load() - - getP25, getP50, getP99 := wc.stats[i].LatencyTracker.GetLatencyPercentiles() - putP25, putP50, putP99 := wc.stats[i].LatencyTracker.PutLatencyPercentiles() - - log.Info().Msgf("Get Count: %v", wc.stats[i].TotalGets.Load()) - log.Info().Msgf("Put Count: %v", wc.stats[i].TotalPuts.Load()) - log.Info().Msgf("Get Latencies - P25: %v, P50: %v, P99: %v", getP25, getP50, getP99) - log.Info().Msgf("Put Latencies - P25: %v, P50: %v, P99: %v", putP25, putP50, putP99) - - } - */ - log.Info().Msgf("GridSearchActive: %v", wc.predictor.GridSearchEstimator.IsGridSearchActive()) - } - }() - } - return wc, nil -} - -func (wc *WrapCache) PutLL(key string, value []byte, exptimeInMinutes uint16) error { - - h32 := wc.Hash(key) - shardIdx := h32 % uint32(len(wc.shards)) - start := time.Now() - - result := filecache.ErrorPool.Get().(chan error) - - wc.shards[shardIdx].WriteCh <- &filecache.WriteRequestV2{ - Key: key, - Value: value, - ExptimeInMinutes: exptimeInMinutes, - Result: result, - } - - if h32%100 < 10 { - wc.stats[shardIdx].ShardWiseActiveEntries.Store(uint64(wc.shards[shardIdx].GetRingBufferActiveEntries())) - } - - op := <-result - filecache.ErrorPool.Put(result) - wc.stats[shardIdx].TotalPuts.Add(1) - wc.stats[shardIdx].LatencyTracker.RecordPut(time.Since(start)) - return op -} - -func (wc *WrapCache) GetLL(key string) ([]byte, bool, bool) { - h32 := wc.Hash(key) - shardIdx := h32 % uint32(len(wc.shards)) - - start := time.Now() - - found, value, _, expired, needsSlowPath := wc.shards[shardIdx].GetFastPath(key) - - if !needsSlowPath { - if found && !expired { - wc.stats[shardIdx].Hits.Add(1) - } else if expired { - wc.stats[shardIdx].Expired.Add(1) - } - - wc.stats[shardIdx].TotalGets.Add(1) - wc.stats[shardIdx].LatencyTracker.RecordGet(time.Since(start)) - return value, found, expired - } - - result := filecache.ReadResultPool.Get().(chan filecache.ReadResultV2) - - req := filecache.ReadRequestPool.Get().(*filecache.ReadRequestV2) - req.Key = key - req.Result = result - - wc.shards[shardIdx].ReadCh <- req - op := <-result - - filecache.ReadResultPool.Put(result) - filecache.ReadRequestPool.Put(req) - - if op.Found && !op.Expired { - wc.stats[shardIdx].Hits.Add(1) - } - if op.Expired { - wc.stats[shardIdx].Expired.Add(1) - } - wc.stats[shardIdx].LatencyTracker.RecordGet(time.Since(start)) - wc.stats[shardIdx].TotalGets.Add(1) - - return op.Data, op.Found, op.Expired -} - -func (wc *WrapCache) Put(key string, value []byte, exptimeInMinutes uint16) error { - - h32 := wc.Hash(key) - shardIdx := h32 % uint32(len(wc.shards)) - - start := time.Now() - defer func() { - wc.stats[shardIdx].LatencyTracker.RecordPut(time.Since(start)) - }() - - wc.shardLocks[shardIdx].Lock() - defer wc.shardLocks[shardIdx].Unlock() - wc.putLocked(shardIdx, h32, key, value, exptimeInMinutes) - return nil -} - -func (wc *WrapCache) putLocked(shardIdx uint32, h32 uint32, key string, value []byte, exptimeInMinutes uint16) { - wc.shards[shardIdx].Put(key, value, exptimeInMinutes) - wc.stats[shardIdx].TotalPuts.Add(1) - if h32%100 < 10 { - wc.stats[shardIdx].ShardWiseActiveEntries.Store(uint64(wc.shards[shardIdx].GetRingBufferActiveEntries())) - } -} - -func (wc *WrapCache) Get(key string) ([]byte, bool, bool) { - h32 := wc.Hash(key) - shardIdx := h32 % uint32(len(wc.shards)) - - start := time.Now() - defer func() { - wc.stats[shardIdx].LatencyTracker.RecordGet(time.Since(start)) - }() - - var keyFound bool - var val []byte - var remainingTTL uint16 - var expired bool - var shouldReWrite bool - if wc.shards[shardIdx].BatchReader != nil { - reqChan := make(chan filecache.ReadResultV2, 1) - wc.shards[shardIdx].BatchReader.Requests <- &filecache.ReadRequestV2{ - Key: key, - Result: reqChan, - } - result := <-reqChan - - keyFound, val, remainingTTL, expired, shouldReWrite = result.Found, result.Data, result.TTL, result.Expired, result.ShouldRewrite - } else { - wc.shardLocks[shardIdx].RLock() - defer wc.shardLocks[shardIdx].RUnlock() - keyFound, val, remainingTTL, expired, shouldReWrite = wc.shards[shardIdx].Get(key) - } - - if keyFound && !expired { - wc.stats[shardIdx].Hits.Add(1) - } - if expired { - wc.stats[shardIdx].Expired.Add(1) - } - wc.stats[shardIdx].TotalGets.Add(1) - if shouldReWrite { - wc.stats[shardIdx].ReWrites.Add(1) - wc.putLocked(shardIdx, h32, key, val, remainingTTL) - } - wc.predictor.Observe(float64(wc.stats[shardIdx].Hits.Load()) / float64(wc.stats[shardIdx].TotalGets.Load())) - return val, keyFound, expired -} - -func (wc *WrapCache) Hash(key string) uint32 { - return uint32(xxhash.Sum64String(key) ^ Seed) -} - -func (wc *WrapCache) GetShardCache(shardIdx int) *filecache.ShardCache { - return wc.shards[shardIdx] -} diff --git a/flashring/internal/cache/freecache.go b/flashring/internal/cache/freecache.go deleted file mode 100644 index df0f0f75..00000000 --- a/flashring/internal/cache/freecache.go +++ /dev/null @@ -1,96 +0,0 @@ -package internal - -import ( - "runtime/debug" - "sync/atomic" - "time" - - filecache "github.com/Meesho/BharatMLStack/flashring/internal/shard" - "github.com/coocood/freecache" - "github.com/rs/zerolog/log" -) - -type Freecache struct { - cache *freecache.Cache - stats *CacheStats -} - -func NewFreecache(config WrapCacheConfig, logStats bool) (*Freecache, error) { - - cache := freecache.NewCache(int(config.FileSize)) - debug.SetGCPercent(20) - - fc := &Freecache{ - cache: cache, - stats: &CacheStats{ - Hits: atomic.Uint64{}, - TotalGets: atomic.Uint64{}, - TotalPuts: atomic.Uint64{}, - ReWrites: atomic.Uint64{}, - Expired: atomic.Uint64{}, - ShardWiseActiveEntries: atomic.Uint64{}, - LatencyTracker: filecache.NewLatencyTracker(), - }, - } - - if logStats { - go func() { - sleepDuration := 10 * time.Second - var prevTotalGets, prevTotalPuts uint64 - for { - time.Sleep(sleepDuration) - - totalGets := fc.stats.TotalGets.Load() - totalPuts := fc.stats.TotalPuts.Load() - getsPerSec := float64(totalGets-prevTotalGets) / sleepDuration.Seconds() - putsPerSec := float64(totalPuts-prevTotalPuts) / sleepDuration.Seconds() - - log.Info().Msgf("Shard %d HitRate: %v", 0, cache.HitRate()) - log.Info().Msgf("Shard %d Expired: %v", 0, cache.ExpiredCount()) - log.Info().Msgf("Shard %d Total: %v", 0, cache.EntryCount()) - log.Info().Msgf("Gets/sec: %v", getsPerSec) - log.Info().Msgf("Puts/sec: %v", putsPerSec) - - getP25, getP50, getP99 := fc.stats.LatencyTracker.GetLatencyPercentiles() - putP25, putP50, putP99 := fc.stats.LatencyTracker.PutLatencyPercentiles() - - log.Info().Msgf("Get Count: %v", totalGets) - log.Info().Msgf("Put Count: %v", totalPuts) - log.Info().Msgf("Get Latencies - P25: %v, P50: %v, P99: %v", getP25, getP50, getP99) - log.Info().Msgf("Put Latencies - P25: %v, P50: %v, P99: %v", putP25, putP50, putP99) - - prevTotalGets = totalGets - prevTotalPuts = totalPuts - } - }() - } - - return fc, nil - -} - -func (c *Freecache) Put(key string, value []byte, exptimeInMinutes uint16) error { - start := time.Now() - defer func() { - c.stats.LatencyTracker.RecordPut(time.Since(start)) - }() - - c.stats.TotalPuts.Add(1) - c.cache.Set([]byte(key), value, int(exptimeInMinutes)*60) - return nil -} - -func (c *Freecache) Get(key string) ([]byte, bool, bool) { - start := time.Now() - defer func() { - c.stats.LatencyTracker.RecordGet(time.Since(start)) - }() - - c.stats.TotalGets.Add(1) - val, err := c.cache.Get([]byte(key)) - if err != nil { - return nil, false, false - } - c.stats.Hits.Add(1) - return val, true, false -} diff --git a/flashring/internal/fs/aligned_page.go b/flashring/internal/fs/aligned_page.go index c499ae36..099ccd9d 100644 --- a/flashring/internal/fs/aligned_page.go +++ b/flashring/internal/fs/aligned_page.go @@ -4,8 +4,6 @@ package fs import ( - "runtime/pprof" - "golang.org/x/sys/unix" ) @@ -16,7 +14,7 @@ const ( MAP_ANON = unix.MAP_ANON ) -var mmapProf = pprof.NewProfile("mmap") // will show up in /debug/pprof/ +// var mmapProf = pprof.NewProfile("mmap") // will show up in /debug/pprof/ type AlignedPage struct { Buf []byte @@ -28,9 +26,9 @@ func NewAlignedPage(pageSize int) *AlignedPage { if err != nil { panic(err) } - if pageSize > 0 { - mmapProf.Add(&b[0], pageSize) // attribute sz bytes to this callsite - } + // if pageSize > 0 { + // mmapProf.Add(&b[0], pageSize) // attribute sz bytes to this callsite + // } return &AlignedPage{ Buf: b, mmap: b, @@ -38,9 +36,9 @@ func NewAlignedPage(pageSize int) *AlignedPage { } func Unmap(p *AlignedPage) error { - if len(p.mmap) > 0 { - mmapProf.Remove(&p.mmap[0]) // release from custom profile - } + // if len(p.mmap) > 0 { + // mmapProf.Remove(&p.mmap[0]) // release from custom profile + // } if p.mmap != nil { err := unix.Munmap(p.mmap) if err != nil { diff --git a/flashring/internal/fs/fs.go b/flashring/internal/fs/fs.go index 186e524e..57a785bc 100644 --- a/flashring/internal/fs/fs.go +++ b/flashring/internal/fs/fs.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "os" + "sync/atomic" "syscall" "unsafe" @@ -32,12 +33,13 @@ var ( ErrFileSizeExceeded = errors.New("file size exceeded. Please punch hole") ErrFileOffsetOutOfRange = errors.New("file offset is out of range") ErrOffsetNotAligned = errors.New("offset is not aligned to block size") + ErrReadTimeout = errors.New("read timeout") ) type Stat struct { - WriteCount int64 - ReadCount int64 - PunchHoleCount int64 + WriteCount atomic.Int64 + ReadCount atomic.Int64 + PunchHoleCount atomic.Int64 CurrentLogicalSize int64 } @@ -59,54 +61,58 @@ type Page interface { Unmap() error } -func createAppendOnlyWriteFileDescriptor(filename string) (int, *os.File, bool, error) { - - // Open file with DIRECT_IO, WRITE_ONLY, CREAT flags - flags := O_DIRECT | O_WRONLY | O_CREAT | O_DSYNC - fd, err := syscall.Open(filename, flags, FILE_MODE) +// openWithDirectIO attempts to open a file with O_DIRECT, falling back to +// regular flags if the filesystem doesn't support it. +func openWithDirectIO(filename string, baseFlags int) (int, bool, error) { + fd, err := syscall.Open(filename, baseFlags|O_DIRECT, FILE_MODE) + if err == nil { + return fd, true, nil + } + log.Warn().Msgf("DIRECT_IO not supported, falling back to regular flags: %v", err) + fd, err = syscall.Open(filename, baseFlags, FILE_MODE) if err != nil { - // If DIRECT_IO is not supported, fall back to regular flags - log.Warn().Msgf("DIRECT_IO not supported, falling back to regular flags: %v", err) - flags = O_WRONLY | O_CREAT | O_DSYNC - fd, err = syscall.Open(filename, flags, FILE_MODE) - if err != nil { - return 0, nil, false, err - } + return 0, false, err } + return fd, false, nil +} + +func fdToFile(fd int, filename string) (*os.File, error) { file := os.NewFile(uintptr(fd), filename) if file == nil { - return 0, nil, false, fmt.Errorf("failed to create file from fd") + return nil, fmt.Errorf("failed to create file from fd") } + return file, nil +} - return fd, file, true, nil +func createAppendOnlyWriteFileDescriptor(filename string) (int, *os.File, bool, error) { + fd, directIO, err := openWithDirectIO(filename, O_WRONLY|O_CREAT|O_DSYNC) + if err != nil { + return 0, nil, false, err + } + file, err := fdToFile(fd, filename) + if err != nil { + return 0, nil, false, err + } + return fd, file, directIO, nil } func createPreAllocatedWriteFileDescriptor(filename string, maxFileSize int64) (int, *os.File, bool, error) { - flags := O_DIRECT | O_WRONLY | O_CREAT | O_DSYNC - fd, err := syscall.Open(filename, flags, FILE_MODE) + fd, directIO, err := openWithDirectIO(filename, O_WRONLY|O_CREAT|O_DSYNC) if err != nil { - log.Warn().Msgf("DIRECT_IO not supported, falling back to regular flags: %v", err) - flags = O_WRONLY | O_CREAT | O_DSYNC - fd, err = syscall.Open(filename, flags, FILE_MODE) - if err != nil { - return 0, nil, false, err - } + return 0, nil, false, err } - // Preallocate file space - err = unix.Fallocate(fd, 0, 0, maxFileSize) - if err != nil { + if err = unix.Fallocate(fd, 0, 0, maxFileSize); err != nil { log.Error().Err(err).Msg("Failed to fallocate file") syscall.Close(fd) return 0, nil, false, err } - file := os.NewFile(uintptr(fd), filename) - if file == nil { - return 0, nil, false, fmt.Errorf("failed to create file from fd") + file, err := fdToFile(fd, filename) + if err != nil { + return 0, nil, false, err } - - return fd, file, true, nil + return fd, file, directIO, nil } func createReadFileDescriptor(filename string) (int, *os.File, bool, error) { @@ -115,15 +121,13 @@ func createReadFileDescriptor(filename string) (int, *os.File, bool, error) { if err != nil { return 0, nil, false, err } - file := os.NewFile(uintptr(fd), filename) - if file == nil { - return 0, nil, false, fmt.Errorf("failed to create file from fd") + file, err := fdToFile(fd, filename) + if err != nil { + return 0, nil, false, err } - return fd, file, true, nil } -// isAligned checks if the buffer is aligned to the block size func isAlignedBuffer(buf []byte, alignment int) bool { pt := uintptr(alignment) if len(buf) == 0 { @@ -136,3 +140,13 @@ func isAlignedBuffer(buf []byte, alignment int) bool { func isAlignedOffset(offset int64, alignment int) bool { return offset%int64(alignment) == 0 } + +// AlignRange computes the block-aligned start offset and total aligned size +// for a read spanning [offset, offset+length). Useful for O_DIRECT reads +// where both offset and buffer size must be block-aligned. +func AlignRange(offset int64, length int, blockSize int64) (alignedStart, alignedSize int64) { + alignedStart = (offset / blockSize) * blockSize + end := offset + int64(length) + alignedEnd := ((end + blockSize - 1) / blockSize) * blockSize + return alignedStart, alignedEnd - alignedStart +} diff --git a/flashring/internal/fs/rolling_appendonly_file.go b/flashring/internal/fs/rolling_appendonly_file.go index 1e97b5c6..01df89ca 100644 --- a/flashring/internal/fs/rolling_appendonly_file.go +++ b/flashring/internal/fs/rolling_appendonly_file.go @@ -56,12 +56,7 @@ func NewRollingAppendFile(config FileConfig) (*RollingAppendFile, error) { LogicalStartOffset: 0, CurrentLogicalOffset: 0, CurrentPhysicalOffset: 0, - Stat: &Stat{ - WriteCount: 0, - ReadCount: 0, - PunchHoleCount: 0, - CurrentLogicalSize: 0, - }, + Stat: &Stat{}, }, nil } @@ -79,7 +74,7 @@ func (r *RollingAppendFile) Pwrite(buf []byte) (currentPhysicalOffset int64, err return 0, err } r.CurrentPhysicalOffset += int64(n) - r.Stat.WriteCount++ + r.Stat.WriteCount.Add(1) return r.CurrentPhysicalOffset, nil } @@ -96,7 +91,7 @@ func (r *RollingAppendFile) Pread(fileOffset int64, buf []byte) (n int32, err er } } syscall.Pread(r.ReadFd, buf, fileOffset) - r.Stat.ReadCount++ + r.Stat.ReadCount.Add(1) return int32(len(buf)), nil } @@ -112,7 +107,7 @@ func (r *RollingAppendFile) TrimHead() (err error) { } r.LogicalStartOffset += int64(r.FilePunchHoleSize) r.CurrentLogicalOffset -= int64(r.FilePunchHoleSize) - r.Stat.PunchHoleCount++ + r.Stat.PunchHoleCount.Add(1) return nil } diff --git a/flashring/internal/fs/rolling_appendonly_file_test.go b/flashring/internal/fs/rolling_appendonly_file_test.go index c4afdd8c..ce858030 100644 --- a/flashring/internal/fs/rolling_appendonly_file_test.go +++ b/flashring/internal/fs/rolling_appendonly_file_test.go @@ -119,8 +119,8 @@ func TestPwrite_Success(t *testing.T) { t.Errorf("Expected CurrentPhysicalOffset %d, got %d", len(data), raf.CurrentPhysicalOffset) } - if raf.Stat.WriteCount != 1 { - t.Errorf("Expected WriteCount 1, got %d", raf.Stat.WriteCount) + if raf.Stat.WriteCount.Load() != 1 { + t.Errorf("Expected WriteCount 1, got %d", raf.Stat.WriteCount.Load()) } } @@ -225,8 +225,8 @@ func TestPread_Success(t *testing.T) { } } - if raf.Stat.ReadCount != 1 { - t.Errorf("Expected ReadCount 1, got %d", raf.Stat.ReadCount) + if raf.Stat.ReadCount.Load() != 1 { + t.Errorf("Expected ReadCount 1, got %d", raf.Stat.ReadCount.Load()) } } @@ -338,8 +338,8 @@ func TestTrimHead_Success(t *testing.T) { t.Errorf("Expected LogicalStartOffset %d, got %d", config.FilePunchHoleSize, raf.LogicalStartOffset) } - if raf.Stat.PunchHoleCount != 1 { - t.Errorf("Expected PunchHoleCount 1, got %d", raf.Stat.PunchHoleCount) + if raf.Stat.PunchHoleCount.Load() != 1 { + t.Errorf("Expected PunchHoleCount 1, got %d", raf.Stat.PunchHoleCount.Load()) } } @@ -421,11 +421,11 @@ func TestMultipleOperations(t *testing.T) { } // Verify statistics - if raf.Stat.WriteCount != 5 { - t.Errorf("Expected WriteCount 5, got %d", raf.Stat.WriteCount) + if raf.Stat.WriteCount.Load() != 5 { + t.Errorf("Expected WriteCount 5, got %d", raf.Stat.WriteCount.Load()) } - if raf.Stat.ReadCount != 5 { - t.Errorf("Expected ReadCount 5, got %d", raf.Stat.ReadCount) + if raf.Stat.ReadCount.Load() != 5 { + t.Errorf("Expected ReadCount 5, got %d", raf.Stat.ReadCount.Load()) } } @@ -447,14 +447,14 @@ func TestStatistics(t *testing.T) { defer cleanup(raf) // Initial state - if raf.Stat.WriteCount != 0 { - t.Errorf("Expected initial WriteCount 0, got %d", raf.Stat.WriteCount) + if raf.Stat.WriteCount.Load() != 0 { + t.Errorf("Expected initial WriteCount 0, got %d", raf.Stat.WriteCount.Load()) } - if raf.Stat.ReadCount != 0 { - t.Errorf("Expected initial ReadCount 0, got %d", raf.Stat.ReadCount) + if raf.Stat.ReadCount.Load() != 0 { + t.Errorf("Expected initial ReadCount 0, got %d", raf.Stat.ReadCount.Load()) } - if raf.Stat.PunchHoleCount != 0 { - t.Errorf("Expected initial PunchHoleCount 0, got %d", raf.Stat.PunchHoleCount) + if raf.Stat.PunchHoleCount.Load() != 0 { + t.Errorf("Expected initial PunchHoleCount 0, got %d", raf.Stat.PunchHoleCount.Load()) } // Perform operations and verify statistics @@ -465,8 +465,8 @@ func TestStatistics(t *testing.T) { if err != nil { t.Fatalf("Pwrite failed: %v", err) } - if raf.Stat.WriteCount != 1 { - t.Errorf("Expected WriteCount 1, got %d", raf.Stat.WriteCount) + if raf.Stat.WriteCount.Load() != 1 { + t.Errorf("Expected WriteCount 1, got %d", raf.Stat.WriteCount.Load()) } // Read operation @@ -474,8 +474,8 @@ func TestStatistics(t *testing.T) { if err != nil { t.Fatalf("Pread failed: %v", err) } - if raf.Stat.ReadCount != 1 { - t.Errorf("Expected ReadCount 1, got %d", raf.Stat.ReadCount) + if raf.Stat.ReadCount.Load() != 1 { + t.Errorf("Expected ReadCount 1, got %d", raf.Stat.ReadCount.Load()) } // Trim operation @@ -483,8 +483,8 @@ func TestStatistics(t *testing.T) { if err != nil { t.Fatalf("TrimHead failed: %v", err) } - if raf.Stat.PunchHoleCount != 1 { - t.Errorf("Expected PunchHoleCount 1, got %d", raf.Stat.PunchHoleCount) + if raf.Stat.PunchHoleCount.Load() != 1 { + t.Errorf("Expected PunchHoleCount 1, got %d", raf.Stat.PunchHoleCount.Load()) } } diff --git a/flashring/internal/fs/wrap_file.go b/flashring/internal/fs/wrap_file.go index fc91e006..d8ebc1bb 100644 --- a/flashring/internal/fs/wrap_file.go +++ b/flashring/internal/fs/wrap_file.go @@ -6,7 +6,10 @@ package fs import ( "os" "syscall" + "time" + "github.com/Meesho/BharatMLStack/flashring/internal/iouring" + "github.com/Meesho/BharatMLStack/flashring/pkg/metrics" "golang.org/x/sys/unix" ) @@ -15,16 +18,17 @@ type WrapAppendFile struct { ReadDirectIO bool wrapped bool blockSize int - WriteFd int // write file descriptor - ReadFd int // read file descriptor - MaxFileSize int64 // max file size in bytes - FilePunchHoleSize int64 // file punch hole size in bytes - PhysicalStartOffset int64 // physical start offset in bytes - LogicalCurrentOffset int64 // file current size in bytes - PhysicalWriteOffset int64 // file current physical offset in bytes - WriteFile *os.File // write file - ReadFile *os.File // read file - Stat *Stat // file statistics + WriteFd int // write file descriptor + ReadFd int // read file descriptor + MaxFileSize int64 // max file size in bytes + FilePunchHoleSize int64 // file punch hole size in bytes + PhysicalStartOffset int64 // physical start offset in bytes + LogicalCurrentOffset int64 // file current size in bytes + PhysicalWriteOffset int64 // file current physical offset in bytes + WriteFile *os.File // write file + ReadFile *os.File // read file + Stat *Stat // file statistics + WriteRing *iouring.IoUringWriter // io_uring writer for batched writes } func NewWrapAppendFile(config FileConfig) (*WrapAppendFile, error) { @@ -57,12 +61,7 @@ func NewWrapAppendFile(config FileConfig) (*WrapAppendFile, error) { PhysicalStartOffset: 0, LogicalCurrentOffset: 0, PhysicalWriteOffset: 0, - Stat: &Stat{ - WriteCount: 0, - ReadCount: 0, - PunchHoleCount: 0, - CurrentLogicalSize: 0, - }, + Stat: &Stat{}, }, nil } @@ -72,20 +71,87 @@ func (r *WrapAppendFile) Pwrite(buf []byte) (currentPhysicalOffset int64, err er return 0, ErrBufNoAlign } } + var startTime = time.Now() n, err := syscall.Pwrite(r.WriteFd, buf, r.PhysicalWriteOffset) + metrics.Timing(metrics.KEY_PWRITE_LATENCY, time.Since(startTime), []string{}) if err != nil { return 0, err } + r.PhysicalWriteOffset += int64(n) if r.PhysicalWriteOffset >= r.MaxFileSize { r.wrapped = true - r.PhysicalWriteOffset = r.PhysicalStartOffset + r.PhysicalWriteOffset = 0 } r.LogicalCurrentOffset += int64(n) - r.Stat.WriteCount++ + r.Stat.WriteCount.Add(1) + return r.PhysicalWriteOffset, nil } +// PwriteBatch writes a large buffer in chunkSize pieces via io_uring. +// Chunks are submitted in sub-batches that fit within the ring's SQ depth, +// so arbitrarily large buffers work regardless of ring size. +// Returns total bytes written and the final PhysicalWriteOffset. +func (r *WrapAppendFile) PwriteBatch(buf []byte, chunkSize int) (totalWritten int, fileOffset int64, err error) { + if r.WriteDirectIO { + if !isAlignedBuffer(buf, r.blockSize) { + return 0, 0, ErrBufNoAlign + } + } + + // Maximum SQEs per submission -- capped to ring depth. + maxPerBatch := r.WriteRing.MaxBatchSize() + + for written := 0; written < len(buf); { + // Build a sub-batch that fits within the ring + var bufs [][]byte + var offsets []uint64 + + for i := 0; i < maxPerBatch && written < len(buf); i++ { + end := written + chunkSize + if end > len(buf) { + end = len(buf) + } + bufs = append(bufs, buf[written:end]) + offsets = append(offsets, uint64(r.PhysicalWriteOffset)) + + // Advance write offset, handle ring-buffer wrap + r.PhysicalWriteOffset += int64(end - written) + if r.PhysicalWriteOffset >= r.MaxFileSize { + r.wrapped = true + r.PhysicalWriteOffset = 0 + } + written = end + } + + results, serr := r.WriteRing.SubmitWriteBatch(r.WriteFd, bufs, offsets) + if serr != nil { + return totalWritten, r.PhysicalWriteOffset, serr + } + + for _, n := range results { + totalWritten += n + r.LogicalCurrentOffset += int64(n) + r.Stat.WriteCount.Add(1) + } + } + + return totalWritten, r.PhysicalWriteOffset, nil +} + +// AdvanceWriteOffset moves the write pointer forward by n bytes without +// writing any data. Used to skip over uninitialized regions in staggered +// memtables so that the memId*capacity file layout is preserved. +func (r *WrapAppendFile) AdvanceWriteOffset(n int64) { + r.PhysicalWriteOffset += n + if r.PhysicalWriteOffset >= r.MaxFileSize { + r.wrapped = true + r.PhysicalWriteOffset = 0 + } + r.LogicalCurrentOffset += n +} + func (r *WrapAppendFile) TrimHeadIfNeeded() bool { if r.wrapped && r.PhysicalWriteOffset == r.PhysicalStartOffset { return true @@ -93,6 +159,31 @@ func (r *WrapAppendFile) TrimHeadIfNeeded() bool { return false } +// isValidReadRegion checks whether [physOffset, physEnd) falls entirely within +// the ring's live data. +// +// When wrapped, the writer and start pointers chase each other around +// [0, MaxFileSize). Three sub-cases: +// +// W == S → ring is full (writer just caught up); entire file is valid. +// W < S → two valid segments: [S, MaxFileSize) and [0, W). +// W > S → contiguous [S, W); the region [W, MaxFileSize) was punched +// by the TrimHead that sent S past W's position. +func (r *WrapAppendFile) isValidReadRegion(physOffset, physEnd int64) bool { + if !r.wrapped { + return physOffset >= r.PhysicalStartOffset && physEnd <= r.PhysicalWriteOffset + } + W, S := r.PhysicalWriteOffset, r.PhysicalStartOffset + if W == S { + return physEnd <= r.MaxFileSize + } + if W < S { + return (physOffset >= S && physEnd <= r.MaxFileSize) || + (physOffset >= 0 && physEnd <= W) + } + return physOffset >= S && physEnd <= W +} + func (r *WrapAppendFile) Pread(fileOffset int64, buf []byte) (int32, error) { if r.ReadDirectIO { if !isAlignedOffset(fileOffset, r.blockSize) { @@ -103,40 +194,53 @@ func (r *WrapAppendFile) Pread(fileOffset int64, buf []byte) (int32, error) { } } - // Validate read window depending on wrap state - readEnd := fileOffset + int64(len(buf)) - valid := false - - if !r.wrapped { - // Single valid region: [PhysicalStartOffset, PhysicalWriteOffset) - valid = fileOffset >= r.PhysicalStartOffset && readEnd <= r.PhysicalWriteOffset - } else { - // Two valid regions: - // 1. [PhysicalStartOffset, MaxFileSize) - // 2. [0, PhysicalWriteOffset) - fileOffset = fileOffset % r.MaxFileSize - readEnd = readEnd % r.MaxFileSize - if fileOffset >= r.PhysicalStartOffset { - valid = readEnd <= r.MaxFileSize - } else { - valid = readEnd <= r.PhysicalWriteOffset - } + physOffset := fileOffset + if r.wrapped { + physOffset = fileOffset % r.MaxFileSize } - if !valid { + physEnd := physOffset + int64(len(buf)) + + if !r.isValidReadRegion(physOffset, physEnd) { return 0, ErrFileOffsetOutOfRange } - n, err := syscall.Pread(r.ReadFd, buf, fileOffset) - // flags := unix.RWF_HIPRI // optionally: | unix.RWF_NOWAIT - // n, err := preadv2(r.ReadFd, buf, fileOffset, flags) + var startTime = time.Now() + n, err := syscall.Pread(r.ReadFd, buf, physOffset) + metrics.Timing(metrics.KEY_PREAD_LATENCY, time.Since(startTime), []string{}) if err != nil { return 0, err } - r.Stat.ReadCount++ + r.Stat.ReadCount.Add(1) return int32(n), nil } +// ValidateReadOffset checks the read window and wraps the offset for ring-buffer +// files. Returns the physical file offset to use, or an error. +// Mirrors the validation logic in Pread so callers that use the batched +// io_uring path get identical safety checks. +func (r *WrapAppendFile) ValidateReadOffset(fileOffset int64, bufLen int) (int64, error) { + if r.ReadDirectIO { + if !isAlignedOffset(fileOffset, r.blockSize) { + return 0, ErrOffsetNotAligned + } + } + + physOffset := fileOffset + if r.wrapped { + physOffset = fileOffset % r.MaxFileSize + } + physEnd := physOffset + int64(bufLen) + + if !r.isValidReadRegion(physOffset, physEnd) { + return 0, ErrFileOffsetOutOfRange + } + + return physOffset, nil +} + func (r *WrapAppendFile) TrimHead() (err error) { + + var startTime = time.Now() if r.WriteDirectIO { if !isAlignedOffset(r.PhysicalStartOffset, r.blockSize) { return ErrOffsetNotAligned @@ -150,7 +254,9 @@ func (r *WrapAppendFile) TrimHead() (err error) { if r.PhysicalStartOffset >= r.MaxFileSize { r.PhysicalStartOffset = 0 } - r.Stat.PunchHoleCount++ + r.Stat.PunchHoleCount.Add(1) + metrics.Incr(metrics.KEY_PUNCH_HOLE_COUNT, []string{}) + metrics.Timing(metrics.KEY_TRIM_HEAD_LATENCY, time.Since(startTime), []string{}) return nil } diff --git a/flashring/internal/fs/wrap_file_test.go b/flashring/internal/fs/wrap_file_test.go index c0fa975d..45375ec5 100644 --- a/flashring/internal/fs/wrap_file_test.go +++ b/flashring/internal/fs/wrap_file_test.go @@ -112,8 +112,8 @@ func TestWrapAppendFile_Pwrite_Success(t *testing.T) { t.Errorf("Expected LogicalCurrentOffset %d, got %d", len(data), waf.LogicalCurrentOffset) } - if waf.Stat.WriteCount != 1 { - t.Errorf("Expected WriteCount 1, got %d", waf.Stat.WriteCount) + if waf.Stat.WriteCount.Load() != 1 { + t.Errorf("Expected WriteCount 1, got %d", waf.Stat.WriteCount.Load()) } if waf.wrapped { @@ -257,8 +257,8 @@ func TestPread_Success_NoWrap(t *testing.T) { } } - if waf.Stat.ReadCount != 1 { - t.Errorf("Expected ReadCount 1, got %d", waf.Stat.ReadCount) + if waf.Stat.ReadCount.Load() != 1 { + t.Errorf("Expected ReadCount 1, got %d", waf.Stat.ReadCount.Load()) } } @@ -268,7 +268,7 @@ func TestPread_Success_WithWrap(t *testing.T) { config := FileConfig{ Filename: filename, - MaxFileSize: 8192, // Small for easy wrapping + MaxFileSize: 8192, FilePunchHoleSize: 4096, BlockSize: 4096, } @@ -279,13 +279,12 @@ func TestPread_Success_WithWrap(t *testing.T) { } defer cleanupWrapFile(waf) - // Fill the file to cause wrapping + // Fill the file: data1 at [0,4096), data2 at [4096,8192). data1 := createAlignedBuffer(4096, 4096) for i := range data1 { data1[i] = byte(1) } - _, err = waf.Pwrite(data1) - if err != nil { + if _, err = waf.Pwrite(data1); err != nil { t.Fatalf("First Pwrite failed: %v", err) } @@ -293,50 +292,91 @@ func TestPread_Success_WithWrap(t *testing.T) { for i := range data2 { data2[i] = byte(2) } - _, err = waf.Pwrite(data2) - if err != nil { + if _, err = waf.Pwrite(data2); err != nil { t.Fatalf("Second Pwrite failed: %v", err) } - // Now write more to wrap around - data3 := createAlignedBuffer(4096, 4096) - for i := range data3 { - data3[i] = byte(3) + // Second Pwrite reaches MaxFileSize → writer wraps to 0. + if !waf.wrapped { + t.Fatalf("Expected wrapped to be true after filling file") } - _, err = waf.Pwrite(data3) - if err != nil { - t.Fatalf("Third Pwrite failed: %v", err) + if waf.PhysicalWriteOffset != 0 { + t.Fatalf("Expected PhysicalWriteOffset=0 after wrap, got %d", waf.PhysicalWriteOffset) } - if !waf.wrapped { - t.Errorf("Expected wrapped to be true") + // W == S == 0 (full ring): both regions are readable. + readData := createAlignedBuffer(4096, 4096) + if _, err = waf.Pread(0, readData); err != nil { + t.Fatalf("Pread [0,4096) in full ring failed: %v", err) + } + if _, err = waf.Pread(4096, readData); err != nil { + t.Fatalf("Pread [4096,8192) in full ring failed: %v", err) } - // Read from valid regions after wrap - // Region 1: [PhysicalStartOffset, MaxFileSize) - should contain data2 - readData := createAlignedBuffer(4096, 4096) + // TrimHead: punch [0,4096), S advances to 4096. + if err = waf.TrimHead(); err != nil { + t.Fatalf("TrimHead failed: %v", err) + } + if waf.PhysicalStartOffset != 4096 { + t.Fatalf("Expected PhysicalStartOffset=4096 after trim, got %d", waf.PhysicalStartOffset) + } + + // Now W(0) < S(4096). Valid region: [4096, 8192). + // Punched region [0,4096) must be rejected. + if _, err = waf.Pread(0, readData); err != ErrFileOffsetOutOfRange { + t.Errorf("Pread from punched region should fail, got %v", err) + } n, err := waf.Pread(4096, readData) if err != nil { - t.Fatalf("Pread from high region failed: %v", err) + t.Fatalf("Pread from old tail failed: %v", err) } if n != 4096 { t.Errorf("Expected read length 4096, got %d", n) } + for i := range readData { + if readData[i] != byte(2) { + t.Errorf("Old tail data mismatch at %d: expected 2, got %d", i, readData[i]) + break + } + } - // Region 2: [0, PhysicalWriteOffset) - should contain data3 - readData2 := createAlignedBuffer(4096, 4096) - n, err = waf.Pread(0, readData2) + // Write data3 into the freed region [0,4096). W advances to 4096. + data3 := createAlignedBuffer(4096, 4096) + for i := range data3 { + data3[i] = byte(3) + } + if _, err = waf.Pwrite(data3); err != nil { + t.Fatalf("Third Pwrite failed: %v", err) + } + + // W == S == 4096 (full ring again): both regions readable. + readOld := createAlignedBuffer(4096, 4096) + n, err = waf.Pread(4096, readOld) if err != nil { - t.Fatalf("Pread from low region failed: %v", err) + t.Fatalf("Pread old tail [4096,8192) after refill failed: %v", err) } if n != 4096 { - t.Errorf("Expected read length 4096, got %d", n) + t.Errorf("Expected 4096 bytes, got %d", n) + } + for i := range readOld { + if readOld[i] != byte(2) { + t.Errorf("Old tail mismatch at %d: expected 2, got %d", i, readOld[i]) + break + } } - // Verify data3 in wrapped position - for i := range readData2 { - if readData2[i] != byte(3) { - t.Errorf("Data mismatch in wrapped region at index %d: expected %d, got %d", i, 3, readData2[i]) + readNew := createAlignedBuffer(4096, 4096) + n, err = waf.Pread(0, readNew) + if err != nil { + t.Fatalf("Pread new data [0,4096) after refill failed: %v", err) + } + if n != 4096 { + t.Errorf("Expected 4096 bytes, got %d", n) + } + for i := range readNew { + if readNew[i] != byte(3) { + t.Errorf("New data mismatch at %d: expected 3, got %d", i, readNew[i]) + break } } } @@ -396,8 +436,8 @@ func TestPread_FileOffsetOutOfRange_WithWrap(t *testing.T) { } defer cleanupWrapFile(waf) - // Cause wrapping - for i := 0; i < 3; i++ { + // Write 2 blocks to cause wrapping (MaxFileSize=8192, block=4096) + for i := 0; i < 2; i++ { data := createAlignedBuffer(4096, 4096) _, err = waf.Pwrite(data) if err != nil { @@ -409,15 +449,18 @@ func TestPread_FileOffsetOutOfRange_WithWrap(t *testing.T) { t.Errorf("Expected wrapped to be true") } - // Try to read from invalid gap between PhysicalWriteOffset and PhysicalStartOffset - // After 3 writes with wrapping, valid regions are [PhysicalStartOffset, MaxFileSize) and [0, PhysicalWriteOffset) - // Try reading from an aligned offset that should be invalid - readData := createAlignedBuffer(4096, 4096) + // After 2 writes: PhysicalStartOffset=0, PhysicalWriteOffset=0 (wrapped). + // TrimHead to advance PhysicalStartOffset to 4096, creating a gap at [0, 4096). + err = waf.TrimHead() + if err != nil { + t.Fatalf("TrimHead failed: %v", err) + } - // Try reading from aligned offset that's out of valid range - // Since PhysicalStartOffset=0 after auto-trim and PhysicalWriteOffset=4096, - // reading from offset 8192 should be out of range (beyond MaxFileSize for wrapped file) - _, err = waf.Pread(8192, readData) // Should be out of range - beyond MaxFileSize + // State: PhysicalStartOffset=4096, PhysicalWriteOffset=0, wrapped=true + // Valid regions: [4096, 8192) and [0, 0) (empty). + // Gap: [0, 4096) — reading from offset 0 should fail. + readData := createAlignedBuffer(4096, 4096) + _, err = waf.Pread(0, readData) if err != ErrFileOffsetOutOfRange { t.Errorf("Expected ErrFileOffsetOutOfRange for gap read, got %v", err) } @@ -531,8 +574,8 @@ func TestWrapAppendFile_TrimHead_Success(t *testing.T) { t.Errorf("Expected PhysicalStartOffset %d, got %d", expectedStartOffset, waf.PhysicalStartOffset) } - if waf.Stat.PunchHoleCount != 1 { - t.Errorf("Expected PunchHoleCount 1, got %d", waf.Stat.PunchHoleCount) + if waf.Stat.PunchHoleCount.Load() != 1 { + t.Errorf("Expected PunchHoleCount 1, got %d", waf.Stat.PunchHoleCount.Load()) } } @@ -597,7 +640,7 @@ func TestTrimHead_OffsetNotAligned(t *testing.T) { } } -func TestPwrite_AutoTrimAfterWrap(t *testing.T) { +func TestPwrite_WrapAndContinue(t *testing.T) { tmpDir := t.TempDir() filename := filepath.Join(tmpDir, "test_wrap_file.dat") @@ -614,7 +657,6 @@ func TestPwrite_AutoTrimAfterWrap(t *testing.T) { } defer cleanupWrapFile(waf) - // Write to cause wrap for i := 0; i < 2; i++ { data := createAlignedBuffer(4096, 4096) _, err = waf.Pwrite(data) @@ -627,18 +669,24 @@ func TestPwrite_AutoTrimAfterWrap(t *testing.T) { t.Errorf("Expected wrapped to be true") } - initialPunchHoleCount := waf.Stat.PunchHoleCount - - // Write again - should trigger auto trim since wrapped && PhysicalWriteOffset == PhysicalStartOffset + // After wrapping, PhysicalWriteOffset resets to PhysicalStartOffset. + // A subsequent write overwrites at that position (trim is done at + // the shard level via DeleteManager, not by Pwrite itself). data := createAlignedBuffer(4096, 4096) + for i := range data { + data[i] = 0xAB + } _, err = waf.Pwrite(data) if err != nil { - t.Fatalf("Auto-trim Pwrite failed: %v", err) + t.Fatalf("Post-wrap Pwrite failed: %v", err) + } + + if waf.Stat.WriteCount.Load() != 3 { + t.Errorf("Expected WriteCount 3, got %d", waf.Stat.WriteCount.Load()) } - // Should have called TrimHead automatically - if waf.Stat.PunchHoleCount <= initialPunchHoleCount { - t.Errorf("Expected PunchHoleCount to increase due to auto-trim, got %d", waf.Stat.PunchHoleCount) + if waf.LogicalCurrentOffset != int64(3*4096) { + t.Errorf("Expected LogicalCurrentOffset %d, got %d", 3*4096, waf.LogicalCurrentOffset) } } @@ -714,8 +762,8 @@ func TestWrapAppendFile_MultipleOperations(t *testing.T) { } // Verify statistics - if waf.Stat.WriteCount != 6 { - t.Errorf("Expected WriteCount 6, got %d", waf.Stat.WriteCount) + if waf.Stat.WriteCount.Load() != 6 { + t.Errorf("Expected WriteCount 6, got %d", waf.Stat.WriteCount.Load()) } } @@ -737,14 +785,14 @@ func TestWrapAppendFile_Statistics(t *testing.T) { defer cleanupWrapFile(waf) // Initial state - if waf.Stat.WriteCount != 0 { - t.Errorf("Expected initial WriteCount 0, got %d", waf.Stat.WriteCount) + if waf.Stat.WriteCount.Load() != 0 { + t.Errorf("Expected initial WriteCount 0, got %d", waf.Stat.WriteCount.Load()) } - if waf.Stat.ReadCount != 0 { - t.Errorf("Expected initial ReadCount 0, got %d", waf.Stat.ReadCount) + if waf.Stat.ReadCount.Load() != 0 { + t.Errorf("Expected initial ReadCount 0, got %d", waf.Stat.ReadCount.Load()) } - if waf.Stat.PunchHoleCount != 0 { - t.Errorf("Expected initial PunchHoleCount 0, got %d", waf.Stat.PunchHoleCount) + if waf.Stat.PunchHoleCount.Load() != 0 { + t.Errorf("Expected initial PunchHoleCount 0, got %d", waf.Stat.PunchHoleCount.Load()) } // Perform operations and verify statistics @@ -755,8 +803,8 @@ func TestWrapAppendFile_Statistics(t *testing.T) { if err != nil { t.Fatalf("Pwrite failed: %v", err) } - if waf.Stat.WriteCount != 1 { - t.Errorf("Expected WriteCount 1, got %d", waf.Stat.WriteCount) + if waf.Stat.WriteCount.Load() != 1 { + t.Errorf("Expected WriteCount 1, got %d", waf.Stat.WriteCount.Load()) } // Read operation @@ -764,8 +812,8 @@ func TestWrapAppendFile_Statistics(t *testing.T) { if err != nil { t.Fatalf("Pread failed: %v", err) } - if waf.Stat.ReadCount != 1 { - t.Errorf("Expected ReadCount 1, got %d", waf.Stat.ReadCount) + if waf.Stat.ReadCount.Load() != 1 { + t.Errorf("Expected ReadCount 1, got %d", waf.Stat.ReadCount.Load()) } // Trim operation @@ -773,8 +821,8 @@ func TestWrapAppendFile_Statistics(t *testing.T) { if err != nil { t.Fatalf("TrimHead failed: %v", err) } - if waf.Stat.PunchHoleCount != 1 { - t.Errorf("Expected PunchHoleCount 1, got %d", waf.Stat.PunchHoleCount) + if waf.Stat.PunchHoleCount.Load() != 1 { + t.Errorf("Expected PunchHoleCount 1, got %d", waf.Stat.PunchHoleCount.Load()) } } diff --git a/flashring/internal/index/constant.go b/flashring/internal/index/constant.go new file mode 100644 index 00000000..0b87ea43 --- /dev/null +++ b/flashring/internal/index/constant.go @@ -0,0 +1,21 @@ +package index + +const ( + LENGTH_MASK = (1 << 16) - 1 + DELTA_EXPTIME_MASK = (1 << 16) - 1 + LAST_ACCESS_MASK = (1 << 16) - 1 + FREQ_MASK = (1 << 16) - 1 + PREV_MASK = (1 << 32) - 1 + NEXT_MASK = (1 << 32) - 1 + + MEM_ID_MASK = (1 << 32) - 1 + OFFSET_MASK = (1 << 32) - 1 + + LENGTH_SHIFT = 48 + DELTA_EXPTIME_SHIFT = 32 + LAST_ACCESS_SHIFT = 16 + FREQ_SHIFT = 0 + + MEM_ID_SHIFT = 32 + OFFSET_SHIFT = 0 +) diff --git a/flashring/internal/index/delete_manager.go b/flashring/internal/index/delete_manager.go new file mode 100644 index 00000000..ae395647 --- /dev/null +++ b/flashring/internal/index/delete_manager.go @@ -0,0 +1,77 @@ +package index + +import ( + "errors" + "fmt" + + "github.com/Meesho/BharatMLStack/flashring/internal/fs" + "github.com/rs/zerolog/log" +) + +type DeleteManager struct { + memtableData map[uint32]int + toBeDeletedMemId uint32 + keyIndex *Index + wrapFile *fs.WrapAppendFile + deleteInProgress bool + deleteAmortizedStep int + deleteCount int +} + +func NewDeleteManager(keyIndex *Index, wrapFile *fs.WrapAppendFile, deleteAmortizedStep int) *DeleteManager { + return &DeleteManager{ + memtableData: make(map[uint32]int), + keyIndex: keyIndex, + wrapFile: wrapFile, + deleteAmortizedStep: deleteAmortizedStep, + } +} + +func (dm *DeleteManager) IncMemtableKeyCount(memId uint32) { + dm.memtableData[memId]++ +} + +func (dm *DeleteManager) ExecuteDeleteIfNeeded() error { + if dm.deleteInProgress { + memtableId, count := dm.keyIndex.Delete(dm.deleteCount) + if count == -1 { + return fmt.Errorf("delete failed") + } + if memtableId != dm.toBeDeletedMemId { + dm.memtableData[dm.toBeDeletedMemId] -= count + log.Debug().Msgf("memtableId: %d, toBeDeletedMemId: %d", memtableId, dm.toBeDeletedMemId) + if dm.memtableData[dm.toBeDeletedMemId] != 0 { + return fmt.Errorf("memtableData[dm.toBeDeletedMemId] != 0") + } + delete(dm.memtableData, dm.toBeDeletedMemId) + dm.toBeDeletedMemId = memtableId + dm.deleteInProgress = false + dm.deleteCount = 0 + return nil + } + dm.memtableData[memtableId] -= count + return nil + } + + trimNeeded := dm.wrapFile.TrimHeadIfNeeded() + nextAddNeedsDelete := dm.keyIndex.GetRB().NextAddNeedsDelete() + + if trimNeeded || nextAddNeedsDelete { + dm.deleteInProgress = true + dm.deleteCount = dm.memtableData[dm.toBeDeletedMemId] / dm.deleteAmortizedStep + if dm.deleteCount == 0 { + dm.deleteCount = dm.memtableData[dm.toBeDeletedMemId] % dm.deleteAmortizedStep + } + memIdAtHead, err := dm.keyIndex.PeekMemIdAtHead() + if err != nil { + return err + } + if memIdAtHead != dm.toBeDeletedMemId { + return fmt.Errorf("memIdAtHead: %d, toBeDeletedMemId: %d", memIdAtHead, dm.toBeDeletedMemId) + } + + dm.wrapFile.TrimHead() + return errors.New("trim needed retry this write") + } + return nil +} diff --git a/flashring/internal/index/encoder.go b/flashring/internal/index/encoder.go new file mode 100644 index 00000000..7638277a --- /dev/null +++ b/flashring/internal/index/encoder.go @@ -0,0 +1,74 @@ +package index + +func encode(key string, length, deltaExptime, lastAccess, freq uint16, memId, offset uint32, entry *Entry) { + d1 := uint64(length&LENGTH_MASK) << LENGTH_SHIFT + d1 |= uint64(deltaExptime&DELTA_EXPTIME_MASK) << DELTA_EXPTIME_SHIFT + d1 |= uint64(lastAccess&LAST_ACCESS_MASK) << LAST_ACCESS_SHIFT + d1 |= uint64(freq&FREQ_MASK) << FREQ_SHIFT + + ByteOrder.PutUint64(entry[:8], d1) + + d2 := uint64(memId&MEM_ID_MASK) << MEM_ID_SHIFT + d2 |= uint64(offset&OFFSET_MASK) << OFFSET_SHIFT + + ByteOrder.PutUint64(entry[8:16], d2) +} + +func encodeHashNextPrev(hhi, hlo uint64, prev, next int32, entry *HashNextPrev) { + entry[0] = hhi + entry[1] = hlo + entry[2] = uint64(uint32(prev))<<32 | uint64(uint32(next)) +} + +func encodeUpdatePrev(prev int32, entry *HashNextPrev) { + next := entry[2] & NEXT_MASK + entry[2] = uint64(uint32(prev))<<32 | next +} + +func encodeUpdateNext(next int32, entry *HashNextPrev) { + prev := (entry[2] >> 32) & PREV_MASK + entry[2] = uint64(uint32(prev))<<32 | uint64(uint32(next)) +} + +func decodeNext(entry *HashNextPrev) int32 { + return int32(uint32(entry[2] & NEXT_MASK)) +} + +func decodePrev(entry *HashNextPrev) int32 { + return int32(uint32(entry[2]>>32) & PREV_MASK) +} + +func decodeHashLo(entry *HashNextPrev) uint64 { + return entry[1] +} + +func decode(entry *Entry) (length, deltaExptime, lastAccess, freq uint16, memId, offset uint32) { + d1 := ByteOrder.Uint64(entry[:8]) + d2 := ByteOrder.Uint64(entry[8:16]) + + length = uint16(d1>>LENGTH_SHIFT) & LENGTH_MASK + deltaExptime = uint16(d1>>DELTA_EXPTIME_SHIFT) & DELTA_EXPTIME_MASK + lastAccess = uint16(d1>>LAST_ACCESS_SHIFT) & LAST_ACCESS_MASK + freq = uint16(d1>>FREQ_SHIFT) & FREQ_MASK + + memId = uint32(d2>>MEM_ID_SHIFT) & MEM_ID_MASK + offset = uint32(d2>>OFFSET_SHIFT) & OFFSET_MASK + + return +} + +func encodeLastAccessNFreq(lastAccess, freq uint16, entry *Entry) { + d1 := ByteOrder.Uint64(entry[:8]) + // Clear lastAccess (bits 31:16) and freq (bits 15:0) before writing new values. + d1 &^= uint64(LAST_ACCESS_MASK)<>MEM_ID_SHIFT) & MEM_ID_MASK + offset = uint32(d2>>OFFSET_SHIFT) & OFFSET_MASK + return +} diff --git a/flashring/internal/index/index.go b/flashring/internal/index/index.go new file mode 100644 index 00000000..26ca1d4b --- /dev/null +++ b/flashring/internal/index/index.go @@ -0,0 +1,178 @@ +package index + +import ( + "errors" + "sync" + "time" + + "github.com/Meesho/BharatMLStack/flashring/internal/maths" + "github.com/cespare/xxhash/v2" + "github.com/zeebo/xxh3" +) + +var ErrGettingHeadEntry = errors.New("getting head entry failed") + +type Status int + +const ( + StatusOK Status = iota + StatusNotFound + StatusExpired +) + +type Index struct { + mu *sync.RWMutex + rm map[uint64]int + rb *RingBuffer + mc *maths.MorrisLogCounter + startAt int64 + hashBits int +} + +func NewIndex(hashBits int, rbInitial, rbMax, deleteAmortizedStep int, mu *sync.RWMutex) *Index { + return &Index{ + mu: mu, + rm: make(map[uint64]int), + rb: NewRingBuffer(rbInitial, rbMax), + mc: maths.New(), + startAt: time.Now().Unix(), + hashBits: hashBits, + } +} + +func (i *Index) Put(key string, length, ttlInMinutes uint16, memId, offset uint32) { + hhi, hlo := hash128(key) + entry, hashNextPrev, idx, _ := i.rb.GetNextFreeSlot() + lastAccess := i.generateLastAccess() + freq := uint16(1) + expiryAt := (time.Now().Unix() / 60) + int64(ttlInMinutes) + delta := uint16(expiryAt - (i.startAt / 60)) + encode(key, length, delta, lastAccess, freq, memId, offset, entry) + + if headIdx, ok := i.rm[hlo]; !ok { + encodeHashNextPrev(hhi, hlo, -1, -1, hashNextPrev) + i.rm[hlo] = idx + } else { + _, headHashNextPrev, _ := i.rb.Get(int(headIdx)) + encodeUpdatePrev(int32(idx), headHashNextPrev) + encodeHashNextPrev(hhi, hlo, -1, int32(headIdx), hashNextPrev) + i.rm[hlo] = idx + } +} + +func (i *Index) Get(key string) (length, lastAccess, remainingTTL uint16, freq uint64, memId, offset uint32, status Status) { + hhi, hlo := hash128(key) + + i.mu.RLock() + idx, ok := i.rm[hlo] + i.mu.RUnlock() + + if !ok { + return 0, 0, 0, 0, 0, 0, StatusNotFound + } + + for { + entry, hashNextPrev, _ := i.rb.Get(int(idx)) + if isHashMatch(hhi, hlo, hashNextPrev) { + length, deltaExptime, oldLastAccess, freq, memId, offset := decode(entry) + exptime := int(deltaExptime) + int(i.startAt/60) + currentTime := int(time.Now().Unix() / 60) + remainingTTL := exptime - currentTime + if remainingTTL <= 0 { + return 0, 0, 0, 0, 0, 0, StatusExpired + } + newLastAccess := i.generateLastAccess() + recency := newLastAccess - oldLastAccess // minutes since previous access + freq = i.incrFreq(freq, hlo) + encodeLastAccessNFreq(newLastAccess, freq, entry) + return length, recency, uint16(remainingTTL), i.mc.Value(uint16(freq)), memId, offset, StatusOK + } + if hasNext(hashNextPrev) { + idx = int(decodeNext(hashNextPrev)) + } else { + return 0, 0, 0, 0, 0, 0, StatusNotFound + } + } +} + +func (ix *Index) Delete(count int) (uint32, int) { + if count == 0 { + return 0, 0 + } + for i := 0; i < count; i++ { + deleted, deletedHashNextPrev, deletedIdx, next := ix.rb.Delete() + if deleted == nil { + return 0, -1 + } + delMemId, _ := DecodeMemIdOffset(deleted) + deletedHlo := decodeHashLo(deletedHashNextPrev) + mapIdx, ok := ix.rm[deletedHlo] + if ok && mapIdx == deletedIdx { + delete(ix.rm, deletedHlo) + } else if ok && hasPrev(deletedHashNextPrev) { + prevIdx := decodePrev(deletedHashNextPrev) + _, hashNextPrev, _ := ix.rb.Get(int(prevIdx)) + encodeUpdateNext(-1, hashNextPrev) + } + + nextMemId, _ := DecodeMemIdOffset(next) + if nextMemId == delMemId+1 { + return nextMemId, i + 1 + } else if nextMemId == delMemId && i == count-1 { + return delMemId, i + 1 + } else if nextMemId == delMemId { + continue + } else { + return 0, -1 + } + } + return 0, -1 +} + +// DeleteKey removes the key from the index map only. Debug use only. +func (ix *Index) DeleteKey(key string) bool { + _, hlo := hash128(key) + if _, ok := ix.rm[hlo]; !ok { + return false + } + delete(ix.rm, hlo) + return true +} + +func (ki *Index) GetRB() *RingBuffer { + return ki.rb +} + +func (ki *Index) PeekMemIdAtHead() (uint32, error) { + entry, _, ok := ki.rb.Get(ki.rb.head) + if !ok { + return 0, ErrGettingHeadEntry + } + memId, _ := DecodeMemIdOffset(entry) + return memId, nil +} + +func (i *Index) generateLastAccess() uint16 { + return uint16((time.Now().Unix() - i.startAt) / 60) +} + +func (i *Index) incrFreq(freq uint16, hlo uint64) uint16 { + newFreq, _ := i.mc.Inc(uint16(freq), hlo) + return uint16(newFreq) +} + +func hash128(key string) (uint64, uint64) { + return xxhash.Sum64String(key), xxh3.HashString(key) +} + +func isHashMatch(hhi, hlo uint64, entry *HashNextPrev) bool { + return entry[0] == hhi && entry[1] == hlo +} + +func hasNext(entry *HashNextPrev) bool { + return int32(entry[2]&NEXT_MASK) != -1 +} + +func hasPrev(entry *HashNextPrev) bool { + return int32((entry[2]>>32)&PREV_MASK) != -1 +} diff --git a/flashring/internal/index/ringbuffer.go b/flashring/internal/index/ringbuffer.go new file mode 100644 index 00000000..02214403 --- /dev/null +++ b/flashring/internal/index/ringbuffer.go @@ -0,0 +1,71 @@ +package index + +// Entry represents a 16-byte index entry. +type Entry [16]byte + +// HashNextPrev stores the dual hash (for collision detection) and linked-list +// pointers for chaining entries that share the same hash-lo bucket. +type HashNextPrev [3]uint64 + +// RingBuffer is a fixed-size circular queue. It maintains a sliding window +// of the most recent entries and wraps around when full, overwriting the oldest. +type RingBuffer struct { + buf []Entry + hashTable []HashNextPrev + head int + tail int + size int + nextIndex int + capacity int + wrapped bool +} + +func NewRingBuffer(initial, max int) *RingBuffer { + if initial <= 0 || initial > max { + panic("invalid capacity") + } + capacity := max + return &RingBuffer{ + buf: make([]Entry, capacity), + hashTable: make([]HashNextPrev, capacity), + capacity: capacity, + } +} + +func (rb *RingBuffer) NextAddNeedsDelete() bool { + return rb.nextIndex == rb.head && rb.wrapped +} + +func (rb *RingBuffer) GetNextFreeSlot() (*Entry, *HashNextPrev, int, bool) { + idx := rb.nextIndex + rb.nextIndex = (rb.nextIndex + 1) % rb.capacity + shouldDelete := false + if rb.nextIndex == rb.head { + rb.wrapped = true + shouldDelete = true + } + return &rb.buf[idx], &rb.hashTable[idx], idx, shouldDelete +} + +func (rb *RingBuffer) Get(index int) (*Entry, *HashNextPrev, bool) { + if index > rb.capacity { + return nil, nil, false + } + return &rb.buf[index], &rb.hashTable[index], true +} + +func (rb *RingBuffer) Delete() (*Entry, *HashNextPrev, int, *Entry) { + deletedIdx := rb.head + deleted := rb.buf[rb.head] + deletedHashNextPrev := rb.hashTable[rb.head] + rb.head = (rb.head + 1) % rb.capacity + return &deleted, &deletedHashNextPrev, deletedIdx, &rb.buf[rb.head] +} + +func (rb *RingBuffer) TailIndex() int { + return rb.nextIndex +} + +func (rb *RingBuffer) ActiveEntries() int { + return (rb.nextIndex - rb.head + rb.capacity) % rb.capacity +} diff --git a/flashring/internal/index/system.go b/flashring/internal/index/system.go new file mode 100644 index 00000000..0a3868b7 --- /dev/null +++ b/flashring/internal/index/system.go @@ -0,0 +1,54 @@ +package index + +import ( + "encoding/binary" + "unsafe" +) + +var ByteOrder *CustomByteOrder + +func init() { + loadByteOrder() +} + +type CustomByteOrder struct { + binary.ByteOrder +} + +func loadByteOrder() { + buf := [2]byte{} + *(*uint16)(unsafe.Pointer(&buf[0])) = uint16(0xABCD) + + switch buf { + case [2]byte{0xCD, 0xAB}: + ByteOrder = &CustomByteOrder{binary.LittleEndian} + case [2]byte{0xAB, 0xCD}: + ByteOrder = &CustomByteOrder{binary.BigEndian} + default: + panic("Could not determine endianness.") + } +} + +func (c *CustomByteOrder) PutInt64(b []byte, v int64) { + c.PutUint64(b, uint64(v)) +} + +func (c *CustomByteOrder) Int64(b []byte) int64 { + return int64(c.Uint64(b)) +} + +func (c *CustomByteOrder) PutInt32(b []byte, v int32) { + c.PutUint32(b, uint32(v)) +} + +func (c *CustomByteOrder) Int32(b []byte) int32 { + return int32(c.Uint32(b)) +} + +func (c *CustomByteOrder) PutUint32(b []byte, v uint32) { + c.ByteOrder.PutUint32(b, v) +} + +func (c *CustomByteOrder) Uint32(b []byte) uint32 { + return c.ByteOrder.Uint32(b) +} diff --git a/flashring/internal/indices/constants.go b/flashring/internal/indices/constants.go deleted file mode 100644 index 7062bcde..00000000 --- a/flashring/internal/indices/constants.go +++ /dev/null @@ -1,84 +0,0 @@ -package indices - -const ( - LENGTH_MASK = (1 << 16) - 1 - LAST_ACCESS_MASK = (1 << 24) - 1 - FREQ_MASK = (1 << 24) - 1 - H10_MASK = (1 << 10) - 1 - EXPTIME_MASK = (1 << 22) - 1 - SLICE_POS_MASK = (1 << 14) - 1 - ROUND_MASK = (1 << 4) - 1 - ROUTE_MASK = (1 << 24) - 1 - MEM_ID_MASK = (1 << 32) - 1 - OFFSET_MASK = (1 << 32) - 1 - ROUND_SHIFT = 60 - ROUTE_SHIFT = 36 - SLICE_POS_SHIFT = 22 - EXPTIME_SHIFT = 0 - SET_BIT_0 = 1 << 0 - SET_BIT_1 = 1 << 1 - SET_BIT_2 = 1 << 2 - SET_BIT_3 = 1 << 3 - SET_BIT_4 = 1 << 4 - SET_BIT_5 = 1 << 5 - SET_BIT_6 = 1 << 6 - SET_BIT_7 = 1 << 7 - SET_BIT_8 = 1 << 8 - SET_BIT_9 = 1 << 9 - SET_BIT_10 = 1 << 10 - SET_BIT_11 = 1 << 11 - SET_BIT_12 = 1 << 12 - SET_BIT_13 = 1 << 13 - SET_BIT_14 = 1 << 14 - SET_BIT_15 = 1 << 15 - SET_BIT_16 = 1 << 16 - SET_BIT_17 = 1 << 17 - SET_BIT_18 = 1 << 18 - SET_BIT_19 = 1 << 19 - SET_BIT_20 = 1 << 20 - SET_BIT_21 = 1 << 21 - SET_BIT_22 = 1 << 22 - SET_BIT_23 = 1 << 23 - SET_BIT_24 = 1 << 24 - SET_BIT_25 = 1 << 25 - SET_BIT_26 = 1 << 26 - SET_BIT_27 = 1 << 27 - SET_BIT_28 = 1 << 28 - SET_BIT_29 = 1 << 29 - SET_BIT_30 = 1 << 30 - SET_BIT_31 = 1 << 31 - SET_BIT_32 = 1 << 32 - SET_BIT_33 = 1 << 33 - SET_BIT_34 = 1 << 34 - SET_BIT_35 = 1 << 35 - SET_BIT_36 = 1 << 36 - SET_BIT_37 = 1 << 37 - SET_BIT_38 = 1 << 38 - SET_BIT_39 = 1 << 39 - SET_BIT_40 = 1 << 40 - SET_BIT_41 = 1 << 41 - SET_BIT_42 = 1 << 42 - SET_BIT_43 = 1 << 43 - SET_BIT_44 = 1 << 44 - SET_BIT_45 = 1 << 45 - SET_BIT_46 = 1 << 46 - SET_BIT_47 = 1 << 47 - SET_BIT_48 = 1 << 48 - SET_BIT_49 = 1 << 49 - SET_BIT_50 = 1 << 50 - SET_BIT_51 = 1 << 51 - SET_BIT_52 = 1 << 52 - SET_BIT_53 = 1 << 53 - SET_BIT_54 = 1 << 54 - SET_BIT_55 = 1 << 55 - SET_BIT_56 = 1 << 56 - SET_BIT_57 = 1 << 57 - SET_BIT_58 = 1 << 58 - SET_BIT_59 = 1 << 59 - SET_BIT_60 = 1 << 60 - SET_BIT_61 = 1 << 61 - SET_BIT_62 = 1 << 62 - SET_BIT_63 = 1 << 63 -) - -var () diff --git a/flashring/internal/indices/delete_manager.go b/flashring/internal/indices/delete_manager.go deleted file mode 100644 index da454722..00000000 --- a/flashring/internal/indices/delete_manager.go +++ /dev/null @@ -1,76 +0,0 @@ -package indices - -import ( - "fmt" - - "github.com/Meesho/BharatMLStack/flashring/internal/fs" - "github.com/rs/zerolog/log" -) - -type DeleteManager struct { - memtableData map[uint32]int - toBeDeletedMemId uint32 - keyIndex *KeyIndex - wrapFile *fs.WrapAppendFile - deleteInProgress bool - deleteAmortizedStep int - deleteCount int -} - -func NewDeleteManager(keyIndex *KeyIndex, wrapFile *fs.WrapAppendFile, deleteAmortizedStep int) *DeleteManager { - return &DeleteManager{ - memtableData: make(map[uint32]int), - toBeDeletedMemId: 0, - keyIndex: keyIndex, - wrapFile: wrapFile, - deleteInProgress: false, - deleteAmortizedStep: deleteAmortizedStep, - } -} - -func (dm *DeleteManager) IncMemtableKeyCount(memId uint32) { - dm.memtableData[memId]++ -} - -func (dm *DeleteManager) ExecuteDeleteIfNeeded() error { - if dm.deleteInProgress { - memtableId, count := dm.keyIndex.Delete(dm.deleteCount) - if count == -1 { - return fmt.Errorf("delete failed") - } - if memtableId != dm.toBeDeletedMemId { - dm.memtableData[dm.toBeDeletedMemId] = dm.memtableData[dm.toBeDeletedMemId] - count - log.Debug().Msgf("memtableId: %d, toBeDeletedMemId: %d", memtableId, dm.toBeDeletedMemId) - if dm.memtableData[dm.toBeDeletedMemId] != 0 { - return fmt.Errorf("memtableData[dm.toBeDeletedMemId] != 0") - } - delete(dm.memtableData, dm.toBeDeletedMemId) - dm.toBeDeletedMemId = memtableId - dm.deleteInProgress = false - dm.deleteCount = 0 - return nil - } else { - dm.memtableData[memtableId] -= count - //log.Debug().Msgf("memtableData[%d] = %d", memtableId, dm.memtableData[memtableId]) - } - return nil - } - - trimNeeded := dm.wrapFile.TrimHeadIfNeeded() - nextAddNeedsDelete := dm.keyIndex.GetRB().NextAddNeedsDelete() - - if trimNeeded || nextAddNeedsDelete { - dm.deleteInProgress = true - dm.deleteCount = int(dm.memtableData[dm.toBeDeletedMemId] / dm.deleteAmortizedStep) - memIdAtHead, err := dm.keyIndex.PeekMemIdAtHead() - if err != nil { - return err - } - if memIdAtHead != dm.toBeDeletedMemId { - return fmt.Errorf("memIdAtHead: %d, toBeDeletedMemId: %d", memIdAtHead, dm.toBeDeletedMemId) - } - dm.wrapFile.TrimHead() - return nil - } - return nil -} diff --git a/flashring/internal/indices/encoder.go b/flashring/internal/indices/encoder.go deleted file mode 100644 index d4e952da..00000000 --- a/flashring/internal/indices/encoder.go +++ /dev/null @@ -1,85 +0,0 @@ -package indices - -/* ------------ -uint64 ------------ -round 4 bits -route 24 bits -slice pos 14 bits -exp in minutes 22 bits ---------- -uint64 ---------- -length 16 bits -access 24 bits -freq 24 bits -------------- -uint64 -------------- -memId 32 bits -offset 32 bits -*/ -func encode(length uint16, memId, offset, lastAccess, freq uint32, exptime uint64, round, route, slicePos int, entry *Entry) { - - d1 := uint64(round&ROUND_MASK) << 60 - d1 |= uint64(route&ROUTE_MASK) << 36 - d1 |= uint64(slicePos&SLICE_POS_MASK) << 22 - d1 |= uint64(exptime & EXPTIME_MASK) - - d2 := uint64(length&LENGTH_MASK) << 48 - d2 |= uint64(lastAccess&LAST_ACCESS_MASK) << 24 - d2 |= uint64(freq & FREQ_MASK) - - d3 := uint64(memId&MEM_ID_MASK) << 32 - d3 |= uint64(offset & OFFSET_MASK) - - ByteOrder.PutUint64(entry[:8], d1) - ByteOrder.PutUint64(entry[8:16], d2) - ByteOrder.PutUint64(entry[16:24], d3) -} - -func encodeD2(length uint16, lastAccess, freq uint32, entry *Entry) { - d2 := uint64(length&LENGTH_MASK) << 48 - d2 |= uint64(lastAccess&LAST_ACCESS_MASK) << 24 - d2 |= uint64(freq & FREQ_MASK) - ByteOrder.PutUint64(entry[8:16], d2) -} - -func extract(entry *Entry) (length uint16, memId, offset, lastAccess, freq uint32, exptime uint64, round, route, slicePos int) { - d1 := ByteOrder.Uint64(entry[:8]) - d2 := ByteOrder.Uint64(entry[8:16]) - d3 := ByteOrder.Uint64(entry[16:24]) - - round = int(d1>>60) & ROUND_MASK - route = int(d1>>36) & ROUTE_MASK - slicePos = int(d1>>22) & SLICE_POS_MASK - exptime = d1 & EXPTIME_MASK - - length = uint16(d2>>48) & LENGTH_MASK - lastAccess = uint32(d2>>24) & LAST_ACCESS_MASK - freq = uint32(d2) & FREQ_MASK - - memId = uint32(d3>>32) & MEM_ID_MASK - offset = uint32(d3) & OFFSET_MASK - return -} - -func extractD1(entry *Entry) (round, route, slicePos int) { - d1 := ByteOrder.Uint64(entry[:8]) - round = int(d1>>60) & ROUND_MASK - route = int(d1>>36) & ROUTE_MASK - slicePos = int(d1>>22) & SLICE_POS_MASK - return -} - -func extractD3(entry *Entry) (memId, offset uint32) { - d3 := ByteOrder.Uint64(entry[16:24]) - memId = uint32(d3>>32) & MEM_ID_MASK - offset = uint32(d3) & OFFSET_MASK - return -} - -func extractMemId(entry *Entry) (memId uint32) { - return ByteOrder.Uint32(entry[8:12]) -} diff --git a/flashring/internal/indices/flat_bitmap.go b/flashring/internal/indices/flat_bitmap.go deleted file mode 100644 index 61000e4c..00000000 --- a/flashring/internal/indices/flat_bitmap.go +++ /dev/null @@ -1,242 +0,0 @@ -package indices - -import ( - "encoding/binary" -) - -const ( - _64_BITS_COUNT = (1 << 18) // 2^24/64 as we are using uint64 -) - -var bitIndex = [64]uint64{ - SET_BIT_0, SET_BIT_1, SET_BIT_2, SET_BIT_3, SET_BIT_4, SET_BIT_5, SET_BIT_6, SET_BIT_7, - SET_BIT_8, SET_BIT_9, SET_BIT_10, SET_BIT_11, SET_BIT_12, SET_BIT_13, SET_BIT_14, SET_BIT_15, - SET_BIT_16, SET_BIT_17, SET_BIT_18, SET_BIT_19, SET_BIT_20, SET_BIT_21, SET_BIT_22, SET_BIT_23, - SET_BIT_24, SET_BIT_25, SET_BIT_26, SET_BIT_27, SET_BIT_28, SET_BIT_29, SET_BIT_30, SET_BIT_31, - SET_BIT_32, SET_BIT_33, SET_BIT_34, SET_BIT_35, SET_BIT_36, SET_BIT_37, SET_BIT_38, SET_BIT_39, - SET_BIT_40, SET_BIT_41, SET_BIT_42, SET_BIT_43, SET_BIT_44, SET_BIT_45, SET_BIT_46, SET_BIT_47, - SET_BIT_48, SET_BIT_49, SET_BIT_50, SET_BIT_51, SET_BIT_52, SET_BIT_53, SET_BIT_54, SET_BIT_55, - SET_BIT_56, SET_BIT_57, SET_BIT_58, SET_BIT_59, SET_BIT_60, SET_BIT_61, SET_BIT_62, SET_BIT_63, -} - -type FlatBitmap struct { - bitmap [_64_BITS_COUNT]uint64 - valueSlice [_64_BITS_COUNT][]Entry12 -} - -func NewFlatBitmap() *FlatBitmap { - return &FlatBitmap{} -} - -// Entry12 is a packed 12-byte entry: [8-byte tag][4-byte idx] in little-endian. -type Entry12 [12]byte - -// buildTag packs last28bits (28 bits) and h2 (up to 34 bits) into a 64-bit tag: -// tag = (last28bits & 0x0FFFFFFF) << 34 | (h2 & ((1<<34)-1)) -func buildTag(last28bits, h2 uint64) uint64 { - const mask28 = 0x0FFFFFFF - const mask34 = (uint64(1) << 34) - 1 - return ((last28bits & mask28) << 34) | (h2 & mask34) -} - -func putEntry(e *Entry12, tag uint64, idx uint32) { - binary.LittleEndian.PutUint64(e[0:8], tag) - binary.LittleEndian.PutUint32(e[8:12], idx) -} - -func getTag(e *Entry12) uint64 { - return binary.LittleEndian.Uint64(e[0:8]) -} - -func getIdx(e *Entry12) uint32 { - return binary.LittleEndian.Uint32(e[8:12]) -} - -func zeroEntry(e *Entry12) { - for i := range e { - e[i] = 0 - } -} - -// FlatBitmapStats contains aggregated statistics for a FlatBitmap instance. -type FlatBitmapStats struct { - BucketsUsed uint32 // number of buckets with at least one bit set - BucketsWithOverflow uint32 // buckets whose slice length > 64 - TotalEntries uint64 // total present entries (tag != 0) - PrimaryEntries uint64 // entries present in primary region (first 64) - OverflowEntries uint64 // entries present in overflow region (index >= 64) - ReusableOverflowSlots uint64 // zeroed overflow slots available for reuse - - AvgValueSliceLen float64 // average slice length among used buckets - MaxValueSliceLen int // maximum slice length among used buckets - AvgOverflowLen float64 // average overflow length among buckets that have overflow - - TotalAllocatedBytes uint64 // bytes allocated for value slices (len * 12) -} - -// Stats computes aggregated statistics by scanning buckets and their slices. -// This is O(number of buckets + total slice length) and intended for diagnostics. -func (fb *FlatBitmap) Stats() FlatBitmapStats { - var st FlatBitmapStats - var sumLen uint64 - var sumOverflowLen uint64 - - for pos := 0; pos < _64_BITS_COUNT; pos++ { - if fb.bitmap[pos] == 0 { - continue - } - st.BucketsUsed++ - sl := fb.valueSlice[pos] - l := len(sl) - if l == 0 { - // Should not normally happen for a used bucket, but guard anyway - continue - } - sumLen += uint64(l) - if l > st.MaxValueSliceLen { - st.MaxValueSliceLen = l - } - st.TotalAllocatedBytes += uint64(l * 12) - - // Primary region present entries - primMax := l - if primMax > 64 { - primMax = 64 - } - for i := 0; i < primMax; i++ { - if getTag(&sl[i]) != 0 { - st.PrimaryEntries++ - st.TotalEntries++ - } - } - - // Overflow region stats - if l > 64 { - st.BucketsWithOverflow++ - overLen := l - 64 - sumOverflowLen += uint64(overLen) - for i := 64; i < l; i++ { - if getTag(&sl[i]) != 0 { - st.OverflowEntries++ - st.TotalEntries++ - } else { - st.ReusableOverflowSlots++ - } - } - } - } - - if st.BucketsUsed > 0 { - st.AvgValueSliceLen = float64(sumLen) / float64(st.BucketsUsed) - } - if st.BucketsWithOverflow > 0 { - st.AvgOverflowLen = float64(sumOverflowLen) / float64(st.BucketsWithOverflow) - } - return st -} - -func (fb *FlatBitmap) Set(next24bits, last28bits, h34 uint64, idx uint32) int { - pos := int((next24bits >> 6) & 0x3FFFF) - bitPos := next24bits & 0x3F - qTag := buildTag(last28bits, h34) - if fb.bitmap[pos] == 0 { - fb.valueSlice[pos] = make([]Entry12, 64) - fb.bitmap[pos] |= bitIndex[bitPos] - putEntry(&fb.valueSlice[pos][bitPos], qTag, idx) - return int(bitPos) - } else if fb.bitmap[pos]&bitIndex[bitPos] == 0 { - fb.bitmap[pos] |= bitIndex[bitPos] - putEntry(&fb.valueSlice[pos][bitPos], qTag, idx) - return int(bitPos) - } else { - // First check the initial position for existing key - if getTag(&fb.valueSlice[pos][bitPos]) == qTag { - putEntry(&fb.valueSlice[pos][bitPos], qTag, idx) - return int(bitPos) - } - - // Then check collision list starting from index 64 - i := 64 - firstZeroIdx := -1 - for i < len(fb.valueSlice[pos]) { - if getTag(&fb.valueSlice[pos][i]) == qTag { - putEntry(&fb.valueSlice[pos][i], qTag, idx) - return int(i) - } else if getTag(&fb.valueSlice[pos][i]) == 0 && firstZeroIdx == -1 { - firstZeroIdx = i - } - i++ - } - if firstZeroIdx != -1 { - putEntry(&fb.valueSlice[pos][firstZeroIdx], qTag, idx) - return int(firstZeroIdx) - } else { - fb.valueSlice[pos] = append(fb.valueSlice[pos], Entry12{}) - putEntry(&fb.valueSlice[pos][len(fb.valueSlice[pos])-1], qTag, idx) - return int(len(fb.valueSlice[pos]) - 1) - } - } -} - -func (fb *FlatBitmap) Get(next24bits, last28bits, h34 uint64) (uint32, int, bool) { - pos := int((next24bits >> 6) & 0x3FFFF) - bitPos := next24bits & 0x3F - if fb.bitmap[pos] == 0 || fb.bitmap[pos]&bitIndex[bitPos] == 0 { - return 0, -1, false - } - if fb.bitmap[pos]&bitIndex[bitPos] == bitIndex[bitPos] { - qTag := buildTag(last28bits, h34) - if getTag(&fb.valueSlice[pos][bitPos]) == qTag { - return getIdx(&fb.valueSlice[pos][bitPos]), int(bitPos), true - } - i := 64 - for i < len(fb.valueSlice[pos]) { - if getTag(&fb.valueSlice[pos][i]) == qTag { - return getIdx(&fb.valueSlice[pos][i]), int(i), true - } - i++ - } - return 0, -1, false - } - return 0, -1, false -} - -func (fb *FlatBitmap) Remove(next24bits, last28bits, h34 uint64) (uint32, bool) { - pos := int((next24bits >> 6) & 0x3FFFF) - bitPos := next24bits & 0x3F - if fb.bitmap[pos] == 0 || fb.bitmap[pos]&bitIndex[bitPos] == 0 { - return 0, false - } - if fb.bitmap[pos]&bitIndex[bitPos] == bitIndex[bitPos] { - qTag := buildTag(last28bits, h34) - if getTag(&fb.valueSlice[pos][bitPos]) == qTag { - idx := getIdx(&fb.valueSlice[pos][bitPos]) - zeroEntry(&fb.valueSlice[pos][bitPos]) - return idx, true - } - i := 64 - for i < len(fb.valueSlice[pos]) { - if getTag(&fb.valueSlice[pos][i]) == qTag { - idx := getIdx(&fb.valueSlice[pos][i]) - zeroEntry(&fb.valueSlice[pos][i]) - return idx, true - } - i++ - } - } - return 0, false -} - -func (fb *FlatBitmap) RemoveV2(next24bits, slicePos int) (uint32, bool) { - pos := int((next24bits >> 6) & 0x3FFFF) - bitPos := next24bits & 0x3F - if fb.bitmap[pos] == 0 || fb.bitmap[pos]&bitIndex[bitPos] == 0 { - return 0, false - } - if fb.bitmap[pos]&bitIndex[bitPos] == bitIndex[bitPos] { - rbIdx := getIdx(&fb.valueSlice[pos][slicePos]) - zeroEntry(&fb.valueSlice[pos][slicePos]) - return uint32(rbIdx), true - } - return 0, false -} diff --git a/flashring/internal/indices/flat_bitmap_bench_test.go b/flashring/internal/indices/flat_bitmap_bench_test.go deleted file mode 100644 index 2c93d7d9..00000000 --- a/flashring/internal/indices/flat_bitmap_bench_test.go +++ /dev/null @@ -1 +0,0 @@ -package indices diff --git a/flashring/internal/indices/flat_bitmap_test.go b/flashring/internal/indices/flat_bitmap_test.go deleted file mode 100644 index 2c93d7d9..00000000 --- a/flashring/internal/indices/flat_bitmap_test.go +++ /dev/null @@ -1 +0,0 @@ -package indices diff --git a/flashring/internal/indices/key_index.go b/flashring/internal/indices/key_index.go deleted file mode 100644 index 3828397d..00000000 --- a/flashring/internal/indices/key_index.go +++ /dev/null @@ -1,120 +0,0 @@ -package indices - -import ( - "errors" - "time" - - "github.com/Meesho/BharatMLStack/flashring/internal/maths" -) - -var ( - ErrGettingHeadEntry = errors.New("getting head entry failed") -) - -type KeyIndex struct { - rm *RoundMap - rb *RingBuffer - mc *maths.MorrisLogCounter - startAt int64 -} - -func NewKeyIndex(rounds int, rbInitial, rbMax, deleteAmortizedStep int) *KeyIndex { - if ByteOrder == nil { - loadByteOrder() - } - return &KeyIndex{ - rm: NewRoundMap(rounds), - rb: NewRingBuffer(rbInitial, rbMax), - mc: maths.New(10), - startAt: time.Now().Unix(), - } -} - -func (ki *KeyIndex) Put(key string, length uint16, memId, offset uint32, exptime uint64) { - lastAccess := ki.GenerateLastAccess() - freq := uint32(1) - h64 := Hash64(key) - h34 := Hash34(key) - entry, idx, _ := ki.rb.GetEntry() - round, next24bits, slicePos := ki.rm.Add(key, uint32(idx), h64, h34) - encode(length, memId, offset, lastAccess, freq, exptime, round, next24bits, slicePos, entry) -} - -func (ki *KeyIndex) GenerateLastAccess() uint32 { - return uint32(time.Now().Unix()-ki.startAt) / 60 -} - -func (ki *KeyIndex) Get(key string) (uint32, uint16, uint32, uint32, uint64, uint64, uint32, bool) { - h64 := Hash64(key) - h34 := Hash34(key) - idx, slicePos, found := ki.rm.Get(h64, h34) - if !found { - return 0, 0, 0, 0, 0, 0, 0, false // TODO: return error - } - entry, ok := ki.rb.Get(int(idx)) - if !ok { - return 0, 0, 0, 0, 0, 0, 0, false // TODO: return error - } - length, memId, offset, lastAccessAt, freq, exptime, _, _, gotSlicePos := extract(entry) - if gotSlicePos != slicePos { - return 0, 0, 0, 0, 0, 0, 0, false // TODO: return error - } - lastAccess := ki.GenerateLastAccess() - freq, _ = ki.mc.Inc(freq) - encodeD2(length, lastAccess, freq, entry) - lastAccess = ki.GenerateLastAccess() - lastAccessAt - return memId, length, offset, lastAccess, ki.mc.Value(freq), exptime, uint32(idx), true -} - -func (ki *KeyIndex) Delete(nKeys int) (uint32, int) { - for i := 0; i < nKeys; i++ { - deleted, next := ki.rb.Delete() - if deleted == nil { - return 0, -1 - } - round, route, slicePos := extractD1(deleted) - ki.rm.RemoveV2(round, route, slicePos) - delMemId := extractMemId(deleted) - nextMemId := extractMemId(next) - if nextMemId == delMemId+1 { - return nextMemId, i + 1 - } else if nextMemId == delMemId && i == nKeys-1 { - return delMemId, i + 1 - } else if nextMemId == delMemId { - continue - } else { - return 0, -1 - } - } - return 0, -1 -} - -func (ki *KeyIndex) GetRB() *RingBuffer { - return ki.rb -} - -func (ki *KeyIndex) PeekMemIdAtHead() (uint32, error) { - entry, ok := ki.rb.Get(ki.rb.head) - if !ok { - return 0, ErrGettingHeadEntry - } - memId, _ := extractD3(entry) - return memId, nil -} - -// Debug methods to expose ring buffer state -func (ki *KeyIndex) GetRingBufferNextIndex() int { - return ki.rb.nextIndex -} - -func (ki *KeyIndex) GetRingBufferSize() int { - return ki.rb.size -} - -func (ki *KeyIndex) GetRingBufferCapacity() int { - return ki.rb.capacity -} - -func (ki *KeyIndex) GetRingBufferActiveEntries() int { - return ki.rb.ActiveEntries() -} diff --git a/flashring/internal/indices/key_index_test.go b/flashring/internal/indices/key_index_test.go deleted file mode 100644 index 2c93d7d9..00000000 --- a/flashring/internal/indices/key_index_test.go +++ /dev/null @@ -1 +0,0 @@ -package indices diff --git a/flashring/internal/indices/rb.go b/flashring/internal/indices/rb.go deleted file mode 100644 index d91862ac..00000000 --- a/flashring/internal/indices/rb.go +++ /dev/null @@ -1,90 +0,0 @@ -package indices - -// Entry represents a 32-byte value. Adjust fields as needed. -type Entry [24]byte - -// RingBuffer is a fixed-size circular queue that wraps around when full. -// It maintains a sliding window of the most recent entries. Add returns an -// absolute index which can be used with Get. -type RingBuffer struct { - buf []Entry - head int - tail int - size int - nextIndex int - capacity int // Fixed capacity (initial = max) - wrapped bool -} - -// NewRingBuffer creates a ring buffer with the given initial and maximum -// capacity. Since we use a fixed-size buffer, initial and max should be the same. -func NewRingBuffer(initial, max int) *RingBuffer { - if initial <= 0 || initial > max { - panic("invalid capacity") - } - // Use max capacity for fixed-size buffer (initial = max in practice) - capacity := max - return &RingBuffer{ - buf: make([]Entry, capacity), - capacity: capacity, - wrapped: false, - } -} - -// Add inserts e into the buffer and returns its absolute index. When the buffer -// is full it wraps around and overwrites the oldest entry. -func (rb *RingBuffer) Add(e *Entry) int { - // Store the entry at current tail position - rb.buf[rb.nextIndex] = *e - idx := rb.nextIndex - rb.nextIndex = (rb.nextIndex + 1) % rb.capacity - if rb.nextIndex == rb.head { - rb.head = (rb.head + 1) % rb.capacity - } - - return idx -} - -func (rb *RingBuffer) NextAddNeedsDelete() bool { - return rb.nextIndex == rb.head && rb.wrapped -} - -func (rb *RingBuffer) GetEntry() (*Entry, int, bool) { - idx := rb.nextIndex - rb.nextIndex = (rb.nextIndex + 1) % rb.capacity - shouldDelete := false - if rb.nextIndex == rb.head { - // rb.head = (rb.head + 1) % rb.capacity - rb.wrapped = true - shouldDelete = true - - } - - return &rb.buf[idx], idx, shouldDelete -} - -// Get retrieves an entry by its absolute index. The boolean return is false if -// the index is out of range (either overwritten or not yet added). -func (rb *RingBuffer) Get(index int) (*Entry, bool) { - // Calculate the valid window based on current state - if index > rb.capacity { - return nil, false - } - return &rb.buf[index], true -} - -// Delete removes the oldest entry from the buffer if it is not empty. -// For a fixed-size ring buffer, this only decreases size if not at capacity. -func (rb *RingBuffer) Delete() (*Entry, *Entry) { - deleted := rb.buf[rb.head] - rb.head = (rb.head + 1) % rb.capacity - return &deleted, &rb.buf[rb.head] -} - -// TailIndex returns the absolute index that will be assigned to the next Add. -func (rb *RingBuffer) TailIndex() int { - return rb.nextIndex -} -func (rb *RingBuffer) ActiveEntries() int { - return (rb.nextIndex - rb.head + rb.capacity) % rb.capacity -} diff --git a/flashring/internal/indices/rb_bench_test.go b/flashring/internal/indices/rb_bench_test.go deleted file mode 100644 index 566975a9..00000000 --- a/flashring/internal/indices/rb_bench_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package indices - -import ( - "testing" -) - -// BenchmarkRingBufferPush50M benchmarks pushing 50 million elements to the ring buffer -func BenchmarkRingBufferPush50M(b *testing.B) { - rb := NewRingBuffer(1000, 50_000_000) - - b.ResetTimer() - b.Run("Add", func(b *testing.B) { - for i := 0; i < b.N; i++ { - rb.Add(&Entry{}) - } - }) - b.Run("Get", func(b *testing.B) { - for i := 0; i < b.N; i++ { - rb.Get(i) - } - }) -} diff --git a/flashring/internal/indices/round_map.go b/flashring/internal/indices/round_map.go deleted file mode 100644 index de995300..00000000 --- a/flashring/internal/indices/round_map.go +++ /dev/null @@ -1,75 +0,0 @@ -package indices - -import ( - "github.com/cespare/xxhash/v2" - "github.com/zeebo/xxh3" -) - -const ( - _LO_28BIT_IN_32BIT = (1 << 28) - 1 - _LO_20BIT_IN_32BIT = (1 << 20) - 1 - _LO_12BIT_IN_32BIT = (1 << 12) - 1 - _LO_24BIT_IN_32BIT = (1 << 24) - 1 - _LO_28BIT_IN_64BIT = (1 << 28) - 1 - _LO_6BIT_IN_32BIT = (1 << 6) - 1 - _LO_9BIT_IN_32BIT = (1 << 9) - 1 - _LO_3BIT_IN_32BIT = (1 << 3) - 1 - _LO_54BIT_IN_64BIT = (1 << 54) - 1 - _LO_34BIT_IN_64BIT = (1 << 34) - 1 -) - -func Hash34(data string) uint64 { - return uint64(xxh3.HashString(data) & _LO_34BIT_IN_64BIT) // mask 10 bits -} - -func Hash64(data string) uint64 { - return xxhash.Sum64String(data) -} - -type RoundMap struct { - bitmaps []*FlatBitmap -} - -func NewRoundMap(numRounds int) *RoundMap { - bitmaps := make([]*FlatBitmap, numRounds) - for i := 0; i < numRounds; i++ { - bitmaps[i] = NewFlatBitmap() - } - return &RoundMap{ - bitmaps: bitmaps, - } -} - -func (rm *RoundMap) Add(key string, idx uint32, h64, h10 uint64) (int, int, int) { - first12bits, next24bits, last28bits := extractHashSegments(h64) // Bits 27–0 - - round := first12bits % uint64(len(rm.bitmaps)) - slicePos := rm.bitmaps[round].Set(uint64(next24bits), uint64(last28bits), h10, idx) - return int(round), int(next24bits), slicePos -} - -func extractHashSegments(h64 uint64) (uint64, uint64, uint64) { - first12bits := (h64 >> 52) & _LO_12BIT_IN_32BIT // Bits 63–52 - next24bits := (h64 >> 28) & _LO_24BIT_IN_32BIT // Bits 51–28 - last28bits := h64 & _LO_28BIT_IN_32BIT - return first12bits, next24bits, last28bits -} - -func (rm *RoundMap) Get(h64, h10 uint64) (uint32, int, bool) { - first12bits, next24bits, last28bits := extractHashSegments(h64) // Bits 27–0 - - round := first12bits % uint64(len(rm.bitmaps)) - return rm.bitmaps[round].Get(uint64(next24bits), uint64(last28bits), h10) -} - -func (rm *RoundMap) Remove(h64, h10 uint64) (uint32, bool) { - - first12bits, next24bits, last28bits := extractHashSegments(h64) // Bits 27–0 - - round := first12bits % uint64(len(rm.bitmaps)) - return rm.bitmaps[round].Remove(uint64(next24bits), uint64(last28bits), h10) -} - -func (rm *RoundMap) RemoveV2(round, next24bits, slicePos int) (uint32, bool) { - return rm.bitmaps[round].RemoveV2(next24bits, slicePos) -} diff --git a/flashring/internal/indices/round_map_bench_test.go b/flashring/internal/indices/round_map_bench_test.go deleted file mode 100644 index 2c93d7d9..00000000 --- a/flashring/internal/indices/round_map_bench_test.go +++ /dev/null @@ -1 +0,0 @@ -package indices diff --git a/flashring/internal/indices/round_map_test.go b/flashring/internal/indices/round_map_test.go deleted file mode 100644 index 2c93d7d9..00000000 --- a/flashring/internal/indices/round_map_test.go +++ /dev/null @@ -1 +0,0 @@ -package indices diff --git a/flashring/internal/indices/system.go b/flashring/internal/indices/system.go deleted file mode 100644 index 8b949f05..00000000 --- a/flashring/internal/indices/system.go +++ /dev/null @@ -1,50 +0,0 @@ -package indices - -import ( - "encoding/binary" - "unsafe" -) - -var ByteOrder *CustomByteOrder - -type CustomByteOrder struct { - binary.ByteOrder -} - -func loadByteOrder() { - buf := [2]byte{} - *(*uint16)(unsafe.Pointer(&buf[0])) = uint16(0xABCD) - - switch buf { - case [2]byte{0xCD, 0xAB}: - ByteOrder = &CustomByteOrder{binary.LittleEndian} - case [2]byte{0xAB, 0xCD}: - ByteOrder = &CustomByteOrder{binary.BigEndian} - default: - panic("Could not determine endianness.") - } -} - -func (c *CustomByteOrder) PutInt64(b []byte, v int64) { - c.PutUint64(b, uint64(v)) -} - -func (c *CustomByteOrder) Int64(b []byte) int64 { - return int64(c.Uint64(b)) -} - -func (c *CustomByteOrder) PutInt32(b []byte, v int32) { - c.PutUint32(b, uint32(v)) -} - -func (c *CustomByteOrder) Int32(b []byte) int32 { - return int32(c.Uint32(b)) -} - -func (c *CustomByteOrder) PutUint32(b []byte, v uint32) { - c.ByteOrder.PutUint32(b, v) -} - -func (c *CustomByteOrder) Uint32(b []byte) uint32 { - return c.ByteOrder.Uint32(b) -} diff --git a/flashring/internal/indicesV2/constant.go b/flashring/internal/indicesV2/constant.go deleted file mode 100644 index ad467899..00000000 --- a/flashring/internal/indicesV2/constant.go +++ /dev/null @@ -1,22 +0,0 @@ -package indicesv2 - -const ( - - //[0]uint64 - LENGTH_MASK = (1 << 16) - 1 - DELTA_EXPTIME_MASK = (1 << 16) - 1 - LAST_ACCESS_MASK = (1 << 16) - 1 - FREQ_MASK = (1 << 16) - 1 - - //[1]uint64 - MEM_ID_MASK = (1 << 32) - 1 - OFFSET_MASK = (1 << 32) - 1 - - LENGTH_SHIFT = 48 - DELTA_EXPTIME_SHIFT = 32 - LAST_ACCESS_SHIFT = 16 - FREQ_SHIFT = 0 - - MEM_ID_SHIFT = 32 - OFFSET_SHIFT = 0 -) diff --git a/flashring/internal/indicesV2/delete_manager.go b/flashring/internal/indicesV2/delete_manager.go deleted file mode 100644 index 6b218915..00000000 --- a/flashring/internal/indicesV2/delete_manager.go +++ /dev/null @@ -1,76 +0,0 @@ -package indicesv2 - -import ( - "fmt" - - "github.com/Meesho/BharatMLStack/flashring/internal/fs" - "github.com/rs/zerolog/log" -) - -type DeleteManager struct { - memtableData map[uint32]int - toBeDeletedMemId uint32 - keyIndex *Index - wrapFile *fs.WrapAppendFile - deleteInProgress bool - deleteAmortizedStep int - deleteCount int -} - -func NewDeleteManager(keyIndex *Index, wrapFile *fs.WrapAppendFile, deleteAmortizedStep int) *DeleteManager { - return &DeleteManager{ - memtableData: make(map[uint32]int), - toBeDeletedMemId: 0, - keyIndex: keyIndex, - wrapFile: wrapFile, - deleteInProgress: false, - deleteAmortizedStep: deleteAmortizedStep, - } -} - -func (dm *DeleteManager) IncMemtableKeyCount(memId uint32) { - dm.memtableData[memId]++ -} - -func (dm *DeleteManager) ExecuteDeleteIfNeeded() error { - if dm.deleteInProgress { - memtableId, count := dm.keyIndex.Delete(dm.deleteCount) - if count == -1 { - return fmt.Errorf("delete failed") - } - if memtableId != dm.toBeDeletedMemId { - dm.memtableData[dm.toBeDeletedMemId] = dm.memtableData[dm.toBeDeletedMemId] - count - log.Debug().Msgf("memtableId: %d, toBeDeletedMemId: %d", memtableId, dm.toBeDeletedMemId) - if dm.memtableData[dm.toBeDeletedMemId] != 0 { - return fmt.Errorf("memtableData[dm.toBeDeletedMemId] != 0") - } - delete(dm.memtableData, dm.toBeDeletedMemId) - dm.toBeDeletedMemId = memtableId - dm.deleteInProgress = false - dm.deleteCount = 0 - return nil - } else { - dm.memtableData[memtableId] -= count - //log.Debug().Msgf("memtableData[%d] = %d", memtableId, dm.memtableData[memtableId]) - } - return nil - } - - trimNeeded := dm.wrapFile.TrimHeadIfNeeded() - nextAddNeedsDelete := dm.keyIndex.GetRB().NextAddNeedsDelete() - - if trimNeeded || nextAddNeedsDelete { - dm.deleteInProgress = true - dm.deleteCount = int(dm.memtableData[dm.toBeDeletedMemId] / dm.deleteAmortizedStep) - memIdAtHead, err := dm.keyIndex.PeekMemIdAtHead() - if err != nil { - return err - } - if memIdAtHead != dm.toBeDeletedMemId { - return fmt.Errorf("memIdAtHead: %d, toBeDeletedMemId: %d", memIdAtHead, dm.toBeDeletedMemId) - } - dm.wrapFile.TrimHead() - return nil - } - return nil -} diff --git a/flashring/internal/indicesV2/encoder.go b/flashring/internal/indicesV2/encoder.go deleted file mode 100644 index 3ccf986a..00000000 --- a/flashring/internal/indicesV2/encoder.go +++ /dev/null @@ -1,53 +0,0 @@ -package indicesv2 - -func encode(key string, length, deltaExptime, lastAccess, freq uint16, memId, offset uint32, entry *Entry) { - - d1 := uint64(length&LENGTH_MASK) << LENGTH_SHIFT - d1 |= uint64(deltaExptime&DELTA_EXPTIME_MASK) << DELTA_EXPTIME_SHIFT - d1 |= uint64(lastAccess&LAST_ACCESS_MASK) << LAST_ACCESS_SHIFT - d1 |= uint64(freq&FREQ_MASK) << FREQ_SHIFT - - ByteOrder.PutUint64(entry[:8], d1) - - d2 := uint64(memId&MEM_ID_MASK) << MEM_ID_SHIFT - d2 |= uint64(offset&OFFSET_MASK) << OFFSET_SHIFT - - ByteOrder.PutUint64(entry[8:16], d2) -} - -func decode(entry *Entry) (length, deltaExptime, lastAccess, freq uint16, memId, offset uint32) { - d1 := ByteOrder.Uint64(entry[:8]) - d2 := ByteOrder.Uint64(entry[8:16]) - - length = uint16(d1>>LENGTH_SHIFT) & LENGTH_MASK - deltaExptime = uint16(d1>>DELTA_EXPTIME_SHIFT) & DELTA_EXPTIME_MASK - lastAccess = uint16(d1>>LAST_ACCESS_SHIFT) & LAST_ACCESS_MASK - freq = uint16(d1>>FREQ_SHIFT) & FREQ_MASK - - memId = uint32(d2>>MEM_ID_SHIFT) & MEM_ID_MASK - offset = uint32(d2>>OFFSET_SHIFT) & OFFSET_MASK - - return length, deltaExptime, lastAccess, freq, memId, offset -} - -func decodeLastAccessNFreq(entry *Entry) (lastAccess, freq uint16) { - d1 := ByteOrder.Uint64(entry[:8]) - lastAccess = uint16(d1>>LAST_ACCESS_SHIFT) & LAST_ACCESS_MASK - freq = uint16(d1>>FREQ_SHIFT) & FREQ_MASK - - return lastAccess, freq -} - -func encodeLastAccessNFreq(lastAccess, freq uint16, entry *Entry) { - d1 := uint64(lastAccess&LAST_ACCESS_MASK) << LAST_ACCESS_SHIFT - d1 |= uint64(freq&FREQ_MASK) << FREQ_SHIFT - - ByteOrder.PutUint64(entry[:8], d1) -} - -func decodeMemIdOffset(entry *Entry) (memId, offset uint32) { - d2 := ByteOrder.Uint64(entry[8:16]) - memId = uint32(d2>>MEM_ID_SHIFT) & MEM_ID_MASK - offset = uint32(d2>>OFFSET_SHIFT) & OFFSET_MASK - return memId, offset -} diff --git a/flashring/internal/indicesV2/index.go b/flashring/internal/indicesV2/index.go deleted file mode 100644 index 0b803f56..00000000 --- a/flashring/internal/indicesV2/index.go +++ /dev/null @@ -1,125 +0,0 @@ -package indicesv2 - -import ( - "errors" - "time" - - "github.com/Meesho/BharatMLStack/flashring/internal/maths" -) - -var ErrGettingHeadEntry = errors.New("getting head entry failed") - -type Status int - -const ( - StatusOK Status = iota - StatusNotFound - StatusExpired -) - -type Index struct { - rm map[string]int - rb *RingBuffer - mc *maths.MorrisLogCounter - startAt int64 - hashBits int -} - -func NewIndex(hashBits int, rbInitial, rbMax, deleteAmortizedStep int) *Index { - if ByteOrder == nil { - loadByteOrder() - } - rm := make(map[string]int) - return &Index{ - rm: rm, - rb: NewRingBuffer(rbInitial, rbMax), - mc: maths.New(12), - startAt: time.Now().Unix(), - hashBits: hashBits, - } -} - -func (i *Index) Put(key string, length, ttlInMinutes uint16, memId, offset uint32) { - if _, ok := i.rm[key]; ok { - idx := i.rm[key] - entry, _ := i.rb.Get(idx) - length, delta, lastAccess, freq, _, _ := decode(entry) - idx, _ = i.rb.PutInNextFreeSlot(func(entry *Entry) string { - encode(key, length, delta, lastAccess, freq, memId, offset, entry) - return key - }) - i.rm[key] = idx - return - } - lastAccess := i.generateLastAccess() - freq := uint16(1) - expiryAt := (time.Now().Unix() / 60) + int64(ttlInMinutes) - delta := uint16(expiryAt - (i.startAt / 60)) - idx, _ := i.rb.PutInNextFreeSlot(func(entry *Entry) string { - encode(key, length, delta, lastAccess, freq, memId, offset, entry) - return key - }) - i.rm[key] = idx -} - -func (i *Index) Get(key string) (length, lastAccess, remainingTTL uint16, freq uint64, memId, offset uint32, status Status) { - if idx, ok := i.rm[key]; ok { - entry, _ := i.rb.Get(idx) - length, deltaExptime, lastAccess, freq, memId, offset := decode(entry) - exptime := int(deltaExptime) + int(i.startAt/60) - currentTime := int(time.Now().Unix() / 60) - remainingTTL := exptime - currentTime - if remainingTTL <= 0 { - return 0, 0, 0, 0, 0, 0, StatusExpired - } - lastAccess = i.generateLastAccess() - freq = i.incrFreq(freq) - encodeLastAccessNFreq(lastAccess, freq, entry) - return length, lastAccess, uint16(remainingTTL), i.mc.Value(uint32(freq)), memId, offset, StatusOK - } - return 0, 0, 0, 0, 0, 0, StatusNotFound -} - -func (ix *Index) Delete(count int) (uint32, int) { - for i := 0; i < count; i++ { - deleted, deletedKey, next, _ := ix.rb.Delete() - if deleted == nil { - return 0, -1 - } - delMemId, _ := decodeMemIdOffset(deleted) - delete(ix.rm, deletedKey) - nextMemId, _ := decodeMemIdOffset(next) - if nextMemId == delMemId+1 { - return nextMemId, i + 1 - } else if nextMemId == delMemId && i == count-1 { - return delMemId, i + 1 - } else if nextMemId == delMemId { - continue - } else { - return 0, -1 - } - } - return 0, -1 -} - -func (ki *Index) GetRB() *RingBuffer { - return ki.rb -} - -func (ki *Index) PeekMemIdAtHead() (uint32, error) { - entry, ok := ki.rb.Get(ki.rb.head) - if !ok { - return 0, ErrGettingHeadEntry - } - memId, _ := decodeMemIdOffset(entry) - return memId, nil -} - -func (i *Index) generateLastAccess() uint16 { - return uint16((time.Now().Unix() - i.startAt) / 60) -} - -func (i *Index) incrFreq(freq uint16) uint16 { - newFreq, _ := i.mc.Inc(uint32(freq)) - return uint16(newFreq) -} diff --git a/flashring/internal/indicesV2/index_test.go b/flashring/internal/indicesV2/index_test.go deleted file mode 100644 index 5915691b..00000000 --- a/flashring/internal/indicesV2/index_test.go +++ /dev/null @@ -1,135 +0,0 @@ -package indicesv2 - -import ( - "fmt" - "testing" -) - -func TestIndexAddRbMax(t *testing.T) { - loadByteOrder() - - // Use equal initial and max capacity for the fixed-size ring buffer. - rbMax := 1000_000 - rbInitial := rbMax - hashBits := 16 - idx := NewIndex(hashBits, rbInitial, rbMax, 1) - - // Insert exactly rbMax distinct keys - for i := 0; i < rbMax; i++ { - key := fmt.Sprintf("k%d", i) - length := uint16(100 + i) - ttlMinutes := uint16(120) // ensure no expiry during test - memID := uint32(1000 + i) - offset := uint32(2000 + i) - idx.Put(key, length, ttlMinutes, memID, offset) - } - - // All keys should be present in the reverse map - if got := len(idx.rm); got != rbMax { - t.Fatalf("expected %d keys in index map, got %d", rbMax, got) - } - - // After filling to capacity, next add should require delete (ring wrapped) - if !idx.rb.NextAddNeedsDelete() { - t.Fatalf("expected ring buffer to report NextAddNeedsDelete == true after %d inserts", rbMax) - } - - // Verify we can Get every inserted key and fields match - for i := 0; i < rbMax; i++ { - key := fmt.Sprintf("k%d", i) - expLength := uint16(100 + i) - expMemID := uint32(1000 + i) - expOffset := uint32(2000 + i) - - length, _, _, _, memID, offset, status := idx.Get(key) - if status != StatusOK { - t.Fatalf("Get(%q) status = %v, want %v", key, status, StatusOK) - } - if length != expLength { - t.Fatalf("Get(%q) length = %d, want %d", key, length, expLength) - } - if memID != expMemID { - t.Fatalf("Get(%q) memID = %d, want %d", key, memID, expMemID) - } - if offset != expOffset { - t.Fatalf("Get(%q) offset = %d, want %d", key, offset, expOffset) - } - } -} - -func TestIndexDeleteAndGet(t *testing.T) { - loadByteOrder() - - // Keep this small and fast - rbMax := 99 - rbInitial := rbMax - hashBits := 16 - idx := NewIndex(hashBits, rbInitial, rbMax, 1) - - // Insert exactly rbMax distinct keys in order - for i := 0; i < 33; i++ { - key := fmt.Sprintf("k%d", i) - length := uint16(100 + i) - ttlMinutes := uint16(120) - memID := uint32(1) - offset := uint32(2000 + i) - idx.Put(key, length, ttlMinutes, memID, offset) - } - - for i := 33; i < 66; i++ { - key := fmt.Sprintf("k%d", i) - length := uint16(100 + i) - ttlMinutes := uint16(120) - memID := uint32(2) - offset := uint32(2000 + i) - idx.Put(key, length, ttlMinutes, memID, offset) - } - for i := 66; i < 99; i++ { - key := fmt.Sprintf("k%d", i) - length := uint16(100 + i) - ttlMinutes := uint16(120) - memID := uint32(3) - offset := uint32(2000 + i) - idx.Put(key, length, ttlMinutes, memID, offset) - } - - if len(idx.rm) != rbMax { - t.Fatalf("expected %d keys after fill, got %d", rbMax, len(idx.rm)) - } - - // Ensure buffer is in the full state (next add would need delete) - if !idx.rb.NextAddNeedsDelete() { - t.Fatalf("expected NextAddNeedsDelete() to be true after fill") - } - - for i := 0; i < 99; i++ { - key := fmt.Sprintf("k%d", i) - _, _, _, _, _, _, st := idx.Get(key) - if st != StatusOK { - t.Fatalf("Get(%q) status=%v, want %v", key, st, StatusOK) - } - } - // Delete oldest entries one-by-one and verify via Get - toDelete := 33 - idx.Delete(toDelete) - - if len(idx.rm) != rbMax-toDelete { - t.Fatalf("expected map size %d after deletes, got %d", rbMax-toDelete, len(idx.rm)) - } - - for i := 0; i < toDelete; i++ { - key := fmt.Sprintf("k%d", i) - _, _, _, _, _, _, st := idx.Get(key) - if st != StatusNotFound { - t.Fatalf("Get(%q) status=%v, want %v", key, st, StatusNotFound) - } - } - - for i := toDelete; i < 99; i++ { - key := fmt.Sprintf("k%d", i) - _, _, _, _, _, _, st := idx.Get(key) - if st != StatusOK { - t.Fatalf("Get(%q) status=%v, want %v", key, st, StatusOK) - } - } -} diff --git a/flashring/internal/indicesV2/rb.go b/flashring/internal/indicesV2/rb.go deleted file mode 100644 index 7394e289..00000000 --- a/flashring/internal/indicesV2/rb.go +++ /dev/null @@ -1,95 +0,0 @@ -package indicesv2 - -// Entry represents a 32-byte value. Adjust fields as needed. -type Entry [16]byte - -// RingBuffer is a fixed-size circular queue that wraps around when full. -// It maintains a sliding window of the most recent entries. Add returns an -// absolute index which can be used with Get. -type RingBuffer struct { - buf []Entry - keyTable []string - head int - tail int - size int - nextIndex int - capacity int // Fixed capacity (initial = max) - wrapped bool -} - -// NewRingBuffer creates a ring buffer with the given initial and maximum -// capacity. Since we use a fixed-size buffer, initial and max should be the same. -func NewRingBuffer(initial, max int) *RingBuffer { - if initial <= 0 || initial > max { - panic("invalid capacity") - } - // Use max capacity for fixed-size buffer (initial = max in practice) - capacity := max - return &RingBuffer{ - buf: make([]Entry, capacity), - keyTable: make([]string, capacity), - capacity: capacity, - wrapped: false, - } -} - -// Add inserts e into the buffer and returns its absolute index. When the buffer -// is full it wraps around and overwrites the oldest entry. -func (rb *RingBuffer) Add(e *Entry) int { - // Store the entry at current tail position - rb.buf[rb.nextIndex] = *e - idx := rb.nextIndex - rb.nextIndex = (rb.nextIndex + 1) % rb.capacity - if rb.nextIndex == rb.head { - rb.head = (rb.head + 1) % rb.capacity - } - - return idx -} - -func (rb *RingBuffer) NextAddNeedsDelete() bool { - return rb.nextIndex == rb.head && rb.wrapped -} - -func (rb *RingBuffer) PutInNextFreeSlot(putFunc func(*Entry) string) (int, bool) { - idx := rb.nextIndex - rb.nextIndex = (rb.nextIndex + 1) % rb.capacity - shouldDelete := false - if rb.nextIndex == rb.head { - // rb.head = (rb.head + 1) % rb.capacity - rb.wrapped = true - shouldDelete = true - - } - key := putFunc(&rb.buf[idx]) - rb.keyTable[idx] = key - - return idx, shouldDelete -} - -// Get retrieves an entry by its absolute index. The boolean return is false if -// the index is out of range (either overwritten or not yet added). -func (rb *RingBuffer) Get(index int) (*Entry, bool) { - // Calculate the valid window based on current state - if index > rb.capacity { - return nil, false - } - return &rb.buf[index], true -} - -// Delete removes the oldest entry from the buffer if it is not empty. -// For a fixed-size ring buffer, this only decreases size if not at capacity. -func (rb *RingBuffer) Delete() (*Entry, string, *Entry, string) { - deleted := rb.buf[rb.head] - deletedKey := rb.keyTable[rb.head] - rb.head = (rb.head + 1) % rb.capacity - return &deleted, deletedKey, &rb.buf[rb.head], rb.keyTable[rb.head] -} - -// TailIndex returns the absolute index that will be assigned to the next Add. -func (rb *RingBuffer) TailIndex() int { - return rb.nextIndex -} -func (rb *RingBuffer) ActiveEntries() int { - return (rb.nextIndex - rb.head + rb.capacity) % rb.capacity -} diff --git a/flashring/internal/indicesV2/rb_bench_test.go b/flashring/internal/indicesV2/rb_bench_test.go deleted file mode 100644 index 0baeece0..00000000 --- a/flashring/internal/indicesV2/rb_bench_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package indicesv2 - -import ( - "testing" -) - -// BenchmarkRingBufferPush50M benchmarks pushing 50 million elements to the ring buffer -func BenchmarkRingBufferPush50M(b *testing.B) { - rb := NewRingBuffer(1000, 50_000_000) - - b.ResetTimer() - b.Run("Add", func(b *testing.B) { - for i := 0; i < b.N; i++ { - rb.Add(&Entry{}) - } - }) - b.Run("Get", func(b *testing.B) { - for i := 0; i < b.N; i++ { - rb.Get(i) - } - }) -} diff --git a/flashring/internal/indicesV2/system.go b/flashring/internal/indicesV2/system.go deleted file mode 100644 index a5368576..00000000 --- a/flashring/internal/indicesV2/system.go +++ /dev/null @@ -1,50 +0,0 @@ -package indicesv2 - -import ( - "encoding/binary" - "unsafe" -) - -var ByteOrder *CustomByteOrder - -type CustomByteOrder struct { - binary.ByteOrder -} - -func loadByteOrder() { - buf := [2]byte{} - *(*uint16)(unsafe.Pointer(&buf[0])) = uint16(0xABCD) - - switch buf { - case [2]byte{0xCD, 0xAB}: - ByteOrder = &CustomByteOrder{binary.LittleEndian} - case [2]byte{0xAB, 0xCD}: - ByteOrder = &CustomByteOrder{binary.BigEndian} - default: - panic("Could not determine endianness.") - } -} - -func (c *CustomByteOrder) PutInt64(b []byte, v int64) { - c.PutUint64(b, uint64(v)) -} - -func (c *CustomByteOrder) Int64(b []byte) int64 { - return int64(c.Uint64(b)) -} - -func (c *CustomByteOrder) PutInt32(b []byte, v int32) { - c.PutUint32(b, uint32(v)) -} - -func (c *CustomByteOrder) Int32(b []byte) int32 { - return int32(c.Uint32(b)) -} - -func (c *CustomByteOrder) PutUint32(b []byte, v uint32) { - c.ByteOrder.PutUint32(b, v) -} - -func (c *CustomByteOrder) Uint32(b []byte) uint32 { - return c.ByteOrder.Uint32(b) -} diff --git a/flashring/internal/indicesV3/constant.go b/flashring/internal/indicesV3/constant.go deleted file mode 100644 index 2abcacff..00000000 --- a/flashring/internal/indicesV3/constant.go +++ /dev/null @@ -1,24 +0,0 @@ -package indicesv2 - -const ( - - //[0]uint64 - LENGTH_MASK = (1 << 16) - 1 - DELTA_EXPTIME_MASK = (1 << 16) - 1 - LAST_ACCESS_MASK = (1 << 16) - 1 - FREQ_MASK = (1 << 16) - 1 - PREV_MASK = (1 << 32) - 1 - NEXT_MASK = (1 << 32) - 1 - - //[1]uint64 - MEM_ID_MASK = (1 << 32) - 1 - OFFSET_MASK = (1 << 32) - 1 - - LENGTH_SHIFT = 48 - DELTA_EXPTIME_SHIFT = 32 - LAST_ACCESS_SHIFT = 16 - FREQ_SHIFT = 0 - - MEM_ID_SHIFT = 32 - OFFSET_SHIFT = 0 -) diff --git a/flashring/internal/indicesV3/delete_manager.go b/flashring/internal/indicesV3/delete_manager.go deleted file mode 100644 index 6b218915..00000000 --- a/flashring/internal/indicesV3/delete_manager.go +++ /dev/null @@ -1,76 +0,0 @@ -package indicesv2 - -import ( - "fmt" - - "github.com/Meesho/BharatMLStack/flashring/internal/fs" - "github.com/rs/zerolog/log" -) - -type DeleteManager struct { - memtableData map[uint32]int - toBeDeletedMemId uint32 - keyIndex *Index - wrapFile *fs.WrapAppendFile - deleteInProgress bool - deleteAmortizedStep int - deleteCount int -} - -func NewDeleteManager(keyIndex *Index, wrapFile *fs.WrapAppendFile, deleteAmortizedStep int) *DeleteManager { - return &DeleteManager{ - memtableData: make(map[uint32]int), - toBeDeletedMemId: 0, - keyIndex: keyIndex, - wrapFile: wrapFile, - deleteInProgress: false, - deleteAmortizedStep: deleteAmortizedStep, - } -} - -func (dm *DeleteManager) IncMemtableKeyCount(memId uint32) { - dm.memtableData[memId]++ -} - -func (dm *DeleteManager) ExecuteDeleteIfNeeded() error { - if dm.deleteInProgress { - memtableId, count := dm.keyIndex.Delete(dm.deleteCount) - if count == -1 { - return fmt.Errorf("delete failed") - } - if memtableId != dm.toBeDeletedMemId { - dm.memtableData[dm.toBeDeletedMemId] = dm.memtableData[dm.toBeDeletedMemId] - count - log.Debug().Msgf("memtableId: %d, toBeDeletedMemId: %d", memtableId, dm.toBeDeletedMemId) - if dm.memtableData[dm.toBeDeletedMemId] != 0 { - return fmt.Errorf("memtableData[dm.toBeDeletedMemId] != 0") - } - delete(dm.memtableData, dm.toBeDeletedMemId) - dm.toBeDeletedMemId = memtableId - dm.deleteInProgress = false - dm.deleteCount = 0 - return nil - } else { - dm.memtableData[memtableId] -= count - //log.Debug().Msgf("memtableData[%d] = %d", memtableId, dm.memtableData[memtableId]) - } - return nil - } - - trimNeeded := dm.wrapFile.TrimHeadIfNeeded() - nextAddNeedsDelete := dm.keyIndex.GetRB().NextAddNeedsDelete() - - if trimNeeded || nextAddNeedsDelete { - dm.deleteInProgress = true - dm.deleteCount = int(dm.memtableData[dm.toBeDeletedMemId] / dm.deleteAmortizedStep) - memIdAtHead, err := dm.keyIndex.PeekMemIdAtHead() - if err != nil { - return err - } - if memIdAtHead != dm.toBeDeletedMemId { - return fmt.Errorf("memIdAtHead: %d, toBeDeletedMemId: %d", memIdAtHead, dm.toBeDeletedMemId) - } - dm.wrapFile.TrimHead() - return nil - } - return nil -} diff --git a/flashring/internal/indicesV3/encoder.go b/flashring/internal/indicesV3/encoder.go deleted file mode 100644 index 6db19207..00000000 --- a/flashring/internal/indicesV3/encoder.go +++ /dev/null @@ -1,82 +0,0 @@ -package indicesv2 - -func encode(key string, length, deltaExptime, lastAccess, freq uint16, memId, offset uint32, entry *Entry) { - - d1 := uint64(length&LENGTH_MASK) << LENGTH_SHIFT - d1 |= uint64(deltaExptime&DELTA_EXPTIME_MASK) << DELTA_EXPTIME_SHIFT - d1 |= uint64(lastAccess&LAST_ACCESS_MASK) << LAST_ACCESS_SHIFT - d1 |= uint64(freq&FREQ_MASK) << FREQ_SHIFT - - ByteOrder.PutUint64(entry[:8], d1) - - d2 := uint64(memId&MEM_ID_MASK) << MEM_ID_SHIFT - d2 |= uint64(offset&OFFSET_MASK) << OFFSET_SHIFT - - ByteOrder.PutUint64(entry[8:16], d2) -} - -func encodeHashNextPrev(hhi, hlo uint64, prev, next int32, entry *HashNextPrev) { - entry[0] = hhi - entry[1] = hlo - entry[2] = uint64(uint32(prev))<<32 | uint64(uint32(next)) -} - -func encodeUpdatePrev(prev int32, entry *HashNextPrev) { - next := entry[2] & NEXT_MASK - entry[2] = uint64(uint32(prev))<<32 | next -} - -func encodeUpdateNext(next int32, entry *HashNextPrev) { - prev := (entry[2] >> 32) & PREV_MASK - entry[2] = uint64(uint32(prev))<<32 | uint64(uint32(next)) -} - -func decodeNext(entry *HashNextPrev) int32 { - return int32(uint32(entry[2] & NEXT_MASK)) -} - -func decodePrev(entry *HashNextPrev) int32 { - return int32(uint32(entry[2]>>32) & PREV_MASK) -} - -func decodeHashLo(entry *HashNextPrev) uint64 { - return entry[1] -} - -func decode(entry *Entry) (length, deltaExptime, lastAccess, freq uint16, memId, offset uint32) { - d1 := ByteOrder.Uint64(entry[:8]) - d2 := ByteOrder.Uint64(entry[8:16]) - - length = uint16(d1>>LENGTH_SHIFT) & LENGTH_MASK - deltaExptime = uint16(d1>>DELTA_EXPTIME_SHIFT) & DELTA_EXPTIME_MASK - lastAccess = uint16(d1>>LAST_ACCESS_SHIFT) & LAST_ACCESS_MASK - freq = uint16(d1>>FREQ_SHIFT) & FREQ_MASK - - memId = uint32(d2>>MEM_ID_SHIFT) & MEM_ID_MASK - offset = uint32(d2>>OFFSET_SHIFT) & OFFSET_MASK - - return length, deltaExptime, lastAccess, freq, memId, offset -} - -func decodeLastAccessNFreq(entry *Entry) (lastAccess, freq uint16) { - d1 := ByteOrder.Uint64(entry[:8]) - lastAccess = uint16(d1>>LAST_ACCESS_SHIFT) & LAST_ACCESS_MASK - freq = uint16(d1>>FREQ_SHIFT) & FREQ_MASK - - return lastAccess, freq -} - -func encodeLastAccessNFreq(lastAccess, freq uint16, entry *Entry) { - d1 := ByteOrder.Uint64(entry[:8]) - d1 |= uint64(lastAccess&LAST_ACCESS_MASK) << LAST_ACCESS_SHIFT - d1 |= uint64(freq&FREQ_MASK) << FREQ_SHIFT - - ByteOrder.PutUint64(entry[:8], d1) -} - -func decodeMemIdOffset(entry *Entry) (memId, offset uint32) { - d2 := ByteOrder.Uint64(entry[8:16]) - memId = uint32(d2>>MEM_ID_SHIFT) & MEM_ID_MASK - offset = uint32(d2>>OFFSET_SHIFT) & OFFSET_MASK - return memId, offset -} diff --git a/flashring/internal/indicesV3/index.go b/flashring/internal/indicesV3/index.go deleted file mode 100644 index 29261585..00000000 --- a/flashring/internal/indicesV3/index.go +++ /dev/null @@ -1,167 +0,0 @@ -package indicesv2 - -import ( - "errors" - "sync" - "time" - - "github.com/Meesho/BharatMLStack/flashring/internal/maths" - "github.com/cespare/xxhash/v2" - "github.com/rs/zerolog/log" - "github.com/zeebo/xxh3" -) - -var ErrGettingHeadEntry = errors.New("getting head entry failed") - -type Status int - -const ( - StatusOK Status = iota - StatusNotFound - StatusExpired -) - -type Index struct { - rm sync.Map - rb *RingBuffer - mc *maths.MorrisLogCounter - startAt int64 - hashBits int -} - -func NewIndex(hashBits int, rbInitial, rbMax, deleteAmortizedStep int) *Index { - if ByteOrder == nil { - loadByteOrder() - } - // rm := make(map[uint64]int) - return &Index{ - rm: sync.Map{}, - rb: NewRingBuffer(rbInitial, rbMax), - mc: maths.New(12), - startAt: time.Now().Unix(), - hashBits: hashBits, - } -} - -func (i *Index) Put(key string, length, ttlInMinutes uint16, memId, offset uint32) { - hhi, hlo := hash128(key) - entry, hashNextPrev, idx, _ := i.rb.GetNextFreeSlot() - lastAccess := i.generateLastAccess() - freq := uint16(1) - expiryAt := (time.Now().Unix() / 60) + int64(ttlInMinutes) - delta := uint16(expiryAt - (i.startAt / 60)) - encode(key, length, delta, lastAccess, freq, memId, offset, entry) - - if headIdx, ok := i.rm.Load(hlo); !ok { - encodeHashNextPrev(hhi, hlo, -1, -1, hashNextPrev) - i.rm.Store(hlo, idx) - return - } else { - _, headHashNextPrev, _ := i.rb.Get(int(headIdx.(int))) - encodeUpdatePrev(int32(idx), headHashNextPrev) - encodeHashNextPrev(hhi, hlo, -1, int32(headIdx.(int)), hashNextPrev) - i.rm.Store(hlo, idx) - return - } - -} - -func (i *Index) Get(key string) (length, lastAccess, remainingTTL uint16, freq uint64, memId, offset uint32, status Status) { - hhi, hlo := hash128(key) - if idx, ok := i.rm.Load(hlo); ok { - entry, hashNextPrev, _ := i.rb.Get(int(idx.(int))) - for { - if isHashMatch(hhi, hlo, hashNextPrev) { - length, deltaExptime, lastAccess, freq, memId, offset := decode(entry) - exptime := int(deltaExptime) + int(i.startAt/60) - currentTime := int(time.Now().Unix() / 60) - remainingTTL := exptime - currentTime - if remainingTTL <= 0 { - return 0, 0, 0, 0, 0, 0, StatusExpired - } - lastAccess = i.generateLastAccess() - freq = i.incrFreq(freq) - encodeLastAccessNFreq(lastAccess, freq, entry) - return length, lastAccess, uint16(remainingTTL), i.mc.Value(uint32(freq)), memId, offset, StatusOK - } - if hasNext(hashNextPrev) { - idx = int(decodeNext(hashNextPrev)) - } else { - return 0, 0, 0, 0, 0, 0, StatusNotFound - } - } - - } - return 0, 0, 0, 0, 0, 0, StatusNotFound -} - -func (ix *Index) Delete(count int) (uint32, int) { - for i := 0; i < count; i++ { - deleted, deletedHashNextPrev, deletedIdx, next := ix.rb.Delete() - if deleted == nil { - return 0, -1 - } - delMemId, _ := decodeMemIdOffset(deleted) - deletedHlo := decodeHashLo(deletedHashNextPrev) - mapIdx, ok := ix.rm.Load(deletedHlo) - if ok && mapIdx.(int) == deletedIdx { - ix.rm.Delete(deletedHlo) - } else if ok && hasPrev(deletedHashNextPrev) { - prevIdx := decodePrev(deletedHashNextPrev) - _, hashNextPrev, _ := ix.rb.Get(int(prevIdx)) - encodeUpdateNext(-1, hashNextPrev) - } else { - log.Warn().Msgf("broken link. Entry in RB but cannot be linked to map. deletedIdx: %d", deletedIdx) - } - - nextMemId, _ := decodeMemIdOffset(next) - if nextMemId == delMemId+1 { - return nextMemId, i + 1 - } else if nextMemId == delMemId && i == count-1 { - return delMemId, i + 1 - } else if nextMemId == delMemId { - continue - } else { - return 0, -1 - } - } - return 0, -1 -} - -func (ki *Index) GetRB() *RingBuffer { - return ki.rb -} - -func (ki *Index) PeekMemIdAtHead() (uint32, error) { - entry, _, ok := ki.rb.Get(ki.rb.head) - if !ok { - return 0, ErrGettingHeadEntry - } - memId, _ := decodeMemIdOffset(entry) - return memId, nil -} - -func (i *Index) generateLastAccess() uint16 { - return uint16((time.Now().Unix() - i.startAt) / 60) -} - -func (i *Index) incrFreq(freq uint16) uint16 { - newFreq, _ := i.mc.Inc(uint32(freq)) - return uint16(newFreq) -} - -func hash128(key string) (uint64, uint64) { - return xxhash.Sum64String(key), xxh3.HashString(key) -} - -func isHashMatch(hhi, hlo uint64, entry *HashNextPrev) bool { - return entry[0] == hhi && entry[1] == hlo -} - -func hasNext(entry *HashNextPrev) bool { - return int32(entry[2]&NEXT_MASK) != -1 -} - -func hasPrev(entry *HashNextPrev) bool { - return int32((entry[2]>>32)&PREV_MASK) != -1 -} diff --git a/flashring/internal/indicesV3/index_test.go b/flashring/internal/indicesV3/index_test.go deleted file mode 100644 index 3eecea9d..00000000 --- a/flashring/internal/indicesV3/index_test.go +++ /dev/null @@ -1,224 +0,0 @@ -package indicesv2 - -import ( - "fmt" - "testing" -) - -func TestIndexAddRbMax(t *testing.T) { - loadByteOrder() - - // Use equal initial and max capacity for the fixed-size ring buffer. - rbMax := 1000_000 - rbInitial := rbMax - hashBits := 16 - idx := NewIndex(hashBits, rbInitial, rbMax, 1) - - // Insert exactly rbMax distinct keys - for i := 0; i < rbMax; i++ { - key := fmt.Sprintf("k%d", i) - length := uint16(100 + i) - ttlMinutes := uint16(120) // ensure no expiry during test - memID := uint32(1000 + i) - offset := uint32(2000 + i) - idx.Put(key, length, ttlMinutes, memID, offset) - } - - // All keys should be present in the reverse map - if got := len(idx.rm); got != rbMax { - t.Fatalf("expected %d keys in index map, got %d", rbMax, got) - } - - // After filling to capacity, next add should require delete (ring wrapped) - if !idx.rb.NextAddNeedsDelete() { - t.Fatalf("expected ring buffer to report NextAddNeedsDelete == true after %d inserts", rbMax) - } - - // Verify we can Get every inserted key and fields match - for i := 0; i < rbMax; i++ { - key := fmt.Sprintf("k%d", i) - expLength := uint16(100 + i) - expMemID := uint32(1000 + i) - expOffset := uint32(2000 + i) - - length, _, _, _, memID, offset, status := idx.Get(key) - if status != StatusOK { - t.Fatalf("Get(%q) status = %v, want %v", key, status, StatusOK) - } - if length != expLength { - t.Fatalf("Get(%q) length = %d, want %d", key, length, expLength) - } - if memID != expMemID { - t.Fatalf("Get(%q) memID = %d, want %d", key, memID, expMemID) - } - if offset != expOffset { - t.Fatalf("Get(%q) offset = %d, want %d", key, offset, expOffset) - } - } -} - -func TestIndexDeleteAndGet(t *testing.T) { - loadByteOrder() - - // Keep this small and fast - rbMax := 99 - rbInitial := rbMax - hashBits := 16 - idx := NewIndex(hashBits, rbInitial, rbMax, 1) - - // Insert exactly rbMax distinct keys in order - for i := 0; i < 33; i++ { - key := fmt.Sprintf("k%d", i) - length := uint16(100 + i) - ttlMinutes := uint16(120) - memID := uint32(1) - offset := uint32(2000 + i) - idx.Put(key, length, ttlMinutes, memID, offset) - } - - for i := 33; i < 66; i++ { - key := fmt.Sprintf("k%d", i) - length := uint16(100 + i) - ttlMinutes := uint16(120) - memID := uint32(2) - offset := uint32(2000 + i) - idx.Put(key, length, ttlMinutes, memID, offset) - } - for i := 66; i < 99; i++ { - key := fmt.Sprintf("k%d", i) - length := uint16(100 + i) - ttlMinutes := uint16(120) - memID := uint32(3) - offset := uint32(2000 + i) - idx.Put(key, length, ttlMinutes, memID, offset) - } - - if len(idx.rm) != rbMax { - t.Fatalf("expected %d keys after fill, got %d", rbMax, len(idx.rm)) - } - - // Ensure buffer is in the full state (next add would need delete) - if !idx.rb.NextAddNeedsDelete() { - t.Fatalf("expected NextAddNeedsDelete() to be true after fill") - } - - for i := 0; i < 99; i++ { - key := fmt.Sprintf("k%d", i) - _, _, _, _, _, _, st := idx.Get(key) - if st != StatusOK { - t.Fatalf("Get(%q) status=%v, want %v", key, st, StatusOK) - } - } - // Delete oldest entries one-by-one and verify via Get - toDelete := 33 - idx.Delete(toDelete) - - if len(idx.rm) != rbMax-toDelete { - t.Fatalf("expected map size %d after deletes, got %d", rbMax-toDelete, len(idx.rm)) - } - - for i := 0; i < toDelete; i++ { - key := fmt.Sprintf("k%d", i) - _, _, _, _, _, _, st := idx.Get(key) - if st != StatusNotFound { - t.Fatalf("Get(%q) status=%v, want %v", key, st, StatusNotFound) - } - } - - for i := toDelete; i < 99; i++ { - key := fmt.Sprintf("k%d", i) - _, _, _, _, _, _, st := idx.Get(key) - if st != StatusOK { - t.Fatalf("Get(%q) status=%v, want %v", key, st, StatusOK) - } - } -} - -func TestIndexDeleteAndGetOverlappingHash(t *testing.T) { - loadByteOrder() - - // Keep this small and fast - rbMax := 99 - rbInitial := rbMax - hashBits := 16 - idx := NewIndex(hashBits, rbInitial, rbMax, 1) - - // Insert exactly rbMax distinct keys in order - for i := 0; i < 33; i++ { - key := fmt.Sprintf("k%d", i%33) - length := uint16(100 + i) - ttlMinutes := uint16(120) - memID := uint32(1) - offset := uint32(2000 + i) - idx.Put(key, length, ttlMinutes, memID, offset) - } - - for i := 33; i < 66; i++ { - key := fmt.Sprintf("k%d", i%33) - length := uint16(100 + i) - ttlMinutes := uint16(120) - memID := uint32(2) - offset := uint32(2000 + i) - idx.Put(key, length, ttlMinutes, memID, offset) - } - for i := 66; i < 99; i++ { - key := fmt.Sprintf("k%d", i) - length := uint16(100 + i) - ttlMinutes := uint16(120) - memID := uint32(3) - offset := uint32(2000 + i) - idx.Put(key, length, ttlMinutes, memID, offset) - } - - if len(idx.rm) != 2*rbMax/3 { - t.Fatalf("expected %d keys after fill, got %d", 2*rbMax/3, len(idx.rm)) - } - - // Ensure buffer is in the full state (next add would need delete) - if !idx.rb.NextAddNeedsDelete() { - t.Fatalf("expected NextAddNeedsDelete() to be true after fill") - } - - for i := 0; i < 99; i++ { - key := fmt.Sprintf("k%d", i) - _, _, _, _, _, _, st := idx.Get(key) - if i >= 0 && i < 33 || i >= 66 && i < 99 { - if st != StatusOK { - t.Fatalf("Get(%q) status=%v, want %v", key, st, StatusOK) - } - } else { - if st != StatusNotFound { - t.Fatalf("Get(%q) status=%v, want %v", key, st, StatusNotFound) - } - } - } - // Delete oldest entries one-by-one and verify via Get - toDelete := 33 - idx.Delete(toDelete) - - if len(idx.rm) != rbMax-toDelete { - t.Fatalf("expected map size %d after deletes, got %d", rbMax-toDelete, len(idx.rm)) - } - - for i := 0; i < toDelete; i++ { - key := fmt.Sprintf("k%d", i) - _, _, _, _, _, _, st := idx.Get(key) - if st != StatusOK { - t.Fatalf("Get(%q) status=%v, want %v", key, st, StatusOK) - } - } - - for i := toDelete; i < 99; i++ { - key := fmt.Sprintf("k%d", i) - _, _, _, _, _, _, st := idx.Get(key) - if i >= 0 && i < 33 || i >= 66 && i < 99 { - if st != StatusOK { - t.Fatalf("Get(%q) status=%v, want %v", key, st, StatusOK) - } - } else { - if st != StatusNotFound { - t.Fatalf("Get(%q) status=%v, want %v", key, st, StatusNotFound) - } - } - } -} diff --git a/flashring/internal/indicesV3/rb.go b/flashring/internal/indicesV3/rb.go deleted file mode 100644 index 10850bb3..00000000 --- a/flashring/internal/indicesV3/rb.go +++ /dev/null @@ -1,94 +0,0 @@ -package indicesv2 - -// Entry represents a 32-byte value. Adjust fields as needed. -type Entry [16]byte -type HashNextPrev [3]uint64 - -// RingBuffer is a fixed-size circular queue that wraps around when full. -// It maintains a sliding window of the most recent entries. Add returns an -// absolute index which can be used with Get. -type RingBuffer struct { - buf []Entry - hashTable []HashNextPrev - head int - tail int - size int - nextIndex int - capacity int // Fixed capacity (initial = max) - wrapped bool -} - -// NewRingBuffer creates a ring buffer with the given initial and maximum -// capacity. Since we use a fixed-size buffer, initial and max should be the same. -func NewRingBuffer(initial, max int) *RingBuffer { - if initial <= 0 || initial > max { - panic("invalid capacity") - } - // Use max capacity for fixed-size buffer (initial = max in practice) - capacity := max - return &RingBuffer{ - buf: make([]Entry, capacity), - hashTable: make([]HashNextPrev, capacity), - capacity: capacity, - wrapped: false, - } -} - -// Add inserts e into the buffer and returns its absolute index. When the buffer -// is full it wraps around and overwrites the oldest entry. -func (rb *RingBuffer) Add(e *Entry) int { - // Store the entry at current tail position - rb.buf[rb.nextIndex] = *e - idx := rb.nextIndex - rb.nextIndex = (rb.nextIndex + 1) % rb.capacity - if rb.nextIndex == rb.head { - rb.head = (rb.head + 1) % rb.capacity - } - - return idx -} - -func (rb *RingBuffer) NextAddNeedsDelete() bool { - return rb.nextIndex == rb.head && rb.wrapped -} - -func (rb *RingBuffer) GetNextFreeSlot() (*Entry, *HashNextPrev, int, bool) { - idx := rb.nextIndex - rb.nextIndex = (rb.nextIndex + 1) % rb.capacity - shouldDelete := false - if rb.nextIndex == rb.head { - // rb.head = (rb.head + 1) % rb.capacity - rb.wrapped = true - shouldDelete = true - - } - return &rb.buf[idx], &rb.hashTable[idx], idx, shouldDelete -} - -// Get retrieves an entry by its absolute index. The boolean return is false if -// the index is out of range (either overwritten or not yet added). -func (rb *RingBuffer) Get(index int) (*Entry, *HashNextPrev, bool) { - // Calculate the valid window based on current state - if index > rb.capacity { - return nil, nil, false - } - return &rb.buf[index], &rb.hashTable[index], true -} - -// Delete removes the oldest entry from the buffer if it is not empty. -// For a fixed-size ring buffer, this only decreases size if not at capacity. -func (rb *RingBuffer) Delete() (*Entry, *HashNextPrev, int, *Entry) { - deletedIdx := rb.head - deleted := rb.buf[rb.head] - deletedHashNextPrev := rb.hashTable[rb.head] - rb.head = (rb.head + 1) % rb.capacity - return &deleted, &deletedHashNextPrev, deletedIdx, &rb.buf[rb.head] -} - -// TailIndex returns the absolute index that will be assigned to the next Add. -func (rb *RingBuffer) TailIndex() int { - return rb.nextIndex -} -func (rb *RingBuffer) ActiveEntries() int { - return (rb.nextIndex - rb.head + rb.capacity) % rb.capacity -} diff --git a/flashring/internal/indicesV3/rb_bench_test.go b/flashring/internal/indicesV3/rb_bench_test.go deleted file mode 100644 index 0baeece0..00000000 --- a/flashring/internal/indicesV3/rb_bench_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package indicesv2 - -import ( - "testing" -) - -// BenchmarkRingBufferPush50M benchmarks pushing 50 million elements to the ring buffer -func BenchmarkRingBufferPush50M(b *testing.B) { - rb := NewRingBuffer(1000, 50_000_000) - - b.ResetTimer() - b.Run("Add", func(b *testing.B) { - for i := 0; i < b.N; i++ { - rb.Add(&Entry{}) - } - }) - b.Run("Get", func(b *testing.B) { - for i := 0; i < b.N; i++ { - rb.Get(i) - } - }) -} diff --git a/flashring/internal/indicesV3/system.go b/flashring/internal/indicesV3/system.go deleted file mode 100644 index a5368576..00000000 --- a/flashring/internal/indicesV3/system.go +++ /dev/null @@ -1,50 +0,0 @@ -package indicesv2 - -import ( - "encoding/binary" - "unsafe" -) - -var ByteOrder *CustomByteOrder - -type CustomByteOrder struct { - binary.ByteOrder -} - -func loadByteOrder() { - buf := [2]byte{} - *(*uint16)(unsafe.Pointer(&buf[0])) = uint16(0xABCD) - - switch buf { - case [2]byte{0xCD, 0xAB}: - ByteOrder = &CustomByteOrder{binary.LittleEndian} - case [2]byte{0xAB, 0xCD}: - ByteOrder = &CustomByteOrder{binary.BigEndian} - default: - panic("Could not determine endianness.") - } -} - -func (c *CustomByteOrder) PutInt64(b []byte, v int64) { - c.PutUint64(b, uint64(v)) -} - -func (c *CustomByteOrder) Int64(b []byte) int64 { - return int64(c.Uint64(b)) -} - -func (c *CustomByteOrder) PutInt32(b []byte, v int32) { - c.PutUint32(b, uint32(v)) -} - -func (c *CustomByteOrder) Int32(b []byte) int32 { - return int32(c.Uint32(b)) -} - -func (c *CustomByteOrder) PutUint32(b []byte, v uint32) { - c.ByteOrder.PutUint32(b, v) -} - -func (c *CustomByteOrder) Uint32(b []byte) uint32 { - return c.ByteOrder.Uint32(b) -} diff --git a/flashring/internal/iouring/batch_writer.go b/flashring/internal/iouring/batch_writer.go new file mode 100644 index 00000000..fbf1ec56 --- /dev/null +++ b/flashring/internal/iouring/batch_writer.go @@ -0,0 +1,284 @@ +//go:build linux +// +build linux + +package iouring + +import ( + "fmt" + "sync" + "sync/atomic" + "syscall" + "time" + + "github.com/Meesho/BharatMLStack/flashring/pkg/metrics" +) + +// WriteResult holds the outcome of a single io_uring pwrite. +type WriteResult struct { + N int + Err error +} + +// batchWriteRequest is a pwrite submitted to the batch writer. +type batchWriteRequest struct { + fd int + buf []byte + offset uint64 + done chan WriteResult +} + +// BatchIoUringWriter decouples submission from completion for write operations, +// mirroring the BatchIoUringReader pattern. The mutex is held only during SQE +// preparation + io_uring_enter (~1-5μs), not during CQE drain. +// +// submitLoop: reqCh → collect batch → prep SQEs → io_uring_enter → loop +// completeLoop: waitCqe → dispatch result to caller → loop +type BatchIoUringWriter struct { + ring *IoUring + reqCh chan *batchWriteRequest + maxBatch int + closeCh chan struct{} + wg sync.WaitGroup + + inflight []atomic.Pointer[batchWriteRequest] + freeSlots chan uint32 + pending atomic.Int32 +} + +// NewBatchIoUringWriter creates a decoupled batch writer with its own io_uring +// ring and starts the submit + completion goroutines. +func NewBatchIoUringWriter(cfg BatchIoUringConfig) (*BatchIoUringWriter, error) { + if cfg.RingDepth == 0 { + cfg.RingDepth = 256 + } + ringDepth := int(cfg.RingDepth) + + maxInflight := cfg.MaxInflight + if maxInflight <= 0 || maxInflight > ringDepth { + maxInflight = ringDepth + } + if cfg.MaxBatch <= 0 || cfg.MaxBatch > maxInflight { + cfg.MaxBatch = maxInflight + } + if cfg.QueueSize == 0 { + cfg.QueueSize = 1024 + } + + ring, err := NewIoUring(cfg.RingDepth, 0) + if err != nil { + return nil, fmt.Errorf("batch io_uring writer init: %w", err) + } + + freeSlots := make(chan uint32, maxInflight) + for i := 0; i < maxInflight; i++ { + freeSlots <- uint32(i) + } + + b := &BatchIoUringWriter{ + ring: ring, + reqCh: make(chan *batchWriteRequest, cfg.QueueSize), + maxBatch: cfg.MaxBatch, + closeCh: make(chan struct{}), + inflight: make([]atomic.Pointer[batchWriteRequest], ringDepth), + freeSlots: freeSlots, + } + b.wg.Add(2) + go b.submitLoop() + go b.completeLoop() + return b, nil +} + +// MaxBatchSize returns the ring depth, which is the maximum number of SQEs +// that can be in-flight at once. +func (b *BatchIoUringWriter) MaxBatchSize() int { + return int(b.ring.sqEntries) +} + +// SubmitWriteBatch submits N pwrite operations and waits for all completions. +// Thread-safe. Unlike the old IoUring.SubmitWriteBatch, the ring mutex is NOT +// held during CQE drain — other batches can be submitted concurrently. +func (b *BatchIoUringWriter) SubmitWriteBatch(fd int, bufs [][]byte, offsets []uint64) ([]int, error) { + n := len(bufs) + if n == 0 { + return nil, nil + } + + startTime := time.Now() + + // Submit all write requests into the channel. The submitLoop will + // collect them into batches and prep SQEs. + doneChans := make([]chan WriteResult, n) + for i := 0; i < n; i++ { + req := &batchWriteRequest{ + fd: fd, + buf: bufs[i], + offset: offsets[i], + done: make(chan WriteResult, 1), + } + doneChans[i] = req.done + b.reqCh <- req + } + + // Collect all completions. + results := make([]int, n) + for i := 0; i < n; i++ { + res := <-doneChans[i] + if res.Err != nil { + return results, res.Err + } + results[i] = res.N + metrics.Timing(metrics.KEY_PWRITE_LATENCY, time.Since(startTime), []string{}) + } + + return results, nil +} + +// Close shuts down both goroutines and releases the io_uring ring. +func (b *BatchIoUringWriter) Close() { + close(b.closeCh) + + b.ring.mu.Lock() + sqe := b.ring.getSqe() + if sqe != nil { + sqe.Opcode = iouringOpNop + sqe.UserData = sentinelUserData + b.ring.submit(0) + } + b.ring.mu.Unlock() + + b.wg.Wait() + b.ring.Close() +} + +// submitLoop collects write requests and submits them as io_uring SQEs. +// Mutex held only during SQE prep + io_uring_enter. +func (b *BatchIoUringWriter) submitLoop() { + defer b.wg.Done() + + batch := make([]*batchWriteRequest, 0, b.maxBatch) + slots := make([]uint32, 0, b.maxBatch) + + for { + select { + case req := <-b.reqCh: + batch = append(batch, req) + case <-b.closeCh: + return + } + + // Non-blocking drain. + for len(batch) < b.maxBatch { + select { + case req := <-b.reqCh: + batch = append(batch, req) + default: + goto submit + } + } + + submit: + for i, req := range batch { + select { + case slot := <-b.freeSlots: + slots = append(slots, slot) + b.inflight[slot].Store(req) + case <-b.closeCh: + for j := i; j < len(batch); j++ { + batch[j].done <- WriteResult{Err: fmt.Errorf("io_uring writer: shutting down")} + } + return + } + } + + b.ring.mu.Lock() + + prepared := 0 + for i, slot := range slots { + sqe := b.ring.getSqe() + if sqe == nil { + for j := i; j < len(slots); j++ { + req := b.inflight[slots[j]].Swap(nil) + b.freeSlots <- slots[j] + if req != nil { + req.done <- WriteResult{ + Err: fmt.Errorf("io_uring writer: SQ full, batch=%d depth=%d", len(batch), b.ring.sqEntries), + } + } + } + break + } + prepWrite(sqe, batch[i].fd, batch[i].buf, batch[i].offset) + sqe.UserData = uint64(slot) + prepared++ + } + + if prepared > 0 { + b.pending.Add(int32(prepared)) + _, err := b.ring.submit(0) + if err != nil { + b.pending.Add(-int32(prepared)) + for i := 0; i < prepared; i++ { + req := b.inflight[slots[i]].Swap(nil) + b.freeSlots <- slots[i] + if req != nil { + req.done <- WriteResult{Err: fmt.Errorf("io_uring_enter: %w", err)} + } + } + } + } + + b.ring.mu.Unlock() + + batch = batch[:0] + slots = slots[:0] + } +} + +// completeLoop drains CQEs and dispatches results to callers. +func (b *BatchIoUringWriter) completeLoop() { + defer b.wg.Done() + + for { + cqe, err := b.ring.waitCqe() + if err != nil { + select { + case <-b.closeCh: + if b.pending.Load() <= 0 { + return + } + default: + } + continue + } + + userData := cqe.UserData + res := cqe.Res + b.ring.seenCqe() + + if userData == sentinelUserData { + if b.pending.Load() <= 0 { + return + } + continue + } + + slot := uint32(userData) + b.pending.Add(-1) + + req := b.inflight[slot].Swap(nil) + b.freeSlots <- slot + + if req == nil { + continue + } + + if res < 0 { + req.done <- WriteResult{ + Err: fmt.Errorf("io_uring pwrite errno %d (%s), fd=%d off=%d len=%d", + -res, syscall.Errno(-res), req.fd, req.offset, len(req.buf)), + } + } else { + req.done <- WriteResult{N: int(res)} + } + } +} diff --git a/flashring/internal/iouring/iouring.go b/flashring/internal/iouring/iouring.go new file mode 100644 index 00000000..721fc9c2 --- /dev/null +++ b/flashring/internal/iouring/iouring.go @@ -0,0 +1,583 @@ +//go:build linux +// +build linux + +// Package iouring provides a minimal io_uring implementation using raw syscalls. +// No external dependencies beyond golang.org/x/sys/unix are needed. +// Compatible with Go 1.24+ (no go:linkname usage). +package iouring + +import ( + "fmt" + "sync" + "sync/atomic" + "syscall" + "time" + "unsafe" + + "github.com/Meesho/BharatMLStack/flashring/pkg/metrics" + "golang.org/x/sys/unix" +) + +// ----------------------------------------------------------------------- +// io_uring syscall numbers (amd64) +// ----------------------------------------------------------------------- + +const ( + sysIOUringSetup = 425 + sysIOUringEnter = 426 + sysIOUringRegister = 427 +) + +// ----------------------------------------------------------------------- +// io_uring constants +// ----------------------------------------------------------------------- + +const ( + // Setup flags + iouringSetupSQPoll = 1 << 1 + + // Enter flags + iouringEnterGetEvents = 1 << 0 + iouringEnterSQWakeup = 1 << 1 + + // SQ flags (read from kernel-shared memory) + iouringSQNeedWakeup = 1 << 0 + + // Opcodes + iouringOpNop = 0 + iouringOpRead = 22 + iouringOpWrite = 23 + + // offsets for mmap + iouringOffSQRing = 0 + iouringOffCQRing = 0x8000000 + iouringOffSQEs = 0x10000000 +) + +// ----------------------------------------------------------------------- +// io_uring kernel structures (must match kernel ABI exactly) +// ----------------------------------------------------------------------- + +// ioUringSqe is the 64-byte submission queue entry. +type ioUringSqe struct { + Opcode uint8 + Flags uint8 + IoPrio uint16 + Fd int32 + Off uint64 // union: off / addr2 + Addr uint64 // union: addr / splice_off_in + Len uint32 + OpFlags uint32 // union: rw_flags, etc. + UserData uint64 + BufIndex uint16 // union: buf_index / buf_group + _ uint16 // personality + _ int32 // splice_fd_in / file_index + _ uint64 // addr3 + _ uint64 // __pad2[0] +} + +// ioUringCqe is the 16-byte completion queue entry. +type ioUringCqe struct { + UserData uint64 + Res int32 + Flags uint32 +} + +// ioUringParams is passed to io_uring_setup. +type ioUringParams struct { + SqEntries uint32 + CqEntries uint32 + Flags uint32 + SqThreadCPU uint32 + SqThreadIdle uint32 + Features uint32 + WqFd uint32 + Resv [3]uint32 + SqOff ioUringSqringOffsets + CqOff ioUringCqringOffsets +} + +type ioUringSqringOffsets struct { + Head uint32 + Tail uint32 + RingMask uint32 + RingEntries uint32 + Flags uint32 + Dropped uint32 + Array uint32 + Resv1 uint32 + Resv2 uint64 +} + +type ioUringCqringOffsets struct { + Head uint32 + Tail uint32 + RingMask uint32 + RingEntries uint32 + Overflow uint32 + Cqes uint32 + Flags uint32 + Resv1 uint32 + Resv2 uint64 +} + +// ----------------------------------------------------------------------- +// IoUring is the main ring handle +// ----------------------------------------------------------------------- + +// IoUring wraps a single io_uring instance with SQ/CQ ring mappings. +type IoUring struct { + fd int + + // SQ ring mapped memory + sqRingPtr []byte + sqMask uint32 + sqEntries uint32 + sqHead *uint32 // kernel-updated + sqTail *uint32 // user-updated + sqFlags *uint32 // kernel-updated (NEED_WAKEUP etc.) + sqArray unsafe.Pointer + sqeTail uint32 // local tracking of next SQE slot + sqeHead uint32 // local tracking of submitted SQEs + sqesMmap []byte + sqesBase unsafe.Pointer // base pointer to SQE array + sqRingSz int + cqRingSz int + sqesSz int + singleMmap bool + + // CQ ring mapped memory + cqRingPtr []byte + cqMask uint32 + cqEntries uint32 + cqHead *uint32 // user-updated + cqTail *uint32 // kernel-updated + cqesBase unsafe.Pointer + + // Setup flags + flags uint32 + + // Mutex for concurrent SQE submission from multiple goroutines + mu sync.Mutex + + // Diagnostic counter -- limits debug output to first N failures + debugCount int +} + +// NewIoUring creates a new io_uring instance with the given queue depth. +// flags can be 0 for normal mode. +func NewIoUring(entries uint32, flags uint32) (*IoUring, error) { + var params ioUringParams + params.Flags = flags + if flags&iouringSetupSQPoll != 0 { + params.SqThreadIdle = 2000 // kernel poll thread sleeps after 2s idle + } + + fd, _, errno := syscall.Syscall(sysIOUringSetup, uintptr(entries), uintptr(unsafe.Pointer(¶ms)), 0) + if errno != 0 { + return nil, fmt.Errorf("io_uring_setup failed: %w", errno) + } + + ring := &IoUring{ + fd: int(fd), + flags: params.Flags, + } + + if err := ring.mapRings(¶ms); err != nil { + syscall.Close(ring.fd) + return nil, err + } + + return ring, nil +} + +func (r *IoUring) mapRings(p *ioUringParams) error { + sqOff := &p.SqOff + cqOff := &p.CqOff + + // Calculate SQ ring size + r.sqRingSz = int(sqOff.Array + p.SqEntries*4) // Array + entries*sizeof(uint32) + + // Calculate CQ ring size + r.cqRingSz = int(cqOff.Cqes + p.CqEntries*uint32(unsafe.Sizeof(ioUringCqe{}))) + + // Check if kernel supports single mmap for both rings + r.singleMmap = (p.Features & 1) != 0 // IORING_FEAT_SINGLE_MMAP = 1 + if r.singleMmap { + if r.cqRingSz > r.sqRingSz { + r.sqRingSz = r.cqRingSz + } + } + + // Map SQ ring + var err error + r.sqRingPtr, err = unix.Mmap(r.fd, iouringOffSQRing, r.sqRingSz, + unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED|unix.MAP_POPULATE) + if err != nil { + return fmt.Errorf("mmap SQ ring: %w", err) + } + + // Map CQ ring (same or separate mapping) + if r.singleMmap { + r.cqRingPtr = r.sqRingPtr + } else { + r.cqRingPtr, err = unix.Mmap(r.fd, iouringOffCQRing, r.cqRingSz, + unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED|unix.MAP_POPULATE) + if err != nil { + unix.Munmap(r.sqRingPtr) + return fmt.Errorf("mmap CQ ring: %w", err) + } + } + + // Map SQE array + r.sqesSz = int(p.SqEntries) * int(unsafe.Sizeof(ioUringSqe{})) + r.sqesMmap, err = unix.Mmap(r.fd, iouringOffSQEs, r.sqesSz, + unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED|unix.MAP_POPULATE) + if err != nil { + unix.Munmap(r.sqRingPtr) + if !r.singleMmap { + unix.Munmap(r.cqRingPtr) + } + return fmt.Errorf("mmap SQEs: %w", err) + } + r.sqesBase = unsafe.Pointer(&r.sqesMmap[0]) + + // Set up SQ ring pointers + sqBase := unsafe.Pointer(&r.sqRingPtr[0]) + r.sqHead = (*uint32)(unsafe.Add(sqBase, sqOff.Head)) + r.sqTail = (*uint32)(unsafe.Add(sqBase, sqOff.Tail)) + r.sqFlags = (*uint32)(unsafe.Add(sqBase, sqOff.Flags)) + r.sqMask = *(*uint32)(unsafe.Add(sqBase, sqOff.RingMask)) + r.sqEntries = *(*uint32)(unsafe.Add(sqBase, sqOff.RingEntries)) + r.sqArray = unsafe.Add(sqBase, sqOff.Array) + + // Set up CQ ring pointers + cqBase := unsafe.Pointer(&r.cqRingPtr[0]) + r.cqHead = (*uint32)(unsafe.Add(cqBase, cqOff.Head)) + r.cqTail = (*uint32)(unsafe.Add(cqBase, cqOff.Tail)) + r.cqMask = *(*uint32)(unsafe.Add(cqBase, cqOff.RingMask)) + r.cqEntries = *(*uint32)(unsafe.Add(cqBase, cqOff.RingEntries)) + r.cqesBase = unsafe.Add(cqBase, cqOff.Cqes) + + return nil +} + +// Close releases all resources associated with the ring. +func (r *IoUring) Close() { + unix.Munmap(r.sqesMmap) + unix.Munmap(r.sqRingPtr) + if !r.singleMmap { + unix.Munmap(r.cqRingPtr) + } + syscall.Close(r.fd) +} + +// ----------------------------------------------------------------------- +// SQE helpers +// ----------------------------------------------------------------------- + +func (r *IoUring) getSqeAt(idx uint32) *ioUringSqe { + return (*ioUringSqe)(unsafe.Add(r.sqesBase, uintptr(idx)*unsafe.Sizeof(ioUringSqe{}))) +} + +func (r *IoUring) getCqeAt(idx uint32) *ioUringCqe { + return (*ioUringCqe)(unsafe.Add(r.cqesBase, uintptr(idx)*unsafe.Sizeof(ioUringCqe{}))) +} + +func (r *IoUring) sqArrayAt(idx uint32) *uint32 { + return (*uint32)(unsafe.Add(r.sqArray, uintptr(idx)*4)) +} + +// getSqe returns the next available SQE, or nil if the SQ is full. +func (r *IoUring) getSqe() *ioUringSqe { + head := atomic.LoadUint32(r.sqHead) + next := r.sqeTail + 1 + if next-head > r.sqEntries { + return nil // SQ full + } + sqe := r.getSqeAt(r.sqeTail & r.sqMask) + r.sqeTail++ + // Zero out the SQE + *sqe = ioUringSqe{} + return sqe +} + +// flushSq flushes locally queued SQEs into the kernel-visible SQ ring. +func (r *IoUring) flushSq() uint32 { + tail := *r.sqTail + toSubmit := r.sqeTail - r.sqeHead + if toSubmit == 0 { + return tail - atomic.LoadUint32(r.sqHead) + } + for ; toSubmit > 0; toSubmit-- { + *r.sqArrayAt(tail & r.sqMask) = r.sqeHead & r.sqMask + tail++ + r.sqeHead++ + } + atomic.StoreUint32(r.sqTail, tail) + return tail - atomic.LoadUint32(r.sqHead) +} + +// ----------------------------------------------------------------------- +// Submission and completion +// ----------------------------------------------------------------------- + +func ioUringEnter(fd int, toSubmit, minComplete, flags uint32) (int, error) { + ret, _, errno := syscall.Syscall6(sysIOUringEnter, + uintptr(fd), uintptr(toSubmit), uintptr(minComplete), uintptr(flags), 0, 0) + if errno != 0 { + return int(ret), errno + } + return int(ret), nil +} + +// submit flushes SQEs and calls io_uring_enter if needed. +// Retries automatically on EINTR (signal interruption). +func (r *IoUring) submit(waitNr uint32) (int, error) { + submitted := r.flushSq() + var flags uint32 = 0 + + // If not using SQPOLL, we always need to enter + if r.flags&iouringSetupSQPoll == 0 { + if waitNr > 0 { + flags |= iouringEnterGetEvents + } + for { + ret, err := ioUringEnter(r.fd, submitted, waitNr, flags) + if err == syscall.EINTR { + continue + } + return ret, err + } + } + + // SQPOLL: only enter if kernel thread needs wakeup + if atomic.LoadUint32(r.sqFlags)&iouringSQNeedWakeup != 0 { + flags |= iouringEnterSQWakeup + } + if waitNr > 0 { + flags |= iouringEnterGetEvents + } + if flags != 0 { + for { + ret, err := ioUringEnter(r.fd, submitted, waitNr, flags) + if err == syscall.EINTR { + continue + } + return ret, err + } + } + return int(submitted), nil +} + +// waitCqe waits for at least one CQE to be available and returns it. +// The caller MUST call SeenCqe after processing. +func (r *IoUring) waitCqe() (*ioUringCqe, error) { + for { + head := atomic.LoadUint32(r.cqHead) + tail := atomic.LoadUint32(r.cqTail) + if head != tail { + cqe := r.getCqeAt(head & r.cqMask) + return cqe, nil + } + // No CQE available, ask the kernel + _, err := ioUringEnter(r.fd, 0, 1, iouringEnterGetEvents) + if err != nil { + if err == syscall.EINTR { + continue // signal interrupted the syscall; retry + } + return nil, err + } + } +} + +// seenCqe advances the CQ head by 1, releasing the CQE slot. +func (r *IoUring) seenCqe() { + atomic.StoreUint32(r.cqHead, atomic.LoadUint32(r.cqHead)+1) +} + +// ----------------------------------------------------------------------- +// PrepRead / PrepWrite helpers +// ----------------------------------------------------------------------- + +func prepRead(sqe *ioUringSqe, fd int, buf []byte, offset uint64) { + if len(buf) == 0 { + sqe.Opcode = iouringOpNop + return + } + sqe.Opcode = iouringOpRead + sqe.Fd = int32(fd) + sqe.Addr = uint64(uintptr(unsafe.Pointer(&buf[0]))) + sqe.Len = uint32(len(buf)) + sqe.Off = offset +} + +func prepWrite(sqe *ioUringSqe, fd int, buf []byte, offset uint64) { + if len(buf) == 0 { + sqe.Opcode = iouringOpNop + return + } + sqe.Opcode = iouringOpWrite + sqe.Fd = int32(fd) + sqe.Addr = uint64(uintptr(unsafe.Pointer(&buf[0]))) + sqe.Len = uint32(len(buf)) + sqe.Off = offset +} + +// ----------------------------------------------------------------------- +// High-level thread-safe API +// ----------------------------------------------------------------------- + +// SubmitRead submits a pread and waits for completion. Thread-safe. +// Returns bytes read or an error. +func (r *IoUring) SubmitRead(fd int, buf []byte, offset uint64) (int, error) { + if len(buf) == 0 { + return 0, nil + } + + r.mu.Lock() + + sqe := r.getSqe() + if sqe == nil { + r.mu.Unlock() + return 0, fmt.Errorf("io_uring: SQ full, no SQE available") + } + prepRead(sqe, fd, buf, offset) + // Tag the SQE so we can verify the CQE belongs to this request + sqe.UserData = offset + + submitted, err := r.submit(1) + if err != nil { + r.mu.Unlock() + return 0, fmt.Errorf("io_uring_enter failed: %w", err) + } + + cqe, err := r.waitCqe() + if err != nil { + r.mu.Unlock() + return 0, fmt.Errorf("io_uring wait cqe: %w", err) + } + + res := cqe.Res + userData := cqe.UserData + cqeFlags := cqe.Flags + r.seenCqe() + r.mu.Unlock() + + if res < 0 { + return 0, fmt.Errorf("io_uring pread errno %d (%s), fd=%d off=%d len=%d submitted=%d ud=%d", + -res, syscall.Errno(-res), fd, offset, len(buf), submitted, userData) + } + + // Diagnostic: if io_uring returned 0 (EOF) or short read, compare with syscall.Pread + if r.debugCount < 20 && int(res) != len(buf) { + r.debugCount++ + pn, perr := syscall.Pread(fd, buf, int64(offset)) + // Also stat the fd to check file size + var stat syscall.Stat_t + fstatErr := syscall.Fstat(fd, &stat) + var fsize int64 + if fstatErr == nil { + fsize = stat.Size + } + fmt.Printf("[io_uring diag] fd=%d off=%d len=%d uring_res=%d uring_ud=%d uring_flags=%d "+ + "submitted=%d pread_n=%d pread_err=%v filesize=%d fstat_err=%v sqeHead=%d sqeTail=%d\n", + fd, offset, len(buf), res, userData, cqeFlags, + submitted, pn, perr, fsize, fstatErr, r.sqeHead, r.sqeTail) + } + + return int(res), nil +} + +// SubmitWriteBatch submits N pwrite operations in a single io_uring_enter call +// and waits for all completions. Thread-safe. +// Returns per-chunk bytes written. On error, partial results may be returned. +func (r *IoUring) SubmitWriteBatch(fd int, bufs [][]byte, offsets []uint64) ([]int, error) { + n := len(bufs) + if n == 0 { + return nil, nil + } + + r.mu.Lock() + defer r.mu.Unlock() + + // Prepare all SQEs + for i := 0; i < n; i++ { + sqe := r.getSqe() + if sqe == nil { + return nil, fmt.Errorf("io_uring: SQ full, need %d slots but ring has %d", n, r.sqEntries) + } + prepWrite(sqe, fd, bufs[i], offsets[i]) + sqe.UserData = uint64(i) + } + + // Submit all at once; kernel waits for all completions + _, err := r.submit(uint32(n)) + if err != nil { + return nil, fmt.Errorf("io_uring_enter: %w", err) + } + + var startTime = time.Now() + + // Drain all CQEs (order may differ from submission) + results := make([]int, n) + for i := 0; i < n; i++ { + cqe, err := r.waitCqe() + if err != nil { + return results, fmt.Errorf("io_uring waitCqe: %w", err) + } + idx := int(cqe.UserData) + res := cqe.Res + r.seenCqe() + + if res < 0 { + return results, fmt.Errorf("io_uring pwrite errno %d (%s), fd=%d off=%d len=%d", + -res, syscall.Errno(-res), fd, offsets[idx], len(bufs[idx])) + } + if idx >= 0 && idx < n { + results[idx] = int(res) + } + + metrics.Timing(metrics.KEY_PWRITE_LATENCY, time.Since(startTime), []string{}) + } + + return results, nil +} + +// SubmitWrite submits a pwrite and waits for completion. Thread-safe. +// Returns bytes written or an error. +func (r *IoUring) SubmitWrite(fd int, buf []byte, offset uint64) (int, error) { + if len(buf) == 0 { + return 0, nil + } + + r.mu.Lock() + + sqe := r.getSqe() + if sqe == nil { + r.mu.Unlock() + return 0, fmt.Errorf("io_uring: SQ full, no SQE available") + } + prepWrite(sqe, fd, buf, offset) + + _, err := r.submit(1) + if err != nil { + r.mu.Unlock() + return 0, fmt.Errorf("io_uring_enter failed: %w", err) + } + + cqe, err := r.waitCqe() + if err != nil { + r.mu.Unlock() + return 0, fmt.Errorf("io_uring wait cqe: %w", err) + } + + res := cqe.Res + r.seenCqe() + r.mu.Unlock() + + if res < 0 { + return 0, fmt.Errorf("io_uring pwrite failed: errno %d (%s)", -res, syscall.Errno(-res)) + } + return int(res), nil +} diff --git a/flashring/internal/iouring/iouring_reader.go b/flashring/internal/iouring/iouring_reader.go new file mode 100644 index 00000000..06594cde --- /dev/null +++ b/flashring/internal/iouring/iouring_reader.go @@ -0,0 +1,384 @@ +//go:build linux +// +build linux + +package iouring + +import ( + "fmt" + "sync" + "sync/atomic" + "syscall" + "time" + + "github.com/Meesho/BharatMLStack/flashring/pkg/metrics" +) + +// ReadResult holds the outcome of a single io_uring pread. +type ReadResult struct { + N int + Err error +} + +// batchReadRequest is a pread submitted to the batch reader. +type batchReadRequest struct { + fd int + buf []byte + offset uint64 + done chan ReadResult +} + +var batchReqPool = sync.Pool{ + New: func() interface{} { + return &batchReadRequest{ + done: make(chan ReadResult, 1), + } + }, +} + +// sentinelUserData is stored in the NOP SQE submitted during Close to unblock +// the completion goroutine. +const sentinelUserData = ^uint64(0) + +// BatchIoUringReader collects pread requests and submits them as io_uring +// batches. Submission and completion run in separate goroutines so the submit +// path is never blocked by CQE draining: +// +// submitLoop: reqCh → collect batch → prep SQEs → io_uring_enter → loop +// completeLoop: waitCqe → dispatch result to caller → loop +// +// This eliminates the head-of-batch queueing delay where new requests had to +// wait for the entire previous batch's CQE drain before being submitted. +type BatchIoUringReader struct { + ring *IoUring + reqCh chan *batchReadRequest + maxBatch int + closeCh chan struct{} + wg sync.WaitGroup + + // In-flight tracking: each SQE gets a slot index as its UserData. + // The submit goroutine stores the request; the completion goroutine + // reads it back when the CQE arrives. + inflight []atomic.Pointer[batchReadRequest] + freeSlots chan uint32 // pool of available slot indices + pending atomic.Int32 // number of SQEs currently in-flight +} + +// BatchIoUringConfig configures the batch reader. +type BatchIoUringConfig struct { + RingDepth uint32 // io_uring SQ/CQ size (default 256) + MaxBatch int // max requests per batch (auto-capped to MaxInflight) + MaxInflight int // max SQEs in-flight across all batches; controls NVMe queue depth (default RingDepth) + Window time.Duration // unused in decoupled mode; kept for API compatibility + QueueSize int // channel buffer size (default 1024) + SQPoll bool // use IORING_SETUP_SQPOLL; kernel polls SQ, eliminating submit syscalls under load +} + +// NewBatchIoUringReader creates a batch reader with its own io_uring ring +// and starts the submit + completion goroutines. +func NewBatchIoUringReader(cfg BatchIoUringConfig) (*BatchIoUringReader, error) { + if cfg.RingDepth == 0 { + cfg.RingDepth = 256 + } + ringDepth := int(cfg.RingDepth) + + maxInflight := cfg.MaxInflight + if maxInflight <= 0 || maxInflight > ringDepth { + maxInflight = ringDepth + } + if cfg.MaxBatch <= 0 || cfg.MaxBatch > maxInflight { + cfg.MaxBatch = maxInflight + } + if cfg.QueueSize == 0 { + cfg.QueueSize = 1024 + } + + var flags uint32 + if cfg.SQPoll { + flags = iouringSetupSQPoll + } + + ring, err := NewIoUring(cfg.RingDepth, flags) + if err != nil && cfg.SQPoll { + // SQPOLL may fail without CAP_SYS_NICE on kernels < 5.13; fall back. + ring, err = NewIoUring(cfg.RingDepth, 0) + } + if err != nil { + return nil, fmt.Errorf("batch io_uring init: %w", err) + } + + freeSlots := make(chan uint32, maxInflight) + for i := 0; i < maxInflight; i++ { + freeSlots <- uint32(i) + } + + b := &BatchIoUringReader{ + ring: ring, + reqCh: make(chan *batchReadRequest, cfg.QueueSize), + maxBatch: cfg.MaxBatch, + closeCh: make(chan struct{}), + inflight: make([]atomic.Pointer[batchReadRequest], ringDepth), + freeSlots: freeSlots, + } + b.wg.Add(2) + go b.submitLoop() + go b.completeLoop() + return b, nil +} + +// Submit sends a pread request into the batch channel and blocks until the +// io_uring completion is received. Thread-safe; called from many goroutines. +func (b *BatchIoUringReader) Submit(fd int, buf []byte, offset uint64) (int, error) { + if len(buf) == 0 { + return 0, nil + } + + var startTime = time.Now() + + req := batchReqPool.Get().(*batchReadRequest) + req.fd = fd + req.buf = buf + req.offset = offset + + b.reqCh <- req + + result := <-req.done + n, err := result.N, result.Err + metrics.Timing(metrics.KEY_PREAD_LATENCY, time.Since(startTime), []string{}) + metrics.Incr(metrics.KEY_PREAD_COUNT, []string{}) + // Reset and return to pool + req.fd = 0 + req.buf = nil + req.offset = 0 + batchReqPool.Put(req) + + return n, err +} + +// SubmitAsync enqueues a pread request and returns immediately with a channel +// that will receive the result when the io_uring completion arrives. +// Unlike Submit, it does not block the caller. Thread-safe. +// The caller must read exactly once from the returned channel. +func (b *BatchIoUringReader) SubmitAsync(fd int, buf []byte, offset uint64) <-chan ReadResult { + if len(buf) == 0 { + ch := make(chan ReadResult, 1) + ch <- ReadResult{} + return ch + } + req := &batchReadRequest{ + fd: fd, + buf: buf, + offset: offset, + done: make(chan ReadResult, 1), + } + b.reqCh <- req + return req.done +} + +// Close shuts down both goroutines and releases the io_uring ring. +func (b *BatchIoUringReader) Close() { + close(b.closeCh) + + // Submit a NOP with sentinel UserData to unblock the completion + // goroutine if it is blocked in waitCqe with no pending I/O. + b.ring.mu.Lock() + sqe := b.ring.getSqe() + if sqe != nil { + sqe.Opcode = iouringOpNop + sqe.UserData = sentinelUserData + b.ring.submit(0) + } + b.ring.mu.Unlock() + + b.wg.Wait() + b.ring.Close() +} + +// submitLoop collects requests from reqCh and submits them as io_uring SQEs. +// It never waits for completions — that happens in completeLoop. The ring +// mutex is held only during SQE preparation + io_uring_enter (~1-5μs). +func (b *BatchIoUringReader) submitLoop() { + defer b.wg.Done() + + batch := make([]*batchReadRequest, 0, b.maxBatch) + slots := make([]uint32, 0, b.maxBatch) + + for { + // Block until the first request arrives. + select { + case req := <-b.reqCh: + batch = append(batch, req) + case <-b.closeCh: + return + } + + // Non-blocking drain of whatever else is already queued. + for len(batch) < b.maxBatch { + select { + case req := <-b.reqCh: + batch = append(batch, req) + default: + goto submit + } + } + + submit: + // Acquire a free slot for each request. Under normal load (~30 + // in-flight out of 256 slots) this never blocks. + for i, req := range batch { + select { + case slot := <-b.freeSlots: + slots = append(slots, slot) + b.inflight[slot].Store(req) + case <-b.closeCh: + for j := i; j < len(batch); j++ { + batch[j].done <- ReadResult{Err: fmt.Errorf("io_uring: shutting down")} + } + return + } + } + + metrics.Timing(metrics.KEY_IOURING_SIZE, time.Duration(len(batch))*time.Millisecond, []string{}) + + b.ring.mu.Lock() + + prepared := 0 + for i, slot := range slots { + sqe := b.ring.getSqe() + if sqe == nil { + for j := i; j < len(slots); j++ { + req := b.inflight[slots[j]].Swap(nil) + b.freeSlots <- slots[j] + if req != nil { + req.done <- ReadResult{ + Err: fmt.Errorf("io_uring: SQ full, batch=%d depth=%d", len(batch), b.ring.sqEntries), + } + } + } + break + } + prepRead(sqe, batch[i].fd, batch[i].buf, batch[i].offset) + sqe.UserData = uint64(slot) + prepared++ + } + + if prepared > 0 { + b.pending.Add(int32(prepared)) + _, err := b.ring.submit(0) + if err != nil { + b.pending.Add(-int32(prepared)) + for i := 0; i < prepared; i++ { + req := b.inflight[slots[i]].Swap(nil) + b.freeSlots <- slots[i] + if req != nil { + req.done <- ReadResult{Err: fmt.Errorf("io_uring_enter: %w", err)} + } + } + } + } + + b.ring.mu.Unlock() + + batch = batch[:0] + slots = slots[:0] + } +} + +// completeLoop continuously drains CQEs and dispatches results to callers. +// It runs independently of submitLoop — the ring's SQ and CQ are separate +// data structures, so no mutex is needed for CQ access (single consumer). +func (b *BatchIoUringReader) completeLoop() { + defer b.wg.Done() + + for { + cqe, err := b.ring.waitCqe() + if err != nil { + select { + case <-b.closeCh: + if b.pending.Load() <= 0 { + return + } + default: + } + continue + } + + userData := cqe.UserData + res := cqe.Res + b.ring.seenCqe() + + // Shutdown NOP — exit once all real I/O has been drained. + if userData == sentinelUserData { + if b.pending.Load() <= 0 { + return + } + continue + } + + slot := uint32(userData) + b.pending.Add(-1) + + req := b.inflight[slot].Swap(nil) + b.freeSlots <- slot + + if req == nil { + continue + } + + if res < 0 { + req.done <- ReadResult{ + Err: fmt.Errorf("io_uring pread errno %d (%s), fd=%d off=%d len=%d", + -res, syscall.Errno(-res), req.fd, req.offset, len(req.buf)), + } + } else { + req.done <- ReadResult{N: int(res)} + } + } +} + +// ParallelBatchIoUringReader distributes pread requests across N independent +// BatchIoUringReader instances (each with its own io_uring ring and goroutines) +// using round-robin. +type ParallelBatchIoUringReader struct { + readers []*BatchIoUringReader + next atomic.Uint64 +} + +// NewParallelBatchIoUringReader creates numRings independent batch readers. +// Each ring gets its own io_uring instance and background goroutines. +func NewParallelBatchIoUringReader(cfg BatchIoUringConfig, numRings int) (*ParallelBatchIoUringReader, error) { + if numRings <= 0 { + numRings = 1 + } + readers := make([]*BatchIoUringReader, numRings) + for i := 0; i < numRings; i++ { + r, err := NewBatchIoUringReader(cfg) + if err != nil { + for j := 0; j < i; j++ { + readers[j].Close() + } + return nil, fmt.Errorf("parallel batch reader ring %d: %w", i, err) + } + readers[i] = r + } + return &ParallelBatchIoUringReader{readers: readers}, nil +} + +// Submit routes the pread to the next ring via round-robin. Thread-safe. +func (p *ParallelBatchIoUringReader) Submit(fd int, buf []byte, offset uint64) (int, error) { + idx := p.next.Add(1) % uint64(len(p.readers)) + return p.readers[idx].Submit(fd, buf, offset) +} + +// SubmitAsync routes the pread to the next ring via round-robin and returns +// immediately with a channel for the result. Thread-safe. +func (p *ParallelBatchIoUringReader) SubmitAsync(fd int, buf []byte, offset uint64) <-chan ReadResult { + idx := p.next.Add(1) % uint64(len(p.readers)) + return p.readers[idx].SubmitAsync(fd, buf, offset) +} + +// Close shuts down all underlying batch readers. +func (p *ParallelBatchIoUringReader) Close() { + for _, r := range p.readers { + r.Close() + } +} diff --git a/flashring/internal/iouring/iouring_test.go b/flashring/internal/iouring/iouring_test.go new file mode 100644 index 00000000..403f9d1e --- /dev/null +++ b/flashring/internal/iouring/iouring_test.go @@ -0,0 +1,103 @@ +//go:build linux +// +build linux + +package iouring + +import ( + "os" + "syscall" + "testing" + "unsafe" +) + +func TestIoUringBasicRead(t *testing.T) { + // 1. Create a temp file with known data + f, err := os.CreateTemp("", "iouring_test_*") + if err != nil { + t.Fatal(err) + } + defer os.Remove(f.Name()) + + data := make([]byte, 4096) + for i := range data { + data[i] = byte(i % 251) // non-zero pattern + } + if _, err := f.Write(data); err != nil { + t.Fatal(err) + } + if err := f.Sync(); err != nil { + t.Fatal(err) + } + f.Close() + + // 2. Open with O_DIRECT | O_RDONLY + fd, err := syscall.Open(f.Name(), syscall.O_RDONLY|syscall.O_DIRECT, 0) + if err != nil { + t.Fatalf("open O_DIRECT: %v", err) + } + defer syscall.Close(fd) + + // 3. Create io_uring ring + ring, err := NewIoUring(32, 0) + if err != nil { + t.Fatalf("NewIoUring: %v", err) + } + defer ring.Close() + + // 4. Allocate aligned buffer + buf := alignedBlock(4096, 4096) + + // 5. Submit read via io_uring + n, err := ring.SubmitRead(fd, buf, 0) + if err != nil { + t.Fatalf("SubmitRead: %v", err) + } + if n != 4096 { + t.Fatalf("SubmitRead returned %d bytes, expected 4096", n) + } + + // 6. Verify data + for i := 0; i < 4096; i++ { + if buf[i] != data[i] { + t.Fatalf("data mismatch at byte %d: got %d, want %d", i, buf[i], data[i]) + } + } + t.Logf("io_uring read of 4096 bytes succeeded and data matches") + + // 7. Test a second read (to verify ring reuse works) + buf2 := alignedBlock(4096, 4096) + n2, err := ring.SubmitRead(fd, buf2, 0) + if err != nil { + t.Fatalf("SubmitRead #2: %v", err) + } + if n2 != 4096 { + t.Fatalf("SubmitRead #2 returned %d bytes, expected 4096", n2) + } + for i := 0; i < 4096; i++ { + if buf2[i] != data[i] { + t.Fatalf("data mismatch #2 at byte %d: got %d, want %d", i, buf2[i], data[i]) + } + } + t.Logf("io_uring second read also succeeded") + + // 8. Test multiple sequential reads to exercise ring cycling + for iter := 0; iter < 100; iter++ { + buf3 := alignedBlock(4096, 4096) + n3, err := ring.SubmitRead(fd, buf3, 0) + if err != nil { + t.Fatalf("SubmitRead iter %d: %v", iter, err) + } + if n3 != 4096 { + t.Fatalf("SubmitRead iter %d returned %d bytes, expected 4096", iter, n3) + } + } + t.Logf("100 sequential io_uring reads succeeded") +} + +// alignedBlock returns a block-aligned buffer. +func alignedBlock(size, alignment int) []byte { + raw := make([]byte, size+alignment) + addr := uintptr(unsafe.Pointer(&raw[0])) + off := (alignment - int(addr%uintptr(alignment))) % alignment + return raw[off : off+size] +} diff --git a/flashring/internal/iouring/iouring_writer.go b/flashring/internal/iouring/iouring_writer.go new file mode 100644 index 00000000..d9f7392b --- /dev/null +++ b/flashring/internal/iouring/iouring_writer.go @@ -0,0 +1,43 @@ +//go:build linux +// +build linux + +package iouring + +// IoUringWriter wraps a BatchIoUringWriter with decoupled submit/complete +// goroutines. The ring mutex is held only during SQE prep + io_uring_enter, +// not during CQE drain, allowing concurrent flush batches from different +// shards to interleave submission. +type IoUringWriter struct { + batch *BatchIoUringWriter +} + +// NewIoUringWriter creates an IoUringWriter backed by a decoupled batch writer. +func NewIoUringWriter(entries uint32, flags uint32) (*IoUringWriter, error) { + b, err := NewBatchIoUringWriter(BatchIoUringConfig{ + RingDepth: entries, + MaxBatch: int(entries), + MaxInflight: int(entries), + QueueSize: 1024, + }) + if err != nil { + return nil, err + } + return &IoUringWriter{batch: b}, nil +} + +// MaxBatchSize returns the maximum number of SQEs that can be submitted in +// a single SubmitWriteBatch call. +func (w *IoUringWriter) MaxBatchSize() int { + return w.batch.MaxBatchSize() +} + +// SubmitWriteBatch submits N pwrite operations and waits for all completions. +// Thread-safe. The ring mutex is NOT held during CQE drain. +func (w *IoUringWriter) SubmitWriteBatch(fd int, bufs [][]byte, offsets []uint64) ([]int, error) { + return w.batch.SubmitWriteBatch(fd, bufs, offsets) +} + +// Close releases the underlying io_uring ring and stops background goroutines. +func (w *IoUringWriter) Close() { + w.batch.Close() +} diff --git a/flashring/internal/maths/estimator.go b/flashring/internal/maths/estimator.go index f477d96e..154298e1 100644 --- a/flashring/internal/maths/estimator.go +++ b/flashring/internal/maths/estimator.go @@ -5,6 +5,8 @@ package maths import ( "math" "time" + + "github.com/rs/zerolog/log" ) const ( @@ -75,6 +77,7 @@ func (g *GridSearchEstimator) RecordHitRate(hitRate float64) { stat.HitRate = (stat.HitRate*float64(stat.Trials) + hitRate) / float64(stat.Trials+1) stat.Trials++ if stat.HitRate < g.bestHitRate*0.9 { + log.Error().Msgf("GridSearchRestarted: hitRate %v bestHitRate %v", stat.HitRate, g.bestHitRate) g.RestartGridSearch() } return @@ -130,6 +133,10 @@ func (g *GridSearchEstimator) GenerateRefinedGrid(base WeightTuple, steps int, d refined := make([]WeightTuple, 0, (2*steps+1)*(2*steps+1)) for i := -steps; i <= steps; i++ { for j := -steps; j <= steps; j++ { + + if i == 0 && j == 0 { + continue + } wf := base.WFreq + float64(i)*delta la := base.WLA + float64(j)*delta if math.Abs(wf-base.WFreq) < g.epsilon && math.Abs(la-base.WLA) < g.epsilon { diff --git a/flashring/internal/maths/freq.go b/flashring/internal/maths/freq.go index 3471c123..3e554e6b 100644 --- a/flashring/internal/maths/freq.go +++ b/flashring/internal/maths/freq.go @@ -2,139 +2,141 @@ package maths /* -Package maths implements a **decimal Morris‑style probabilistic counter** -compressed into a single `uint32`. +Package maths implements a binary Morris-style probabilistic counter +compressed into a single uint16. ------------------------------------------------------------------------ How the algorithm works ------------------------------------------------------------------------ -1. **Layout (24 bits)** -| exponent : 20 bits | mantissa : 4 bits | -e m (0‑9) -The counter encodes ≈ `m · 10ᵉ`. -* Mantissa cycles at 10 (`mOverflow = 10`). -* `expClamp` chosen at construction bounds the maximum exponent. - -2. **Increment rule** -On each logical “event”, we increment the *stored* value only with -probability **1 / 10ᵉ** (Bernoulli trial). -- A 32‑bit xorshift PRNG generates `rand32()`. -- We pre‑compute `th[e] = ⌊2³² / 10ᵉ⌋`. -- `rand32() < th[e]` ⇢ *hit* ⇒ advance mantissa (`m++`). -- When `m == 10` we reset `m = 0` and bump the exponent - (until `expClamp`, then we saturate). - -This is the classic idea introduced by **Robert Morris** for “counting -large numbers of events in small registers” :contentReference[oaicite:0]{index=0}. - -3. **Decoding** -To retrieve an approximate frequency, multiply -`m · 10ᵉ` (done with a tiny `pow10` table). - -4. **Error guarantees** -For mantissa 0‑9 the standard deviation of the estimate is -`σ ≈ √m · 10ᵉ`, so the relative error is ≤ `1/√m` -(≤ 33 % worst‑case, ≤ 10 % once `m ≥ 10`). -Such accuracy is typical for Morris‑style counters used in -streaming & LFU/TinyLFU cache admission :contentReference[oaicite:1]{index=1}. - -5. **Complexity & footprint** -* **State per key:** 4 bytes. -* **Increment:** ~7 integer ops for PRNG + 1 compare + a few bit‑ops - ⇒ ~5‑7 ns on modern CPUs (counter update is usually cheaper than the - surrounding map/slice access). -* **No floating‑point or division** in the hot path; thresholds - are prepared once in `New`. ------------------------------------------------------------------------- -References ------------------------------------------------------------------------- -* R. Morris. “Counting large numbers of events in small registers.” -*Communications of the ACM*, 21(10): 840‑842, 1978. :contentReference[oaicite:2]{index=2} -* P. Flajolet. “Approximate Counting: A Detailed Analysis.” *BIT* 25, 1985. :contentReference[oaicite:3]{index=3} -* G. Gundersen, “Approximate Counting with Morris’s Algorithm,” blog post, 2019. :contentReference[oaicite:4]{index=4} + 1. Layout (16 bits) + + ┌─ exponent (4 bits) ─┬─ mantissa (12 bits) ─┐ + │ e (0–15) │ m (0–4095) │ + └──────────────────────┴───────────────────────┘ + + The counter encodes the approximate value: m × 2ᵉ (equivalently m << e). + + 2. Increment rule + + On each key access, the counter is incremented probabilistically: + - Probability of increment = 1 / 2ᵉ. + - The caller supplies an external hash (hlo). We compare its lower + 32 bits against a precomputed threshold: th[e] = (2³² - 1) >> e. + - If uint32(hlo) < th[e] → hit → mantissa advances (m++). + - If uint32(hlo) >= th[e] → miss → counter unchanged. + + + 3. Mantissa overflow + + When m reaches 4096 (overflows 12 bits), we halve the mantissa + and bump the exponent: m = 2048, e++. + + This preserves the approximate decoded value across the transition: + Before: m=4095, e=0 → Value = 4095 × 1 = 4095 + After: m=2048, e=1 → Value = 2048 × 2 = 4096 + + At max exponent (e=15), the counter saturates at m=4095, e=15 + (decoded value = 4095 × 32768 = 134,184,960). + + 4. Decoding + + Value = m << e + + Examples: + Encoded (e=0, m=42) → 42 << 0 = 42 (exact) + Encoded (e=0, m=4000) → 4000 << 0 = 4000 (exact) + Encoded (e=1, m=2048) → 2048 << 1 = 4096 (step size = 2) + Encoded (e=2, m=2500) → 2500 << 2 = 10000 (step size = 4) + + 5. Resolution + + At exponent e, the step between consecutive representable values is 2ᵉ: + e=0: step 1, exact integers 0 – 4,095 + e=1: step 2, even numbers 4,096 – 8,190 + e=2: step 4 8,192 – 16,380 + ... + e=15: step 32,768 up to ~134 million + + For cache frequency tracking, most keys stay in e=0 (exact counts up + to 4095) or e=1 (step of 2), giving ~6,600 distinct values under 10K + compared to ~37 with the previous base-10 design. + + 6. Complexity & footprint + + State per key: 2 bytes (uint16), stored inline in the index entry. + Increment: 1 compare + a few bit-ops, no floating-point. + Thresholds: precomputed once in New() (16 entries). */ -// 4‑bit mantissa (0‑9). 20‑bit exponent (0 … expClamp). +// 12-bit mantissa (0–4095). 4-bit exponent (0–15). const ( - mBits = 4 - eBits = 24 - mBits - mMask = (1 << mBits) - 1 // 0xF + mBits = 12 + mMask = (1 << mBits) - 1 // 0x0FFF eShift = mBits - mOverflow = 10 // mantissa cycles at 10 + mOverflow = 1 << mBits // 4096 ) -// ----------- fast RNG (xorshift32) -------- -var rng uint32 = 0x7263b8e4 // non‑zero seed - type MorrisLogCounter struct { - th []uint32 // thresholds th[e] = floor(2^32 / 10^e) - pow10 []uint64 // pow10[e] = 10^e - expClamp uint32 - rng uint32 + th []uint32 // th[e] = (2^32 - 1) >> e; increment probability = 1/2^e + pow2 []uint64 // pow2[e] = 2^e; used for decoding + expClamp uint32 // maximum exponent, fixed at 15 } -// New prepares tables for a desired exponent ceiling. -// expClamp must fit in the 20‑bit exponent field. -func New(expClamp uint32) *MorrisLogCounter { - if expClamp >= 1< 0 { - p10 *= 10 - } - pow10[e] = p10 - th[e] = uint32(max32 / p10) // floor(2^32 / 10^e) + max32 := uint64(^uint32(0)) // 2^32 - 1 + + for e := 0; e <= maxExp; e++ { + th[e] = uint32(max32 >> e) + pow2[e] = 1 << uint(e) } return &MorrisLogCounter{ th: th, - pow10: pow10, - expClamp: expClamp, - rng: rng, + pow2: pow2, + expClamp: maxExp, } } -func (c *MorrisLogCounter) Inc(v uint32) (uint32, bool) { - m := v & mMask // mantissa - e := v >> eShift // exponent (0 … expClamp) +// Inc probabilistically increments the counter. hlo is the lower 64 bits +// of the key's hash, used as the randomness source for the Bernoulli trial. +// Returns the (possibly updated) counter and whether an increment occurred. +func (c *MorrisLogCounter) Inc(v uint16, hlo uint64) (uint16, bool) { + m := v & mMask + e := v >> eShift - // 1 / 10^e probability check - if c.rand32() >= c.th[e] { - return v, false // miss + // Bernoulli trial: increment with probability 1/2^e. + // At e=0 this is ~100% (th[0] = 0xFFFFFFFF). + // At e=1 this is ~50%, at e=2 ~25%, etc. + if uint32(hlo) >= c.th[e] { + return v, false } - // hit m++ if m == mOverflow { - m = 0 - if e < c.expClamp { + // Mantissa overflowed 12 bits. Halve mantissa and bump exponent + // to keep the decoded value approximately continuous. + if e < 15 { + m = m >> 1 // 4096 → 2048 e++ - } else { // saturated at top state - m = mOverflow - 1 + } else { + // Saturate: can't increase exponent further. + m = mOverflow - 1 // clamp at 4095 } } - return (e << eShift) | m, true + return (e << eShift) | (m & mMask), true } -func (c *MorrisLogCounter) Value(v uint32) uint64 { +// Value decodes the counter into an approximate frequency: m × 2^e. +func (c *MorrisLogCounter) Value(v uint16) uint64 { m := uint64(v & mMask) e := v >> eShift - return m * c.pow10[e] -} - -func (c *MorrisLogCounter) rand32() uint32 { - r := rng - r ^= r << 13 - r ^= r >> 17 - r ^= r << 5 - rng = r - return r + return m << e } diff --git a/flashring/internal/maths/freq_test.go b/flashring/internal/maths/freq_test.go index 4eae335f..67101765 100644 --- a/flashring/internal/maths/freq_test.go +++ b/flashring/internal/maths/freq_test.go @@ -5,84 +5,39 @@ import ( ) func TestNew(t *testing.T) { - tests := []struct { - name string - expClamp uint32 - wantErr bool - }{ - { - name: "valid small expClamp", - expClamp: 5, - wantErr: false, - }, - { - name: "valid zero expClamp", - expClamp: 0, - wantErr: false, - }, - { - name: "valid medium expClamp", - expClamp: 15, // smaller reasonable test value - wantErr: false, - }, - { - name: "invalid expClamp exceeds 20-bit", - expClamp: 1 << eBits, // exceeds 20-bit capacity - wantErr: true, - }, + counter := New() + if counter == nil { + t.Fatal("New() returned nil") } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - defer func() { - if r := recover(); (r != nil) != tt.wantErr { - t.Errorf("New() panic = %v, wantErr %v", r != nil, tt.wantErr) - } - }() - - counter := New(tt.expClamp) - if !tt.wantErr { - if counter == nil { - t.Error("New() returned nil for valid input") - return - } - if counter.expClamp != tt.expClamp { - t.Errorf("New() expClamp = %v, want %v", counter.expClamp, tt.expClamp) - } - if len(counter.th) != int(tt.expClamp+1) { - t.Errorf("New() threshold table length = %v, want %v", len(counter.th), tt.expClamp+1) - } - if len(counter.pow10) != int(tt.expClamp+1) { - t.Errorf("New() pow10 table length = %v, want %v", len(counter.pow10), tt.expClamp+1) - } - } - }) + if counter.expClamp != 15 { + t.Errorf("expClamp = %v, want 15", counter.expClamp) + } + if len(counter.th) != 16 { + t.Errorf("threshold table length = %v, want 16", len(counter.th)) + } + if len(counter.pow2) != 16 { + t.Errorf("pow2 table length = %v, want 16", len(counter.pow2)) } } -func TestPow10Table(t *testing.T) { - counter := New(5) +func TestPow2Table(t *testing.T) { + counter := New() - expected := []uint64{1, 10, 100, 1000, 10000, 100000} + expected := []uint64{1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768} for i, exp := range expected { - if counter.pow10[i] != exp { - t.Errorf("pow10[%d] = %v, want %v", i, counter.pow10[i], exp) + if counter.pow2[i] != exp { + t.Errorf("pow2[%d] = %v, want %v", i, counter.pow2[i], exp) } } } func TestThresholdTable(t *testing.T) { - counter := New(3) + counter := New() - // th[e] should equal floor(2^32 / 10^e) - max32 := uint64(^uint32(0)) // 2^32 - 1 + max32 := uint64(^uint32(0)) - for e := uint32(0); e <= 3; e++ { - var pow10e uint64 = 1 - for i := uint32(0); i < e; i++ { - pow10e *= 10 - } - expected := uint32(max32 / pow10e) + for e := uint32(0); e <= 15; e++ { + expected := uint32(max32 >> e) if counter.th[e] != expected { t.Errorf("th[%d] = %v, want %v", e, counter.th[e], expected) } @@ -90,37 +45,47 @@ func TestThresholdTable(t *testing.T) { } func TestValue(t *testing.T) { - counter := New(5) + counter := New() tests := []struct { name string - v uint32 + v uint16 expected uint64 }{ { name: "mantissa 0, exponent 0", - v: 0, // m=0, e=0 + v: 0, expected: 0, }, { name: "mantissa 5, exponent 0", - v: 5, // m=5, e=0 - expected: 5, + v: 5, + expected: 5, // 5 << 0 }, { name: "mantissa 3, exponent 1", - v: (1 << eShift) | 3, // m=3, e=1 - expected: 30, + v: (1 << eShift) | 3, + expected: 6, // 3 << 1 + }, + { + name: "mantissa 100, exponent 2", + v: (2 << eShift) | 100, + expected: 400, // 100 << 2 }, { - name: "mantissa 7, exponent 2", - v: (2 << eShift) | 7, // m=7, e=2 - expected: 700, + name: "mantissa 4095, exponent 0", + v: 4095, + expected: 4095, // 4095 << 0 }, { - name: "mantissa 9, exponent 3", - v: (3 << eShift) | 9, // m=9, e=3 - expected: 9000, + name: "mantissa 2048, exponent 1", + v: (1 << eShift) | 2048, + expected: 4096, // 2048 << 1 + }, + { + name: "mantissa 2048, exponent 15", + v: (15 << eShift) | 2048, + expected: 2048 << 15, // 67108864 }, } @@ -135,113 +100,84 @@ func TestValue(t *testing.T) { } func TestIncBasicBehavior(t *testing.T) { - counter := New(5) - - // Test mantissa increment when increment succeeds - // We'll force hits by setting a predictable RNG state - originalRng := rng - defer func() { rng = originalRng }() + counter := New() - // Set RNG to always return 0 (guaranteed hit) - rng = 0 - - v := uint32(5) // m=5, e=0 - newV, hit := counter.Inc(v) + // With e=0, th[0] = 0xFFFFFFFF, so any hlo will hit (uint32(hlo) < th[0]) + v := uint16(5) // m=5, e=0 + newV, hit := counter.Inc(v, 0) if !hit { - t.Error("Inc() should have hit with RNG=0") + t.Error("Inc() should always hit at e=0") } - expectedM := uint32(6) - expectedE := uint32(0) - expectedV := (expectedE << eShift) | expectedM - + expectedV := uint16(6) // m=6, e=0 if newV != expectedV { t.Errorf("Inc(%v) = %v, want %v", v, newV, expectedV) } } func TestIncMantissaOverflow(t *testing.T) { - counter := New(5) + counter := New() - // Force hits by setting RNG to 0 - originalRng := rng - defer func() { rng = originalRng }() - rng = 0 - - // Test mantissa overflow: m=9 -> m=0, e++ - v := uint32(9) // m=9, e=0 - newV, hit := counter.Inc(v) + // m=4095 (mOverflow-1), e=0 -> increment should cause overflow + v := uint16(mOverflow - 1) // m=4095, e=0 + newV, hit := counter.Inc(v, 0) if !hit { - t.Error("Inc() should have hit with RNG=0") + t.Error("Inc() should always hit at e=0") } - expectedM := uint32(0) - expectedE := uint32(1) + // On overflow: m becomes 4096>>1 = 2048, e becomes 1 + expectedM := uint16(mOverflow >> 1) // 2048 + expectedE := uint16(1) expectedV := (expectedE << eShift) | expectedM if newV != expectedV { - t.Errorf("Inc(%v) = %v, want %v (m=0, e=1)", v, newV, expectedV) + t.Errorf("Inc(%v) = %v, want %v (m=2048, e=1)", v, newV, expectedV) + } + + // Verify the decoded value is reasonable + // Before: Value(4095) = 4095 << 0 = 4095 + // After: Value(newV) = 2048 << 1 = 4096 + valBefore := counter.Value(v) + valAfter := counter.Value(newV) + if valAfter <= valBefore { + t.Errorf("Value should increase after overflow: before=%v, after=%v", valBefore, valAfter) } } func TestIncExponentSaturation(t *testing.T) { - counter := New(2) // expClamp = 2 + counter := New() - // Force hits by setting RNG to 0 - originalRng := rng - defer func() { rng = originalRng }() - rng = 0 - - // Test saturation at expClamp: m=9, e=expClamp - v := (uint32(2) << eShift) | 9 // m=9, e=2 (at expClamp) - newV, hit := counter.Inc(v) + // m=4095, e=15 (max exponent) -> should saturate + v := (uint16(15) << eShift) | uint16(mOverflow-1) // m=4095, e=15 + newV, hit := counter.Inc(v, 0) if !hit { - t.Error("Inc() should have hit with RNG=0") + t.Error("Inc() should hit") } - // Should saturate at m=9, e=2 (not overflow) - expectedM := uint32(9) // mOverflow - 1 - expectedE := uint32(2) // stays at expClamp - expectedV := (expectedE << eShift) | expectedM - - if newV != expectedV { - t.Errorf("Inc(%v) = %v, want %v (saturated)", v, newV, expectedV) + // Should saturate: m stays at 4095, e stays at 15 + if newV != v { + t.Errorf("Inc(%v) = %v, want %v (saturated at max)", v, newV, v) } } func TestIncMissBehavior(t *testing.T) { - counter := New(5) - - originalRng := rng - defer func() { rng = originalRng }() - - // Use a higher exponent where th[e] is smaller and easier to exceed - // th[3] = 4294967 (from debug output) - v := uint32((3 << eShift) | 5) // m=5, e=3 - - // Find an RNG value that will cause rand32() to return >= th[3] - // We'll try a few seeds until we find one that causes a miss - missFound := false - for seed := uint32(0xFFFFFF00); seed != 0; seed++ { - rng = seed - testRand := counter.rand32() - if testRand >= counter.th[3] { - // Reset and use this seed - rng = seed - newV, hit := counter.Inc(v) - - if !hit && newV == v { - missFound = true - break - } - } - } + counter := New() + + // At e=1, th[1] = 0xFFFFFFFF >> 1 = 0x7FFFFFFF + // hlo with uint32 >= 0x7FFFFFFF should miss + v := (uint16(1) << eShift) | 5 // m=5, e=1 + hlo := uint64(0xFFFFFFFF) // uint32(hlo) = 0xFFFFFFFF >= th[1] + + newV, hit := counter.Inc(v, hlo) - if !missFound { - t.Skip("Could not find RNG seed that causes miss - test may be flaky") + if hit { + t.Error("Inc() should miss when uint32(hlo) >= th[e]") + } + if newV != v { + t.Errorf("Inc() on miss should return original value: got %v, want %v", newV, v) } } @@ -250,46 +186,42 @@ func TestIncStatisticalBehavior(t *testing.T) { t.Skip("skipping statistical test in short mode") } - counter := New(10) - - // Reset RNG to ensure reproducible but varied sequence - originalRng := rng - defer func() { rng = originalRng }() - rng = 12345 + counter := New() - // Test with e=0 (should hit approximately 100% of the time) - v := uint32(5) // m=5, e=0 + // Test with e=0 (should hit ~100% of the time since th[0] = 0xFFFFFFFF) + v := uint16(5) hits := 0 trials := 1000 for i := 0; i < trials; i++ { - _, hit := counter.Inc(v) + _, hit := counter.Inc(v, uint64(i)) if hit { hits++ } } - // With e=0, probability should be close to 1.0 hitRate := float64(hits) / float64(trials) - if hitRate < 0.95 { // Allow some variance due to PRNG - t.Errorf("Hit rate for e=0 = %v, want > 0.95", hitRate) + if hitRate < 0.99 { + t.Errorf("Hit rate for e=0 = %v, want ~1.0", hitRate) } - // Test with e=1 (should hit approximately 10% of the time) - v = (1 << eShift) | 5 // m=5, e=1 + // Test with e=1 (should hit approximately 50% of the time) + // th[1] = 0x7FFFFFFF, so uint32(hlo) < th[1] means lower half hits + v = (1 << eShift) | 5 hits = 0 for i := 0; i < trials; i++ { - _, hit := counter.Inc(v) + // Use Knuth multiplicative hash to spread uint32 values evenly + hlo := uint64(uint32(i) * 2654435761) + _, hit := counter.Inc(v, hlo) if hit { hits++ } } hitRate = float64(hits) / float64(trials) - // Allow reasonable variance: 0.05 to 0.15 for 10% expected - if hitRate < 0.05 || hitRate > 0.15 { - t.Errorf("Hit rate for e=1 = %v, want ~0.10 (0.05-0.15)", hitRate) + if hitRate < 0.35 || hitRate > 0.65 { + t.Errorf("Hit rate for e=1 = %v, want ~0.50 (0.35-0.65)", hitRate) } } @@ -298,63 +230,40 @@ func TestIntegrationCountingApproximation(t *testing.T) { t.Skip("skipping integration test in short mode") } - counter := New(10) - - // Reset RNG to ensure reproducible results - originalRng := rng - defer func() { rng = originalRng }() - rng = 98765 + counter := New() - // Simulate counting events - start with higher initial state - v := uint32(5) // start with m=5, e=0 to avoid edge cases - actualIncrements := 0 + v := uint16(0) + totalEvents := 100000 - // Perform many logical increments - for i := 0; i < 10000; i++ { - newV, hit := counter.Inc(v) + for i := 0; i < totalEvents; i++ { + newV, hit := counter.Inc(v, uint64(i*2654435761)) // Knuth multiplicative hash for spread if hit { v = newV - actualIncrements++ } } - // Get the approximate count approxCount := counter.Value(v) - // Since we started with m=5, the base count is 5 - // The approximation should account for this - if actualIncrements == 0 && approxCount == 5 { - // If no actual increments happened, approxCount should still be the initial value - return - } - - // The approximation should be reasonable - // Given the probabilistic nature, we expect some error - if actualIncrements > 0 && approxCount > 0 { - ratio := float64(approxCount) / float64(actualIncrements+5) // +5 for initial value - - // The ratio should be reasonably close to 1.0 - // Morris counters can have significant variance, so we allow a wide range - if ratio < 0.1 || ratio > 10.0 { - t.Errorf("Approximation ratio = %v, actualIncrements = %v, approxCount = %v", - ratio, actualIncrements, approxCount) - } + // The approximation should be in the right ballpark + ratio := float64(approxCount) / float64(totalEvents) + if ratio < 0.1 || ratio > 10.0 { + t.Errorf("Approximation ratio = %v, totalEvents = %v, approxCount = %v", + ratio, totalEvents, approxCount) } } func TestBitPacking(t *testing.T) { - // Test that mantissa and exponent are properly packed/unpacked - counter := New(5) + counter := New() tests := []struct { - mantissa uint32 - exponent uint32 + mantissa uint16 + exponent uint16 }{ {0, 0}, - {9, 0}, - {0, 5}, - {7, 3}, - {15, 2}, // This tests mantissa > 9 (should mask to 4 bits) + {4095, 0}, + {0, 15}, + {2048, 3}, + {100, 7}, } for _, tt := range tests { @@ -363,37 +272,34 @@ func TestBitPacking(t *testing.T) { extractedM := v & mMask extractedE := v >> eShift - expectedM := tt.mantissa & mMask // masked to 4 bits - - if extractedM != expectedM { - t.Errorf("Mantissa packing: got %v, want %v", extractedM, expectedM) + if extractedM != tt.mantissa&mMask { + t.Errorf("Mantissa packing: got %v, want %v", extractedM, tt.mantissa&mMask) } if extractedE != tt.exponent { t.Errorf("Exponent packing: got %v, want %v", extractedE, tt.exponent) } - // Test Value() decoding decoded := counter.Value(v) - expected := uint64(expectedM) * counter.pow10[tt.exponent] + expected := uint64(tt.mantissa&mMask) << tt.exponent if decoded != expected { - t.Errorf("Value() = %v, want %v", decoded, expected) + t.Errorf("Value() = %v, want %v (m=%v, e=%v)", decoded, expected, tt.mantissa, tt.exponent) } } } func BenchmarkInc(b *testing.B) { - counter := New(10) - v := uint32(123) + counter := New() + v := uint16(123) b.ResetTimer() for i := 0; i < b.N; i++ { - v, _ = counter.Inc(v) + v, _ = counter.Inc(v, uint64(i)) } } func BenchmarkValue(b *testing.B) { - counter := New(10) - v := uint32(123) + counter := New() + v := uint16(123) b.ResetTimer() for i := 0; i < b.N; i++ { diff --git a/flashring/internal/maths/predictor.go b/flashring/internal/maths/predictor.go index edf3b128..86b70279 100644 --- a/flashring/internal/maths/predictor.go +++ b/flashring/internal/maths/predictor.go @@ -1,6 +1,10 @@ package maths -import "time" +import ( + "time" + + "github.com/Meesho/BharatMLStack/flashring/pkg/metrics" +) type Params struct { Freq uint64 @@ -13,15 +17,38 @@ type Predictor struct { GridSearchEstimator *GridSearchEstimator ReWriteScoreThreshold float32 MaxMemTableCount uint32 + freqBands FreqBands + recencyBands RecencyBands hitRateCh chan float64 } +// FreqBands defines the upper bounds for frequency band labels. +// Keys with freq <= Cold are "cold", <= Warm are "warm", <= Hot are "hot", +// and anything above Hot is "very_hot". +type FreqBands struct { + Cold uint64 + Warm uint64 + Hot uint64 +} + +// RecencyBands defines upper-bound thresholds for recency band labels. +// lastAccess represents how long ago a key was accessed (higher = older). +// Keys with lastAccess <= Hot are "very_hot", <= Warm are "hot", +// <= Cold are "warm", and anything above Cold is "cold". +type RecencyBands struct { + Hot uint64 + Warm uint64 + Cold uint64 +} + type PredictorConfig struct { ReWriteScoreThreshold float32 Weights []WeightTuple SampleDuration time.Duration MaxMemTableCount uint32 GridSearchEpsilon float64 + FreqBands FreqBands + RecencyBands RecencyBands } func NewPredictor(config PredictorConfig) *Predictor { @@ -30,11 +57,21 @@ func NewPredictor(config PredictorConfig) *Predictor { WLA: config.Weights[0].WLA, } gridSearchEstimator := NewGridSearchEstimator(config.SampleDuration, config.Weights, estimator, config.GridSearchEpsilon) + fb := config.FreqBands + if fb.Cold == 0 && fb.Warm == 0 && fb.Hot == 0 { + fb = FreqBands{Cold: 1, Warm: 5, Hot: 20} + } + rb := config.RecencyBands + if rb.Hot == 0 && rb.Warm == 0 && rb.Cold == 0 { + rb = RecencyBands{Hot: 5, Warm: 50, Cold: 500} + } p := &Predictor{ Estimator: estimator, GridSearchEstimator: gridSearchEstimator, ReWriteScoreThreshold: config.ReWriteScoreThreshold, MaxMemTableCount: config.MaxMemTableCount, + freqBands: fb, + recencyBands: rb, hitRateCh: make(chan float64, 1024), } go func() { @@ -45,9 +82,91 @@ func NewPredictor(config PredictorConfig) *Predictor { return p } +func scoreBucket(score float32) string { + switch { + case score < 0.1: + return "0.0-0.1" + case score < 0.3: + return "0.1-0.3" + case score < 0.5: + return "0.3-0.5" + case score < 0.7: + return "0.5-0.7" + case score < 1.0: + return "0.7-1.0" + default: + return "1.0+" + } +} + +func ringZone(keyMemId, activeMemId, maxMemTableCount uint32) string { + risk := (activeMemId - keyMemId + maxMemTableCount) % maxMemTableCount + pct := float64(risk) / float64(maxMemTableCount) + switch { + case pct < 0.25: + return "0-25%" + case pct < 0.50: + return "25-50%" + case pct < 0.75: + return "50-75%" + default: + return "75-100%" + } +} + +func freqBand(freq uint64, fb FreqBands) string { + switch { + case freq <= fb.Cold: + return "cold" + case freq <= fb.Warm: + return "warm" + case freq <= fb.Hot: + return "hot" + default: + return "very_hot" + } +} + +func recencyBand(lastAccess uint64, rb RecencyBands) string { + switch { + case lastAccess <= rb.Hot: + return "very_hot" + case lastAccess <= rb.Warm: + return "hot" + case lastAccess <= rb.Cold: + return "warm" + default: + return "cold" + } +} + func (p *Predictor) Predict(freq uint64, lastAccess uint64, keyMemId uint32, activeMemId uint32) bool { score := p.Estimator.CalculateRewriteScore(freq, lastAccess, keyMemId, activeMemId, p.MaxMemTableCount) - return score > p.ReWriteScoreThreshold + rewrite := score > p.ReWriteScoreThreshold + + computeMetrics(keyMemId, activeMemId, p, freq, lastAccess, rewrite, score) + + return rewrite +} + +func computeMetrics(keyMemId uint32, activeMemId uint32, p *Predictor, freq uint64, lastAccess uint64, rewrite bool, score float32) { + zone := ringZone(keyMemId, activeMemId, p.MaxMemTableCount) + fBand := freqBand(freq, p.freqBands) + rBand := recencyBand(lastAccess, p.recencyBands) + decision := "skip" + if rewrite { + decision = "rewrite" + } + + metrics.Timing(metrics.KEY_ACCESS_FREQ, time.Duration(freq)*time.Millisecond, nil) + metrics.Timing(metrics.KEY_LAST_ACCESS, time.Duration(lastAccess)*time.Millisecond, nil) + metrics.Incr(metrics.KEY_REWRITE_SCORE, metrics.BuildTag(metrics.NewTag(metrics.TAG_SCORE_BUCKET, scoreBucket(score)))) + metrics.Incr(metrics.KEY_REWRITE_DECISION, metrics.BuildTag( + metrics.NewTag(metrics.TAG_DECISION, decision), + metrics.NewTag(metrics.TAG_RING_ZONE, zone), + metrics.NewTag(metrics.TAG_FREQ_BAND, fBand), + metrics.NewTag(metrics.TAG_RECENCY_BAND, rBand), + )) } func (p *Predictor) Observe(hitRate float64) { diff --git a/flashring/internal/maths/predictor_test.go b/flashring/internal/maths/predictor_test.go index 56f6590d..f9bdfa0e 100644 --- a/flashring/internal/maths/predictor_test.go +++ b/flashring/internal/maths/predictor_test.go @@ -315,44 +315,42 @@ func TestGridSearchRefinement(t *testing.T) { } estimator := &Estimator{WFreq: 0.2, WLA: 0.2} - gridSearch := NewGridSearchEstimator( - 10*time.Millisecond, - initialTuples, - estimator, - 0.01, // Larger epsilon - ) - - // Test grid refinement with delta larger than epsilon - base := WeightTuple{WFreq: 0.2, WLA: 0.2} - _, ok := gridSearch.GenerateRefinedGrid(base, 1, 0.1) - - // The function returns false when it encounters the center point (i=0, j=0) - // where both differences are 0 (which is < epsilon), so it will return false - // This is actually the expected behavior - it means the grid is too fine - if ok { - t.Error("Grid refinement should return false due to center point having zero difference") - } - - // Test with a different approach - use larger delta relative to epsilon - gridSearch2 := NewGridSearchEstimator( - 10*time.Millisecond, - initialTuples, - estimator, - 0.001, // Smaller epsilon - ) - - // Test with delta much larger than epsilon and non-zero base that avoids zero differences - base2 := WeightTuple{WFreq: 0.5, WLA: 0.5} - _, ok2 := gridSearch2.GenerateRefinedGrid(base2, 2, 0.1) - - // This should also return false due to the center point issue - if ok2 { - t.Error("Grid refinement should return false due to center point check") - } - // The function logic checks if differences are small at any point during iteration - // and returns false when it finds the center point where difference is 0 - // This seems to be the intended behavior to detect when refinement should stop + t.Run("delta > epsilon: refinement possible", func(t *testing.T) { + gs := NewGridSearchEstimator(10*time.Millisecond, initialTuples, estimator, 0.01) + base := WeightTuple{WFreq: 0.2, WLA: 0.2} + refined, ok := gs.GenerateRefinedGrid(base, 1, 0.1) + if !ok { + t.Error("Expected ok=true when delta > epsilon (refinement still useful)") + } + // 3x3 grid minus the center = 8 points; all have positive wf and la + // (base 0.2 ± 0.1 yields 0.1..0.3) + if len(refined) != 8 { + t.Errorf("Expected 8 refined tuples, got %d", len(refined)) + } + }) + + t.Run("delta < epsilon: convergence detected", func(t *testing.T) { + gs := NewGridSearchEstimator(10*time.Millisecond, initialTuples, estimator, 0.1) + base := WeightTuple{WFreq: 0.2, WLA: 0.2} + _, ok := gs.GenerateRefinedGrid(base, 1, 0.01) + if ok { + t.Error("Expected ok=false when delta < epsilon (converged)") + } + }) + + t.Run("larger grid with delta > epsilon", func(t *testing.T) { + gs := NewGridSearchEstimator(10*time.Millisecond, initialTuples, estimator, 0.001) + base := WeightTuple{WFreq: 0.5, WLA: 0.5} + refined, ok := gs.GenerateRefinedGrid(base, 2, 0.1) + if !ok { + t.Error("Expected ok=true when delta >> epsilon") + } + // 5x5 grid minus center = 24 points; all positive (0.3..0.7) + if len(refined) != 24 { + t.Errorf("Expected 24 refined tuples, got %d", len(refined)) + } + }) } func TestGridSearchConvergence(t *testing.T) { diff --git a/flashring/internal/memtables/manager.go b/flashring/internal/memtables/manager.go index a86fb108..3227c2fb 100644 --- a/flashring/internal/memtables/manager.go +++ b/flashring/internal/memtables/manager.go @@ -3,6 +3,7 @@ package memtables import ( "github.com/Meesho/BharatMLStack/flashring/internal/allocators" "github.com/Meesho/BharatMLStack/flashring/internal/fs" + "github.com/Meesho/BharatMLStack/flashring/pkg/metrics" "github.com/rs/zerolog/log" ) @@ -16,14 +17,13 @@ type MemtableManager struct { nextFileOffset int64 nextId uint32 semaphore chan int - stats Stats } -type Stats struct { - Flushes int64 -} - -func NewMemtableManager(file *fs.WrapAppendFile, capacity int32) (*MemtableManager, error) { +// NewMemtableManager creates a double-buffered memtable pair. +// flushStaggerOffset advances the active memtable's write position so that +// different shards fill (and therefore flush) at staggered times, avoiding +// synchronized flush storms that compete with reads for NVMe bandwidth. +func NewMemtableManager(file *fs.WrapAppendFile, capacity int32, flushStaggerOffset int) (*MemtableManager, error) { allocatorConfig := allocators.SlabAlignedPageAllocatorConfig{ SizeClasses: []allocators.SizeClass{ {Size: int(capacity), MinCount: 2}, @@ -53,6 +53,11 @@ func NewMemtableManager(file *fs.WrapAppendFile, capacity int32) (*MemtableManag if err != nil { return nil, err } + // Pre-advance the active memtable so this shard's first flush happens + // earlier/later than its peers, spreading flush I/O over time. + memtable1.currentOffset = flushStaggerOffset + memtable1.flushStartOffset = flushStaggerOffset + memtableManager := &MemtableManager{ file: file, Capacity: capacity, @@ -62,7 +67,6 @@ func NewMemtableManager(file *fs.WrapAppendFile, capacity int32) (*MemtableManag nextFileOffset: 2 * int64(capacity), nextId: 2, semaphore: make(chan int, 1), - stats: Stats{}, } return memtableManager, nil } @@ -92,7 +96,7 @@ func (mm *MemtableManager) flushConsumer(memtable *Memtable) { memtable.Id = mm.nextId mm.nextId++ mm.nextFileOffset += int64(n) - mm.stats.Flushes++ + metrics.Incr(metrics.KEY_MEMTABLE_FLUSH_COUNT, append(metrics.GetShardTag(memtable.ShardIdx), metrics.GetMemtableTag(memtable.Id)...)) } func (mm *MemtableManager) Flush() error { diff --git a/flashring/internal/memtables/manager_bench_test.go b/flashring/internal/memtables/manager_bench_test.go index 28738185..8e1b7406 100644 --- a/flashring/internal/memtables/manager_bench_test.go +++ b/flashring/internal/memtables/manager_bench_test.go @@ -29,7 +29,7 @@ func createManagerBenchmarkFile(b *testing.B) *fs.WrapAppendFile { func Benchmark_Puts(b *testing.B) { file := createManagerBenchmarkFile(b) - manager, err := NewMemtableManager(file, 1024*1024*1024) + manager, err := NewMemtableManager(file, 1024*1024*1024, 0) if err != nil { b.Fatalf("Failed to create memtable manager: %v", err) } @@ -48,7 +48,7 @@ func Benchmark_Puts(b *testing.B) { } } - b.ReportMetric(float64(manager.stats.Flushes), "flushes") + // b.ReportMetric(float64(manager.stats.Flushes), "flushes") b.ReportMetric(float64(b.N*16*1024)/1024/1024, "MB/s") b.ReportAllocs() diff --git a/flashring/internal/memtables/manager_test.go b/flashring/internal/memtables/manager_test.go index 3772f0c5..4c931b9c 100644 --- a/flashring/internal/memtables/manager_test.go +++ b/flashring/internal/memtables/manager_test.go @@ -7,6 +7,7 @@ import ( "time" "github.com/Meesho/BharatMLStack/flashring/internal/fs" + "github.com/Meesho/BharatMLStack/flashring/internal/iouring" ) // Helper function to create a mock file for testing @@ -25,15 +26,28 @@ func createTestFileForManager(t *testing.T) *fs.WrapAppendFile { if err != nil { t.Fatalf("Failed to create test file: %v", err) } + + writeRing, err := iouring.NewIoUringWriter(32, 0) + if err != nil { + t.Fatalf("Failed to create io_uring write ring: %v", err) + } + file.WriteRing = writeRing return file } +func cleanupManagerFile(file *fs.WrapAppendFile) { + if file.WriteRing != nil { + file.WriteRing.Close() + } + file.Close() +} + func TestNewMemtableManager_Success(t *testing.T) { capacity := int32(fs.BLOCK_SIZE * 2) // 8192 bytes file := createTestFileForManager(t) - defer file.Close() + defer cleanupManagerFile(file) - manager, err := NewMemtableManager(file, capacity) + manager, err := NewMemtableManager(file, capacity, 0) if err != nil { t.Fatalf("NewMemtableManager failed: %v", err) } @@ -77,9 +91,9 @@ func TestNewMemtableManager_InvalidCapacity(t *testing.T) { // Test with capacity not aligned to block size capacity := int32(fs.BLOCK_SIZE + 1) // Should fail alignment check file := createTestFileForManager(t) - defer file.Close() + defer cleanupManagerFile(file) - _, err := NewMemtableManager(file, capacity) + _, err := NewMemtableManager(file, capacity, 0) if err == nil { t.Errorf("Expected NewMemtableManager to fail with invalid capacity") } @@ -88,7 +102,7 @@ func TestNewMemtableManager_InvalidCapacity(t *testing.T) { func TestNewMemtableManager_NilFile(t *testing.T) { capacity := int32(fs.BLOCK_SIZE * 2) - _, err := NewMemtableManager(nil, capacity) + _, err := NewMemtableManager(nil, capacity, 0) if err == nil { t.Errorf("Expected NewMemtableManager to fail with nil file") } @@ -97,9 +111,9 @@ func TestNewMemtableManager_NilFile(t *testing.T) { func TestMemtableManager_GetMemtable(t *testing.T) { capacity := int32(fs.BLOCK_SIZE * 2) file := createTestFileForManager(t) - defer file.Close() + defer cleanupManagerFile(file) - manager, err := NewMemtableManager(file, capacity) + manager, err := NewMemtableManager(file, capacity, 0) if err != nil { t.Fatalf("NewMemtableManager failed: %v", err) } @@ -122,9 +136,9 @@ func TestMemtableManager_GetMemtable(t *testing.T) { func TestMemtableManager_GetMemtableById(t *testing.T) { capacity := int32(fs.BLOCK_SIZE * 2) file := createTestFileForManager(t) - defer file.Close() + defer cleanupManagerFile(file) - manager, err := NewMemtableManager(file, capacity) + manager, err := NewMemtableManager(file, capacity, 0) if err != nil { t.Fatalf("NewMemtableManager failed: %v", err) } @@ -151,9 +165,9 @@ func TestMemtableManager_GetMemtableById(t *testing.T) { func TestMemtableManager_Flush(t *testing.T) { capacity := int32(fs.BLOCK_SIZE * 2) file := createTestFileForManager(t) - defer file.Close() + defer cleanupManagerFile(file) - manager, err := NewMemtableManager(file, capacity) + manager, err := NewMemtableManager(file, capacity, 0) if err != nil { t.Fatalf("NewMemtableManager failed: %v", err) } @@ -196,9 +210,9 @@ func TestMemtableManager_Flush(t *testing.T) { func TestMemtableManager_FlushSwapsBetweenMemtables(t *testing.T) { capacity := int32(fs.BLOCK_SIZE * 2) file := createTestFileForManager(t) - defer file.Close() + defer cleanupManagerFile(file) - manager, err := NewMemtableManager(file, capacity) + manager, err := NewMemtableManager(file, capacity, 0) if err != nil { t.Fatalf("NewMemtableManager failed: %v", err) } @@ -230,9 +244,9 @@ func TestMemtableManager_FlushSwapsBetweenMemtables(t *testing.T) { func TestMemtableManager_FlushConcurrency(t *testing.T) { capacity := int32(fs.BLOCK_SIZE * 2) file := createTestFileForManager(t) - defer file.Close() + defer cleanupManagerFile(file) - manager, err := NewMemtableManager(file, capacity) + manager, err := NewMemtableManager(file, capacity, 0) if err != nil { t.Fatalf("NewMemtableManager failed: %v", err) } @@ -280,9 +294,9 @@ func TestMemtableManager_FlushConcurrency(t *testing.T) { func TestMemtableManager_GetMemtableAfterFlush(t *testing.T) { capacity := int32(fs.BLOCK_SIZE * 2) file := createTestFileForManager(t) - defer file.Close() + defer cleanupManagerFile(file) - manager, err := NewMemtableManager(file, capacity) + manager, err := NewMemtableManager(file, capacity, 0) if err != nil { t.Fatalf("NewMemtableManager failed: %v", err) } @@ -317,9 +331,9 @@ func TestMemtableManager_GetMemtableAfterFlush(t *testing.T) { func TestMemtableManager_Integration(t *testing.T) { capacity := int32(fs.BLOCK_SIZE * 2) file := createTestFileForManager(t) - defer file.Close() + defer cleanupManagerFile(file) - manager, err := NewMemtableManager(file, capacity) + manager, err := NewMemtableManager(file, capacity, 0) if err != nil { t.Fatalf("NewMemtableManager failed: %v", err) } diff --git a/flashring/internal/memtables/memtable.go b/flashring/internal/memtables/memtable.go index bc92f0ff..7eb1ef49 100644 --- a/flashring/internal/memtables/memtable.go +++ b/flashring/internal/memtables/memtable.go @@ -4,7 +4,6 @@ import ( "errors" "github.com/Meesho/BharatMLStack/flashring/internal/fs" - "github.com/rs/zerolog/log" ) var ( @@ -25,6 +24,13 @@ type Memtable struct { readyForFlush bool next *Memtable prev *Memtable + ShardIdx uint32 + + // flushStartOffset is the byte offset within the page where real data + // begins. On the first (staggered) memtable this equals the stagger + // offset so Flush() skips the uninitialized region. Reset to 0 after + // the first flush. + flushStartOffset int } type MemtableConfig struct { @@ -32,6 +38,7 @@ type MemtableConfig struct { id uint32 page *fs.AlignedPage file *fs.WrapAppendFile + shardIdx uint32 } func NewMemtable(config MemtableConfig) (*Memtable, error) { @@ -49,6 +56,7 @@ func NewMemtable(config MemtableConfig) (*Memtable, error) { } return &Memtable{ Id: config.id, + ShardIdx: config.shardIdx, capacity: config.capacity, currentOffset: 0, file: config.file, @@ -98,15 +106,30 @@ func (m *Memtable) Flush() (n int, fileOffset int64, err error) { if !m.readyForFlush { return 0, 0, ErrMemtableNotReadyForFlush } - fileOffset, err = m.file.Pwrite(m.page.Buf) + + chunkSize := fs.BLOCK_SIZE + + // When the memtable has a stagger offset (first cycle only), skip the + // uninitialized region: advance the file's write pointer past it, then + // write only the real data. Total file advancement = flushStartOffset + + // len(usedBuf) = capacity, preserving the memId*capacity layout. + startOff := m.flushStartOffset + if startOff > 0 { + m.file.AdvanceWriteOffset(int64(startOff)) + } + + buf := m.page.Buf[startOff:] + + // PwriteBatch submits all chunks via io_uring. + totalWritten, fileOffset, err := m.file.PwriteBatch(buf, chunkSize) if err != nil { return 0, 0, err - } else { - log.Debug().Msgf("Flushed memtable %d to file %d", m.Id, fileOffset) } + m.currentOffset = 0 m.readyForFlush = false - return len(m.page.Buf), fileOffset, nil + m.flushStartOffset = 0 // subsequent flushes write the full page + return startOff + totalWritten, fileOffset, nil } func (m *Memtable) Discard() { diff --git a/flashring/internal/memtables/memtable_test.go b/flashring/internal/memtables/memtable_test.go index 2d694218..4f60d07b 100644 --- a/flashring/internal/memtables/memtable_test.go +++ b/flashring/internal/memtables/memtable_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/Meesho/BharatMLStack/flashring/internal/fs" + "github.com/Meesho/BharatMLStack/flashring/internal/iouring" ) // Helper function to create a mock file for testing @@ -23,6 +24,12 @@ func createTestFile(t *testing.T) *fs.WrapAppendFile { if err != nil { t.Fatalf("Failed to create test file: %v", err) } + + writeRing, err := iouring.NewIoUringWriter(32, 0) + if err != nil { + t.Fatalf("Failed to create io_uring write ring: %v", err) + } + file.WriteRing = writeRing return file } @@ -34,6 +41,9 @@ func createTestPage(size int) *fs.AlignedPage { // Helper function to cleanup resources func cleanup(file *fs.WrapAppendFile, page *fs.AlignedPage) { if file != nil { + if file.WriteRing != nil { + file.WriteRing.Close() + } file.Close() } if page != nil { diff --git a/flashring/internal/pools/leaky_pool.go b/flashring/internal/pools/leaky_pool.go index b2a59487..81bb42dd 100644 --- a/flashring/internal/pools/leaky_pool.go +++ b/flashring/internal/pools/leaky_pool.go @@ -2,63 +2,56 @@ package pools import "sync" -type LeakyPool struct { - availabilityList []interface{} - Meta interface{} - createFunc func() interface{} - preDrefHook func(obj interface{}) - capacity int - usage int - idx int - lock sync.RWMutex - stats *Stats +// LeakyPool is a bounded object pool. When all objects are in use, Get creates +// new ones via createFunc. When returned objects exceed capacity, the excess is +// dropped (optionally via a pre-deref hook for cleanup like unmapping pages). +type LeakyPool[T any] struct { + available []T + Meta any + createFunc func() T + preDrefHook func(obj T) + capacity int + usage int + idx int + mu sync.Mutex } -type Stats struct { - Usage int - Capacity int -} - -type LeakyPoolConfig struct { +type LeakyPoolConfig[T any] struct { Capacity int - Meta interface{} - CreateFunc func() interface{} + Meta any + CreateFunc func() T } -func NewLeakyPool(config LeakyPoolConfig) *LeakyPool { - return &LeakyPool{ - availabilityList: make([]interface{}, config.Capacity), - Meta: config.Meta, - capacity: config.Capacity, - createFunc: config.CreateFunc, - usage: 0, - idx: -1, - preDrefHook: nil, - stats: &Stats{Usage: 0, Capacity: config.Capacity}, +func NewLeakyPool[T any](config LeakyPoolConfig[T]) *LeakyPool[T] { + return &LeakyPool[T]{ + available: make([]T, config.Capacity), + Meta: config.Meta, + capacity: config.Capacity, + createFunc: config.CreateFunc, + usage: 0, + idx: -1, } } -func (p *LeakyPool) RegisterPreDrefHook(hook func(obj interface{})) { +func (p *LeakyPool[T]) RegisterPreDrefHook(hook func(obj T)) { p.preDrefHook = hook } -func (p *LeakyPool) Get() interface{} { - p.lock.Lock() - defer p.lock.Unlock() +func (p *LeakyPool[T]) Get() T { + p.mu.Lock() + defer p.mu.Unlock() p.usage++ - if p.idx == -1 && p.usage > p.capacity { - return p.createFunc() - } else if p.idx == -1 { + if p.idx == -1 { return p.createFunc() } - o := p.availabilityList[p.idx] + o := p.available[p.idx] p.idx-- return o } -func (p *LeakyPool) Put(obj interface{}) { - p.lock.Lock() - defer p.lock.Unlock() +func (p *LeakyPool[T]) Put(obj T) { + p.mu.Lock() + defer p.mu.Unlock() p.usage-- p.idx++ if p.idx == p.capacity { @@ -68,5 +61,5 @@ func (p *LeakyPool) Put(obj interface{}) { p.idx-- return } - p.availabilityList[p.idx] = obj + p.available[p.idx] = obj } diff --git a/flashring/internal/pools/pool.go b/flashring/internal/pools/pool.go index 86dfa5b7..fb62dff0 100644 --- a/flashring/internal/pools/pool.go +++ b/flashring/internal/pools/pool.go @@ -1,7 +1,7 @@ package pools -type Pool interface { - Get() interface{} - Put(obj interface{}) - RegisterPreDrefHook(hook func(obj interface{})) +// Pool is a generic object pool that reuses pre-allocated objects. +type Pool[T any] interface { + Get() T + Put(obj T) } diff --git a/flashring/internal/server/resp.go b/flashring/internal/server/resp.go index dc202b6d..3021e961 100644 --- a/flashring/internal/server/resp.go +++ b/flashring/internal/server/resp.go @@ -10,12 +10,10 @@ import ( "time" ) -// KV is the minimal cache interface required by the RESP server. +// Cache is the minimal interface required by the RESP server. // Implementations should be safe for concurrent use. -type KV interface { - // Put stores the value with optional expire time in unix seconds (0 for no expiry). - Put(key string, value []byte, exptime uint64) error - // Get returns value, keyFound, expired +type Cache interface { + Put(key string, value []byte, ttl time.Duration) error Get(key string) ([]byte, bool, bool) } @@ -23,18 +21,13 @@ type KV interface { // GET and SET only. It is optimized for low overhead and pipelined requests. // // Supported commands (case-insensitive): -// - *2\r\n$3\r\nGET\r\n$\r\n\r\n -// - *3\r\n$3\r\nSET\r\n$\r\n\r\n$\r\n\r\n -// - SET with EX seconds (optional): -// *5 ... SET key val EX seconds -// -// Inline protocol is not supported to keep parsing fast and simple. -func ServeRESP(addr string, cache KV) error { +// - GET key +// - SET key value [EX seconds] +func ServeRESP(addr string, cache Cache) error { ln, err := net.Listen("tcp", addr) if err != nil { return err } - // Accept loop for { conn, err := ln.Accept() if err != nil { @@ -44,7 +37,6 @@ func ServeRESP(addr string, cache KV) error { } return err } - // Configure TCP for low latency if tc, ok := conn.(*net.TCPConn); ok { _ = tc.SetNoDelay(true) _ = tc.SetKeepAlive(true) @@ -54,98 +46,76 @@ func ServeRESP(addr string, cache KV) error { } } -func handleConn(conn net.Conn, cache KV) { +func handleConn(conn net.Conn, cache Cache) { defer conn.Close() - // Generous buffers for pipelining r := bufio.NewReaderSize(conn, 64*1024) w := bufio.NewWriterSize(conn, 64*1024) + for { cmd, args, perr := readRESPArray(r) if perr != nil { if perr == io.EOF || errors.Is(perr, net.ErrClosed) { return } - // Protocol error: close connection per Redis behavior return } if len(cmd) == 0 { - // Ignore empty continue } - // Fast upper-case compare for GET/SET without heap allocs - if len(cmd) == 3 && (cmd[0]|0x20) == 'g' && (cmd[1]|0x20) == 'e' && (cmd[2]|0x20) == 't' { - // GET key + + switch { + case len(cmd) == 3 && (cmd[0]|0x20) == 'g' && (cmd[1]|0x20) == 'e' && (cmd[2]|0x20) == 't': if len(args) != 1 { writeError(w, "wrong number of arguments for 'get'") - if w.Flush() != nil { - return - } - continue - } - key := b2s(args[0]) - val, found, expired := cache.Get(key) - if !found || expired { - writeBulkNil(w) } else { - writeBulk(w, val) - } - if w.Flush() != nil { - return + val, found, expired := cache.Get(b2s(args[0])) + if !found || expired { + writeBulkNil(w) + } else { + writeBulk(w, val) + } } - continue - } - if len(cmd) >= 3 && (cmd[0]|0x20) == 's' && (cmd[1]|0x20) == 'e' && (cmd[2]|0x20) == 't' { - // SET key value [EX seconds] + + case len(cmd) >= 3 && (cmd[0]|0x20) == 's' && (cmd[1]|0x20) == 'e' && (cmd[2]|0x20) == 't': if len(args) != 2 && len(args) != 4 { writeError(w, "wrong number of arguments for 'set'") - if w.Flush() != nil { - return - } - continue - } - key := b2s(args[0]) - value := args[1] - var ex uint64 - if len(args) == 4 { - // Expect EX seconds - if !bytes.EqualFold(args[2], []byte("EX")) { - writeError(w, "only EX option is supported") - if w.Flush() != nil { - return + } else { + key := b2s(args[0]) + value := args[1] + var ttl time.Duration + if len(args) == 4 { + if !bytes.EqualFold(args[2], []byte("EX")) { + writeError(w, "only EX option is supported") + if w.Flush() != nil { + return + } + continue } - continue - } - secs, err := parseUint(args[3]) - if err != nil { - writeError(w, "invalid expire seconds") - if w.Flush() != nil { - return + secs, err := parseUint(args[3]) + if err != nil { + writeError(w, "invalid expire seconds") + if w.Flush() != nil { + return + } + continue } - continue + ttl = time.Duration(secs) * time.Second } - ex = secs + _ = cache.Put(key, value, ttl) + writeSimpleString(w, "OK") } - _ = cache.Put(key, value, ex) - writeSimpleString(w, "OK") - if w.Flush() != nil { - return - } - continue + + default: + writeError(w, "unknown command") } - // Unknown command - writeError(w, "unknown command") + if w.Flush() != nil { return } } } -// RESP helpers - -// readRESPArray parses a RESP Array of Bulk Strings and returns command and args. -// It assumes arrays consisting only of bulk strings; inline protocol is not supported. func readRESPArray(r *bufio.Reader) (cmd []byte, args [][]byte, err error) { - // Expect '*' b, err := r.ReadByte() if err != nil { return nil, nil, err @@ -160,13 +130,11 @@ func readRESPArray(r *bufio.Reader) (cmd []byte, args [][]byte, err error) { if n <= 0 { return nil, nil, nil } - // First element is command bs, err := readBulkString(r) if err != nil { return nil, nil, err } cmd = bs - // Remaining are args if n > 1 { args = make([][]byte, 0, n-1) for i := 1; i < n; i++ { @@ -193,14 +161,12 @@ func readBulkString(r *bufio.Reader) ([]byte, error) { return nil, err } if n < 0 { - // Null bulk string return nil, nil } buf := make([]byte, n) if _, err := io.ReadFull(r, buf); err != nil { return nil, err } - // Read trailing CRLF if err := expectCRLF(r); err != nil { return nil, err } @@ -208,27 +174,18 @@ func readBulkString(r *bufio.Reader) ([]byte, error) { } func readIntCRLF(r *bufio.Reader) (int, error) { - // Read until CR line, err := r.ReadSlice('\r') if err != nil { return 0, err } - // Next must be '\n' if b, err := r.ReadByte(); err != nil || b != '\n' { if err == nil { err = io.ErrUnexpectedEOF } return 0, err } - // Trim trailing CR line = line[:len(line)-1] - // Parse signed/unsigned int - // Use strconv for correctness; line is small - i, err := strconv.Atoi(b2s(line)) - if err != nil { - return 0, err - } - return i, nil + return strconv.Atoi(b2s(line)) } func expectCRLF(r *bufio.Reader) error { @@ -271,7 +228,5 @@ func writeBulkNil(w *bufio.Writer) { w.WriteString("$-1\r\n") } -// b2s converts []byte to string with allocation. -// We intentionally avoid unsafe tricks for portability. func b2s(b []byte) string { return string(b) } func parseUint(b []byte) (uint64, error) { return strconv.ParseUint(string(b), 10, 64) } diff --git a/flashring/internal/shard/batch_reader.go b/flashring/internal/shard/batch_reader.go deleted file mode 100644 index 3896834b..00000000 --- a/flashring/internal/shard/batch_reader.go +++ /dev/null @@ -1,156 +0,0 @@ -package filecache - -import ( - "fmt" - "sort" - "sync" - "time" -) - -// ===========batching reads ========== -// ReadRequest represents a single read request -type ReadRequest struct { - Key string - Length uint16 - MemId uint32 - Offset uint32 - Result chan ReadResult -} - -// ReadResult contains the response for a read request -type ReadResult struct { - Found bool - Data []byte - TTL uint16 - Expired bool - ShouldRewrite bool - Error error -} - -// BatchReader handles batching of disk reads -type BatchReader struct { - requests chan *ReadRequest - batchWindow time.Duration - maxBatchSize int - shardCache *ShardCache - stopCh chan struct{} - wg sync.WaitGroup -} - -// Config for BatchReader -type BatchReaderConfig struct { - BatchWindow time.Duration // e.g., 5-10μs - MaxBatchSize int // e.g., 32-64 requests -} - -func NewBatchReader(config BatchReaderConfig, sc *ShardCache) *BatchReader { - br := &BatchReader{ - requests: make(chan *ReadRequest, config.MaxBatchSize*2), - batchWindow: config.BatchWindow, - maxBatchSize: config.MaxBatchSize, - shardCache: sc, - stopCh: make(chan struct{}), - } - - // Start batch processor goroutine - br.wg.Add(1) - go br.processBatches() - - return br -} - -func (br *BatchReader) processBatches() { - defer br.wg.Done() - - for { - select { - case <-br.stopCh: - return - case firstReq := <-br.requests: - batch := br.collectBatch(firstReq) - br.shardCache.Stats.BatchTracker.RecordBatchSize(len(batch)) - br.executeBatch(batch) - } - } -} - -func (br *BatchReader) collectBatch(firstReq *ReadRequest) []*ReadRequest { - batch := make([]*ReadRequest, 0, br.maxBatchSize) - batch = append(batch, firstReq) - - timer := time.NewTimer(br.batchWindow) - - for len(batch) < br.maxBatchSize { - select { - case req := <-br.requests: - batch = append(batch, req) - case <-timer.C: - return batch - } - } - - return batch -} - -func (br *BatchReader) executeBatch(batch []*ReadRequest) { - // Separate memtable hits from disk reads - diskReads := make([]*ReadRequest, 0, len(batch)) - - for _, req := range batch { - mt := br.shardCache.mm.GetMemtableById(req.MemId) - if mt != nil { - // Fast path: memtable hit - buf, exists := mt.GetBufForRead(int(req.Offset), req.Length) - if exists { - result := br.shardCache.processBuffer(req.Key, buf, req.Length) - req.Result <- result - continue - } - } - // Needs disk read - diskReads = append(diskReads, req) - } - - if len(diskReads) == 0 { - return - } - - // Sort disk reads by file offset - sort.Slice(diskReads, func(i, j int) bool { - offsetI := uint64(diskReads[i].MemId)*uint64(br.shardCache.mm.Capacity) + - uint64(diskReads[i].Offset) - offsetJ := uint64(diskReads[j].MemId)*uint64(br.shardCache.mm.Capacity) + - uint64(diskReads[j].Offset) - return offsetI < offsetJ - }) - - // Execute disk reads (could be parallelized or merged here) - var wg sync.WaitGroup - for _, req := range diskReads { - wg.Add(1) - go func(r *ReadRequest) { - defer wg.Done() - result := br.executeReadFromDisk(r) - r.Result <- result - }(req) - } - wg.Wait() -} - -func (br *BatchReader) executeReadFromDisk(req *ReadRequest) ReadResult { - buf := make([]byte, req.Length) - fileOffset := uint64(req.MemId)*uint64(br.shardCache.mm.Capacity) + - uint64(req.Offset) - - n := br.shardCache.readFromDisk(int64(fileOffset), req.Length, buf) - if n != int(req.Length) { - return ReadResult{Error: fmt.Errorf("bad read length")} - } - - return br.shardCache.processBuffer(req.Key, buf, req.Length) -} - -func (br *BatchReader) Close() { - close(br.stopCh) - br.wg.Wait() -} diff --git a/flashring/internal/shard/batch_reader_v2.go b/flashring/internal/shard/batch_reader_v2.go deleted file mode 100644 index 2aa99b09..00000000 --- a/flashring/internal/shard/batch_reader_v2.go +++ /dev/null @@ -1,132 +0,0 @@ -package filecache - -import ( - "fmt" - "sync" - "time" -) - -type ReadRequestV2 struct { - Key string - Result chan ReadResultV2 -} - -type ReadResultV2 struct { - Found bool - Data []byte - TTL uint16 - Expired bool - ShouldRewrite bool - Error error -} - -type WriteRequestV2 struct { - Key string - Value []byte - ExptimeInMinutes uint16 - Result chan error -} - -type BatchReaderV2 struct { - Requests chan *ReadRequestV2 - batchWindow time.Duration - maxBatchSize int - shardCache *ShardCache - stopCh chan struct{} - wg sync.WaitGroup - shardLock *sync.RWMutex -} - -type BatchReaderV2Config struct { - BatchWindow time.Duration - MaxBatchSize int -} - -var ReadRequestPool = sync.Pool{ - New: func() interface{} { - return &ReadRequestV2{} - }, -} - -var ReadResultPool = sync.Pool{ - New: func() interface{} { - return make(chan ReadResultV2, 1) - }, -} - -var ErrorPool = sync.Pool{ - New: func() interface{} { - return make(chan error, 1) - }, -} - -var BufPool = sync.Pool{ - New: func() interface{} { - // Allocate max expected size - use pointer to avoid allocation on Put - buf := make([]byte, 4096) - return &buf - }, -} - -func NewBatchReaderV2(config BatchReaderV2Config, sc *ShardCache, sl *sync.RWMutex) *BatchReaderV2 { - br := &BatchReaderV2{ - Requests: make(chan *ReadRequestV2, config.MaxBatchSize*2), - batchWindow: config.BatchWindow, - maxBatchSize: config.MaxBatchSize, - shardCache: sc, - stopCh: make(chan struct{}), - shardLock: sl, - } - - // Start batch processor goroutine - br.wg.Add(1) - go br.processBatchesV2() - - return br -} - -func (br *BatchReaderV2) processBatchesV2() { - defer br.wg.Done() - - for { - select { - case <-br.stopCh: - return - case firstReq := <-br.Requests: - batch := br.collectBatchV2(firstReq) - br.shardCache.Stats.BatchTracker.RecordBatchSize(len(batch)) - br.executeBatchV2(batch) - } - } -} - -func (br *BatchReaderV2) collectBatchV2(firstReq *ReadRequestV2) []*ReadRequestV2 { - batch := make([]*ReadRequestV2, 0, br.maxBatchSize) - batch = append(batch, firstReq) - - timer := time.NewTimer(br.batchWindow) - - for len(batch) < br.maxBatchSize { - select { - case req := <-br.Requests: - batch = append(batch, req) - case <-timer.C: - return batch - } - } - - return batch -} - -func (br *BatchReaderV2) executeBatchV2(batch []*ReadRequestV2) { - br.shardLock.RLock() - defer br.shardLock.RUnlock() - for _, req := range batch { - found, data, ttl, expired, shouldRewrite := br.shardCache.Get(req.Key) - if !found { - req.Result <- ReadResultV2{Error: fmt.Errorf("key not found")} - } else { - req.Result <- ReadResultV2{Found: found, Data: data, TTL: ttl, Expired: expired, ShouldRewrite: shouldRewrite} - } - } -} diff --git a/flashring/internal/shard/batch_tracker.go b/flashring/internal/shard/batch_tracker.go deleted file mode 100644 index 5658d0e2..00000000 --- a/flashring/internal/shard/batch_tracker.go +++ /dev/null @@ -1,55 +0,0 @@ -package filecache - -import ( - "sort" - "sync" -) - -type BatchTracker struct { - mu sync.RWMutex - getBatch []int - maxSamples int - getIndex int -} - -// const defaultMaxSamples = 100000 - -func NewBatchTracker() *BatchTracker { - return &BatchTracker{ - getBatch: make([]int, defaultMaxSamples), - maxSamples: defaultMaxSamples, - } -} - -func (bt *BatchTracker) RecordBatchSize(batchSize int) { - bt.mu.Lock() - defer bt.mu.Unlock() - bt.getBatch[bt.getIndex] = batchSize - bt.getIndex = (bt.getIndex + 1) % bt.maxSamples -} - -func (bt *BatchTracker) GetBatchSizePercentiles() (p25, p50, p99 int) { - bt.mu.RLock() - defer bt.mu.RUnlock() - - samples := bt.getIndex - if samples > int(bt.maxSamples) { - samples = int(bt.maxSamples) - } - - if samples == 0 { - return 0, 0, 0 - } - - batchSizesCopy := make([]int, samples) - copy(batchSizesCopy, bt.getBatch[:samples]) - sort.Slice(batchSizesCopy, func(i, j int) bool { - return batchSizesCopy[i] < batchSizesCopy[j] - }) - - p25 = batchSizesCopy[int(float64(samples)*0.25)] - p50 = batchSizesCopy[int(float64(samples)*0.50)] - p99 = batchSizesCopy[int(float64(samples)*0.99)] - - return p25, p50, p99 -} diff --git a/flashring/internal/shard/latency_tracker.go b/flashring/internal/shard/latency_tracker.go deleted file mode 100644 index eeb109c8..00000000 --- a/flashring/internal/shard/latency_tracker.go +++ /dev/null @@ -1,96 +0,0 @@ -package filecache - -import ( - "sort" - "sync" - "time" -) - -type LatencyTracker struct { - mu sync.RWMutex - getLatencies []time.Duration - putLatencies []time.Duration - maxSamples int - getIndex int - putIndex int - getCount int64 - putCount int64 -} - -const defaultMaxSamples = 100000 - -func NewLatencyTracker() *LatencyTracker { - return &LatencyTracker{ - getLatencies: make([]time.Duration, defaultMaxSamples), - putLatencies: make([]time.Duration, defaultMaxSamples), - maxSamples: defaultMaxSamples, - } -} - -func (lt *LatencyTracker) RecordGet(duration time.Duration) { - lt.mu.Lock() - defer lt.mu.Unlock() - lt.getLatencies[lt.getIndex] = duration - lt.getIndex = (lt.getIndex + 1) % lt.maxSamples - lt.getCount++ -} - -func (lt *LatencyTracker) RecordPut(duration time.Duration) { - lt.mu.Lock() - defer lt.mu.Unlock() - lt.putLatencies[lt.putIndex] = duration - lt.putIndex = (lt.putIndex + 1) % lt.maxSamples - lt.putCount++ -} - -func (lt *LatencyTracker) GetLatencyPercentiles() (p25, p50, p99 time.Duration) { - lt.mu.RLock() - defer lt.mu.RUnlock() - - samples := lt.getCount - if samples > int64(lt.maxSamples) { - samples = int64(lt.maxSamples) - } - - if samples == 0 { - return 0, 0, 0 - } - - latenciesCopy := make([]time.Duration, samples) - copy(latenciesCopy, lt.getLatencies[:samples]) - sort.Slice(latenciesCopy, func(i, j int) bool { - return latenciesCopy[i] < latenciesCopy[j] - }) - - p25 = latenciesCopy[int(float64(samples)*0.25)] - p50 = latenciesCopy[int(float64(samples)*0.50)] - p99 = latenciesCopy[int(float64(samples)*0.99)] - - return p25, p50, p99 -} - -func (lt *LatencyTracker) PutLatencyPercentiles() (p25, p50, p99 time.Duration) { - lt.mu.RLock() - defer lt.mu.RUnlock() - - samples := lt.putCount - if samples > int64(lt.maxSamples) { - samples = int64(lt.maxSamples) - } - - if samples == 0 { - return 0, 0, 0 - } - - latenciesCopy := make([]time.Duration, samples) - copy(latenciesCopy, lt.putLatencies[:samples]) - sort.Slice(latenciesCopy, func(i, j int) bool { - return latenciesCopy[i] < latenciesCopy[j] - }) - - p25 = latenciesCopy[int(float64(samples)*0.25)] - p50 = latenciesCopy[int(float64(samples)*0.50)] - p99 = latenciesCopy[int(float64(samples)*0.99)] - - return p25, p50, p99 -} diff --git a/flashring/internal/shard/shard_cache.go b/flashring/internal/shard/shard_cache.go index 78e19deb..ca501f6d 100644 --- a/flashring/internal/shard/shard_cache.go +++ b/flashring/internal/shard/shard_cache.go @@ -8,43 +8,24 @@ import ( "github.com/Meesho/BharatMLStack/flashring/internal/allocators" "github.com/Meesho/BharatMLStack/flashring/internal/fs" - indices "github.com/Meesho/BharatMLStack/flashring/internal/indicesV3" + "github.com/Meesho/BharatMLStack/flashring/internal/index" + "github.com/Meesho/BharatMLStack/flashring/internal/iouring" "github.com/Meesho/BharatMLStack/flashring/internal/maths" "github.com/Meesho/BharatMLStack/flashring/internal/memtables" + "github.com/Meesho/BharatMLStack/flashring/pkg/metrics" "github.com/rs/zerolog/log" ) type ShardCache struct { - keyIndex *indices.Index + keyIndex *index.Index file *fs.WrapAppendFile + iouringReader *iouring.ParallelBatchIoUringReader mm *memtables.MemtableManager readPageAllocator *allocators.SlabAlignedPageAllocator - dm *indices.DeleteManager + dm *index.DeleteManager predictor *maths.Predictor startAt int64 - Stats *Stats - - //batching reads - BatchReader *BatchReaderV2 - - //Lockless read and write - ReadCh chan *ReadRequestV2 - WriteCh chan *WriteRequestV2 -} - -type Stats struct { - KeyNotFoundCount int - KeyExpiredCount int - BadDataCount int - BadLengthCount int - BadCR32Count int - BadKeyCount int - MemIdCount map[uint32]int - LastDeletedMemId uint32 - DeletedKeyCount int - BadCRCMemIds map[uint32]int - BadKeyMemIds map[uint32]int - BatchTracker *BatchTracker + ShardIdx uint32 } type ShardCacheConfig struct { @@ -56,17 +37,20 @@ type ShardCacheConfig struct { MaxFileSize int64 BlockSize int Directory string - AsyncReadWorkers int - AsyncQueueDepth int Predictor *maths.Predictor - //batching reads - EnableBatching bool - BatchWindow time.Duration - MaxBatchSize int + // Global batched io_uring reader (shared across all shards). + IoUringReader *iouring.ParallelBatchIoUringReader + + // Dedicated io_uring writer for batched writes (shared across all shards). + IoUringWriter *iouring.IoUringWriter + + // FlushStaggerOffset pre-advances the first memtable so shards flush at + // staggered times instead of all at once. + FlushStaggerOffset int } -func NewShardCache(config ShardCacheConfig, sl *sync.RWMutex) *ShardCache { +func NewShardCache(config ShardCacheConfig, sl *sync.RWMutex) (*ShardCache, error) { filename := fmt.Sprintf("%s/%d.bin", config.Directory, time.Now().UnixNano()) punchHoleSize := config.MemtableSize fsConf := fs.FileConfig{ @@ -77,25 +61,33 @@ func NewShardCache(config ShardCacheConfig, sl *sync.RWMutex) *ShardCache { } file, err := fs.NewWrapAppendFile(fsConf) if err != nil { - log.Panic().Err(err).Msg("Failed to create file") + return nil, fmt.Errorf("create shard file: %w", err) } - memtableManager, err := memtables.NewMemtableManager(file, config.MemtableSize) + memtableManager, err := memtables.NewMemtableManager(file, config.MemtableSize, config.FlushStaggerOffset) if err != nil { - log.Panic().Err(err).Msg("Failed to create memtable manager") + file.Close() + return nil, fmt.Errorf("create memtable manager: %w", err) } - ki := indices.NewIndex(0, config.RbInitial, config.RbMax, config.DeleteAmortizedStep) + ki := index.NewIndex(0, config.RbInitial, config.RbMax, config.DeleteAmortizedStep, sl) + sizeClasses := make([]allocators.SizeClass, 0) i := fs.BLOCK_SIZE - iMax := (1 << 16) + minCount := 24 + iMax := (1 << 17) for i < iMax { - sizeClasses = append(sizeClasses, allocators.SizeClass{Size: i, MinCount: 1000}) + sizeClasses = append(sizeClasses, allocators.SizeClass{Size: i, MinCount: minCount}) i *= 2 + minCount /= 2 } readPageAllocator, err := allocators.NewSlabAlignedPageAllocator(allocators.SlabAlignedPageAllocatorConfig{SizeClasses: sizeClasses}) if err != nil { - log.Panic().Err(err).Msg("Failed to create read page allocator") + file.Close() + return nil, fmt.Errorf("create read page allocator: %w", err) } - dm := indices.NewDeleteManager(ki, file, config.DeleteAmortizedStep) + dm := index.NewDeleteManager(ki, file, config.DeleteAmortizedStep) + + file.WriteRing = config.IoUringWriter + sc := &ShardCache{ keyIndex: ki, mm: memtableManager, @@ -104,51 +96,21 @@ func NewShardCache(config ShardCacheConfig, sl *sync.RWMutex) *ShardCache { dm: dm, predictor: config.Predictor, startAt: time.Now().Unix(), - Stats: &Stats{ - MemIdCount: make(map[uint32]int), - BadCRCMemIds: make(map[uint32]int), - BadKeyMemIds: make(map[uint32]int), - BatchTracker: NewBatchTracker(), - }, } - // Initialize batch reader if enabled - if config.EnableBatching { - sc.BatchReader = NewBatchReaderV2(BatchReaderV2Config{ - BatchWindow: config.BatchWindow, - MaxBatchSize: config.MaxBatchSize, - }, sc, sl) + if config.IoUringReader == nil { + file.Close() + return nil, fmt.Errorf("BatchIoUringReader is required") } + sc.iouringReader = config.IoUringReader - sc.ReadCh = make(chan *ReadRequestV2, 500) - sc.WriteCh = make(chan *WriteRequestV2, 500) - - go sc.startReadWriteRoutines() - - return sc -} - -// function that starts go routine to process the read and write requests -func (fc *ShardCache) startReadWriteRoutines() { - go func() { - for { - select { - case writeReq := <-fc.WriteCh: // Writes get priority - err := fc.Put(writeReq.Key, writeReq.Value, writeReq.ExptimeInMinutes) - writeReq.Result <- err - case readReq := <-fc.ReadCh: - found, data, ttl, expired, shouldRewrite := fc.GetSlowPath(readReq.Key) - readReq.Result <- ReadResultV2{Found: found, Data: data, TTL: ttl, Expired: expired, ShouldRewrite: shouldRewrite, Error: nil} - } - } - }() + return sc, nil } func (fc *ShardCache) Put(key string, value []byte, ttlMinutes uint16) error { size := 4 + len(key) + len(value) mt, mtId, _ := fc.mm.GetMemtable() - err := fc.dm.ExecuteDeleteIfNeeded() - if err != nil { + if err := fc.dm.ExecuteDeleteIfNeeded(); err != nil { return err } buf, offset, length, readyForFlush := mt.GetBufForAppend(uint16(size)) @@ -160,220 +122,296 @@ func (fc *ShardCache) Put(key string, value []byte, ttlMinutes uint16) error { copy(buf[4:], key) copy(buf[4+len(key):], value) crc := crc32.ChecksumIEEE(buf[4:]) - indices.ByteOrder.PutUint32(buf[0:4], crc) + index.ByteOrder.PutUint32(buf[0:4], crc) fc.keyIndex.Put(key, length, ttlMinutes, mtId, uint32(offset)) fc.dm.IncMemtableKeyCount(mtId) - fc.Stats.MemIdCount[mtId]++ return nil } func (fc *ShardCache) Get(key string) (bool, []byte, uint16, bool, bool) { length, lastAccess, remainingTTL, freq, memId, offset, status := fc.keyIndex.Get(key) - if status == indices.StatusNotFound { - fc.Stats.KeyNotFoundCount++ + if status == index.StatusNotFound { + metrics.Incr(metrics.KEY_KEY_NOT_FOUND_COUNT, []string{}) return false, nil, 0, false, false } - if status == indices.StatusExpired { - fc.Stats.KeyExpiredCount++ + metrics.Timing(metrics.KEY_DATA_LENGTH, time.Duration(length), []string{}) + + if status == index.StatusExpired { + metrics.Incr(metrics.KEY_KEY_EXPIRED_COUNT, []string{}) return false, nil, 0, true, false } _, currMemId, _ := fc.mm.GetMemtable() shouldReWrite := fc.predictor.Predict(uint64(freq), uint64(lastAccess), memId, currMemId) - exists := true var buf []byte - memtableExists := true mt := fc.mm.GetMemtableById(memId) if mt == nil { - memtableExists = false - } - if !memtableExists { - bufPtr := BufPool.Get().(*[]byte) - buf = *bufPtr - defer BufPool.Put(bufPtr) + metrics.Incr(metrics.KEY_MEMTABLE_MISS, []string{}) + buf = make([]byte, length) fileOffset := uint64(memId)*uint64(fc.mm.Capacity) + uint64(offset) - n := fc.readFromDisk(int64(fileOffset), length, buf) + n := fc.readFromDiskAsync(int64(fileOffset), length, buf) if n != int(length) { - fc.Stats.BadLengthCount++ + metrics.Incr(metrics.KEY_BAD_LENGTH_COUNT, []string{}) return false, nil, 0, false, shouldReWrite } } else { + metrics.Incr(metrics.KEY_MEMTABLE_HIT, []string{}) + var exists bool buf, exists = mt.GetBufForRead(int(offset), length) if !exists { - panic("memtable exists but buf not found") + return false, nil, 0, false, shouldReWrite } } - gotCR32 := indices.ByteOrder.Uint32(buf[0:4]) - computedCR32 := crc32.ChecksumIEEE(buf[4:]) + gotCR32 := index.ByteOrder.Uint32(buf[0:4]) + computedCR32 := crc32.ChecksumIEEE(buf[4:length]) gotKey := string(buf[4 : 4+len(key)]) if gotCR32 != computedCR32 { - fc.Stats.BadCR32Count++ - fc.Stats.BadCRCMemIds[memId]++ + metrics.Incr(metrics.KEY_BAD_CR32_COUNT, []string{}) return false, nil, 0, false, shouldReWrite } if gotKey != key { - fc.Stats.BadKeyCount++ - fc.Stats.BadKeyMemIds[memId]++ + metrics.Incr(metrics.KEY_BAD_KEY_COUNT, []string{}) return false, nil, 0, false, shouldReWrite } valLen := int(length) - 4 - len(key) return true, buf[4+len(key) : 4+len(key)+valLen], remainingTTL, false, shouldReWrite } -// GetFastPath attempts to read from memtable only (no disk I/O). -// Returns: (found, data, ttl, expired, needsSlowPath) -// If needsSlowPath is true, caller should use GetSlowPath for disk read. -func (fc *ShardCache) GetFastPath(key string) (bool, []byte, uint16, bool, bool) { - length, lastAccess, remainingTTL, freq, memId, offset, status := fc.keyIndex.Get(key) - if status == indices.StatusNotFound { - fc.Stats.KeyNotFoundCount++ - return false, nil, 0, false, false // needsSlowPath = false (not found) - } +func (fc *ShardCache) readFromDiskAsync(fileOffset int64, length uint16, buf []byte) int { + alignedStart, alignedSize := fs.AlignRange(fileOffset, int(length), fs.BLOCK_SIZE) + page := fc.readPageAllocator.Get(int(alignedSize)) - if status == indices.StatusExpired { - fc.Stats.KeyExpiredCount++ - return false, nil, 0, true, false // needsSlowPath = false (expired) - } + readBuf := page.Buf[:alignedSize] - // Check if data is in memtable - mt := fc.mm.GetMemtableById(memId) - if mt == nil { - // Data not in memtable, needs disk read - signal slow path needed - return false, nil, remainingTTL, false, true // needsSlowPath = true + var n int + var err error + var validOffset int64 + validOffset, err = fc.file.ValidateReadOffset(alignedStart, int(alignedSize)) + if err == nil { + n, err = fc.iouringReader.Submit(fc.file.ReadFd, readBuf, uint64(validOffset)) } - // Fast path: read from memtable - buf, exists := mt.GetBufForRead(int(offset), length) - if !exists { - panic("memtable exists but buf not found") + if err != nil || n != int(alignedSize) { + if err != nil && err != fs.ErrFileOffsetOutOfRange { + log.Warn().Err(err). + Int64("offset", alignedStart). + Int64("alignedReadSize", alignedSize). + Int("n", n). + Msg("io_uring pread failed") + } + fc.readPageAllocator.Put(page) + return 0 } - // Validate CRC and key - gotCR32 := indices.ByteOrder.Uint32(buf[0:4]) - computedCR32 := crc32.ChecksumIEEE(buf[4:]) - if gotCR32 != computedCR32 { - fc.Stats.BadCR32Count++ - fc.Stats.BadCRCMemIds[memId]++ - _, currMemId, _ := fc.mm.GetMemtable() - shouldReWrite := fc.predictor.Predict(uint64(freq), uint64(lastAccess), memId, currMemId) - _ = shouldReWrite // Not returning shouldReWrite in fast path for simplicity - return false, nil, 0, false, false - } + start := int(fileOffset - alignedStart) + copied := copy(buf, page.Buf[start:start+int(length)]) + fc.readPageAllocator.Put(page) + return copied +} - gotKey := string(buf[4 : 4+len(key)]) - if gotKey != key { - fc.Stats.BadKeyCount++ - fc.Stats.BadKeyMemIds[memId]++ - return false, nil, 0, false, false - } +func (fc *ShardCache) Flush() { + fc.mm.Flush() +} - valLen := int(length) - 4 - len(key) - return true, buf[4+len(key) : 4+len(key)+valLen], remainingTTL, false, false // needsSlowPath = false +func (fc *ShardCache) Close() { + fc.file.Close() +} + +// DeleteKey removes the key from the index only. Debug use only. +func (fc *ShardCache) DeleteKey(key string) bool { + return fc.keyIndex.DeleteKey(key) } -// GetSlowPath reads data from disk. Used when GetFastPath indicates needsSlowPath. -// Returns: (found, data, ttl, expired, shouldRewrite) -func (fc *ShardCache) GetSlowPath(key string) (bool, []byte, uint16, bool, bool) { +func (fc *ShardCache) GetRingBufferActiveEntries() int { + return fc.keyIndex.GetRB().ActiveEntries() +} + +// --------------------------------------------------------------------------- +// MGet support — separate functions that duplicate parts of Get/readFromDiskAsync +// to allow the caller to split index lookups from disk I/O. +// --------------------------------------------------------------------------- + +// MGetMeta holds the result of an index lookup for batch gets. +type MGetMeta struct { + Found bool + Expired bool + ShouldReWrite bool + RemainingTTL uint16 + // Value is non-nil when the data was found in a memtable (no disk read needed). + Value []byte + NeedsDiskRead bool + Length uint16 + FileOffset int64 +} + +// PendingRead represents an in-flight async io_uring disk read. +type PendingRead struct { + done <-chan iouring.ReadResult + page *fs.AlignedPage + alignedSize int + pageOffset int + length uint16 +} + +// GetMetaForMGet performs an index lookup and memtable check for a single key +// without issuing any disk I/O. This is the first phase of an MGet operation. +func (fc *ShardCache) GetMetaForMGet(key string) MGetMeta { length, lastAccess, remainingTTL, freq, memId, offset, status := fc.keyIndex.Get(key) - if status == indices.StatusNotFound { - fc.Stats.KeyNotFoundCount++ - return false, nil, 0, false, false + + if status == index.StatusNotFound { + metrics.Incr(metrics.KEY_KEY_NOT_FOUND_COUNT, []string{}) + return MGetMeta{} } - if status == indices.StatusExpired { - fc.Stats.KeyExpiredCount++ - return false, nil, 0, true, false + metrics.Timing(metrics.KEY_DATA_LENGTH, time.Duration(length), []string{}) + + if status == index.StatusExpired { + metrics.Incr(metrics.KEY_KEY_EXPIRED_COUNT, []string{}) + return MGetMeta{Expired: true} } _, currMemId, _ := fc.mm.GetMemtable() shouldReWrite := fc.predictor.Predict(uint64(freq), uint64(lastAccess), memId, currMemId) - // Check memtable again (might have changed since fast path check) mt := fc.mm.GetMemtableById(memId) if mt != nil { - // Data is now in memtable, use fast path logic + metrics.Incr(metrics.KEY_MEMTABLE_HIT, []string{}) buf, exists := mt.GetBufForRead(int(offset), length) if !exists { - panic("memtable exists but buf not found") + return MGetMeta{ShouldReWrite: shouldReWrite} + } + return MGetMeta{ + Found: true, + Value: buf, + Length: length, + RemainingTTL: remainingTTL, + ShouldReWrite: shouldReWrite, } - return fc.validateAndReturnBuffer(key, buf, length, memId, remainingTTL, shouldReWrite) } - // Read from disk - bufPtr := BufPool.Get().(*[]byte) - buf := *bufPtr - defer BufPool.Put(bufPtr) - fileOffset := uint64(memId)*uint64(fc.mm.Capacity) + uint64(offset) - n := fc.readFromDisk(int64(fileOffset), length, buf) - if n != int(length) { - fc.Stats.BadLengthCount++ - return false, nil, 0, false, shouldReWrite - } + metrics.Incr(metrics.KEY_MEMTABLE_MISS, []string{}) + fileOffset := int64(uint64(memId)*uint64(fc.mm.Capacity) + uint64(offset)) - return fc.validateAndReturnBuffer(key, buf, length, memId, remainingTTL, shouldReWrite) + return MGetMeta{ + Found: true, + NeedsDiskRead: true, + Length: length, + FileOffset: fileOffset, + RemainingTTL: remainingTTL, + ShouldReWrite: shouldReWrite, + } } -// validateAndReturnBuffer validates CRC and key, then returns the value -func (fc *ShardCache) validateAndReturnBuffer(key string, buf []byte, length uint16, memId uint32, remainingTTL uint16, shouldReWrite bool) (bool, []byte, uint16, bool, bool) { - gotCR32 := indices.ByteOrder.Uint32(buf[0:4]) - computedCR32 := crc32.ChecksumIEEE(buf[4:]) - if gotCR32 != computedCR32 { - fc.Stats.BadCR32Count++ - fc.Stats.BadCRCMemIds[memId]++ - return false, nil, 0, false, shouldReWrite - } +// SubmitDiskReadAsync enqueues an aligned disk read via io_uring without +// blocking for completion. Returns a PendingRead handle for CollectDiskRead. +func (fc *ShardCache) SubmitDiskReadAsync(fileOffset int64, length uint16) (*PendingRead, error) { + alignedStart, alignedSize := fs.AlignRange(fileOffset, int(length), fs.BLOCK_SIZE) + page := fc.readPageAllocator.Get(int(alignedSize)) + readBuf := page.Buf[:alignedSize] - gotKey := string(buf[4 : 4+len(key)]) - if gotKey != key { - fc.Stats.BadKeyCount++ - fc.Stats.BadKeyMemIds[memId]++ - return false, nil, 0, false, shouldReWrite + validOffset, err := fc.file.ValidateReadOffset(alignedStart, int(alignedSize)) + if err != nil { + fc.readPageAllocator.Put(page) + return nil, err } - valLen := int(length) - 4 - len(key) - return true, buf[4+len(key) : 4+len(key)+valLen], remainingTTL, false, shouldReWrite + done := fc.iouringReader.SubmitAsync(fc.file.ReadFd, readBuf, uint64(validOffset)) + + return &PendingRead{ + done: done, + page: page, + alignedSize: int(alignedSize), + pageOffset: int(fileOffset - alignedStart), + length: length, + }, nil } -func (fc *ShardCache) readFromDisk(fileOffset int64, length uint16, buf []byte) int { - alignedStartOffset := (fileOffset / fs.BLOCK_SIZE) * fs.BLOCK_SIZE - endndOffset := fileOffset + int64(length) - endAlignedOffset := ((endndOffset + fs.BLOCK_SIZE - 1) / fs.BLOCK_SIZE) * fs.BLOCK_SIZE - alignedReadSize := endAlignedOffset - alignedStartOffset - page := fc.readPageAllocator.Get(int(alignedReadSize)) - fc.file.Pread(alignedStartOffset, page.Buf) - start := int(fileOffset - alignedStartOffset) - n := copy(buf, page.Buf[start:start+int(length)]) - fc.readPageAllocator.Put(page) - return n +// CollectDiskRead blocks until the pending io_uring read completes, copies +// the result into a new buffer, and frees the aligned page. Returns nil on failure. +func (fc *ShardCache) CollectDiskRead(pr *PendingRead) []byte { + result := <-pr.done + defer fc.readPageAllocator.Put(pr.page) + + if result.Err != nil || result.N != pr.alignedSize { + if result.Err != nil { + log.Warn().Err(result.Err).Msg("io_uring pread failed in MGet") + } + return nil + } + + buf := make([]byte, pr.length) + copy(buf, pr.page.Buf[pr.pageOffset:pr.pageOffset+int(pr.length)]) + return buf } -func (fc *ShardCache) GetRingBufferActiveEntries() int { - return fc.keyIndex.GetRB().ActiveEntries() +// CoalescedPendingRead represents an in-flight async io_uring disk read that +// covers a merged aligned region shared by multiple keys. +type CoalescedPendingRead struct { + done <-chan iouring.ReadResult + page *fs.AlignedPage + alignedSize int } -// batching reads -func (fc *ShardCache) processBuffer(key string, buf []byte, length uint16) ReadResult { - gotCR32 := indices.ByteOrder.Uint32(buf[0:4]) - computedCR32 := crc32.ChecksumIEEE(buf[4:]) - gotKey := string(buf[4 : 4+len(key)]) +// SubmitCoalescedReadAsync enqueues a single aligned disk read that covers +// multiple keys whose file offsets fall within [alignedStart, alignedStart+alignedSize). +func (fc *ShardCache) SubmitCoalescedReadAsync(alignedStart int64, alignedSize int) (*CoalescedPendingRead, error) { + page := fc.readPageAllocator.Get(alignedSize) + readBuf := page.Buf[:alignedSize] - if gotCR32 != computedCR32 { - fc.Stats.BadCR32Count++ - return ReadResult{Found: false, Error: fmt.Errorf("crc mismatch")} + validOffset, err := fc.file.ValidateReadOffset(alignedStart, alignedSize) + if err != nil { + fc.readPageAllocator.Put(page) + return nil, err } - if gotKey != key { - fc.Stats.BadKeyCount++ - return ReadResult{Found: false, Error: fmt.Errorf("key mismatch")} + + done := fc.iouringReader.SubmitAsync(fc.file.ReadFd, readBuf, uint64(validOffset)) + + return &CoalescedPendingRead{ + done: done, + page: page, + alignedSize: alignedSize, + }, nil +} + +// CollectCoalescedRead blocks until the coalesced io_uring read completes and +// returns the full aligned buffer. The caller extracts individual key regions +// using each key's offset relative to the aligned start. +func (fc *ShardCache) CollectCoalescedRead(pr *CoalescedPendingRead) []byte { + result := <-pr.done + defer fc.readPageAllocator.Put(pr.page) + + if result.Err != nil || result.N != pr.alignedSize { + if result.Err != nil { + log.Warn().Err(result.Err).Msg("io_uring coalesced pread failed in MGet") + } + return nil } - valLen := int(length) - 4 - len(key) - value := make([]byte, valLen) - copy(value, buf[4+len(key):4+len(key)+valLen]) + buf := make([]byte, pr.alignedSize) + copy(buf, pr.page.Buf[:pr.alignedSize]) + return buf +} - return ReadResult{ - Found: true, - Data: value, +// ValidateAndExtract checks the CRC32 and key, then extracts the value from +// a raw data buffer. Used by MGet for both memtable and disk-read results. +func (fc *ShardCache) ValidateAndExtract(buf []byte, key string, length uint16) ([]byte, bool) { + if int(length) > len(buf) || length < 4 { + metrics.Incr(metrics.KEY_BAD_LENGTH_COUNT, []string{}) + return nil, false } + gotCRC := index.ByteOrder.Uint32(buf[0:4]) + computedCRC := crc32.ChecksumIEEE(buf[4:length]) + if gotCRC != computedCRC { + metrics.Incr(metrics.KEY_BAD_CR32_COUNT, []string{}) + return nil, false + } + gotKey := string(buf[4 : 4+len(key)]) + if gotKey != key { + metrics.Incr(metrics.KEY_BAD_KEY_COUNT, []string{}) + return nil, false + } + valLen := int(length) - 4 - len(key) + return buf[4+len(key) : 4+len(key)+valLen], true } diff --git a/flashring/pkg/async/context.go b/flashring/pkg/async/context.go deleted file mode 100644 index 0c01bd35..00000000 --- a/flashring/pkg/async/context.go +++ /dev/null @@ -1 +0,0 @@ -package async diff --git a/flashring/pkg/cache/badger.go b/flashring/pkg/cache/badger.go new file mode 100644 index 00000000..f53f0c3e --- /dev/null +++ b/flashring/pkg/cache/badger.go @@ -0,0 +1,52 @@ +package cache + +import ( + "time" + + badger "github.com/dgraph-io/badger/v4" +) + +type Badger struct { + cache *badger.DB +} + +func NewBadger(config Config, dir string) (*Badger, error) { + options := badger.DefaultOptions(dir) + options.MetricsEnabled = false + options.BlockCacheSize = 1024 << 20 + options.IndexCacheSize = 512 << 20 + options.NumMemtables = 40 + options.MemTableSize = 1024 << 20 + options.ValueThreshold = 1024 + options.SyncWrites = false + + db, err := badger.Open(options) + if err != nil { + return nil, err + } + return &Badger{cache: db}, nil +} + +func (b *Badger) Put(key string, value []byte, ttl time.Duration) error { + return b.cache.Update(func(txn *badger.Txn) error { + entry := badger.NewEntry([]byte(key), value).WithTTL(ttl) + return txn.SetEntry(entry) + }) +} + +func (b *Badger) Get(key string) ([]byte, bool, bool) { + var val []byte + err := b.cache.View(func(txn *badger.Txn) error { + item, err := txn.Get([]byte(key)) + if err != nil { + return err + } + val, err = item.ValueCopy(val) + return err + }) + return val, err != badger.ErrKeyNotFound, false +} + +func (b *Badger) Close() error { + return b.cache.Close() +} diff --git a/flashring/pkg/cache/cache.go b/flashring/pkg/cache/cache.go new file mode 100644 index 00000000..0bcb3826 --- /dev/null +++ b/flashring/pkg/cache/cache.go @@ -0,0 +1,457 @@ +package cache + +import ( + "fmt" + "os" + "path/filepath" + "sort" + "strconv" + "sync" + "time" + + "github.com/Meesho/BharatMLStack/flashring/internal/fs" + "github.com/Meesho/BharatMLStack/flashring/internal/iouring" + "github.com/Meesho/BharatMLStack/flashring/internal/maths" + filecache "github.com/Meesho/BharatMLStack/flashring/internal/shard" + "github.com/cespare/xxhash/v2" + "github.com/rs/zerolog/log" + + "github.com/Meesho/BharatMLStack/flashring/pkg/metrics" +) + +const ( + rounds = 1 + maxKeysShard = (1 << 26) // 67M + blockSize = 4096 + maxCoalescedReadSz = 65536 // must match the largest slab allocator size class +) + +// Cache is the common interface for all cache backends. +type Cache interface { + Put(key string, value []byte, ttl time.Duration) error + Get(key string) (value []byte, found bool, expired bool) + Close() error +} + +// Config holds all parameters for creating a WrapCache. +type Config struct { + NumShards int + KeysPerShard int + FileSize int64 + MemtableSize int32 + ReWriteScoreThreshold float32 + GridSearchEpsilon float64 + SampleDuration time.Duration + FreqBands []int + RecencyBands []int +} + +var ( + ErrNumShardLessThan1 = fmt.Errorf("num shards must be greater than 0") + ErrKeysPerShardLessThan1 = fmt.Errorf("keys per shard must be greater than 0") + ErrKeysPerShardGreaterThan67M = fmt.Errorf("keys per shard must be less than 67M") + ErrMemtableSizeLessThan1 = fmt.Errorf("memtable size must be greater than 0") + ErrMemtableSizeGreaterThan1GB = fmt.Errorf("memtable size must be less than 1GB") + ErrMemtableSizeNotMultipleOf4KB = fmt.Errorf("memtable size must be a multiple of 4KB") + ErrFileSizeLessThan1 = fmt.Errorf("file size must be greater than 0") + ErrFileSizeNotMultipleOf4KB = fmt.Errorf("file size must be a multiple of 4KB") +) + +func (c *Config) validate() error { + checks := []struct { + cond bool + err error + }{ + {c.NumShards <= 0, ErrNumShardLessThan1}, + {c.KeysPerShard <= 0, ErrKeysPerShardLessThan1}, + {c.KeysPerShard > maxKeysShard, ErrKeysPerShardGreaterThan67M}, + {c.MemtableSize <= 0, ErrMemtableSizeLessThan1}, + {c.MemtableSize > 1<<30, ErrMemtableSizeGreaterThan1GB}, + {c.MemtableSize%blockSize != 0, ErrMemtableSizeNotMultipleOf4KB}, + {c.FileSize <= 0, ErrFileSizeLessThan1}, + {c.FileSize%blockSize != 0, ErrFileSizeNotMultipleOf4KB}, + } + for _, ch := range checks { + if ch.cond { + return ch.err + } + } + return nil +} + +// WrapCache is the primary disk-backed NVMe cache. +type WrapCache struct { + shards []*filecache.ShardCache + shardLocks []sync.RWMutex + predictor *maths.Predictor + iouringReader *iouring.ParallelBatchIoUringReader + iouringWriter *iouring.IoUringWriter + seed uint64 +} + +var defaultWeights = []maths.WeightTuple{ + {WFreq: 0.1, WLA: 0.1}, + {WFreq: 0.45, WLA: 0.1}, + {WFreq: 0.9, WLA: 0.1}, + {WFreq: 0.1, WLA: 0.45}, + {WFreq: 0.45, WLA: 0.45}, + {WFreq: 0.9, WLA: 0.45}, + {WFreq: 0.1, WLA: 0.9}, + {WFreq: 0.45, WLA: 0.9}, + {WFreq: 0.9, WLA: 0.9}, +} + +func NewWrapCache(config Config, mountPoint string) (*WrapCache, error) { + if err := config.validate(); err != nil { + return nil, err + } + + files, err := os.ReadDir(mountPoint) + if err != nil { + return nil, fmt.Errorf("read mount point: %w", err) + } + for _, file := range files { + os.Remove(filepath.Join(mountPoint, file.Name())) + } + + maxMemTableCount := config.FileSize / int64(config.MemtableSize) + predictor := maths.NewPredictor(maths.PredictorConfig{ + ReWriteScoreThreshold: config.ReWriteScoreThreshold, + Weights: defaultWeights, + SampleDuration: config.SampleDuration, + MaxMemTableCount: uint32(maxMemTableCount), + GridSearchEpsilon: config.GridSearchEpsilon, + FreqBands: maths.FreqBands{Cold: uint64(config.FreqBands[0]), Warm: uint64(config.FreqBands[1]), Hot: uint64(config.FreqBands[2])}, + RecencyBands: maths.RecencyBands{Hot: uint64(config.RecencyBands[0]), Warm: uint64(config.RecencyBands[1]), Cold: uint64(config.RecencyBands[2])}, + }) + + readRing, err := iouring.NewParallelBatchIoUringReader(iouring.BatchIoUringConfig{ + RingDepth: 512, + MaxBatch: 512, + MaxInflight: 512, + QueueSize: 2048, + Window: 0, + SQPoll: true, + }, 1) + if err != nil { + log.Panic().Err(err).Msg("Failed to create batched io_uring reader") + } + + writeRing, err := iouring.NewIoUringWriter(256, 0) + if err != nil { + log.Panic().Err(err).Msg("Failed to create io_uring write ring") + } + + seed := xxhash.Sum64String(strconv.Itoa(int(time.Now().UnixNano()))) + + metrics.BuildShardTags(config.NumShards) + shardLocks := make([]sync.RWMutex, config.NumShards) + shards := make([]*filecache.ShardCache, config.NumShards) + + // Stagger each shard's first memtable fill level so flushes are spread + // evenly over time instead of all firing at once. Shard i starts with + // i/N of its memtable already "used", so it fills sooner by that fraction. + // After the first cycle the stagger is self-sustaining. + staggerStep := (int(config.MemtableSize) / config.NumShards) &^ (blockSize - 1) // block-align + + for i := 0; i < config.NumShards; i++ { + shards[i], err = filecache.NewShardCache(filecache.ShardCacheConfig{ + MemtableSize: config.MemtableSize, + Rounds: rounds, + RbInitial: config.KeysPerShard, + RbMax: config.KeysPerShard, + DeleteAmortizedStep: 10000, + MaxFileSize: config.FileSize, + BlockSize: blockSize, + Directory: mountPoint, + Predictor: predictor, + IoUringReader: readRing, + IoUringWriter: writeRing, + FlushStaggerOffset: i * staggerStep, + }, &shardLocks[i]) + if err != nil { + for j := 0; j < i; j++ { + shards[j].Close() + } + readRing.Close() + writeRing.Close() + return nil, fmt.Errorf("create shard %d: %w", i, err) + } + } + + return &WrapCache{ + shards: shards, + shardLocks: shardLocks, + predictor: predictor, + iouringReader: readRing, + iouringWriter: writeRing, + seed: seed, + }, nil +} + +func (wc *WrapCache) Put(key string, value []byte, ttl time.Duration) error { + h32 := wc.hash(key) + shardIdx := h32 % uint32(len(wc.shards)) + + start := time.Now() + defer func() { + metrics.Timing(metrics.KEY_PUT_LATENCY, time.Since(start), metrics.GetShardTag(shardIdx)) + }() + + wc.shardLocks[shardIdx].Lock() + metrics.Timing(metrics.LATENCY_WLOCK, time.Since(start), []string{}) + defer wc.shardLocks[shardIdx].Unlock() + + ttlMinutes := uint16(ttl.Minutes()) + if ttlMinutes == 0 && ttl > 0 { + ttlMinutes = 1 + } + + if err := wc.shards[shardIdx].Put(key, value, ttlMinutes); err != nil { + return fmt.Errorf("put failed for key %s: %w", key, err) + } + metrics.Incr(metrics.KEY_PUTS, metrics.GetShardTag(shardIdx)) + if h32%100 < 10 { + metrics.Incr(metrics.KEY_RINGBUFFER_ACTIVE_ENTRIES, metrics.GetShardTag(shardIdx)) + } + return nil +} + +func (wc *WrapCache) Get(key string) ([]byte, bool, bool) { + h32 := wc.hash(key) + shardIdx := h32 % uint32(len(wc.shards)) + + start := time.Now() + defer func() { + metrics.Timing(metrics.KEY_GET_LATENCY, time.Since(start), metrics.GetShardTag(shardIdx)) + }() + + keyFound, val, remainingTTL, expired, shouldReWrite := wc.shards[shardIdx].Get(key) + + if keyFound && !expired { + metrics.Incr(metrics.KEY_HITS, metrics.GetShardTag(shardIdx)) + } + if expired { + metrics.Incr(metrics.KEY_EXPIRED_ENTRIES, metrics.GetShardTag(shardIdx)) + } + metrics.Incr(metrics.KEY_GETS, metrics.GetShardTag(shardIdx)) + + if shouldReWrite { + metrics.Incr(metrics.KEY_REWRITES, metrics.GetShardTag(shardIdx)) + valCopy := make([]byte, len(val)) + copy(valCopy, val) + go wc.rewrite(key, valCopy, remainingTTL) + } + + return val, keyFound, expired +} + +// Delete removes the key from the index only. The data remains on disk +// but becomes unreachable via Get. Debug use only. +func (wc *WrapCache) Delete(key string) bool { + h32 := wc.hash(key) + shardIdx := h32 % uint32(len(wc.shards)) + + wc.shardLocks[shardIdx].Lock() + defer wc.shardLocks[shardIdx].Unlock() + + return wc.shards[shardIdx].DeleteKey(key) +} + +// rewrite puts the value back into the cache asynchronously to move +// hot data closer to the write head. +func (wc *WrapCache) rewrite(key string, value []byte, remainingTTLMinutes uint16) { + wc.Put(key, value, time.Duration(remainingTTLMinutes)*time.Minute) +} + +func (wc *WrapCache) Close() error { + for i := range wc.shards { + wc.shardLocks[i].Lock() + wc.shards[i].Flush() + wc.shards[i].Close() + wc.shardLocks[i].Unlock() + } + wc.iouringReader.Close() + wc.iouringWriter.Close() + return nil +} + +// MGetResult holds the result for a single key in a batch get. +type MGetResult struct { + Value []byte + Found bool + Expired bool +} + +// MGet fetches multiple keys in a single call, batching disk I/O through +// io_uring for significantly lower and more consistent latency than issuing +// individual Get calls (even concurrently via goroutines). +// +// The operation runs in four phases on a single goroutine: +// 1. Index lookups + memtable checks for every key (in-memory, fast). +// 2. Coalesce: sort pending reads by (shard, offset), merge overlapping +// aligned ranges so nearby keys share a single io_uring pread. +// 3. Submit one pread per coalesced group in a tight loop. +// 4. Collect completions, scatter to individual keys, validate CRC32. +func (wc *WrapCache) MGet(keys []string) []MGetResult { + results := make([]MGetResult, len(keys)) + + type pendingEntry struct { + keyIdx int + key string + shardIdx uint32 + meta filecache.MGetMeta + } + + var diskReads []pendingEntry + + // ── Phase 1: index lookups + memtable checks (sequential, all in-memory) ── + for i, key := range keys { + h32 := wc.hash(key) + shardIdx := h32 % uint32(len(wc.shards)) + + meta := wc.shards[shardIdx].GetMetaForMGet(key) + metrics.Incr(metrics.KEY_GETS, metrics.GetShardTag(shardIdx)) + + if meta.Expired { + metrics.Incr(metrics.KEY_EXPIRED_ENTRIES, metrics.GetShardTag(shardIdx)) + results[i] = MGetResult{Expired: true} + continue + } + + if !meta.Found { + continue + } + + // Memtable hit — validate and return inline. + if meta.Value != nil { + val, ok := wc.shards[shardIdx].ValidateAndExtract(meta.Value, key, meta.Length) + if ok { + metrics.Incr(metrics.KEY_HITS, metrics.GetShardTag(shardIdx)) + results[i] = MGetResult{Value: val, Found: true} + } + if meta.ShouldReWrite && ok { + metrics.Incr(metrics.KEY_REWRITES, metrics.GetShardTag(shardIdx)) + valCopy := make([]byte, len(val)) + copy(valCopy, val) + go wc.rewrite(key, valCopy, meta.RemainingTTL) + } + continue + } + + // Needs disk read — collect for coalescing. + if meta.NeedsDiskRead { + diskReads = append(diskReads, pendingEntry{ + keyIdx: i, + key: key, + shardIdx: shardIdx, + meta: meta, + }) + } + } + + if len(diskReads) == 0 { + return results + } + + // ── Phase 2: coalesce nearby disk reads ── + // Sort by (shard, file offset) so keys that map to overlapping or + // adjacent 4KB-aligned blocks end up next to each other. + sort.Slice(diskReads, func(i, j int) bool { + if diskReads[i].shardIdx != diskReads[j].shardIdx { + return diskReads[i].shardIdx < diskReads[j].shardIdx + } + return diskReads[i].meta.FileOffset < diskReads[j].meta.FileOffset + }) + + type coalescedGroup struct { + shardIdx uint32 + alignedStart int64 + alignedEnd int64 // exclusive + members []int // indices into diskReads + pending *filecache.CoalescedPendingRead + } + + groups := make([]coalescedGroup, 0, len(diskReads)) + for i, dr := range diskReads { + aStart, aSize := fs.AlignRange(dr.meta.FileOffset, int(dr.meta.Length), fs.BLOCK_SIZE) + aEnd := aStart + aSize + + if len(groups) > 0 { + last := &groups[len(groups)-1] + // Merge if same shard, overlapping/adjacent, and the result + // still fits within the slab allocator's largest size class. + mergedEnd := last.alignedEnd + if aEnd > mergedEnd { + mergedEnd = aEnd + } + if dr.shardIdx == last.shardIdx && aStart <= last.alignedEnd && + mergedEnd-last.alignedStart <= maxCoalescedReadSz { + last.alignedEnd = mergedEnd + last.members = append(last.members, i) + continue + } + } + + groups = append(groups, coalescedGroup{ + shardIdx: dr.shardIdx, + alignedStart: aStart, + alignedEnd: aEnd, + members: []int{i}, + }) + } + + // ── Phase 3: submit one io_uring pread per coalesced group ── + for g := range groups { + size := int(groups[g].alignedEnd - groups[g].alignedStart) + pr, err := wc.shards[groups[g].shardIdx].SubmitCoalescedReadAsync( + groups[g].alignedStart, size) + if err != nil { + continue + } + groups[g].pending = pr + } + + // ── Phase 4: collect completions, scatter to individual keys ── + for _, grp := range groups { + if grp.pending == nil { + continue + } + + coalescedBuf := wc.shards[grp.shardIdx].CollectCoalescedRead(grp.pending) + if coalescedBuf == nil { + continue + } + + for _, memberIdx := range grp.members { + dr := diskReads[memberIdx] + bufOffset := int(dr.meta.FileOffset - grp.alignedStart) + if bufOffset < 0 || bufOffset+int(dr.meta.Length) > len(coalescedBuf) { + continue + } + + keyBuf := coalescedBuf[bufOffset : bufOffset+int(dr.meta.Length)] + val, ok := wc.shards[dr.shardIdx].ValidateAndExtract(keyBuf, dr.key, dr.meta.Length) + if ok { + metrics.Incr(metrics.KEY_HITS, metrics.GetShardTag(dr.shardIdx)) + results[dr.keyIdx] = MGetResult{Value: val, Found: true} + } + if dr.meta.ShouldReWrite && ok { + metrics.Incr(metrics.KEY_REWRITES, metrics.GetShardTag(dr.shardIdx)) + valCopy := make([]byte, len(val)) + copy(valCopy, val) + go wc.rewrite(dr.key, valCopy, dr.meta.RemainingTTL) + } + } + } + + return results +} + +func (wc *WrapCache) hash(key string) uint32 { + return uint32(xxhash.Sum64String(key) ^ wc.seed) +} + +func (wc *WrapCache) Hash(key string) uint32 { + return wc.hash(key) +} diff --git a/flashring/pkg/cache/freecache.go b/flashring/pkg/cache/freecache.go new file mode 100644 index 00000000..d9047153 --- /dev/null +++ b/flashring/pkg/cache/freecache.go @@ -0,0 +1,35 @@ +package cache + +import ( + "runtime/debug" + "time" + + "github.com/coocood/freecache" +) + +type Freecache struct { + cache *freecache.Cache +} + +func NewFreecache(sizeBytes int) (*Freecache, error) { + cache := freecache.NewCache(sizeBytes) + debug.SetGCPercent(20) + return &Freecache{cache: cache}, nil +} + +func (c *Freecache) Put(key string, value []byte, ttl time.Duration) error { + c.cache.Set([]byte(key), value, int(ttl.Seconds())) + return nil +} + +func (c *Freecache) Get(key string) ([]byte, bool, bool) { + val, err := c.cache.Get([]byte(key)) + if err != nil { + return nil, false, false + } + return val, true, false +} + +func (c *Freecache) Close() error { + return nil +} diff --git a/flashring/pkg/hierbitmap/map.go b/flashring/pkg/hierbitmap/map.go deleted file mode 100644 index 18b2b180..00000000 --- a/flashring/pkg/hierbitmap/map.go +++ /dev/null @@ -1,23 +0,0 @@ -package hierbitmap - -type Bitmap64 [64]uint64 - -type Level3 struct { - Leafs [64]interface{} - Sum Bitmap64 -} - -type Level2 struct { - Nodes [64]Level3 - Sum Bitmap64 -} - -type Level1 struct { - Nodes [64]Level2 - Sum Bitmap64 -} - -type Level0 struct { - Level1 [64]Level1 - Sum Bitmap64 -} diff --git a/flashring/pkg/metrics/metric.go b/flashring/pkg/metrics/metric.go new file mode 100644 index 00000000..ab62bf0b --- /dev/null +++ b/flashring/pkg/metrics/metric.go @@ -0,0 +1,221 @@ +package metrics + +import ( + "os" + "strconv" + "strings" + "sync" + "time" + + "github.com/DataDog/datadog-go/v5/statsd" + "github.com/rs/zerolog/log" + "github.com/spf13/viper" +) + +// Flashring metric keys +const ( + KEY_GET_LATENCY = "flashring_get_latency" + KEY_PUT_LATENCY = "flashring_put_latency" + KEY_RTHROUGHPUT = "flashring_rthroughput" + KEY_WTHROUGHPUT = "flashring_wthroughput" + KEY_HITRATE = "flashring_hitrate" + KEY_ACTIVE_ENTRIES = "flashring_active_entries" + KEY_EXPIRED_ENTRIES = "flashring_expired_entries" + KEY_REWRITES = "flashring_rewrites" + KEY_GETS = "flashring_gets" + KEY_PUTS = "flashring_puts" + KEY_HITS = "flashring_hits" + + KEY_KEY_NOT_FOUND_COUNT = "flashring_key_not_found_count" + KEY_KEY_EXPIRED_COUNT = "flashring_key_expired_count" + KEY_BAD_DATA_COUNT = "flashring_bad_data_count" + KEY_BAD_LENGTH_COUNT = "flashring_bad_length_count" + KEY_BAD_CR32_COUNT = "flashring_bad_cr32_count" + KEY_BAD_KEY_COUNT = "flashring_bad_key_count" + KEY_DELETED_KEY_COUNT = "flashring_deleted_key_count" + + KEY_WRITE_COUNT = "flashring_write_count" + KEY_PUNCH_HOLE_COUNT = "flashring_punch_hole_count" + KEY_PREAD_COUNT = "flashring_pread_count" + + KEY_TRIM_HEAD_LATENCY = "flashring_wrap_file_trim_head_latency" + KEY_PREAD_LATENCY = "flashring_pread_latency" + KEY_PWRITE_LATENCY = "flashring_pwrite_latency" + + KEY_MEMTABLE_FLUSH_COUNT = "flashring_memtable_flush_count" + + LATENCY_RLOCK = "flashring_rlock_latency" + LATENCY_WLOCK = "flashring_wlock_latency" + + KEY_RINGBUFFER_ACTIVE_ENTRIES = "flashring_ringbuffer_active_entries" + KEY_MEMTABLE_ENTRY_COUNT = "flashring_memtable_entry_count" + KEY_MEMTABLE_HIT = "flashring_memtable_hit" + KEY_MEMTABLE_MISS = "flashring_memtable_miss" + KEY_DATA_LENGTH = "flashring_data_length" + KEY_IOURING_SIZE = "flashring_iouring_size" + KEY_REWRITE_SCORE = "flashring_rewrite_score" + KEY_REWRITE_DECISION = "flashring_rewrite_decision" + KEY_ACCESS_FREQ = "flashring_access_freq" + KEY_LAST_ACCESS = "flashring_last_access" +) + +// Rewrite predictor tag keys +const ( + TAG_SCORE_BUCKET = "score_bucket" + TAG_DECISION = "decision" + TAG_RING_ZONE = "ring_zone" + TAG_FREQ_BAND = "freq_band" + TAG_RECENCY_BAND = "recency_band" +) + +// Flashring tag keys +const ( + TAG_LATENCY_PERCENTILE = "latency_percentile" + TAG_VALUE_P25 = "p25" + TAG_VALUE_P50 = "p50" + TAG_VALUE_P99 = "p99" + TAG_SHARD_IDX = "shard_idx" + TAG_MEMTABLE_ID = "memtable_id" +) + +// Application-level metric keys +const ( + ApiRequestCount = "api_request_count" + ApiRequestLatency = "api_request_latency" + ExternalApiRequestCount = "external_api_request_count" + ExternalApiRequestLatency = "external_api_request_latency" + DBCallLatency = "db_call_latency" + DBCallCount = "db_call_count" + MethodLatency = "method_latency" + MethodCount = "method_count" +) + +var ( + statsDClient = getDefaultClient() + samplingRate = 0.1 + telegrafAddress = "localhost:8125" + initialized = false + once sync.Once + + // When false, all Timing/Count/Incr/Gauge calls are no-ops (zero allocations). + // Controlled by FLASHRING_METRICS_ENABLED env var ("true"/"1" to enable). + // Defaults to true for backward compatibility. + metricsEnabled = loadMetricsEnabled() + + shardTags []string +) + +func loadMetricsEnabled() bool { + v := os.Getenv("FLASHRING_METRICS_ENABLED") + if v == "" { + return false + } + return strings.EqualFold(v, "true") || v == "1" +} + +// Init initializes the metrics client +func Init() { + if initialized { + log.Debug().Msgf("Metrics already initialized!") + return + } + once.Do(func() { + var err error + samplingRate = viper.GetFloat64("APP_METRIC_SAMPLING_RATE") + globalTags := getGlobalTags() + + statsDClient, err = statsd.New( + telegrafAddress, + statsd.WithTags(globalTags), + ) + + if err != nil { + log.Panic().AnErr("StatsD client initialization failed", err) + } + log.Info().Msgf("Metrics client initialized with telegraf address - %s, global tags - %v, and "+ + "sampling rate - %f, flashring metrics enabled - %v", telegrafAddress, globalTags, samplingRate, metricsEnabled) + initialized = true + }) +} + +func getDefaultClient() *statsd.Client { + client, _ := statsd.New("localhost:8125") + return client +} + +func getGlobalTags() []string { + env := viper.GetString("APP_ENV") + if len(env) == 0 { + log.Warn().Msg("APP_ENV is not set") + } + service := viper.GetString("APP_NAME") + if len(service) == 0 { + log.Warn().Msg("APP_NAME is not set") + } + return []string{ + TagAsString(TagEnv, env), + TagAsString(TagService, service), + } +} + +// Timing sends timing information. No-op when metrics are disabled. +func Timing(name string, value time.Duration, tags []string) { + if !metricsEnabled { + return + } + err := statsDClient.Timing(name, value, tags, samplingRate) + if err != nil { + log.Warn().AnErr("Error occurred while doing statsd timing", err) + } +} + +// Count increases metric counter by value. No-op when metrics are disabled. +func Count(name string, value int64, tags []string) { + if !metricsEnabled { + return + } + err := statsDClient.Count(name, value, tags, samplingRate) + if err != nil { + log.Warn().AnErr("Error occurred while doing statsd count", err) + } +} + +// Incr increases metric counter by 1. No-op when metrics are disabled. +func Incr(name string, tags []string) { + if !metricsEnabled { + return + } + Count(name, 1, tags) +} + +// Gauge sets a gauge value. No-op when metrics are disabled. +func Gauge(name string, value float64, tags []string) { + if !metricsEnabled { + return + } + err := statsDClient.Gauge(name, value, tags, samplingRate) + if err != nil { + log.Warn().AnErr("Error occurred while doing statsd gauge", err) + } +} + +// Enabled returns whether flashring metrics are enabled. +func Enabled() bool { + return metricsEnabled +} + +func GetShardTag(shardIdx uint32) []string { + return shardTags[shardIdx : shardIdx+1] +} + +func GetMemtableTag(memtableId uint32) []string { + return BuildTag(NewTag(TAG_MEMTABLE_ID, strconv.Itoa(int(memtableId)))) +} + +func BuildShardTags(shardCount int) { + tags := make([]string, 0, shardCount) + for i := 0; i < shardCount; i++ { + tags = append(tags, BuildTag(NewTag(TAG_SHARD_IDX, strconv.Itoa(int(i))))...) + } + shardTags = tags +} diff --git a/flashring/pkg/metrics/tag.go b/flashring/pkg/metrics/tag.go new file mode 100644 index 00000000..d77ac38e --- /dev/null +++ b/flashring/pkg/metrics/tag.go @@ -0,0 +1,55 @@ +package metrics + +// Tag constants +const ( + TagEnv = "env" + TagService = "service" + TagPath = "path" + TagMethod = "method" + TagHttpStatusCode = "http_status_code" + TagGrpcStatusCode = "grpc_status_code" + TagExternalService = "external_service" + TagExternalServicePath = "external_service_path" + TagExternalServiceMethod = "external_service_method" + TagExternalServiceStatusCode = "external_service_status_code" + TagZkRealtimeTotalUpdateEvent = "zk_realtime_total_update_event" + TagZkRealtimeFailureEvent = "zk_realtime_failure_event" + TagZkRealtimeSuccessEvent = "zk_realtime_success_event" + TagZkRealtimeEventUpdateLatency = "zk_realtime_event_update_latency" + TagCommunicationProtocol = "communication_protocol" + TagUserContext = "user_context" + + TagValueCommunicationProtocolHttp = "http" + TagValueCommunicationProtocolGrpc = "grpc" +) + +type Tag struct { + Name string + Value string +} + +func NewTag(name, value string) Tag { + return Tag{ + Name: name, + Value: value, + } +} + +// BuildTag builds a tag from the given name and value +func BuildTag(tags ...Tag) []string { + allTags := make([]string, 0) + for _, tag := range tags { + allTags = append(allTags, TagAsString(tag.Name, tag.Value)) + } + return allTags +} + +func TagAsString(name string, value string) string { + return name + ":" + value +} + +func UpdateTags(tags *[]string, newTags ...Tag) { + for _, tag := range newTags { + *tags = append(*tags, TagAsString(tag.Name, tag.Value)) + } +} diff --git a/flashring/pkg/ycsb/README.md b/flashring/pkg/ycsb/README.md deleted file mode 100644 index a31d76e9..00000000 --- a/flashring/pkg/ycsb/README.md +++ /dev/null @@ -1,178 +0,0 @@ -# YCSB Adapter for LRU Cache - -This package provides a Yahoo! Cloud Serving Benchmark (YCSB) adapter for the LRU cache implementation, enabling standardized performance testing and comparison with other storage systems. - -## Overview - -The YCSB adapter implements standard YCSB workloads for our LRU cache: - -- **Workload A**: Read/Update heavy (50%/50%) - Update heavy workload -- **Workload B**: Read heavy (95%/5%) - Read mostly workload -- **Workload C**: Read only (100%) - Read only workload -- **Workload D**: Read latest (95%/5%) - Read latest workload -- **Workload F**: Read-modify-write (50%/50%) - Transaction workload - -## Features - -- ✅ Standard YCSB database interface implementation -- ✅ Configurable cache capacity and eviction threshold -- ✅ Multiple request distributions (uniform, zipfian, latest) -- ✅ Comprehensive performance metrics -- ✅ Cache hit rate tracking -- ✅ Memory allocation profiling - -## Configuration - -```go -config := YCSBConfig{ - Capacity: 1000000, // 1M cache capacity - EvictionThreshold: 0.7, // 70% eviction threshold - SlabSizes: []int{64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384}, -} - -db, err := NewLRUCacheDB(config) -``` - -## Usage Examples - -### Basic Usage - -```go -// Create database with default configuration -db, err := NewLRUCacheDBWithDefaults() -if err != nil { - log.Fatal(err) -} - -// Insert a record -ctx := context.Background() -values := map[string][]byte{ - "field0": []byte("test data"), -} -err = db.Insert(ctx, "table", "key1", values) - -// Read a record -result, err := db.Read(ctx, "table", "key1", []string{"field0"}) -if err != nil { - log.Printf("Record not found: %v", err) -} else { - fmt.Printf("Value: %s\n", result["field0"]) -} - -// Update a record -err = db.Update(ctx, "table", "key1", values) - -// Get cache statistics -stats := db.GetStats() -fmt.Printf("Hit rate: %.2f%%\n", - float64(stats.HitCount)/float64(stats.HitCount+stats.MissCount)*100) -``` - -## Running Benchmarks - -### All YCSB Workloads -```bash -cd ssd-cache -go test -bench=BenchmarkYCSB_AllWorkloads -benchtime=1x -v ./pkg/ycsb/ -``` - -### Individual Workloads -```bash -# Test read/update heavy workload -go test -bench=BenchmarkYCSB_WorkloadA -benchtime=1x -v ./pkg/ycsb/ - -# Test read-heavy workload -go test -bench=BenchmarkYCSB_WorkloadB -benchtime=1x -v ./pkg/ycsb/ - -# Test read-only workload -go test -bench=BenchmarkYCSB_WorkloadC -benchtime=1x -v ./pkg/ycsb/ -``` - -### Custom Benchmark Parameters - -The benchmarks use these default parameters: -- **Load Phase**: 1M records inserted -- **Run Phase**: 500K operations executed -- **Cache Capacity**: 500K (creating memory pressure) -- **Record Size**: 1KB (100 bytes × 10 fields) - -## Benchmark Output - -Example output includes comprehensive metrics: - -``` -=== YCSB WorkloadA Benchmark Results === -Description: Read/Update heavy (50%/50%) - Update heavy workload - ---- Performance Metrics --- -Load Throughput: 285,432.50 ops/sec -Run Throughput: 892,145.23 ops/sec -Average Latency: 1,120.45 ns/op - ---- Cache Statistics --- -Cache Hit Rate: 78.45% (392,250/500,000) -Final Cache Size: 350,000 -Eviction Events: 12 -Total Items Evicted: 840,000 - ---- Memory Metrics --- -Allocations per Operation: 3.24 -Bytes per Operation: 156.78 -``` - -## Request Distributions - -### Uniform Distribution -All keys have equal probability of being accessed. - -### Zipfian Distribution -Follows the 80/20 rule - 80% of requests target 20% of keys (hot data). - -### Latest Distribution -Favors recently inserted keys (temporal locality). - -## Limitations - -- **Scan Operations**: Not supported (LRU cache doesn't maintain key ordering) -- **Delete Operations**: Not explicitly supported (relies on LRU eviction) -- **Range Queries**: Not applicable to key-value cache - -## Integration with go-ycsb - -To integrate with the official [go-ycsb](https://github.com/pingcap/go-ycsb) project: - -1. Register the database adapter: -```go -func init() { - RegisterDB("lru", func() DB { - db, _ := NewLRUCacheDBWithDefaults() - return db - }) -} -``` - -2. Use with go-ycsb CLI: -```bash -./go-ycsb load lru -P workloads/workloada -./go-ycsb run lru -P workloads/workloada -``` - -## Performance Characteristics - -The LRU cache adapter demonstrates: - -- **High Throughput**: 500K+ ops/sec for mixed workloads -- **Low Latency**: Sub-microsecond average latency -- **Predictable Eviction**: LRU policy ensures consistent behavior -- **Memory Efficiency**: Slab allocation reduces fragmentation - -## Comparison with Other Systems - -YCSB results can be directly compared with other storage systems tested using the same workloads, providing standardized performance benchmarks for: - -- **Redis/Memcached**: In-memory key-value stores -- **RocksDB/LevelDB**: Persistent key-value stores -- **Cassandra/ScyllaDB**: Distributed databases -- **MySQL/PostgreSQL**: Relational databases - -This enables objective performance comparisons and helps identify the LRU cache's optimal use cases. \ No newline at end of file diff --git a/flashring/pkg/ycsb/bazel_workspace/BUILD.bazel b/flashring/pkg/ycsb/bazel_workspace/BUILD.bazel deleted file mode 100644 index b54e6c91..00000000 --- a/flashring/pkg/ycsb/bazel_workspace/BUILD.bazel +++ /dev/null @@ -1,8 +0,0 @@ -cc_binary( - name = "hello_world", - srcs = ["hello_world.cc"], - deps = [ - "@abseil-cpp//absl/container:flat_hash_map", - "@abseil-cpp//absl/strings", - ], -) \ No newline at end of file diff --git a/flashring/pkg/ycsb/bazel_workspace/MODULE.bazel b/flashring/pkg/ycsb/bazel_workspace/MODULE.bazel deleted file mode 100644 index 74a4cb57..00000000 --- a/flashring/pkg/ycsb/bazel_workspace/MODULE.bazel +++ /dev/null @@ -1,5 +0,0 @@ -# MODULE.bazel - -# Choose the most recent version available at -# https://registry.bazel.build/modules/abseil-cpp. -bazel_dep(name = "abseil-cpp", version = "20240116.0") \ No newline at end of file diff --git a/flashring/pkg/ycsb/bazel_workspace/MODULE.bazel.lock b/flashring/pkg/ycsb/bazel_workspace/MODULE.bazel.lock deleted file mode 100644 index e44f7cdc..00000000 --- a/flashring/pkg/ycsb/bazel_workspace/MODULE.bazel.lock +++ /dev/null @@ -1,205 +0,0 @@ -{ - "lockFileVersion": 18, - "registryFileHashes": { - "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", - "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", - "https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589", - "https://bcr.bazel.build/modules/abseil-cpp/20230125.1/MODULE.bazel": "89047429cb0207707b2dface14ba7f8df85273d484c2572755be4bab7ce9c3a0", - "https://bcr.bazel.build/modules/abseil-cpp/20230802.0.bcr.1/MODULE.bazel": "1c8cec495288dccd14fdae6e3f95f772c1c91857047a098fad772034264cc8cb", - "https://bcr.bazel.build/modules/abseil-cpp/20230802.0/MODULE.bazel": "d253ae36a8bd9ee3c5955384096ccb6baf16a1b1e93e858370da0a3b94f77c16", - "https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915", - "https://bcr.bazel.build/modules/abseil-cpp/20240116.0/MODULE.bazel": "98dc378d64c12a4e4741ad3362f87fb737ee6a0886b2d90c3cdbb4d93ea3e0bf", - "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed", - "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/source.json": "9be551b8d4e3ef76875c0d744b5d6a504a27e3ae67bc6b28f46415fd2d2957da", - "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", - "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", - "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", - "https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d", - "https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a", - "https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58", - "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", - "https://bcr.bazel.build/modules/bazel_features/1.30.0/source.json": "b07e17f067fe4f69f90b03b36ef1e08fe0d1f3cac254c1241a1818773e3423bc", - "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", - "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", - "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", - "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", - "https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686", - "https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a", - "https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5", - "https://bcr.bazel.build/modules/bazel_skylib/1.4.1/MODULE.bazel": "a0dcb779424be33100dcae821e9e27e4f2901d9dfd5333efe5ac6a8d7ab75e1d", - "https://bcr.bazel.build/modules/bazel_skylib/1.4.2/MODULE.bazel": "3bd40978e7a1fac911d5989e6b09d8f64921865a45822d8b09e815eaa726a651", - "https://bcr.bazel.build/modules/bazel_skylib/1.5.0/MODULE.bazel": "32880f5e2945ce6a03d1fbd588e9198c0a959bb42297b2cfaf1685b7bc32e138", - "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917", - "https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d", - "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b", - "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/source.json": "f121b43eeefc7c29efbd51b83d08631e2347297c95aac9764a701f2a6a2bb953", - "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", - "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", - "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", - "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", - "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", - "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/source.json": "41e9e129f80d8c8bf103a7acc337b76e54fad1214ac0a7084bf24f4cd924b8b4", - "https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f", - "https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075", - "https://bcr.bazel.build/modules/jsoncpp/1.9.5/source.json": "4108ee5085dd2885a341c7fab149429db457b3169b86eb081fa245eadf69169d", - "https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", - "https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5", - "https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f", - "https://bcr.bazel.build/modules/platforms/0.0.11/source.json": "f7e188b79ebedebfe75e9e1d098b8845226c7992b307e28e1496f23112e8fc29", - "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", - "https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37", - "https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615", - "https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814", - "https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d", - "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", - "https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c", - "https://bcr.bazel.build/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d", - "https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df", - "https://bcr.bazel.build/modules/protobuf/29.0/MODULE.bazel": "319dc8bf4c679ff87e71b1ccfb5a6e90a6dbc4693501d471f48662ac46d04e4e", - "https://bcr.bazel.build/modules/protobuf/29.0/source.json": "b857f93c796750eef95f0d61ee378f3420d00ee1dd38627b27193aa482f4f981", - "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", - "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e", - "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/source.json": "be4789e951dd5301282729fe3d4938995dc4c1a81c2ff150afc9f1b0504c6022", - "https://bcr.bazel.build/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206", - "https://bcr.bazel.build/modules/re2/2023-09-01/source.json": "e044ce89c2883cd957a2969a43e79f7752f9656f6b20050b62f90ede21ec6eb4", - "https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8", - "https://bcr.bazel.build/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e", - "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", - "https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002", - "https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191", - "https://bcr.bazel.build/modules/rules_cc/0.0.14/MODULE.bazel": "5e343a3aac88b8d7af3b1b6d2093b55c347b8eefc2e7d1442f7a02dc8fea48ac", - "https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc", - "https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87", - "https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c", - "https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f", - "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", - "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", - "https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513", - "https://bcr.bazel.build/modules/rules_cc/0.1.1/source.json": "d61627377bd7dd1da4652063e368d9366fc9a73920bfa396798ad92172cf645c", - "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", - "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", - "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e", - "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", - "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86", - "https://bcr.bazel.build/modules/rules_java/6.0.0/MODULE.bazel": "8a43b7df601a7ec1af61d79345c17b31ea1fedc6711fd4abfd013ea612978e39", - "https://bcr.bazel.build/modules/rules_java/6.4.0/MODULE.bazel": "e986a9fe25aeaa84ac17ca093ef13a4637f6107375f64667a15999f77db6c8f6", - "https://bcr.bazel.build/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31", - "https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a", - "https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6", - "https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab", - "https://bcr.bazel.build/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2", - "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", - "https://bcr.bazel.build/modules/rules_java/8.12.0/MODULE.bazel": "8e6590b961f2defdfc2811c089c75716cb2f06c8a4edeb9a8d85eaa64ee2a761", - "https://bcr.bazel.build/modules/rules_java/8.12.0/source.json": "cbd5d55d9d38d4008a7d00bee5b5a5a4b6031fcd4a56515c9accbcd42c7be2ba", - "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", - "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909", - "https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", - "https://bcr.bazel.build/modules/rules_jvm_external/5.3/MODULE.bazel": "bf93870767689637164657731849fb887ad086739bd5d360d90007a581d5527d", - "https://bcr.bazel.build/modules/rules_jvm_external/6.1/MODULE.bazel": "75b5fec090dbd46cf9b7d8ea08cf84a0472d92ba3585b476f44c326eda8059c4", - "https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0", - "https://bcr.bazel.build/modules/rules_jvm_external/6.3/source.json": "6f5f5a5a4419ae4e37c35a5bb0a6ae657ed40b7abc5a5189111b47fcebe43197", - "https://bcr.bazel.build/modules/rules_kotlin/1.9.0/MODULE.bazel": "ef85697305025e5a61f395d4eaede272a5393cee479ace6686dba707de804d59", - "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3", - "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/source.json": "2faa4794364282db7c06600b7e5e34867a564ae91bda7cae7c29c64e9466b7d5", - "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", - "https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d", - "https://bcr.bazel.build/modules/rules_license/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c", - "https://bcr.bazel.build/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb", - "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", - "https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff", - "https://bcr.bazel.build/modules/rules_pkg/1.0.1/source.json": "bd82e5d7b9ce2d31e380dd9f50c111d678c3bdaca190cb76b0e1c71b05e1ba8a", - "https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06", - "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", - "https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73", - "https://bcr.bazel.build/modules/rules_proto/7.0.2/MODULE.bazel": "bf81793bd6d2ad89a37a40693e56c61b0ee30f7a7fdbaf3eabbf5f39de47dea2", - "https://bcr.bazel.build/modules/rules_proto/7.0.2/source.json": "1e5e7260ae32ef4f2b52fd1d0de8d03b606a44c91b694d2f1afb1d3b28a48ce1", - "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f", - "https://bcr.bazel.build/modules/rules_python/0.23.1/MODULE.bazel": "49ffccf0511cb8414de28321f5fcf2a31312b47c40cc21577144b7447f2bf300", - "https://bcr.bazel.build/modules/rules_python/0.25.0/MODULE.bazel": "72f1506841c920a1afec76975b35312410eea3aa7b63267436bfb1dd91d2d382", - "https://bcr.bazel.build/modules/rules_python/0.28.0/MODULE.bazel": "cba2573d870babc976664a912539b320cbaa7114cd3e8f053c720171cde331ed", - "https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58", - "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", - "https://bcr.bazel.build/modules/rules_python/0.40.0/MODULE.bazel": "9d1a3cd88ed7d8e39583d9ffe56ae8a244f67783ae89b60caafc9f5cf318ada7", - "https://bcr.bazel.build/modules/rules_python/0.40.0/source.json": "939d4bd2e3110f27bfb360292986bb79fd8dcefb874358ccd6cdaa7bda029320", - "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", - "https://bcr.bazel.build/modules/rules_shell/0.2.0/source.json": "7f27af3c28037d9701487c4744b5448d26537cc66cdef0d8df7ae85411f8de95", - "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", - "https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c", - "https://bcr.bazel.build/modules/stardoc/0.5.6/MODULE.bazel": "c43dabc564990eeab55e25ed61c07a1aadafe9ece96a4efabb3f8bf9063b71ef", - "https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c", - "https://bcr.bazel.build/modules/stardoc/0.7.1/MODULE.bazel": "3548faea4ee5dda5580f9af150e79d0f6aea934fc60c1cc50f4efdd9420759e7", - "https://bcr.bazel.build/modules/stardoc/0.7.1/source.json": "b6500ffcd7b48cd72c29bb67bcac781e12701cc0d6d55d266a652583cfcdab01", - "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", - "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", - "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", - "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806", - "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198" - }, - "selectedYankedVersions": {}, - "moduleExtensions": { - "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { - "general": { - "bzlTransitiveDigest": "hUTp2w+RUVdL7ma5esCXZJAFnX7vLbVfLd7FwnQI6bU=", - "usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "com_github_jetbrains_kotlin_git": { - "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_compiler_git_repository", - "attributes": { - "urls": [ - "https://github.com/JetBrains/kotlin/releases/download/v1.9.23/kotlin-compiler-1.9.23.zip" - ], - "sha256": "93137d3aab9afa9b27cb06a824c2324195c6b6f6179d8a8653f440f5bd58be88" - } - }, - "com_github_jetbrains_kotlin": { - "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_capabilities_repository", - "attributes": { - "git_repository_name": "com_github_jetbrains_kotlin_git", - "compiler_version": "1.9.23" - } - }, - "com_github_google_ksp": { - "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:ksp.bzl%ksp_compiler_plugin_repository", - "attributes": { - "urls": [ - "https://github.com/google/ksp/releases/download/1.9.23-1.0.20/artifacts.zip" - ], - "sha256": "ee0618755913ef7fd6511288a232e8fad24838b9af6ea73972a76e81053c8c2d", - "strip_version": "1.9.23-1.0.20" - } - }, - "com_github_pinterest_ktlint": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_file", - "attributes": { - "sha256": "01b2e0ef893383a50dbeb13970fe7fa3be36ca3e83259e01649945b09d736985", - "urls": [ - "https://github.com/pinterest/ktlint/releases/download/1.3.0/ktlint" - ], - "executable": true - } - }, - "rules_android": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "sha256": "cd06d15dd8bb59926e4d65f9003bfc20f9da4b2519985c27e190cddc8b7a7806", - "strip_prefix": "rules_android-0.1.1", - "urls": [ - "https://github.com/bazelbuild/rules_android/archive/v0.1.1.zip" - ] - } - } - }, - "recordedRepoMappingEntries": [ - [ - "rules_kotlin+", - "bazel_tools", - "bazel_tools" - ] - ] - } - } - } -} diff --git a/flashring/pkg/ycsb/bazel_workspace/bazel-bazel_workspace b/flashring/pkg/ycsb/bazel_workspace/bazel-bazel_workspace deleted file mode 120000 index 27644f1c..00000000 --- a/flashring/pkg/ycsb/bazel_workspace/bazel-bazel_workspace +++ /dev/null @@ -1 +0,0 @@ -/home/a0d00kc/.cache/bazel/_bazel_a0d00kc/ea88c144588668cbf32ba2f0c98bda83/execroot/_main \ No newline at end of file diff --git a/flashring/pkg/ycsb/bazel_workspace/bazel-bin b/flashring/pkg/ycsb/bazel_workspace/bazel-bin deleted file mode 120000 index ad7980a0..00000000 --- a/flashring/pkg/ycsb/bazel_workspace/bazel-bin +++ /dev/null @@ -1 +0,0 @@ -/home/a0d00kc/.cache/bazel/_bazel_a0d00kc/ea88c144588668cbf32ba2f0c98bda83/execroot/_main/bazel-out/k8-fastbuild/bin \ No newline at end of file diff --git a/flashring/pkg/ycsb/bazel_workspace/bazel-out b/flashring/pkg/ycsb/bazel_workspace/bazel-out deleted file mode 120000 index 550ba267..00000000 --- a/flashring/pkg/ycsb/bazel_workspace/bazel-out +++ /dev/null @@ -1 +0,0 @@ -/home/a0d00kc/.cache/bazel/_bazel_a0d00kc/ea88c144588668cbf32ba2f0c98bda83/execroot/_main/bazel-out \ No newline at end of file diff --git a/flashring/pkg/ycsb/bazel_workspace/bazel-testlogs b/flashring/pkg/ycsb/bazel_workspace/bazel-testlogs deleted file mode 120000 index 3af07959..00000000 --- a/flashring/pkg/ycsb/bazel_workspace/bazel-testlogs +++ /dev/null @@ -1 +0,0 @@ -/home/a0d00kc/.cache/bazel/_bazel_a0d00kc/ea88c144588668cbf32ba2f0c98bda83/execroot/_main/bazel-out/k8-fastbuild/testlogs \ No newline at end of file diff --git a/flashring/pkg/ycsb/bazel_workspace/hello_world.cc b/flashring/pkg/ycsb/bazel_workspace/hello_world.cc deleted file mode 100644 index 28e07e08..00000000 --- a/flashring/pkg/ycsb/bazel_workspace/hello_world.cc +++ /dev/null @@ -1,56 +0,0 @@ -#include -#include -#include -#include - -#include "absl/container/flat_hash_map.h" - -constexpr int kNumElements = 1'000'000; - -int main() { - absl::flat_hash_map map; - map.reserve(kNumElements); - - - // Random number generator - std::mt19937 rng(42); - std::uniform_int_distribution dist(1, kNumElements * 10); - - std::vector keys; - keys.reserve(kNumElements); - for (int i = 0; i < kNumElements; ++i) { - keys.push_back(dist(rng)); - } - - // Insertion benchmark - auto start_insert = std::chrono::high_resolution_clock::now(); - for (int i = 0; i < kNumElements; ++i) { - map[keys[i]] = i; - } - auto end_insert = std::chrono::high_resolution_clock::now(); - std::chrono::duration insert_duration = end_insert - start_insert; - std::cout << "Insertion of " << kNumElements << " items took: " << insert_duration.count() << " seconds\n"; - - // Lookup benchmark - auto start_lookup = std::chrono::high_resolution_clock::now(); - size_t found = 0; - for (int i = 0; i < kNumElements; ++i) { - if (map.find(keys[i]) != map.end()) { - ++found; - } - } - auto end_lookup = std::chrono::high_resolution_clock::now(); - std::chrono::duration lookup_duration = end_lookup - start_lookup; - std::cout << "Lookup of " << kNumElements << " items took: " << lookup_duration.count() << " seconds. Found: " << found << "\n"; - - // Optional: Deletion benchmark - auto start_erase = std::chrono::high_resolution_clock::now(); - for (int i = 0; i < kNumElements; ++i) { - map.erase(keys[i]); - } - auto end_erase = std::chrono::high_resolution_clock::now(); - std::chrono::duration erase_duration = end_erase - start_erase; - std::cout << "Deletion of " << kNumElements << " items took: " << erase_duration.count() << " seconds\n"; - - return 0; -} diff --git a/flashring/pkg/ycsb/simdmap/match16_avx2_amd64.s b/flashring/pkg/ycsb/simdmap/match16_avx2_amd64.s deleted file mode 100644 index ede44804..00000000 --- a/flashring/pkg/ycsb/simdmap/match16_avx2_amd64.s +++ /dev/null @@ -1,23 +0,0 @@ -//go:build amd64 && avx2 -// +build amd64,avx2 - -#include "textflag.h" - -// func match16_simd(ctrl *byte, h2 byte) uint16 -TEXT ·match16_simd(SB),NOSPLIT,$0-0 - // DI = &ctrl[0]; SIL = h2 (byte parameter) - - // Load 16 control bytes into Y0 - VMOVDQU (DI), Y0 - - // Broadcast h2 from memory operand directly into Y1 - VPBROADCASTB h2+8(FP), Y1 - - // Compare Y0 bytes with broadcasted h2 - VPCMPEQB Y1, Y0, Y2 - - // Extract the MSBs of comparison result into AX as 16‑bit mask - VPMOVMSKB Y2, AX - - VZEROUPPER - RET diff --git a/flashring/pkg/ycsb/simdmap/match16_switch_avx2.go b/flashring/pkg/ycsb/simdmap/match16_switch_avx2.go deleted file mode 100644 index ea660045..00000000 --- a/flashring/pkg/ycsb/simdmap/match16_switch_avx2.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build amd64 && avx2 -// +build amd64,avx2 - -package simdmap - -// Link‑time swap to SIMD fast‑path. -func init() { match16 = match16_simd } diff --git a/flashring/pkg/ycsb/simdmap/simdmap.go b/flashring/pkg/ycsb/simdmap/simdmap.go deleted file mode 100644 index 6c53d120..00000000 --- a/flashring/pkg/ycsb/simdmap/simdmap.go +++ /dev/null @@ -1,377 +0,0 @@ -// // SPDX‑License‑Identifier: Apache‑2.0 -// // Package simdmap is a Swiss‑table open‑addressing hash map with an -// // optional AVX2‑vectorised probe loop for amd64. When the build tag -// // `avx2` is *not* supplied or the CPU lacks AVX2, the implementation -// // falls back to a tight scalar probe, keeping the package portable. -// // -// // Build (Go 1.22+): -// // -// // $ go test -tags avx2 ./... # AVX2 fast‑path on Intel/AMD ≥ Haswell/Zen1 -// // $ go test ./... # scalar path (any GOARCH) -// // -// // The key type is fixed to uint64 (a 64‑bit fingerprint like xxhash). -// // You will typically store metadata such as {Off uint64; Len uint32} as V. -// package simdmap - -// import ( -// "math/bits" -// "unsafe" -// ) - -// // --------------------------------------------------------------------- // -// // Constants and tiny helpers -// // --------------------------------------------------------------------- // - -// const ( -// groupSize = 16 // 16 control bytes per Swiss group -// ctrlEmpty = 0x80 -// ctrlTomb = 0xfe -// loadFactor = 7 // 7/8 = 87.5 % -// ) - -// type entry[V any] struct { -// hash uint64 -// val V -// } - -// type Map[V any] struct { -// mask uintptr -// ctrl []byte -// slots []entry[V] -// size uintptr -// growth uintptr -// } - -// // roundUpToGroups returns next power‑of‑two group count ≥ x. -// func roundUpToGroups(x uintptr) uintptr { -// if x < groupSize { -// x = groupSize -// } -// return uintptr(1) << bits.Len(uint(x-1)) -// } - -// func New[V any](capacity int) *Map[V] { -// groups := roundUpToGroups(uintptr(capacity)) -// n := groups * groupSize - -// m := &Map[V]{ -// mask: n - 1, -// ctrl: make([]byte, n+groupSize), // sentinel group -// slots: make([]entry[V], n), -// growth: (n * loadFactor) / 8, -// } -// for i := range m.ctrl { -// m.ctrl[i] = ctrlEmpty -// } -// return m -// } - -// // --------------------------------------------------------------------- // -// // SIMD probe helpers -// // --------------------------------------------------------------------- // - -// //go:noescape -// func match16_simd(ctrl *byte, h2 byte) uint16 // provided in .s when avx2 tag - -// func match16_scalar(ctrl *byte, h2 byte) uint16 { -// var m uint16 -// b := (*[groupSize]byte)(unsafe.Pointer(ctrl)) -// for i := 0; i < groupSize; i++ { -// if b[i] == h2 { -// m |= 1 << uint(i) -// } -// } -// return m -// } - -// // --------------------------------------------------------------------- // -// // Build‑tag specific swap‑in of the SIMD fast‑path -// // --------------------------------------------------------------------- // - -// var match16 = match16_scalar // overridden when the avx2 build‑tag is used - -// // --------------------------------------------------------------------- // -// // Probe and API -// // --------------------------------------------------------------------- // - -// func (m *Map[V]) findSlot(h uint64) (uintptr, bool) { -// h1 := uintptr(h >> 7) -// h2 := byte(h & 0x7f) -// maskGroups := m.mask & ^uintptr(groupSize-1) - -// for { -// grp := h1 & maskGroups -// cptr := (*byte)(unsafe.Pointer(&m.ctrl[grp])) - -// if mask := match16(cptr, h2); mask != 0 { -// for mask != 0 { -// i := bits.TrailingZeros16(mask) -// idx := grp + uintptr(i) -// if m.slots[idx].hash == h { -// return idx, true -// } -// mask &^= 1 << uint(i) -// } -// } -// for i := 0; i < groupSize; i++ { -// if *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(cptr)) + uintptr(i))) >= ctrlEmpty { -// return grp + uintptr(i), false -// } -// } -// h1 += groupSize -// } -// } - -// func (m *Map[V]) Get(hash uint64) (V, bool) { -// var zero V -// idx, ok := m.findSlot(hash) -// if !ok { -// return zero, false -// } -// return m.slots[idx].val, true -// } - -// func (m *Map[V]) putEntry(hash uint64, v V) { -// idx, found := m.findSlot(hash) -// if !found { -// m.size++ -// } -// m.ctrl[idx] = byte(hash & 0x7f) -// m.slots[idx] = entry[V]{hash: hash, val: v} -// } - -// func (m *Map[V]) Put(hash uint64, v V) { -// m.putEntry(hash, v) -// if m.size >= m.growth { -// m.rehash() -// } -// } - -// func (m *Map[V]) Delete(hash uint64) bool { -// idx, ok := m.findSlot(hash) -// if !ok { -// return false -// } -// m.ctrl[idx] = ctrlTomb -// m.size-- -// return true -// } - -// // --------------------------------------------------------------------- // -// // Resize -// // --------------------------------------------------------------------- // - -// func (m *Map[V]) rehash() { -// oldCtrl, oldSlots := m.ctrl, m.slots -// newLen := uintptr(len(oldSlots) * 2) - -// m.ctrl = make([]byte, newLen+groupSize) -// for i := range m.ctrl { -// m.ctrl[i] = ctrlEmpty -// } -// m.slots = make([]entry[V], newLen) -// m.mask = newLen - 1 -// m.size = 0 -// m.growth = (newLen * loadFactor) / 8 - -// for i, c := range oldCtrl[:len(oldSlots)] { -// if c < ctrlEmpty { -// e := oldSlots[i] -// m.putEntry(e.hash, e.val) -// } -// } -// } - -// SPDX‑License‑Identifier: Apache‑2.0 -// Incremental‑rehash version of simdmap. -// Only the growth logic has changed; probe loop and SIMD assembly are -// untouched. A single `Put` moves at most `migrateStep` live entries -// from the old table to the new, flattening the latency spike to <5 µs. -// -// Build / tags unchanged: -// -// go test -tags avx2 ./... -package simdmap - -import ( - "math/bits" - "unsafe" -) - -const ( - groupSize = 16 - ctrlEmpty = 0x80 - ctrlTomb = 0xfe - loadFactor = 7 - migrateStep = 128 // live entries moved per mutation (tune!) -) - -type entry[V any] struct { - hash uint64 - val V -} - -type Map[V any] struct { - // active table - mask uintptr - ctrl []byte - slots []entry[V] - size uintptr - growth uintptr - - // incremental‑rehash state (nil when not migrating) - oldCtrl []byte - oldSlots []entry[V] - rehashAt uintptr // next index to migrate -} - -// --- constructor unchanged ------------------------------------------------- - -func roundUpToGroups(x uintptr) uintptr { - if x < groupSize { - x = groupSize - } - return uintptr(1) << bits.Len(uint(x-1)) -} - -func New[V any](capHint int) *Map[V] { - groups := roundUpToGroups(uintptr(capHint)) - n := groups * groupSize - m := &Map[V]{ - mask: n - 1, - ctrl: make([]byte, n+groupSize), - slots: make([]entry[V], n), - growth: (n * loadFactor) / 8, - } - for i := range m.ctrl { - m.ctrl[i] = ctrlEmpty - } - return m -} - -// --- SIMD probe machinery (unchanged) ------------------------------------- - -//go:noescape -func match16_simd(*byte, byte) uint16 - -func match16_scalar(ctrl *byte, h2 byte) uint16 { - var m uint16 - b := (*[groupSize]byte)(unsafe.Pointer(ctrl)) - for i := 0; i < groupSize; i++ { - if b[i] == h2 { - m |= 1 << uint(i) - } - } - return m -} - -var match16 = match16_scalar // overridden by build‑tag file - -func (m *Map[V]) findSlot(h uint64) (uintptr, bool) { - h1 := uintptr(h >> 7) - h2 := byte(h & 0x7f) - maskGroups := m.mask & ^uintptr(groupSize-1) - for { - grp := h1 & maskGroups - cptr := (*byte)(unsafe.Pointer(&m.ctrl[grp])) - if mask := match16(cptr, h2); mask != 0 { - for mask != 0 { - i := bits.TrailingZeros16(mask) - idx := grp + uintptr(i) - if m.slots[idx].hash == h { - return idx, true - } - mask &^= 1 << uint(i) - } - } - for i := 0; i < groupSize; i++ { - if *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(cptr)) + uintptr(i))) >= ctrlEmpty { - return grp + uintptr(i), false - } - } - h1 += groupSize - } -} - -// ---------------- incremental migration helpers --------------------------- - -func (m *Map[V]) migrateSome() { - if m.oldCtrl == nil { // not in rehash - return - } - moved := 0 - oldLen := uintptr(len(m.oldSlots)) - - for moved < migrateStep && m.rehashAt < oldLen { - c := m.oldCtrl[m.rehashAt] - if c < ctrlEmpty { - e := m.oldSlots[m.rehashAt] - m.putEntry(e.hash, e.val) // into new table - moved++ - } - m.rehashAt++ - } - - // finished? - if m.rehashAt >= oldLen { - m.oldCtrl, m.oldSlots = nil, nil - } -} - -func (m *Map[V]) startRehash() { - if m.oldCtrl != nil { - return // already running - } - m.oldCtrl, m.oldSlots = m.ctrl, m.slots - - newLen := uintptr(len(m.oldSlots) * 2) - m.ctrl = make([]byte, newLen+groupSize) - for i := range m.ctrl { - m.ctrl[i] = ctrlEmpty - } - m.slots = make([]entry[V], newLen) - m.mask = newLen - 1 - m.size = 0 - m.growth = (newLen * loadFactor) / 8 - m.rehashAt = 0 -} - -// ---------------- public API (Put/Get/Delete) ----------------------------- - -func (m *Map[V]) Get(hash uint64) (V, bool) { - m.migrateSome() - var zero V - idx, ok := m.findSlot(hash) - if !ok { - return zero, false - } - return m.slots[idx].val, true -} - -func (m *Map[V]) putEntry(hash uint64, v V) { - idx, found := m.findSlot(hash) - if !found { - m.size++ - } - m.ctrl[idx] = byte(hash & 0x7f) - m.slots[idx] = entry[V]{hash: hash, val: v} -} - -func (m *Map[V]) Put(hash uint64, v V) { - m.migrateSome() - m.putEntry(hash, v) - if m.size >= m.growth { - m.startRehash() - } -} - -func (m *Map[V]) Delete(hash uint64) bool { - m.migrateSome() - idx, ok := m.findSlot(hash) - if !ok { - return false - } - m.ctrl[idx] = ctrlTomb - m.size-- - return true -} diff --git a/flashring/pkg/ycsb/simdmap/simdmap_test.go b/flashring/pkg/ycsb/simdmap/simdmap_test.go deleted file mode 100644 index 39ab13e6..00000000 --- a/flashring/pkg/ycsb/simdmap/simdmap_test.go +++ /dev/null @@ -1,156 +0,0 @@ -package simdmap - -import ( - crand "crypto/rand" - "encoding/binary" - "math/rand" - "testing" -) - -func TestPutGet(t *testing.T) { - m := New[int](1 << 10) - - // Insert 10 000 random keys. - kvs := make([]uint64, 10_000) - for i := range kvs { - _ = binary.Read(crand.Reader, binary.LittleEndian, &kvs[i]) - m.Put(kvs[i], int(i)) - } - - // Verify all keys are present. - for i, k := range kvs { - v, ok := m.Get(k) - if !ok || v != i { - t.Fatalf("key %d lost: got (%d,%v)", k, v, ok) - } - } - - // Delete half, ensure they’re gone. - for i := 0; i < len(kvs); i += 2 { - m.Delete(kvs[i]) - if _, ok := m.Get(kvs[i]); ok { - t.Fatalf("key %d should have been deleted", kvs[i]) - } - } -} - -func BenchmarkMixed_SIMDMap(b *testing.B) { - - //m := map[uint64]struct{}{} - sm := New[struct{}](1_000_000) - b.Run("simdmap-put", func(b *testing.B) { - - var h uint64 - b.ResetTimer() - for i := 0; i < b.N; i++ { - _ = binary.Read(crand.Reader, binary.LittleEndian, &h) - sm.Put(h, struct{}{}) - } - b.StopTimer() - b.ReportAllocs() - }) - - b.Run("simdmap-get", func(b *testing.B) { - var h uint64 - b.ResetTimer() - for i := 0; i < b.N; i++ { - _ = binary.Read(crand.Reader, binary.LittleEndian, &h) - _, _ = sm.Get(h) - } - b.StopTimer() - b.ReportAllocs() - }) - -} - -func BenchmarkMixed_GOMap(b *testing.B) { - m := make(map[uint64]struct{}, 1_000_000) - b.Run("map-put", func(b *testing.B) { - var h uint64 - b.ResetTimer() - for i := 0; i < b.N; i++ { - _ = binary.Read(crand.Reader, binary.LittleEndian, &h) - m[h] = struct{}{} - } - b.StopTimer() - b.ReportAllocs() - }) - - b.Run("map-get", func(b *testing.B) { - - var h uint64 - b.ResetTimer() - for i := 0; i < b.N; i++ { - _ = binary.Read(crand.Reader, binary.LittleEndian, &h) - _, _ = m[h] - } - b.StopTimer() - b.ReportAllocs() - }) -} - -func BenchmarkGet_Hit(b *testing.B) { - m := New[struct{}](1 << 20) - - // Fill the map with 1 M random keys - keys := make([]uint64, 1<<20) - for i := range keys { - _ = binary.Read(crand.Reader, binary.LittleEndian, &keys[i]) - m.Put(keys[i], struct{}{}) - } - - // Deterministic PRNG for benchmark loop - rng := rand.New(rand.NewSource(42)) - - b.ResetTimer() - for i := 0; i < b.N; i++ { - k := keys[rng.Intn(len(keys))] - _, _ = m.Get(k) - } -} - -// -------- ultra‑cheap 64‑bit key generator (SplitMix64) ------------- -var x uint64 = 0x9e3779b97f4a7c15 - -func next() uint64 { - z := x + 0x9e3779b97f4a7c15 - x = z - z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9 - z = (z ^ (z >> 27)) * 0x94d049bb133111eb - return z ^ (z >> 31) -} - -// ------------ reusable key slice: *zero* cost in hot loop ----------- -const N = 1 << 20 // 1 048 576 keys -var keys [N]uint64 - -func init() { - for i := range keys { - keys[i] = next() - } -} - -// ----------------------- benchmarks --------------------------------- -func BenchmarkPutGet_SIMD(b *testing.B) { - for i := 0; i < b.N; i++ { - m := New[struct{}](N) // capacity == live set - for _, k := range keys { - m.Put(k, struct{}{}) - } - for _, k := range keys { - _, _ = m.Get(k) - } - } -} - -func BenchmarkPutGet_Go(b *testing.B) { - for i := 0; i < b.N; i++ { - m := make(map[uint64]struct{}, N) // same load‑factor - for _, k := range keys { - m[k] = struct{}{} - } - for _, k := range keys { - _, _ = m[k] - } - } -} diff --git a/flashring/pkg/ycsb/ycsb_bench_test.go b/flashring/pkg/ycsb/ycsb_bench_test.go deleted file mode 100644 index 03665d86..00000000 --- a/flashring/pkg/ycsb/ycsb_bench_test.go +++ /dev/null @@ -1,354 +0,0 @@ -package ycsb - -import ( - "context" - "fmt" - "math/rand" - "runtime" - "testing" - "time" -) - -// YCSB Workload configurations based on standard YCSB workloads -type WorkloadConfig struct { - Name string - ReadProportion float64 - UpdateProportion float64 - InsertProportion float64 - ScanProportion float64 - ReadModifyWriteProp float64 - RequestDistribution string // "uniform", "zipfian", "latest" - Description string -} - -// Standard YCSB Workloads -var ( - WorkloadA = WorkloadConfig{ - Name: "WorkloadA", - ReadProportion: 0.5, - UpdateProportion: 0.5, - InsertProportion: 0.0, - ScanProportion: 0.0, - ReadModifyWriteProp: 0.0, - RequestDistribution: "zipfian", - Description: "Read/Update heavy (50%/50%) - Update heavy workload", - } - - WorkloadB = WorkloadConfig{ - Name: "WorkloadB", - ReadProportion: 0.95, - UpdateProportion: 0.05, - InsertProportion: 0.0, - ScanProportion: 0.0, - ReadModifyWriteProp: 0.0, - RequestDistribution: "zipfian", - Description: "Read heavy (95%/5%) - Read mostly workload", - } - - WorkloadC = WorkloadConfig{ - Name: "WorkloadC", - ReadProportion: 1.0, - UpdateProportion: 0.0, - InsertProportion: 0.0, - ScanProportion: 0.0, - ReadModifyWriteProp: 0.0, - RequestDistribution: "zipfian", - Description: "Read only (100%) - Read only workload", - } - - WorkloadD = WorkloadConfig{ - Name: "WorkloadD", - ReadProportion: 0.95, - UpdateProportion: 0.0, - InsertProportion: 0.05, - ScanProportion: 0.0, - ReadModifyWriteProp: 0.0, - RequestDistribution: "latest", - Description: "Read latest (95%/5%) - Read latest workload", - } - - WorkloadF = WorkloadConfig{ - Name: "WorkloadF", - ReadProportion: 0.5, - UpdateProportion: 0.0, - InsertProportion: 0.0, - ScanProportion: 0.0, - ReadModifyWriteProp: 0.5, - RequestDistribution: "zipfian", - Description: "Read-modify-write (50%/50%) - Transaction workload", - } -) - -// BenchmarkYCSB_AllWorkloads runs all standard YCSB workloads -func BenchmarkYCSB_AllWorkloads(b *testing.B) { - workloads := []WorkloadConfig{WorkloadA, WorkloadB, WorkloadC, WorkloadD, WorkloadF} - - for _, workload := range workloads { - b.Run(workload.Name, func(b *testing.B) { - benchmarkYCSBWorkload(b, workload) - }) - } -} - -// BenchmarkYCSB_WorkloadA tests read/update heavy workload -func BenchmarkYCSB_WorkloadA(b *testing.B) { - benchmarkYCSBWorkload(b, WorkloadA) -} - -// BenchmarkYCSB_WorkloadB tests read heavy workload -func BenchmarkYCSB_WorkloadB(b *testing.B) { - benchmarkYCSBWorkload(b, WorkloadB) -} - -// BenchmarkYCSB_WorkloadC tests read only workload -func BenchmarkYCSB_WorkloadC(b *testing.B) { - benchmarkYCSBWorkload(b, WorkloadC) -} - -func benchmarkYCSBWorkload(b *testing.B, workload WorkloadConfig) { - const ( - recordCount = 1000000 // 1M records for load phase - operationCount = 500000 // 500K operations for run phase - fieldLength = 100 // 100 bytes per field - fieldCount = 10 // 10 fields per record - ) - - // Create YCSB configuration - config := YCSBConfig{ - Capacity: 500000, // 500K capacity (half of record count) - EvictionThreshold: 0.7, // 70% eviction threshold - SlabSizes: []int{64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384}, - } - - // Create test data - testValue := make([]byte, fieldLength*fieldCount) - for i := range testValue { - testValue[i] = byte(i % 256) - } - - // Initialize random seed - rand.Seed(time.Now().UnixNano()) - - b.ResetTimer() - - for n := 0; n < b.N; n++ { - b.StopTimer() - - // Create fresh database for each iteration - db, err := NewLRUCacheDB(config) - if err != nil { - b.Fatalf("Failed to create LRU cache DB: %v", err) - } - - var memStatsBefore, memStatsAfter runtime.MemStats - runtime.GC() - runtime.ReadMemStats(&memStatsBefore) - - // Counters for operation tracking - var readOps, updateOps, insertOps, rmwOps int64 - var readHits, readMisses int64 - - b.StartTimer() - startTime := time.Now() - - // Load phase: Insert initial records - ctx := context.Background() - for i := 0; i < recordCount; i++ { - key := fmt.Sprintf("user%010d", i) - values := map[string][]byte{ - "field0": testValue, - } - err := db.Insert(ctx, "usertable", key, values) - if err != nil { - b.Fatalf("Insert failed: %v", err) - } - } - - loadDuration := time.Since(startTime) - - // Run phase: Execute workload operations - runStartTime := time.Now() - for i := 0; i < operationCount; i++ { - key := generateKey(i, recordCount, workload.RequestDistribution) - operation := selectOperation(workload) - - switch operation { - case "read": - _, err := db.Read(ctx, "usertable", key, []string{"field0"}) - if err != nil { - readMisses++ - } else { - readHits++ - } - readOps++ - - case "update": - values := map[string][]byte{ - "field0": testValue, - } - err := db.Update(ctx, "usertable", key, values) - if err != nil { - b.Errorf("Update failed: %v", err) - } - updateOps++ - - case "insert": - // For insert operations, use a new key - newKey := fmt.Sprintf("user%010d", recordCount+i) - values := map[string][]byte{ - "field0": testValue, - } - err := db.Insert(ctx, "usertable", newKey, values) - if err != nil { - b.Errorf("Insert failed: %v", err) - } - insertOps++ - - case "readmodifywrite": - // Read-modify-write operation - _, err := db.Read(ctx, "usertable", key, []string{"field0"}) - if err != nil { - readMisses++ - } else { - readHits++ - // Modify and write back - values := map[string][]byte{ - "field0": testValue, - } - err = db.Update(ctx, "usertable", key, values) - if err != nil { - b.Errorf("Read-modify-write update failed: %v", err) - } - } - rmwOps++ - } - } - - runDuration := time.Since(runStartTime) - totalDuration := time.Since(startTime) - - b.StopTimer() - - runtime.GC() - runtime.ReadMemStats(&memStatsAfter) - - // Get cache statistics - stats := db.GetStats() - - // Calculate metrics - totalOps := readOps + updateOps + insertOps + rmwOps - throughput := float64(totalOps) / runDuration.Seconds() - loadThroughput := float64(recordCount) / loadDuration.Seconds() - - // Calculate hit rates - cacheHitRate := float64(stats.HitCount) / float64(stats.HitCount+stats.MissCount) * 100 - workloadHitRate := float64(readHits) / float64(readHits+readMisses) * 100 - - // Calculate memory metrics - allocsPerOp := float64(memStatsAfter.Mallocs-memStatsBefore.Mallocs) / float64(totalOps+recordCount) - bytesPerOp := float64(memStatsAfter.TotalAlloc-memStatsBefore.TotalAlloc) / float64(totalOps+recordCount) - - // Report benchmark metrics - b.ReportMetric(throughput, "ops/sec") - b.ReportMetric(float64(runDuration.Nanoseconds())/float64(totalOps), "ns/op") - b.ReportMetric(workloadHitRate, "hit_rate_%") - b.ReportMetric(allocsPerOp, "allocs/op") - b.ReportMetric(bytesPerOp, "B/op") - - // Log detailed stats on first iteration - if n == 0 { - b.Logf("\n=== YCSB %s Benchmark Results ===", workload.Name) - b.Logf("Description: %s", workload.Description) - b.Logf("\n--- Workload Configuration ---") - b.Logf("Read Proportion: %.1f%%", workload.ReadProportion*100) - b.Logf("Update Proportion: %.1f%%", workload.UpdateProportion*100) - b.Logf("Insert Proportion: %.1f%%", workload.InsertProportion*100) - b.Logf("Read-Modify-Write Proportion: %.1f%%", workload.ReadModifyWriteProp*100) - b.Logf("Request Distribution: %s", workload.RequestDistribution) - - b.Logf("\n--- Performance Metrics ---") - b.Logf("Load Throughput: %.2f ops/sec", loadThroughput) - b.Logf("Run Throughput: %.2f ops/sec", throughput) - b.Logf("Average Latency: %.2f ns/op", float64(runDuration.Nanoseconds())/float64(totalOps)) - - b.Logf("\n--- Operation Breakdown ---") - b.Logf("Read Operations: %d (%.1f%%)", readOps, float64(readOps)/float64(totalOps)*100) - b.Logf("Update Operations: %d (%.1f%%)", updateOps, float64(updateOps)/float64(totalOps)*100) - b.Logf("Insert Operations: %d (%.1f%%)", insertOps, float64(insertOps)/float64(totalOps)*100) - b.Logf("Read-Modify-Write Operations: %d (%.1f%%)", rmwOps, float64(rmwOps)/float64(totalOps)*100) - - b.Logf("\n--- Cache Statistics ---") - b.Logf("Cache Hit Rate: %.2f%% (%d/%d)", cacheHitRate, stats.HitCount, stats.HitCount+stats.MissCount) - b.Logf("Workload Hit Rate: %.2f%% (%d/%d)", workloadHitRate, readHits, readHits+readMisses) - b.Logf("Final Cache Size: %d", stats.Size) - b.Logf("Cache Capacity: %d", stats.Capacity) - b.Logf("Eviction Events: %d", stats.EvictCount) - b.Logf("Total Items Evicted: %d", stats.EvictItemCount) - - b.Logf("\n--- Timing Breakdown ---") - b.Logf("Load Phase Duration: %v", loadDuration) - b.Logf("Run Phase Duration: %v", runDuration) - b.Logf("Total Duration: %v", totalDuration) - - b.Logf("\n--- Memory Metrics ---") - b.Logf("Allocations per Operation: %.2f", allocsPerOp) - b.Logf("Bytes per Operation: %.2f", bytesPerOp) - } - } -} - -// selectOperation selects an operation based on workload proportions -func selectOperation(workload WorkloadConfig) string { - r := rand.Float64() - - if r < workload.ReadProportion { - return "read" - } - r -= workload.ReadProportion - - if r < workload.UpdateProportion { - return "update" - } - r -= workload.UpdateProportion - - if r < workload.InsertProportion { - return "insert" - } - r -= workload.InsertProportion - - if r < workload.ReadModifyWriteProp { - return "readmodifywrite" - } - - // Default to read if something goes wrong - return "read" -} - -// generateKey generates a key based on the request distribution -func generateKey(operationIndex, recordCount int, distribution string) string { - var keyIndex int - - switch distribution { - case "uniform": - keyIndex = rand.Intn(recordCount) - case "zipfian": - // Simplified Zipfian: 80% of requests go to 20% of keys - if rand.Float64() < 0.8 { - keyIndex = rand.Intn(recordCount / 5) // Top 20% of keys - } else { - keyIndex = recordCount/5 + rand.Intn(recordCount*4/5) // Bottom 80% of keys - } - case "latest": - // Latest distribution: favor recently inserted keys - if rand.Float64() < 0.8 { - // 80% chance to access the most recent 10% of keys - keyIndex = recordCount*9/10 + rand.Intn(recordCount/10) - } else { - keyIndex = rand.Intn(recordCount * 9 / 10) - } - default: - keyIndex = rand.Intn(recordCount) - } - - return fmt.Sprintf("user%010d", keyIndex) -} diff --git a/go-sdk/README.md b/go-sdk/README.md index f67077bb..7830de9e 100644 --- a/go-sdk/README.md +++ b/go-sdk/README.md @@ -4,12 +4,13 @@ # BharatMLStack Go SDK -A Go SDK for interacting with BharatMLStack components, providing easy-to-use client libraries for the Online Feature Store and other services. +A Go SDK for interacting with BharatMLStack components, providing easy-to-use client libraries for the Online Feature Store and Interaction Store services. ## Features - **Online Feature Store Client**: Complete gRPC client for feature retrieval and persistence -- **Multiple API Methods**: Support for `RetrieveFeatures`, `RetrieveDecodedFeatures`, and `PersistFeatures` +- **Interaction Store Client**: Complete gRPC client for user interaction tracking (clicks, orders) +- **Multiple API Methods**: Support for feature operations and interaction persistence/retrieval - **Protocol Buffer Support**: Generated clients from proto definitions with full type safety - **Batch Processing**: Configurable batch sizes for efficient bulk operations - **Authentication**: Built-in support for caller ID and token-based authentication @@ -26,7 +27,7 @@ go get github.com/Meesho/BharatMLStack/go-sdk ## Configuration -The SDK requires a configuration object with the following fields: +### Online Feature Store (ONFS) Configuration | Field | Type | Required | Description | |-------|------|----------|-------------| @@ -38,6 +39,16 @@ The SDK requires a configuration object with the following fields: | `PlainText` | bool | No | Use plaintext connection instead of TLS (default: false) | | `BatchSize` | int | No | Maximum batch size for bulk operations (default: 50) | +### Interaction Store Configuration + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `Host` | string | Yes | Server hostname (e.g., "localhost", "interaction-store.example.com") | +| `Port` | string | Yes | Server port (e.g., "8080", "443") | +| `CallerId` | string | Yes | Unique identifier for your service/application | +| `DeadLine` | int | No | Request timeout in milliseconds (default: 5000) | +| `PlainText` | bool | No | Use plaintext connection instead of TLS (default: false) | + ## Usage ### Basic Usage @@ -172,6 +183,181 @@ func main() { } ``` +## Interaction Store Client + +The Interaction Store client provides APIs for tracking and retrieving user interactions like clicks and orders. + +### Basic Usage + +```go +package main + +import ( + "context" + "log" + + interactionstore "github.com/Meesho/BharatMLStack/go-sdk/pkg/interaction-store" +) + +func main() { + config := &interactionstore.Config{ + Host: "localhost", + Port: "8090", + PlainText: true, // For local development + CallerId: "my-service", + } + + // Initialize client (timing and count can be nil) + client := interactionstore.NewClientV1(config, nil, nil) + + // Your interaction operations here... +} +``` + +### Complete Example + +```go +package main + +import ( + "context" + "log" + "time" + + interactionstore "github.com/Meesho/BharatMLStack/go-sdk/pkg/interaction-store" +) + +func main() { + // Create configuration + config := &interactionstore.Config{ + Host: "localhost", + Port: "8090", + DeadLine: 5000, // 5 seconds timeout in milliseconds + PlainText: true, // Use plaintext connection for local development + CallerId: "your-service-id", + } + + // Timing and count functions (can be nil for basic usage) + timing := func(name string, value time.Duration, tags []string) { + log.Printf("Timing: %s took %v with tags %v", name, value, tags) + } + count := func(name string, value int64, tags []string) { + log.Printf("Count: %s = %d with tags %v", name, value, tags) + } + + // Initialize the client + client := interactionstore.InitClient(interactionstore.Version1, config, timing, count) + + ctx := context.Background() + + // Example: Persist click data + clickRequest := &interactionstore.PersistClickDataRequest{ + UserId: "user123", + Data: []interactionstore.ClickData{ + { + CatalogId: 100, + ProductId: 200, + Timestamp: time.Now().UnixMilli(), + Metadata: `{"source": "homepage"}`, + }, + }, + } + + clickResponse, err := client.PersistClickData(ctx, clickRequest) + if err != nil { + log.Fatalf("Failed to persist click data: %v", err) + } + log.Printf("Persist click result: %s", clickResponse.Message) + + // Example: Persist order data + orderRequest := &interactionstore.PersistOrderDataRequest{ + UserId: "user123", + Data: []interactionstore.OrderData{ + { + CatalogId: 100, + ProductId: 200, + SubOrderNum: "SUB001", + Timestamp: time.Now().UnixMilli(), + Metadata: `{"payment_method": "upi"}`, + }, + }, + } + + orderResponse, err := client.PersistOrderData(ctx, orderRequest) + if err != nil { + log.Fatalf("Failed to persist order data: %v", err) + } + log.Printf("Persist order result: %s", orderResponse.Message) + + // Example: Retrieve click interactions + retrieveRequest := &interactionstore.RetrieveDataRequest{ + UserId: "user123", + StartTimestamp: time.Now().Add(-24 * time.Hour).UnixMilli(), + EndTimestamp: time.Now().UnixMilli(), + Limit: 100, + } + + clicks, err := client.RetrieveClickInteractions(ctx, retrieveRequest) + if err != nil { + log.Fatalf("Failed to retrieve clicks: %v", err) + } + log.Printf("Retrieved %d click events", len(clicks.Data)) + + // Example: Retrieve order interactions + orders, err := client.RetrieveOrderInteractions(ctx, retrieveRequest) + if err != nil { + log.Fatalf("Failed to retrieve orders: %v", err) + } + log.Printf("Retrieved %d order events", len(orders.Data)) + + // Example: Retrieve multiple interaction types at once + multiRequest := &interactionstore.RetrieveInteractionsRequest{ + UserId: "user123", + InteractionTypes: []interactionstore.InteractionType{ + interactionstore.InteractionTypeClick, + interactionstore.InteractionTypeOrder, + }, + StartTimestamp: time.Now().Add(-24 * time.Hour).UnixMilli(), + EndTimestamp: time.Now().UnixMilli(), + Limit: 100, + } + + interactions, err := client.RetrieveInteractions(ctx, multiRequest) + if err != nil { + log.Fatalf("Failed to retrieve interactions: %v", err) + } + for key, data := range interactions.Data { + log.Printf("Interaction type %s: %d clicks, %d orders", + key, len(data.ClickEvents), len(data.OrderEvents)) + } +} +``` + +### Interaction Store API Reference + +#### Persist Methods + +| Method | Description | +|--------|-------------| +| `PersistClickData(ctx, request)` | Persist click interaction data for a user | +| `PersistOrderData(ctx, request)` | Persist order interaction data for a user | + +#### Retrieve Methods + +| Method | Description | +|--------|-------------| +| `RetrieveClickInteractions(ctx, request)` | Retrieve click interactions for a user within a time range | +| `RetrieveOrderInteractions(ctx, request)` | Retrieve order interactions for a user within a time range | +| `RetrieveInteractions(ctx, request)` | Retrieve multiple interaction types for a user | + +#### Data Types + +| Type | Fields | +|------|--------| +| `ClickData` | `CatalogId`, `ProductId`, `Timestamp`, `Metadata` | +| `OrderData` | `CatalogId`, `ProductId`, `SubOrderNum`, `Timestamp`, `Metadata` | +| `InteractionType` | `InteractionTypeClick (0)`, `InteractionTypeOrder (1)` | + ## Development ### Prerequisites @@ -200,6 +386,7 @@ go test -v ./... # Run specific package tests go test -v ./pkg/onfs +go test -v ./pkg/interaction-store # Run with race detection go test -race ./... diff --git a/go-sdk/VERSION b/go-sdk/VERSION index 60453e69..8b3a0227 100644 --- a/go-sdk/VERSION +++ b/go-sdk/VERSION @@ -1 +1 @@ -v1.0.0 \ No newline at end of file +v1.3.0 \ No newline at end of file diff --git a/go-sdk/cmd/main.go b/go-sdk/cmd/main.go index 411b6e40..e7762df1 100644 --- a/go-sdk/cmd/main.go +++ b/go-sdk/cmd/main.go @@ -13,6 +13,7 @@ import ( "encoding/json" + interactionstore "github.com/Meesho/BharatMLStack/go-sdk/pkg/interaction-store" "github.com/Meesho/BharatMLStack/go-sdk/pkg/onfs" clientv3 "go.etcd.io/etcd/client/v3" ) @@ -25,14 +26,28 @@ type CLIConfig struct { BatchSize int Mode string InputFile string + // Interaction Store specific config + ISHost string + ISPort string } +// ServiceType indicates which service to use +type ServiceType int + +const ( + ServiceONFS ServiceType = iota + ServiceInteractionStore +) + type Session struct { - CallerID string - CallerToken string - Client onfs.Client - IsLoggedIn bool - AppName string + CallerID string + CallerToken string + Client onfs.Client + ISClient interactionstore.Client + IsONFSLoggedIn bool + IsISLoggedIn bool + AppName string + ActiveService ServiceType } // EtcdConfig holds etcd connection configuration @@ -259,10 +274,13 @@ func (e *EtcdSchemaResolver) GetKeySchema(entityLabel string) ([]string, error) func main() { config := parseCLIArgs() - fmt.Println("🚀 ONFS CLI Tool - SQL-like Interface") + fmt.Println("🚀 BharatMLStack CLI Tool - SQL-like Interface") + fmt.Println("Supports: Online Feature Store (ONFS) & Interaction Store (IS)") fmt.Println("Type 'help' for available commands or 'exit' to quit") - session := &Session{} + session := &Session{ + ActiveService: ServiceONFS, + } if config.Mode == "interactive" { runInteractiveMode(config, session) @@ -278,6 +296,8 @@ func parseCLIArgs() *CLIConfig { flag.StringVar(&config.Host, "host", "localhost", "ONFS service host") flag.StringVar(&config.Port, "port", "8089", "ONFS service port") + flag.StringVar(&config.ISHost, "is-host", "localhost", "Interaction Store service host") + flag.StringVar(&config.ISPort, "is-port", "9700", "Interaction Store service port") flag.BoolVar(&config.PlainText, "plaintext", true, "Use plaintext connection (no TLS)") flag.IntVar(&config.Timeout, "timeout", 30000, "Request timeout in milliseconds") flag.IntVar(&config.BatchSize, "batch-size", 50, "Batch size for requests") @@ -293,11 +313,8 @@ func runInteractiveMode(config *CLIConfig, session *Session) { scanner := bufio.NewScanner(os.Stdin) for { - if session.IsLoggedIn { - fmt.Printf("onfs[%s]> ", session.CallerID) - } else { - fmt.Print("onfs> ") - } + prompt := getPrompt(session) + fmt.Print(prompt) if !scanner.Scan() { break @@ -317,6 +334,27 @@ func runInteractiveMode(config *CLIConfig, session *Session) { } } +func getPrompt(session *Session) string { + var service string + var loggedIn bool + var callerID string + + if session.ActiveService == ServiceInteractionStore { + service = "is" + loggedIn = session.IsISLoggedIn + callerID = session.CallerID + } else { + service = "onfs" + loggedIn = session.IsONFSLoggedIn + callerID = session.CallerID + } + + if loggedIn { + return fmt.Sprintf("%s[%s]> ", service, callerID) + } + return fmt.Sprintf("%s> ", service) +} + func runFileMode(config *CLIConfig, session *Session) { data, err := os.ReadFile(config.InputFile) if err != nil { @@ -338,18 +376,41 @@ func runFileMode(config *CLIConfig, session *Session) { func executeCommand(config *CLIConfig, session *Session, command string) { command = strings.TrimSpace(command) + lowerCmd := strings.ToLower(command) switch { case command == "help": showHelp() - case strings.HasPrefix(strings.ToLower(command), "login"): + // Service switching commands + case lowerCmd == "use onfs": + session.ActiveService = ServiceONFS + fmt.Println("🔄 Switched to Online Feature Store (ONFS)") + case lowerCmd == "use is" || lowerCmd == "use interaction-store": + session.ActiveService = ServiceInteractionStore + fmt.Println("🔄 Switched to Interaction Store") + // ONFS commands + case strings.HasPrefix(lowerCmd, "login") && session.ActiveService == ServiceONFS: handleLogin(config, session, command) - case strings.HasPrefix(strings.ToLower(command), "insert"): + case strings.HasPrefix(lowerCmd, "insert") && session.ActiveService == ServiceONFS: handleInsert(session, command) - case strings.HasPrefix(strings.ToLower(command), "select_decoded"): + case strings.HasPrefix(lowerCmd, "select_decoded") && session.ActiveService == ServiceONFS: handleSelect(session, command, true) - case strings.HasPrefix(strings.ToLower(command), "select"): + case strings.HasPrefix(lowerCmd, "select") && session.ActiveService == ServiceONFS: handleSelect(session, command, false) + // Interaction Store commands + case strings.HasPrefix(lowerCmd, "login") && session.ActiveService == ServiceInteractionStore: + handleISLogin(config, session, command) + case strings.HasPrefix(lowerCmd, "persist_click"): + handleISPersistClick(session, command) + case strings.HasPrefix(lowerCmd, "persist_order"): + handleISPersistOrder(session, command) + case strings.HasPrefix(lowerCmd, "retrieve_clicks"): + handleISRetrieveClicks(session, command) + case strings.HasPrefix(lowerCmd, "retrieve_orders"): + handleISRetrieveOrders(session, command) + case strings.HasPrefix(lowerCmd, "retrieve_interactions"): + handleISRetrieveInteractions(session, command) + // Common commands case command == "status": showStatus(session) case command == "logout": @@ -360,14 +421,21 @@ func executeCommand(config *CLIConfig, session *Session, command string) { } func showHelp() { - fmt.Println("\n📖 ONFS CLI Commands:") + fmt.Println("\n📖 BharatMLStack CLI Commands:") + fmt.Println() + fmt.Println("🔀 Service Selection:") + fmt.Println(" use onfs - Switch to Online Feature Store") + fmt.Println(" use is - Switch to Interaction Store") fmt.Println() fmt.Println("🔐 Session Management:") fmt.Println(" login - Login with credentials and app context") fmt.Println(" logout - Logout from current session") fmt.Println(" status - Show current session status") fmt.Println() - fmt.Println("�� Data Operations:") + fmt.Println("═══════════════════════════════════════════════════════════════") + fmt.Println("📦 ONLINE FEATURE STORE (ONFS) Commands:") + fmt.Println("═══════════════════════════════════════════════════════════════") + fmt.Println() fmt.Println(" INSERT Syntax:") fmt.Println(" insert into . () values ()") fmt.Println(" insert into . () values ()") @@ -376,18 +444,44 @@ func showHelp() { fmt.Println(" select from . where =") fmt.Println(" select_decoded from . where =") fmt.Println() - fmt.Println("📚 Examples:") - fmt.Println(" login onfs onfs-cli test") - fmt.Println(" insert into user.profile (age,location) values (25,'NYC')") - fmt.Println(" insert into user.profile,preferences (age,location,theme) values (25,'NYC','dark')") - fmt.Println(" select age,location from user.profile where user_id=123") - fmt.Println(" select_decoded age,location from user.profile where user_id=123") + fmt.Println(" Examples:") + fmt.Println(" use onfs") + fmt.Println(" login onfs onfs-cli test") + fmt.Println(" insert into user.profile (age,location) values (25,'NYC')") + fmt.Println(" select age,location from user.profile where user_id=123") + fmt.Println() + fmt.Println("═══════════════════════════════════════════════════════════════") + fmt.Println("🔄 INTERACTION STORE Commands:") + fmt.Println("═══════════════════════════════════════════════════════════════") + fmt.Println() + fmt.Println(" PERSIST Commands:") + fmt.Println(" persist_click [metadata]") + fmt.Println(" persist_order [metadata]") + fmt.Println() + fmt.Println(" RETRIEVE Commands:") + fmt.Println(" retrieve_clicks ") + fmt.Println(" retrieve_orders ") + fmt.Println(" retrieve_interactions ") + fmt.Println(" (types: click,order or click or order)") + fmt.Println() + fmt.Println(" Examples:") + fmt.Println(" use is") + fmt.Println(" login is-app is-caller") + fmt.Println(" persist_click user123 100 200 1704067200000") + fmt.Println(" persist_click user123 100 200 1704067200000 '{\"source\":\"homepage\"}'") + fmt.Println(" persist_order user123 100 200 SUB001 1704067200000") + fmt.Println(" persist_order user123 100 200 SUB001 1704067200000 '{\"payment\":\"upi\"}'") + fmt.Println(" retrieve_clicks user123 1704067200000 1704153600000 100") + fmt.Println(" retrieve_orders user123 1704067200000 1704153600000 100") + fmt.Println(" retrieve_interactions user123 click,order 1704067200000 1704153600000 100") fmt.Println() fmt.Println("💡 Data Types:") fmt.Println(" Strings: 'value' or \"value\"") fmt.Println(" Numbers: 123, 45.67") fmt.Println(" Booleans: true, false") fmt.Println(" Arrays: [1,2,3] or ['a','b','c']") + fmt.Println(" Timestamps: Unix milliseconds (e.g., 1704067200000)") + fmt.Println(" Metadata: JSON string (e.g., '{\"source\":\"homepage\"}')") fmt.Println() fmt.Println("🚪 Other:") fmt.Println(" help - Show this help") @@ -467,7 +561,7 @@ func handleLogin(config *CLIConfig, session *Session, command string) { session.CallerID = callerID session.CallerToken = callerToken session.Client = client - session.IsLoggedIn = true + session.IsONFSLoggedIn = true session.AppName = appName fmt.Printf("✅ Successfully logged in as %s for app %s\n", callerID, appName) @@ -581,34 +675,72 @@ func contains(slice []string, item string) bool { } func handleLogout(session *Session) { - if !session.IsLoggedIn { - fmt.Println("❌ Not logged in") - return + if session.ActiveService == ServiceInteractionStore { + if !session.IsISLoggedIn { + fmt.Println("❌ Not logged in to Interaction Store") + return + } + fmt.Printf("👋 Logging out %s from Interaction Store\n", session.CallerID) + session.IsISLoggedIn = false + session.ISClient = nil + } else { + if !session.IsONFSLoggedIn { + fmt.Println("❌ Not logged in to ONFS") + return + } + fmt.Printf("👋 Logging out %s from app %s\n", session.CallerID, session.AppName) + session.IsONFSLoggedIn = false + session.Client = nil } - fmt.Printf("👋 Logging out %s from app %s\n", session.CallerID, session.AppName) - session.IsLoggedIn = false - session.CallerID = "" - session.CallerToken = "" - session.AppName = "" - session.Client = nil + // Clear shared fields if both are logged out + if !session.IsONFSLoggedIn && !session.IsISLoggedIn { + session.CallerID = "" + session.CallerToken = "" + session.AppName = "" + } } func showStatus(session *Session) { fmt.Println("\n📊 Session Status:") - if session.IsLoggedIn { - fmt.Printf(" Status: ✅ Logged in\n") - fmt.Printf(" App Name: %s\n", session.AppName) - fmt.Printf(" Caller ID: %s\n", session.CallerID) - fmt.Printf(" Token: %s\n", session.CallerToken) + + // Show active service + if session.ActiveService == ServiceInteractionStore { + fmt.Println(" Active Service: 🔄 Interaction Store") + } else { + fmt.Println(" Active Service: 📦 Online Feature Store (ONFS)") + } + + fmt.Println() + + // ONFS Status + fmt.Println(" 📦 ONFS:") + if session.IsONFSLoggedIn { + fmt.Printf(" Status: ✅ Logged in\n") + fmt.Printf(" App Name: %s\n", session.AppName) + fmt.Printf(" Caller ID: %s\n", session.CallerID) } else { - fmt.Printf(" Status: ❌ Not logged in\n") - fmt.Println(" Use 'login ' to authenticate") + fmt.Printf(" Status: ❌ Not logged in\n") } + + fmt.Println() + + // Interaction Store Status + fmt.Println(" 🔄 Interaction Store:") + if session.IsISLoggedIn { + fmt.Printf(" Status: ✅ Logged in\n") + fmt.Printf(" Caller ID: %s\n", session.CallerID) + } else { + fmt.Printf(" Status: ❌ Not logged in\n") + } + + fmt.Println() + fmt.Println(" Use 'use onfs' or 'use is' to switch services") + fmt.Println(" Use 'login ' to authenticate") } func handleInsert(session *Session, command string) { - if !session.IsLoggedIn { + if !session.IsONFSLoggedIn { fmt.Println("❌ Please login first using: login ") return } @@ -886,7 +1018,7 @@ func handleInsertWithTypeInference(session *Session, command string) { } func handleSelect(session *Session, command string, decoded bool) { - if !session.IsLoggedIn { + if !session.IsONFSLoggedIn { fmt.Println("❌ Please login first using: login ") return } @@ -1166,3 +1298,433 @@ func printDecodedResult(result *onfs.DecodedResult) { fmt.Printf(" Values: %v\n", row.Columns) } } + +// ═══════════════════════════════════════════════════════════════════════════ +// INTERACTION STORE HANDLERS +// ═══════════════════════════════════════════════════════════════════════════ + +// handleISLogin handles login for Interaction Store +func handleISLogin(config *CLIConfig, session *Session, command string) { + parts := strings.Fields(command) + if len(parts) != 3 { + fmt.Println("❌ Usage: login ") + return + } + + appName := parts[1] + callerID := parts[2] + + fmt.Printf("🔐 Logging in to Interaction Store as %s...\n", callerID) + + // Create Interaction Store client + isConfig := &interactionstore.Config{ + Host: config.ISHost, + Port: config.ISPort, + DeadLine: config.Timeout, + PlainText: config.PlainText, + CallerId: callerID, + } + + timing := func(name string, value time.Duration, tags []string) { + fmt.Printf("📊 [METRIC] %s: %v\n", name, value) + } + + count := func(name string, value int64, tags []string) { + fmt.Printf("📊 [METRIC] %s: %d\n", name, value) + } + + // Reset registry for re-initialization + interactionstore.ResetRegistry() + + client := interactionstore.InitClient(interactionstore.Version1, isConfig, timing, count) + + session.CallerID = callerID + session.ISClient = client + session.IsISLoggedIn = true + session.AppName = appName + + fmt.Printf("✅ Successfully logged in to Interaction Store as %s\n", callerID) +} + +// handleISPersistClick handles persist_click command +func handleISPersistClick(session *Session, command string) { + if !session.IsISLoggedIn { + fmt.Println("❌ Please login to Interaction Store first using: use is && login ") + return + } + + // Parse: persist_click [metadata] + parts := strings.Fields(command) + if len(parts) < 5 { + fmt.Println("❌ Usage: persist_click [metadata]") + return + } + + userID := parts[1] + catalogID, err := strconv.ParseInt(parts[2], 10, 32) + if err != nil { + fmt.Printf("❌ Invalid catalog_id: %v\n", err) + return + } + productID, err := strconv.ParseInt(parts[3], 10, 32) + if err != nil { + fmt.Printf("❌ Invalid product_id: %v\n", err) + return + } + timestamp, err := strconv.ParseInt(parts[4], 10, 64) + if err != nil { + fmt.Printf("❌ Invalid timestamp: %v\n", err) + return + } + + metadata := "" + if len(parts) > 5 { + metadata = strings.Join(parts[5:], " ") + metadata = strings.Trim(metadata, "'\"") + } + + request := &interactionstore.PersistClickDataRequest{ + UserId: userID, + Data: []interactionstore.ClickData{ + { + CatalogId: int32(catalogID), + ProductId: int32(productID), + Timestamp: timestamp, + Metadata: metadata, + }, + }, + } + + fmt.Printf("📝 Persisting click data for user: %s\n", userID) + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + response, err := session.ISClient.PersistClickData(ctx, request) + if err != nil { + fmt.Printf("❌ Error persisting click data: %v\n", err) + return + } + + fmt.Printf("✅ Click data persisted: %s\n", response.Message) +} + +// handleISPersistOrder handles persist_order command +func handleISPersistOrder(session *Session, command string) { + if !session.IsISLoggedIn { + fmt.Println("❌ Please login to Interaction Store first using: use is && login ") + return + } + + // Parse: persist_order [metadata] + parts := strings.Fields(command) + if len(parts) < 6 { + fmt.Println("❌ Usage: persist_order [metadata]") + return + } + + userID := parts[1] + catalogID, err := strconv.ParseInt(parts[2], 10, 32) + if err != nil { + fmt.Printf("❌ Invalid catalog_id: %v\n", err) + return + } + productID, err := strconv.ParseInt(parts[3], 10, 32) + if err != nil { + fmt.Printf("❌ Invalid product_id: %v\n", err) + return + } + subOrderNum := parts[4] + timestamp, err := strconv.ParseInt(parts[5], 10, 64) + if err != nil { + fmt.Printf("❌ Invalid timestamp: %v\n", err) + return + } + + metadata := "" + if len(parts) > 6 { + metadata = strings.Join(parts[6:], " ") + metadata = strings.Trim(metadata, "'\"") + } + + request := &interactionstore.PersistOrderDataRequest{ + UserId: userID, + Data: []interactionstore.OrderData{ + { + CatalogId: int32(catalogID), + ProductId: int32(productID), + SubOrderNum: subOrderNum, + Timestamp: timestamp, + Metadata: metadata, + }, + }, + } + + fmt.Printf("📝 Persisting order data for user: %s\n", userID) + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + response, err := session.ISClient.PersistOrderData(ctx, request) + if err != nil { + fmt.Printf("❌ Error persisting order data: %v\n", err) + return + } + + fmt.Printf("✅ Order data persisted: %s\n", response.Message) +} + +// handleISRetrieveClicks handles retrieve_clicks command +func handleISRetrieveClicks(session *Session, command string) { + if !session.IsISLoggedIn { + fmt.Println("❌ Please login to Interaction Store first using: use is && login ") + return + } + + // Parse: retrieve_clicks + parts := strings.Fields(command) + if len(parts) != 5 { + fmt.Println("❌ Usage: retrieve_clicks ") + return + } + + userID := parts[1] + startTS, err := strconv.ParseInt(parts[2], 10, 64) + if err != nil { + fmt.Printf("❌ Invalid start_timestamp: %v\n", err) + return + } + endTS, err := strconv.ParseInt(parts[3], 10, 64) + if err != nil { + fmt.Printf("❌ Invalid end_timestamp: %v\n", err) + return + } + limit, err := strconv.ParseInt(parts[4], 10, 32) + if err != nil { + fmt.Printf("❌ Invalid limit: %v\n", err) + return + } + + request := &interactionstore.RetrieveDataRequest{ + UserId: userID, + StartTimestamp: startTS, + EndTimestamp: endTS, + Limit: int32(limit), + } + + fmt.Printf("🔍 Retrieving click interactions for user: %s\n", userID) + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + response, err := session.ISClient.RetrieveClickInteractions(ctx, request) + if err != nil { + fmt.Printf("❌ Error retrieving click data: %v\n", err) + return + } + + printClickResults(response) +} + +// handleISRetrieveOrders handles retrieve_orders command +func handleISRetrieveOrders(session *Session, command string) { + if !session.IsISLoggedIn { + fmt.Println("❌ Please login to Interaction Store first using: use is && login ") + return + } + + // Parse: retrieve_orders + parts := strings.Fields(command) + if len(parts) != 5 { + fmt.Println("❌ Usage: retrieve_orders ") + return + } + + userID := parts[1] + startTS, err := strconv.ParseInt(parts[2], 10, 64) + if err != nil { + fmt.Printf("❌ Invalid start_timestamp: %v\n", err) + return + } + endTS, err := strconv.ParseInt(parts[3], 10, 64) + if err != nil { + fmt.Printf("❌ Invalid end_timestamp: %v\n", err) + return + } + limit, err := strconv.ParseInt(parts[4], 10, 32) + if err != nil { + fmt.Printf("❌ Invalid limit: %v\n", err) + return + } + + request := &interactionstore.RetrieveDataRequest{ + UserId: userID, + StartTimestamp: startTS, + EndTimestamp: endTS, + Limit: int32(limit), + } + + fmt.Printf("🔍 Retrieving order interactions for user: %s\n", userID) + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + response, err := session.ISClient.RetrieveOrderInteractions(ctx, request) + if err != nil { + fmt.Printf("❌ Error retrieving order data: %v\n", err) + return + } + + printOrderResults(response) +} + +// handleISRetrieveInteractions handles retrieve_interactions command +func handleISRetrieveInteractions(session *Session, command string) { + if !session.IsISLoggedIn { + fmt.Println("❌ Please login to Interaction Store first using: use is && login ") + return + } + + // Parse: retrieve_interactions + parts := strings.Fields(command) + if len(parts) != 6 { + fmt.Println("❌ Usage: retrieve_interactions ") + fmt.Println(" types: click,order or click or order") + return + } + + userID := parts[1] + typesStr := parts[2] + startTS, err := strconv.ParseInt(parts[3], 10, 64) + if err != nil { + fmt.Printf("❌ Invalid start_timestamp: %v\n", err) + return + } + endTS, err := strconv.ParseInt(parts[4], 10, 64) + if err != nil { + fmt.Printf("❌ Invalid end_timestamp: %v\n", err) + return + } + limit, err := strconv.ParseInt(parts[5], 10, 32) + if err != nil { + fmt.Printf("❌ Invalid limit: %v\n", err) + return + } + + // Parse interaction types + var interactionTypes []interactionstore.InteractionType + typesParts := strings.Split(typesStr, ",") + for _, t := range typesParts { + t = strings.TrimSpace(strings.ToLower(t)) + switch t { + case "click": + interactionTypes = append(interactionTypes, interactionstore.InteractionTypeClick) + case "order": + interactionTypes = append(interactionTypes, interactionstore.InteractionTypeOrder) + default: + fmt.Printf("❌ Unknown interaction type: %s (use: click, order)\n", t) + return + } + } + + request := &interactionstore.RetrieveInteractionsRequest{ + UserId: userID, + InteractionTypes: interactionTypes, + StartTimestamp: startTS, + EndTimestamp: endTS, + Limit: int32(limit), + } + + fmt.Printf("🔍 Retrieving interactions for user: %s (types: %s)\n", userID, typesStr) + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + response, err := session.ISClient.RetrieveInteractions(ctx, request) + if err != nil { + fmt.Printf("❌ Error retrieving interactions: %v\n", err) + return + } + + printInteractionsResults(response) +} + +// printClickResults prints click interaction results +func printClickResults(response *interactionstore.RetrieveClickDataResponse) { + fmt.Println("\n📊 Click Interactions Result:") + fmt.Printf("Total Events: %d\n", len(response.Data)) + + if len(response.Data) == 0 { + fmt.Println(" No click events found.") + return + } + + fmt.Println("\n📝 Events:") + for i, event := range response.Data { + fmt.Printf(" %d. CatalogID=%d, ProductID=%d, Timestamp=%d", + i+1, event.CatalogId, event.ProductId, event.Timestamp) + if event.Metadata != "" { + fmt.Printf(", Metadata=%s", event.Metadata) + } + fmt.Println() + } +} + +// printOrderResults prints order interaction results +func printOrderResults(response *interactionstore.RetrieveOrderDataResponse) { + fmt.Println("\n📊 Order Interactions Result:") + fmt.Printf("Total Events: %d\n", len(response.Data)) + + if len(response.Data) == 0 { + fmt.Println(" No order events found.") + return + } + + fmt.Println("\n📝 Events:") + for i, event := range response.Data { + fmt.Printf(" %d. CatalogID=%d, ProductID=%d, SubOrderNum=%s, Timestamp=%d", + i+1, event.CatalogId, event.ProductId, event.SubOrderNum, event.Timestamp) + if event.Metadata != "" { + fmt.Printf(", Metadata=%s", event.Metadata) + } + fmt.Println() + } +} + +// printInteractionsResults prints mixed interaction results +func printInteractionsResults(response *interactionstore.RetrieveInteractionsResponse) { + fmt.Println("\n📊 Interactions Result:") + + if len(response.Data) == 0 { + fmt.Println(" No interactions found.") + return + } + + for interactionType, data := range response.Data { + fmt.Printf("\n📁 %s:\n", interactionType) + + if len(data.ClickEvents) > 0 { + fmt.Printf(" Click Events (%d):\n", len(data.ClickEvents)) + for i, event := range data.ClickEvents { + fmt.Printf(" %d. CatalogID=%d, ProductID=%d, Timestamp=%d", + i+1, event.CatalogId, event.ProductId, event.Timestamp) + if event.Metadata != "" { + fmt.Printf(", Metadata=%s", event.Metadata) + } + fmt.Println() + } + } + + if len(data.OrderEvents) > 0 { + fmt.Printf(" Order Events (%d):\n", len(data.OrderEvents)) + for i, event := range data.OrderEvents { + fmt.Printf(" %d. CatalogID=%d, ProductID=%d, SubOrderNum=%s, Timestamp=%d", + i+1, event.CatalogId, event.ProductId, event.SubOrderNum, event.Timestamp) + if event.Metadata != "" { + fmt.Printf(", Metadata=%s", event.Metadata) + } + fmt.Println() + } + } + } +} diff --git a/go-sdk/go.mod b/go-sdk/go.mod index 76d8dda8..19d67f2d 100644 --- a/go-sdk/go.mod +++ b/go-sdk/go.mod @@ -3,31 +3,72 @@ module github.com/Meesho/BharatMLStack/go-sdk go 1.24.4 require ( + github.com/DataDog/datadog-go/v5 v5.8.3 + github.com/gin-gonic/gin v1.11.0 github.com/rs/zerolog v1.34.0 - github.com/stretchr/testify v1.10.0 + github.com/soheilhy/cmux v0.1.5 + github.com/spf13/viper v1.21.0 + github.com/stretchr/testify v1.11.1 + github.com/x448/float16 v0.8.4 go.etcd.io/etcd/client/v3 v3.5.17 + golang.org/x/net v0.42.0 google.golang.org/grpc v1.68.2 - google.golang.org/protobuf v1.36.6 + google.golang.org/protobuf v1.36.9 ) require ( + github.com/Microsoft/go-winio v0.5.0 // indirect + github.com/bytedance/sonic v1.14.0 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect github.com/coreos/go-semver v0.3.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.27.0 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-yaml v1.18.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/quic-go/qpack v0.5.1 // indirect + github.com/quic-go/quic-go v0.54.0 // indirect + github.com/sagikazarmark/locafero v0.11.0 // indirect + github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect + github.com/spf13/afero v1.15.0 // indirect + github.com/spf13/cast v1.10.0 // indirect + github.com/spf13/pflag v1.0.10 // indirect github.com/stretchr/objx v0.5.2 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.0 // indirect go.etcd.io/etcd/api/v3 v3.5.17 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.17 // indirect go.uber.org/atomic v1.9.0 // indirect + go.uber.org/mock v0.5.0 // indirect go.uber.org/multierr v1.9.0 // indirect go.uber.org/zap v1.21.0 // indirect - golang.org/x/net v0.32.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/text v0.21.0 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/arch v0.20.0 // indirect + golang.org/x/crypto v0.40.0 // indirect + golang.org/x/mod v0.26.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/text v0.28.0 // indirect + golang.org/x/tools v0.35.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go-sdk/go.sum b/go-sdk/go.sum index 32cd513f..9e87fe57 100644 --- a/go-sdk/go.sum +++ b/go-sdk/go.sum @@ -1,5 +1,15 @@ +github.com/DataDog/datadog-go/v5 v5.8.3 h1:s58CUJ9s8lezjhTNJO/SxkPBv2qZjS3ktpRSqGF5n0s= +github.com/DataDog/datadog-go/v5 v5.8.3/go.mod h1:K9kcYBlxkcPP8tvvjZZKs/m1edNAUFzBbdpTUKfCsuw= +github.com/Microsoft/go-winio v0.5.0 h1:Elr9Wn+sGKPlkaBvwu4mTrxtmOp3F3yV9qhaHbXGjwU= +github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= +github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= @@ -8,41 +18,117 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= +github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= +github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= +github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= +github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= +github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= +github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= +github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= +github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= +github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= +github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -57,52 +143,70 @@ go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= +golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= +golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= -golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= +golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -113,11 +217,12 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/grpc v1.68.2 h1:EWN8x60kqfCcBXzbfPpEezgdYRZA9JCxtySmCtTUs2E= google.golang.org/grpc v1.68.2/go.mod h1:AOXp0/Lj+nW5pJEgw8KQ6L1Ka+NTyJOABlSgfCrCN5A= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/go-sdk/pkg/api/context.go b/go-sdk/pkg/api/context.go new file mode 100644 index 00000000..72333d45 --- /dev/null +++ b/go-sdk/pkg/api/context.go @@ -0,0 +1,42 @@ +package api + +import ( + netHttp "net/http" + "strconv" + + "github.com/Meesho/BharatMLStack/go-sdk/pkg/api/http" + "github.com/gin-gonic/gin" +) + +// Deprecated: Use RequestContext instead of RequestMeta +type RequestMeta struct { + UserId string + UserContext string + AppVersionCode int + ClientId string + Pincode string + AppSession string + FeedSession string +} + +// Deprecated: GetRequestMeta Build RequestMeta from gin context +func GetRequestMeta(context *gin.Context) *RequestMeta { + rawAppVersionCode := context.Request.Header.Get(http.HeaderAppVersionCode) + appVersionCode, _ := strconv.Atoi(rawAppVersionCode) + return &RequestMeta{ + UserId: context.Request.Header.Get(http.HeaderUserId), + UserContext: context.Request.Header.Get(http.HeaderUserContext), + AppVersionCode: appVersionCode, + ClientId: context.Request.Header.Get(http.HeaderClientId), + Pincode: context.Request.Header.Get(http.HeaderUserPincode), + } +} + +// Deprecated: UpdateHeaders Update headers from request meta +func UpdateHeaders(header *netHttp.Header, requestMeta *RequestMeta) { + header.Set(http.HeaderUserId, requestMeta.UserId) + header.Set(http.HeaderUserContext, requestMeta.UserContext) + header.Set(http.HeaderAppVersionCode, strconv.Itoa(requestMeta.AppVersionCode)) + header.Set(http.HeaderClientId, requestMeta.ClientId) + header.Set(http.HeaderUserPincode, requestMeta.Pincode) +} diff --git a/helix-client/pkg/api/error.go b/go-sdk/pkg/api/error.go similarity index 100% rename from helix-client/pkg/api/error.go rename to go-sdk/pkg/api/error.go diff --git a/helix-client/pkg/api/grpc_error.go b/go-sdk/pkg/api/grpc_error.go similarity index 100% rename from helix-client/pkg/api/grpc_error.go rename to go-sdk/pkg/api/grpc_error.go diff --git a/helix-client/pkg/api/http/header.go b/go-sdk/pkg/api/http/header.go similarity index 100% rename from helix-client/pkg/api/http/header.go rename to go-sdk/pkg/api/http/header.go diff --git a/helix-client/pkg/api/http/helper.go b/go-sdk/pkg/api/http/helper.go similarity index 100% rename from helix-client/pkg/api/http/helper.go rename to go-sdk/pkg/api/http/helper.go diff --git a/helix-client/pkg/api/http_grpc_error_mapper.go b/go-sdk/pkg/api/http_grpc_error_mapper.go similarity index 100% rename from helix-client/pkg/api/http_grpc_error_mapper.go rename to go-sdk/pkg/api/http_grpc_error_mapper.go diff --git a/go-sdk/pkg/api/request_context.go b/go-sdk/pkg/api/request_context.go new file mode 100644 index 00000000..34c8e820 --- /dev/null +++ b/go-sdk/pkg/api/request_context.go @@ -0,0 +1,210 @@ +package api + +import ( + "context" + "errors" + "fmt" + netHttp "net/http" + "strconv" + + "github.com/Meesho/BharatMLStack/go-sdk/pkg/api/http" + enum "github.com/Meesho/BharatMLStack/go-sdk/pkg/enums" + "github.com/gin-gonic/gin" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +type RequestContext struct { + UserId string + UserContext enum.UserContext + AppVersionCode int + ClientId string + UserStateCode string + UserPinCode string + UserCity string + AppSession string + FeedSession string + InstanceId string + SessionId string + UserLongitude string + UserLatitude string + UserAddressId string + UserCountry string + UserLanguage string + UserLocation string +} + +const ( + RequestContextValue = "REQUEST_CONTEXT" +) + +func GetRequestContextForGRPC(ctx context.Context) (*RequestContext, error) { + requestContext := RequestContext{} + mdContext, ok := metadata.FromIncomingContext(ctx) + if !ok { + return nil, status.Errorf(codes.InvalidArgument, "metadata is not provided") + } + + // get user id (mandatory) + userId, err := getMetadataValue(mdContext, http.HeaderUserId) + if err != nil { + return nil, err + } + if len(userId) == 0 { + return nil, errors.New("user id is empty in headers") + } + requestContext.UserId = userId + + // get user context (mandatory) + userContext, err := getMetadataValue(mdContext, http.HeaderUserContext) + if err != nil { + return nil, err + } + if len(userContext) == 0 { + return nil, errors.New("user context is empty in headers") + } else { + if userCtx, err := enum.ParseUserContext(userContext); err != nil { + return nil, err + } else { + requestContext.UserContext = userCtx + } + } + + // get app version code (optional) + rawAppVersionCode, err := getMetadataValue(mdContext, http.HeaderAppVersionCode) + if err != nil { + return nil, err + } + if len(rawAppVersionCode) != 0 { + if appVersionCode, err := strconv.Atoi(rawAppVersionCode); err != nil { + return nil, errors.New("app_version_code should be an integer") + } else { + requestContext.AppVersionCode = appVersionCode + } + } + + // get client id (optional) + requestContext.ClientId, _ = getMetadataValue(mdContext, http.HeaderClientId) + + // get userStateCode + requestContext.UserStateCode, _ = getMetadataValue(mdContext, http.HeaderUserStateCode) + + // get userPinCode + requestContext.UserPinCode, _ = getMetadataValue(mdContext, http.HeaderUserPincode) + + // get userCity + requestContext.UserCity, _ = getMetadataValue(mdContext, http.HeaderUserCity) + requestContext.FeedSession, _ = getMetadataValue(mdContext, http.HeaderFeedSession) + requestContext.AppSession, _ = getMetadataValue(mdContext, http.HeaderAppSession) + requestContext.InstanceId, _ = getMetadataValue(mdContext, http.HeaderInstanceId) + requestContext.SessionId, _ = getMetadataValue(mdContext, http.HeaderSessionId) + requestContext.UserLongitude, _ = getMetadataValue(mdContext, http.HeaderUserLongitude) + requestContext.UserLatitude, _ = getMetadataValue(mdContext, http.HeaderUserLatitude) + requestContext.UserAddressId, _ = getMetadataValue(mdContext, http.HeaderUserAddressId) + requestContext.UserCountry, _ = getMetadataValue(mdContext, http.HeaderCountry) + requestContext.UserLanguage, _ = getMetadataValue(mdContext, http.HeaderLanguage) + requestContext.UserLocation, _ = getMetadataValue(mdContext, http.HeaderAppUserLocation) + return &requestContext, nil +} + +func getMetadataValue(md metadata.MD, key string) (string, error) { + values := md.Get(key) + if len(values) == 0 { + return "", fmt.Errorf("metadata key '%s' is missing ", key) + } + return values[0], nil +} + +// UpdateWithHeaders Update headers from request context +func UpdateWithGRPCHeaders(headers map[string]string, context *RequestContext) { + headers[http.HeaderUserId] = context.UserId + headers[http.HeaderUserContext] = context.UserContext.String() + headers[http.HeaderAppVersionCode] = strconv.Itoa(context.AppVersionCode) + headers[http.HeaderClientId] = context.ClientId + headers[http.HeaderUserStateCode] = context.UserStateCode + headers[http.HeaderUserPincode] = context.UserPinCode + headers[http.HeaderUserCity] = context.UserCity + headers[http.HeaderInstanceId] = context.InstanceId + headers[http.HeaderSessionId] = context.SessionId + headers[http.HeaderUserLongitude] = context.UserLongitude + headers[http.HeaderUserLatitude] = context.UserLatitude + headers[http.HeaderUserAddressId] = context.UserAddressId + headers[http.HeaderCountry] = context.UserCountry + headers[http.HeaderLanguage] = context.UserLanguage +} + +// GetRequestContext Build RequestContext from gin context +func GetRequestContext(context *gin.Context) (*RequestContext, error) { + requestContext := &RequestContext{} + // get user id (mandatory) + userId := context.Request.Header.Get(http.HeaderUserId) + if len(userId) == 0 { + return nil, errors.New("user id is missing in headers") + } + + requestContext.UserId = userId + // get user context (mandatory) + userContext := context.Request.Header.Get(http.HeaderUserContext) + if len(userContext) == 0 { + return nil, errors.New("user context is missing in headers") + } else { + if userCtx, err := enum.ParseUserContext(userContext); err != nil { + return nil, err + } else { + requestContext.UserContext = userCtx + } + } + + // get app version code (optional) + rawAppVersionCode := context.Request.Header.Get(http.HeaderAppVersionCode) + if len(rawAppVersionCode) != 0 { + if appVersionCode, err := strconv.Atoi(rawAppVersionCode); err != nil { + return nil, errors.New("app_version_code should be an integer") + } else { + requestContext.AppVersionCode = appVersionCode + } + } + + // get client id (optional) + requestContext.ClientId = context.Request.Header.Get(http.HeaderClientId) + + // get userStateCode + requestContext.UserStateCode = context.Request.Header.Get(http.HeaderUserStateCode) + + // get userPinCode + requestContext.UserPinCode = context.Request.Header.Get(http.HeaderUserPincode) + + // get userCity + requestContext.UserCity = context.Request.Header.Get(http.HeaderUserCity) + + requestContext.FeedSession = context.Request.Header.Get(http.HeaderFeedSession) + requestContext.AppSession = context.Request.Header.Get(http.HeaderAppSession) + requestContext.InstanceId = context.Request.Header.Get(http.HeaderInstanceId) + requestContext.SessionId = context.Request.Header.Get(http.HeaderSessionId) + requestContext.UserLongitude = context.Request.Header.Get(http.HeaderUserLongitude) + requestContext.UserLatitude = context.Request.Header.Get(http.HeaderUserLatitude) + requestContext.UserAddressId = context.Request.Header.Get(http.HeaderUserAddressId) + requestContext.UserCountry = context.Request.Header.Get(http.HeaderCountry) + requestContext.UserLanguage = context.Request.Header.Get(http.HeaderLanguage) + requestContext.UserLocation = context.Request.Header.Get(http.HeaderAppUserLocation) + return requestContext, nil +} + +// UpdateWithHeaders Update headers from request context +func UpdateWithHeaders(header *netHttp.Header, context *RequestContext) { + header.Set(http.HeaderUserId, context.UserId) + header.Set(http.HeaderUserContext, context.UserContext.Name()) + header.Set(http.HeaderAppVersionCode, strconv.Itoa(context.AppVersionCode)) + header.Set(http.HeaderClientId, context.ClientId) + header.Set(http.HeaderUserStateCode, context.UserStateCode) + header.Set(http.HeaderUserPincode, context.UserPinCode) + header.Set(http.HeaderUserCity, context.UserCity) + header.Set(http.HeaderInstanceId, context.InstanceId) + header.Set(http.HeaderSessionId, context.SessionId) + header.Set(http.HeaderUserLongitude, context.UserLongitude) + header.Set(http.HeaderUserLatitude, context.UserLatitude) + header.Set(http.HeaderUserAddressId, context.UserAddressId) + header.Set(http.HeaderCountry, context.UserCountry) + header.Set(http.HeaderLanguage, context.UserLanguage) +} diff --git a/go-sdk/pkg/api/request_context_test.go b/go-sdk/pkg/api/request_context_test.go new file mode 100644 index 00000000..773e3448 --- /dev/null +++ b/go-sdk/pkg/api/request_context_test.go @@ -0,0 +1,845 @@ +package api + +import ( + "context" + "fmt" + + httpHeaders "github.com/Meesho/BharatMLStack/go-sdk/pkg/api/http" + + "net/http" + "net/http/httptest" + "testing" + + enum "github.com/Meesho/BharatMLStack/go-sdk/pkg/enums" + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + "google.golang.org/grpc/metadata" +) + +// TestGetRequestContext tests the GetRequestContext function +func TestGetRequestContextForGRPC(t *testing.T) { + + userContext, _ := enum.ParseUserContext("anonymous") + + tests := []struct { + name string + headers map[string]string + expectedResult *RequestContext + expectedError string + }{ + { + name: "Valid headers with all fields", + headers: map[string]string{ + httpHeaders.HeaderUserId: "12345", + httpHeaders.HeaderUserContext: "anonymous", + httpHeaders.HeaderAppVersionCode: "2", + httpHeaders.HeaderClientId: "client_1", + httpHeaders.HeaderUserStateCode: "state_1", + httpHeaders.HeaderUserPincode: "123456", + }, + expectedResult: &RequestContext{ + UserId: "12345", + UserContext: userContext, + AppVersionCode: 2, + ClientId: "client_1", + UserStateCode: "state_1", + UserPinCode: "123456", + }, + expectedError: "", + }, + { + name: "Missing user id", + headers: map[string]string{ + httpHeaders.HeaderUserContext: "anonymous", + httpHeaders.HeaderAppVersionCode: "2", + httpHeaders.HeaderClientId: "client_1", + httpHeaders.HeaderUserStateCode: "state_1", + httpHeaders.HeaderUserPincode: "123456", + }, + expectedResult: nil, + expectedError: "metadata key 'USER-ID' is missing ", + }, + { + name: "Empty user id", + headers: map[string]string{ + httpHeaders.HeaderUserId: "", + httpHeaders.HeaderUserContext: "anonymous", + httpHeaders.HeaderAppVersionCode: "2", + httpHeaders.HeaderClientId: "client_1", + httpHeaders.HeaderUserStateCode: "state_1", + httpHeaders.HeaderUserPincode: "123456", + }, + expectedResult: nil, + expectedError: "user id is empty in headers", + }, + { + name: "Empty user context", + headers: map[string]string{ + httpHeaders.HeaderUserId: "123", + httpHeaders.HeaderUserContext: "", + httpHeaders.HeaderAppVersionCode: "2", + httpHeaders.HeaderClientId: "client_1", + httpHeaders.HeaderUserStateCode: "state_1", + httpHeaders.HeaderUserPincode: "123456", + }, + expectedResult: nil, + expectedError: "user context is empty in headers", + }, + { + name: "Missing user context", + headers: map[string]string{ + httpHeaders.HeaderUserId: "12345", + httpHeaders.HeaderAppVersionCode: "2", + httpHeaders.HeaderClientId: "client_1", + httpHeaders.HeaderUserStateCode: "state_1", + httpHeaders.HeaderUserPincode: "123456", + }, + expectedResult: nil, + expectedError: "metadata key 'USER-CONTEXT' is missing ", + }, + { + name: "Invalid user context", + headers: map[string]string{ + httpHeaders.HeaderUserId: "12345", + httpHeaders.HeaderUserContext: "invalid_context", + httpHeaders.HeaderAppVersionCode: "2", + httpHeaders.HeaderClientId: "client_1", + httpHeaders.HeaderUserStateCode: "state_1", + httpHeaders.HeaderUserPincode: "123456", + }, + expectedResult: nil, + expectedError: fmt.Sprintf("%q is not a valid UserContext", "invalid_context"), + }, + { + name: "Non-integer app version code", + headers: map[string]string{ + httpHeaders.HeaderUserId: "12345", + httpHeaders.HeaderUserContext: "anonymous", + httpHeaders.HeaderAppVersionCode: "non_integer", + httpHeaders.HeaderClientId: "client_1", + httpHeaders.HeaderUserStateCode: "state_1", + httpHeaders.HeaderUserPincode: "123456", + }, + expectedResult: nil, + expectedError: "app_version_code should be an integer", + }, + { + name: "Missing user state code", + headers: map[string]string{ + httpHeaders.HeaderUserId: "12345", + httpHeaders.HeaderUserContext: "anonymous", + httpHeaders.HeaderAppVersionCode: "2", + httpHeaders.HeaderClientId: "client_1", + httpHeaders.HeaderUserPincode: "123456", + }, + expectedResult: &RequestContext{ + UserId: "12345", + UserContext: userContext, + AppVersionCode: 2, + ClientId: "client_1", + UserStateCode: "", + UserPinCode: "123456", + }, + expectedError: "", + }, + { + name: "Missing user pin code", + headers: map[string]string{ + httpHeaders.HeaderUserId: "12345", + httpHeaders.HeaderUserContext: "anonymous", + httpHeaders.HeaderAppVersionCode: "2", + httpHeaders.HeaderClientId: "client_1", + httpHeaders.HeaderUserStateCode: "state_1", + }, + expectedResult: &RequestContext{ + UserId: "12345", + UserContext: userContext, + AppVersionCode: 2, + ClientId: "client_1", + UserStateCode: "state_1", + UserPinCode: "", + }, + expectedError: "", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctx := context.Background() + headers := make(map[string]string) + for key, value := range test.headers { + headers[key] = value + } + ctx = metadata.NewIncomingContext(ctx, metadata.New(headers)) + + result, err := GetRequestContextForGRPC(ctx) + if test.expectedError == "" { + assert.NoError(t, err) + assert.Equal(t, test.expectedResult, result) + } else { + assert.EqualError(t, err, test.expectedError) + assert.Nil(t, result) + } + }) + } +} + +func TestGetRequestContextForGrpc_error(t *testing.T) { + + t.Run("test.name", func(t *testing.T) { + ctx := context.Background() + result, err := GetRequestContextForGRPC(ctx) + assert.EqualError(t, err, "rpc error: code = InvalidArgument desc = metadata is not provided") + assert.Nil(t, result) + + }) + +} + +// TestUpdateWithHeaders tests the UpdateWithHeaders function +func TestUpdateWithGRPCHeaders(t *testing.T) { + userContext, _ := enum.ParseUserContext("anonymous") + + header := make(map[string]string) + context := &RequestContext{ + UserId: "12345", + UserContext: userContext, + AppVersionCode: 2, + ClientId: "client_1", + UserStateCode: "state_1", + UserPinCode: "123456", + UserCity: "city_1", + } + + UpdateWithGRPCHeaders(header, context) + + assert.Equal(t, "12345", header[httpHeaders.HeaderUserId]) + assert.Equal(t, "anonymous", header[httpHeaders.HeaderUserContext]) + assert.Equal(t, "2", header[httpHeaders.HeaderAppVersionCode]) + assert.Equal(t, "client_1", header[httpHeaders.HeaderClientId]) + assert.Equal(t, "state_1", header[httpHeaders.HeaderUserStateCode]) + assert.Equal(t, "123456", header[httpHeaders.HeaderUserPincode]) + assert.Equal(t, "city_1", header[httpHeaders.HeaderUserCity]) +} + +// TestGetRequestContext tests the GetRequestContext function +func TestGetRequestContext(t *testing.T) { + gin.SetMode(gin.TestMode) + + userContext, _ := enum.ParseUserContext("anonymous") + + tests := []struct { + name string + headers map[string]string + expectedResult *RequestContext + expectedError string + }{ + { + name: "Valid headers with all fields", + headers: map[string]string{ + httpHeaders.HeaderUserId: "12345", + httpHeaders.HeaderUserContext: "anonymous", + httpHeaders.HeaderAppVersionCode: "2", + httpHeaders.HeaderClientId: "client_1", + httpHeaders.HeaderUserStateCode: "state_1", + httpHeaders.HeaderUserPincode: "123456", + }, + expectedResult: &RequestContext{ + UserId: "12345", + UserContext: userContext, + AppVersionCode: 2, + ClientId: "client_1", + UserStateCode: "state_1", + UserPinCode: "123456", + }, + expectedError: "", + }, + { + name: "Missing user id", + headers: map[string]string{ + httpHeaders.HeaderUserContext: "anonymous", + httpHeaders.HeaderAppVersionCode: "2", + httpHeaders.HeaderClientId: "client_1", + httpHeaders.HeaderUserStateCode: "state_1", + httpHeaders.HeaderUserPincode: "123456", + }, + expectedResult: nil, + expectedError: "user id is missing in headers", + }, + { + name: "Missing user context", + headers: map[string]string{ + httpHeaders.HeaderUserId: "12345", + httpHeaders.HeaderAppVersionCode: "2", + httpHeaders.HeaderClientId: "client_1", + httpHeaders.HeaderUserStateCode: "state_1", + httpHeaders.HeaderUserPincode: "123456", + }, + expectedResult: nil, + expectedError: "user context is missing in headers", + }, + { + name: "Invalid user context", + headers: map[string]string{ + httpHeaders.HeaderUserId: "12345", + httpHeaders.HeaderUserContext: "invalid_context", + httpHeaders.HeaderAppVersionCode: "2", + httpHeaders.HeaderClientId: "client_1", + httpHeaders.HeaderUserStateCode: "state_1", + httpHeaders.HeaderUserPincode: "123456", + }, + expectedResult: nil, + expectedError: fmt.Sprintf("%q is not a valid UserContext", "invalid_context"), + }, + { + name: "Non-integer app version code", + headers: map[string]string{ + httpHeaders.HeaderUserId: "12345", + httpHeaders.HeaderUserContext: "anonymous", + httpHeaders.HeaderAppVersionCode: "non_integer", + httpHeaders.HeaderClientId: "client_1", + httpHeaders.HeaderUserStateCode: "state_1", + httpHeaders.HeaderUserPincode: "123456", + }, + expectedResult: nil, + expectedError: "app_version_code should be an integer", + }, + { + name: "Missing user state code", + headers: map[string]string{ + httpHeaders.HeaderUserId: "12345", + httpHeaders.HeaderUserContext: "anonymous", + httpHeaders.HeaderAppVersionCode: "2", + httpHeaders.HeaderClientId: "client_1", + httpHeaders.HeaderUserPincode: "123456", + }, + expectedResult: &RequestContext{ + UserId: "12345", + UserContext: userContext, + AppVersionCode: 2, + ClientId: "client_1", + UserStateCode: "", + UserPinCode: "123456", + }, + expectedError: "", + }, + { + name: "Missing user pin code", + headers: map[string]string{ + httpHeaders.HeaderUserId: "12345", + httpHeaders.HeaderUserContext: "anonymous", + httpHeaders.HeaderAppVersionCode: "2", + httpHeaders.HeaderClientId: "client_1", + httpHeaders.HeaderUserStateCode: "state_1", + }, + expectedResult: &RequestContext{ + UserId: "12345", + UserContext: userContext, + AppVersionCode: 2, + ClientId: "client_1", + UserStateCode: "state_1", + UserPinCode: "", + }, + expectedError: "", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + context, _ := gin.CreateTestContext(httptest.NewRecorder()) + request, _ := http.NewRequest(http.MethodGet, "/", nil) + for key, value := range test.headers { + request.Header.Set(key, value) + } + context.Request = request + + result, err := GetRequestContext(context) + if test.expectedError == "" { + assert.NoError(t, err) + assert.Equal(t, test.expectedResult, result) + } else { + assert.EqualError(t, err, test.expectedError) + assert.Nil(t, result) + } + }) + } +} + +// TestUpdateWithHeaders tests the UpdateWithHeaders function +func TestUpdateWithHTTPHeaders(t *testing.T) { + userContext, _ := enum.ParseUserContext("anonymous") + + header := &http.Header{} + context := &RequestContext{ + UserId: "12345", + UserContext: userContext, + AppVersionCode: 2, + ClientId: "client_1", + UserStateCode: "state_1", + UserPinCode: "123456", + UserCity: "city_1", + } + + UpdateWithHeaders(header, context) + + assert.Equal(t, "12345", header.Get(httpHeaders.HeaderUserId)) + assert.Equal(t, "anonymous", header.Get(httpHeaders.HeaderUserContext)) + assert.Equal(t, "2", header.Get(httpHeaders.HeaderAppVersionCode)) + assert.Equal(t, "client_1", header.Get(httpHeaders.HeaderClientId)) + assert.Equal(t, "state_1", header.Get(httpHeaders.HeaderUserStateCode)) + assert.Equal(t, "123456", header.Get(httpHeaders.HeaderUserPincode)) + assert.Equal(t, "city_1", header.Get(httpHeaders.HeaderUserCity)) +} + +// TestGetRequestContextForGRPC_WithFeedAndAppSession tests FeedSession and AppSession fields +func TestGetRequestContextForGRPC_WithFeedAndAppSession(t *testing.T) { + userContext, _ := enum.ParseUserContext("anonymous") + + tests := []struct { + name string + headers map[string]string + expectedResult *RequestContext + expectedError string + }{ + { + name: "Valid headers with FeedSession and AppSession", + headers: map[string]string{ + httpHeaders.HeaderUserId: "12345", + httpHeaders.HeaderUserContext: "anonymous", + httpHeaders.HeaderAppVersionCode: "2", + httpHeaders.HeaderClientId: "client_1", + httpHeaders.HeaderUserCity: "city_1", + httpHeaders.HeaderFeedSession: "feed_session_123", + httpHeaders.HeaderAppSession: "app_session_456", + }, + expectedResult: &RequestContext{ + UserId: "12345", + UserContext: userContext, + AppVersionCode: 2, + ClientId: "client_1", + UserCity: "city_1", + FeedSession: "feed_session_123", + AppSession: "app_session_456", + }, + expectedError: "", + }, + { + name: "Valid headers without FeedSession and AppSession", + headers: map[string]string{ + httpHeaders.HeaderUserId: "12345", + httpHeaders.HeaderUserContext: "anonymous", + httpHeaders.HeaderAppVersionCode: "2", + httpHeaders.HeaderClientId: "client_1", + httpHeaders.HeaderUserCity: "city_1", + }, + expectedResult: &RequestContext{ + UserId: "12345", + UserContext: userContext, + AppVersionCode: 2, + ClientId: "client_1", + UserCity: "city_1", + FeedSession: "", + AppSession: "", + }, + expectedError: "", + }, + { + name: "Valid headers with empty FeedSession and AppSession", + headers: map[string]string{ + httpHeaders.HeaderUserId: "12345", + httpHeaders.HeaderUserContext: "anonymous", + httpHeaders.HeaderAppVersionCode: "2", + httpHeaders.HeaderClientId: "client_1", + httpHeaders.HeaderUserCity: "city_1", + httpHeaders.HeaderFeedSession: "", + httpHeaders.HeaderAppSession: "", + }, + expectedResult: &RequestContext{ + UserId: "12345", + UserContext: userContext, + AppVersionCode: 2, + ClientId: "client_1", + UserCity: "city_1", + FeedSession: "", + AppSession: "", + }, + expectedError: "", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctx := context.Background() + headers := make(map[string]string) + for key, value := range test.headers { + headers[key] = value + } + ctx = metadata.NewIncomingContext(ctx, metadata.New(headers)) + + result, err := GetRequestContextForGRPC(ctx) + if test.expectedError == "" { + assert.NoError(t, err) + assert.Equal(t, test.expectedResult, result) + } else { + assert.EqualError(t, err, test.expectedError) + assert.Nil(t, result) + } + }) + } +} + +// TestGetRequestContext_WithFeedAndAppSession tests FeedSession and AppSession fields for HTTP requests +func TestGetRequestContext_WithFeedAndAppSession(t *testing.T) { + gin.SetMode(gin.TestMode) + userContext, _ := enum.ParseUserContext("anonymous") + + tests := []struct { + name string + headers map[string]string + expectedResult *RequestContext + expectedError string + }{ + { + name: "Valid headers with FeedSession and AppSession", + headers: map[string]string{ + httpHeaders.HeaderUserId: "12345", + httpHeaders.HeaderUserContext: "anonymous", + httpHeaders.HeaderAppVersionCode: "2", + httpHeaders.HeaderClientId: "client_1", + httpHeaders.HeaderUserCity: "city_1", + httpHeaders.HeaderFeedSession: "feed_session_123", + httpHeaders.HeaderAppSession: "app_session_456", + }, + expectedResult: &RequestContext{ + UserId: "12345", + UserContext: userContext, + AppVersionCode: 2, + ClientId: "client_1", + UserCity: "city_1", + FeedSession: "feed_session_123", + AppSession: "app_session_456", + }, + expectedError: "", + }, + { + name: "Valid headers without FeedSession and AppSession", + headers: map[string]string{ + httpHeaders.HeaderUserId: "12345", + httpHeaders.HeaderUserContext: "anonymous", + httpHeaders.HeaderAppVersionCode: "2", + httpHeaders.HeaderClientId: "client_1", + httpHeaders.HeaderUserCity: "city_1", + }, + expectedResult: &RequestContext{ + UserId: "12345", + UserContext: userContext, + AppVersionCode: 2, + ClientId: "client_1", + UserCity: "city_1", + FeedSession: "", + AppSession: "", + }, + expectedError: "", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + context, _ := gin.CreateTestContext(httptest.NewRecorder()) + request, _ := http.NewRequest(http.MethodGet, "/", nil) + for key, value := range test.headers { + request.Header.Set(key, value) + } + context.Request = request + + result, err := GetRequestContext(context) + if test.expectedError == "" { + assert.NoError(t, err) + assert.Equal(t, test.expectedResult, result) + } else { + assert.EqualError(t, err, test.expectedError) + assert.Nil(t, result) + } + }) + } +} + +// TestUpdateWithGRPCHeaders_WithAllFields tests UpdateWithGRPCHeaders with all fields including FeedSession and AppSession +func TestUpdateWithGRPCHeaders_WithAllFields(t *testing.T) { + userContext, _ := enum.ParseUserContext("anonymous") + + header := make(map[string]string) + context := &RequestContext{ + UserId: "12345", + UserContext: userContext, + AppVersionCode: 2, + ClientId: "client_1", + UserStateCode: "state_1", + UserPinCode: "123456", + UserCity: "city_1", + FeedSession: "feed_session_123", + AppSession: "app_session_456", + } + + UpdateWithGRPCHeaders(header, context) + + assert.Equal(t, "12345", header[httpHeaders.HeaderUserId]) + assert.Equal(t, "anonymous", header[httpHeaders.HeaderUserContext]) + assert.Equal(t, "2", header[httpHeaders.HeaderAppVersionCode]) + assert.Equal(t, "client_1", header[httpHeaders.HeaderClientId]) + assert.Equal(t, "state_1", header[httpHeaders.HeaderUserStateCode]) + assert.Equal(t, "123456", header[httpHeaders.HeaderUserPincode]) + assert.Equal(t, "city_1", header[httpHeaders.HeaderUserCity]) + + // Note: FeedSession and AppSession are not included in UpdateWithGRPCHeaders function + // This is intentional as per the current implementation +} + +// TestUpdateWithHTTPHeaders_WithAllFields tests UpdateWithHeaders with all fields including FeedSession and AppSession +func TestUpdateWithHTTPHeaders_WithAllFields(t *testing.T) { + userContext, _ := enum.ParseUserContext("anonymous") + + header := &http.Header{} + context := &RequestContext{ + UserId: "12345", + UserContext: userContext, + AppVersionCode: 2, + ClientId: "client_1", + UserStateCode: "state_1", + UserPinCode: "123456", + UserCity: "city_1", + FeedSession: "feed_session_123", + AppSession: "app_session_456", + } + + UpdateWithHeaders(header, context) + + assert.Equal(t, "12345", header.Get(httpHeaders.HeaderUserId)) + assert.Equal(t, "anonymous", header.Get(httpHeaders.HeaderUserContext)) + assert.Equal(t, "2", header.Get(httpHeaders.HeaderAppVersionCode)) + assert.Equal(t, "client_1", header.Get(httpHeaders.HeaderClientId)) + assert.Equal(t, "state_1", header.Get(httpHeaders.HeaderUserStateCode)) + assert.Equal(t, "123456", header.Get(httpHeaders.HeaderUserPincode)) + assert.Equal(t, "city_1", header.Get(httpHeaders.HeaderUserCity)) + + // Note: FeedSession and AppSession are not included in UpdateWithHeaders function + // This is intentional as per the current implementation +} + +// TestGetMetadataValue tests the getMetadataValue helper function +func TestGetMetadataValue(t *testing.T) { + tests := []struct { + name string + metadata map[string]string + key string + expectedValue string + expectedError string + }{ + { + name: "Valid metadata with key", + metadata: map[string]string{ + "test-key": "test-value", + }, + key: "test-key", + expectedValue: "test-value", + expectedError: "", + }, + { + name: "Missing key in metadata", + metadata: map[string]string{ + "other-key": "other-value", + }, + key: "test-key", + expectedValue: "", + expectedError: "metadata key 'test-key' is missing ", + }, + { + name: "Empty metadata", + metadata: map[string]string{}, + key: "test-key", + expectedValue: "", + expectedError: "metadata key 'test-key' is missing ", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + md := metadata.New(test.metadata) + value, err := getMetadataValue(md, test.key) + + if test.expectedError == "" { + assert.NoError(t, err) + assert.Equal(t, test.expectedValue, value) + } else { + assert.EqualError(t, err, test.expectedError) + assert.Equal(t, test.expectedValue, value) + } + }) + } +} + +// TestGetRequestContextForGRPC_EdgeCases tests additional edge cases +func TestGetRequestContextForGRPC_EdgeCases(t *testing.T) { + userContext, _ := enum.ParseUserContext("anonymous") + + tests := []struct { + name string + headers map[string]string + expectedResult *RequestContext + expectedError string + }{ + { + name: "Zero app version code", + headers: map[string]string{ + httpHeaders.HeaderUserId: "12345", + httpHeaders.HeaderUserContext: "anonymous", + httpHeaders.HeaderAppVersionCode: "0", + httpHeaders.HeaderClientId: "client_1", + }, + expectedResult: &RequestContext{ + UserId: "12345", + UserContext: userContext, + AppVersionCode: 0, + ClientId: "client_1", + }, + expectedError: "", + }, + { + name: "Negative app version code", + headers: map[string]string{ + httpHeaders.HeaderUserId: "12345", + httpHeaders.HeaderUserContext: "anonymous", + httpHeaders.HeaderAppVersionCode: "-1", + httpHeaders.HeaderClientId: "client_1", + }, + expectedResult: &RequestContext{ + UserId: "12345", + UserContext: userContext, + AppVersionCode: -1, + ClientId: "client_1", + }, + expectedError: "", + }, + { + name: "Empty string app version code", + headers: map[string]string{ + httpHeaders.HeaderUserId: "12345", + httpHeaders.HeaderUserContext: "anonymous", + httpHeaders.HeaderAppVersionCode: "", + httpHeaders.HeaderClientId: "client_1", + }, + expectedResult: &RequestContext{ + UserId: "12345", + UserContext: userContext, + AppVersionCode: 0, + ClientId: "client_1", + }, + expectedError: "", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctx := context.Background() + headers := make(map[string]string) + for key, value := range test.headers { + headers[key] = value + } + ctx = metadata.NewIncomingContext(ctx, metadata.New(headers)) + + result, err := GetRequestContextForGRPC(ctx) + if test.expectedError == "" { + assert.NoError(t, err) + assert.Equal(t, test.expectedResult, result) + } else { + assert.EqualError(t, err, test.expectedError) + assert.Nil(t, result) + } + }) + } +} + +// TestGetRequestContext_EdgeCases tests additional edge cases for HTTP requests +func TestGetRequestContext_EdgeCases(t *testing.T) { + gin.SetMode(gin.TestMode) + userContext, _ := enum.ParseUserContext("anonymous") + + tests := []struct { + name string + headers map[string]string + expectedResult *RequestContext + expectedError string + }{ + { + name: "Zero app version code", + headers: map[string]string{ + httpHeaders.HeaderUserId: "12345", + httpHeaders.HeaderUserContext: "anonymous", + httpHeaders.HeaderAppVersionCode: "0", + httpHeaders.HeaderClientId: "client_1", + }, + expectedResult: &RequestContext{ + UserId: "12345", + UserContext: userContext, + AppVersionCode: 0, + ClientId: "client_1", + }, + expectedError: "", + }, + { + name: "Empty string app version code", + headers: map[string]string{ + httpHeaders.HeaderUserId: "12345", + httpHeaders.HeaderUserContext: "anonymous", + httpHeaders.HeaderAppVersionCode: "", + httpHeaders.HeaderClientId: "client_1", + }, + expectedResult: &RequestContext{ + UserId: "12345", + UserContext: userContext, + AppVersionCode: 0, + ClientId: "client_1", + }, + expectedError: "", + }, + { + name: "Minimum required headers only", + headers: map[string]string{ + httpHeaders.HeaderUserId: "12345", + httpHeaders.HeaderUserContext: "anonymous", + }, + expectedResult: &RequestContext{ + UserId: "12345", + UserContext: userContext, + AppVersionCode: 0, + ClientId: "", + UserStateCode: "", + UserPinCode: "", + UserCity: "", + FeedSession: "", + AppSession: "", + }, + expectedError: "", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + context, _ := gin.CreateTestContext(httptest.NewRecorder()) + request, _ := http.NewRequest(http.MethodGet, "/", nil) + for key, value := range test.headers { + request.Header.Set(key, value) + } + context.Request = request + + result, err := GetRequestContext(context) + if test.expectedError == "" { + assert.NoError(t, err) + assert.Equal(t, test.expectedResult, result) + } else { + assert.EqualError(t, err, test.expectedError) + assert.Nil(t, result) + } + }) + } +} diff --git a/go-sdk/pkg/clients/inferflow/README.md b/go-sdk/pkg/clients/inferflow/README.md new file mode 100644 index 00000000..ef82948f --- /dev/null +++ b/go-sdk/pkg/clients/inferflow/README.md @@ -0,0 +1,150 @@ +# Inferflow Client + +A Go client library for interacting with the Inferflow Predict service, supporting PointWise, PairWise, and SlateWise inference APIs. + +## Features + +- **Three Inference Patterns**: PointWise (per-target scoring), PairWise (pair-level ranking), SlateWise (group-level scoring) +- **gRPC Communication**: Efficient binary protocol via gRPC +- **Authentication**: Built-in caller ID and token-based auth via metadata +- **Configurable Timeouts**: Per-client deadline configuration +- **Singleton Pattern**: Thread-safe client initialization with `sync.Once` + +## Quick Start + +### 1. Configuration + +Set environment variables with the `INFERFLOW_CLIENT_V1_` prefix: + +```bash +export INFERFLOW_CLIENT_V1_HOST=inferflow.svc +export INFERFLOW_CLIENT_V1_PORT=8080 +export INFERFLOW_CLIENT_V1_DEADLINE_MS=500 +export INFERFLOW_CLIENT_V1_PLAINTEXT=true +export INFERFLOW_CLIENT_V1_AUTH_TOKEN=your-token +export APP_NAME=my-service +``` + +### 2. Initialize Client + +```go +import "github.com/Meesho/BharatMLStack/go-sdk/pkg/clients/inferflow" + +// From environment variables +client := inferflow.GetInferflowClient(1) + +// Or from explicit config +client := inferflow.GetInferflowClientFromConfig(1, inferflow.ClientConfig{ + Host: "inferflow.svc", + Port: "8080", + DeadlineExceedMS: 500, + PlainText: true, + AuthToken: "your-token", +}, "my-service") +``` + +### 3. PointWise Inference + +Score each target independently against context features. + +**Use cases:** CTR prediction, fraud scoring, relevance ranking. + +```go +import grpc "github.com/Meesho/BharatMLStack/go-sdk/pkg/clients/inferflow/client/grpc" + +resp, err := client.InferPointWise(&grpc.PointWiseRequest{ + ModelConfigId: "ranking_model_v1", + TrackingId: "req-123", + TenantId: "tenant-1", + TargetInputSchema: []*grpc.FeatureSchema{ + {Name: "price", DataType: grpc.DataType_DataTypeFP32}, + }, + Targets: []*grpc.Target{ + {Id: "product-1", FeatureValues: [][]byte{priceBytes}}, + {Id: "product-2", FeatureValues: [][]byte{priceBytes}}, + }, + ContextFeatures: []*grpc.ContextFeature{ + {Name: "user_segment", Value: segmentBytes, DataType: grpc.DataType_DataTypeString}, + }, +}) +// resp.TargetScores contains per-target scores +``` + +### 4. PairWise Inference + +Score pairs of targets relative to each other. + +**Use cases:** Preference learning, comparison-based ranking. + +```go +resp, err := client.InferPairWise(&grpc.PairWiseRequest{ + ModelConfigId: "pairwise_model_v1", + TrackingId: "req-456", + TenantId: "tenant-1", + TargetInputSchema: targetSchema, + PairInputSchema: pairSchema, + Targets: targets, + Pairs: []*grpc.TargetPair{ + {FirstTargetIndex: 0, SecondTargetIndex: 1, FeatureValues: pairFeatures}, + }, +}) +// resp.PairScores contains per-pair scores +// resp.TargetScores contains optional per-target scores +``` + +### 5. SlateWise Inference + +Score groups (slates) of targets together, capturing inter-item effects. + +**Use cases:** Whole-page optimization, slate-level reranking, diversity-aware scoring. + +```go +resp, err := client.InferSlateWise(&grpc.SlateWiseRequest{ + ModelConfigId: "slate_model_v1", + TrackingId: "req-789", + TenantId: "tenant-1", + TargetInputSchema: targetSchema, + SlateInputSchema: slateSchema, + Targets: targets, + Slates: []*grpc.TargetSlate{ + {TargetIndices: []int32{0, 1, 2}, FeatureValues: slateFeatures}, + }, +}) +// resp.SlateScores contains per-slate scores +// resp.TargetScores contains optional per-target scores +``` + +## Configuration Options + +| Option | Env Var Suffix | Type | Description | Default | +|--------|---------------|------|-------------|---------| +| `Host` | `HOST` | string | Inferflow service hostname | Required | +| `Port` | `PORT` | string | Inferflow service port | `8080` | +| `DeadlineExceedMS` | `DEADLINE_MS` | int | Request timeout (ms) | `200` | +| `PlainText` | `PLAINTEXT` | bool | Use plaintext connection | `true` | +| `AuthToken` | `AUTH_TOKEN` | string | Authentication token | `""` | + +## API Reference + +### InferflowClient Interface + +```go +type InferflowClient interface { + InferPointWise(request *grpc.PointWiseRequest) (*grpc.PointWiseResponse, error) + InferPairWise(request *grpc.PairWiseRequest) (*grpc.PairWiseResponse, error) + InferSlateWise(request *grpc.SlateWiseRequest) (*grpc.SlateWiseResponse, error) +} +``` + +## Testing + +```bash +go test -v ./pkg/clients/inferflow/... +``` + +## Dependencies + +- `google.golang.org/grpc` — gRPC framework +- `google.golang.org/protobuf` — Protocol Buffers +- `github.com/rs/zerolog` — Structured logging +- `github.com/spf13/viper` — Configuration management diff --git a/go-sdk/pkg/clients/inferflow/client/grpc/predict.pb.go b/go-sdk/pkg/clients/inferflow/client/grpc/predict.pb.go new file mode 100644 index 00000000..1a10406e --- /dev/null +++ b/go-sdk/pkg/clients/inferflow/client/grpc/predict.pb.go @@ -0,0 +1,1318 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v5.28.3 +// source: predict.proto + +package grpc + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type DataType int32 + +const ( + DataType_DataTypeUnknown DataType = 0 + DataType_DataTypeFP8E5M2 DataType = 1 + DataType_DataTypeFP8E4M3 DataType = 2 + DataType_DataTypeFP16 DataType = 3 + DataType_DataTypeFP32 DataType = 4 + DataType_DataTypeFP64 DataType = 5 + DataType_DataTypeInt8 DataType = 6 + DataType_DataTypeInt16 DataType = 7 + DataType_DataTypeInt32 DataType = 8 + DataType_DataTypeInt64 DataType = 9 + DataType_DataTypeUint8 DataType = 10 + DataType_DataTypeUint16 DataType = 11 + DataType_DataTypeUint32 DataType = 12 + DataType_DataTypeUint64 DataType = 13 + DataType_DataTypeString DataType = 14 + DataType_DataTypeBool DataType = 15 + DataType_DataTypeFP8E5M2Vector DataType = 16 + DataType_DataTypeFP8E4M3Vector DataType = 17 + DataType_DataTypeFP16Vector DataType = 18 + DataType_DataTypeFP32Vector DataType = 19 + DataType_DataTypeFP64Vector DataType = 20 + DataType_DataTypeInt8Vector DataType = 21 + DataType_DataTypeInt16Vector DataType = 22 + DataType_DataTypeInt32Vector DataType = 23 + DataType_DataTypeInt64Vector DataType = 24 + DataType_DataTypeUint8Vector DataType = 25 + DataType_DataTypeUint16Vector DataType = 26 + DataType_DataTypeUint32Vector DataType = 27 + DataType_DataTypeUint64Vector DataType = 28 + DataType_DataTypeStringVector DataType = 29 + DataType_DataTypeBoolVector DataType = 30 +) + +// Enum value maps for DataType. +var ( + DataType_name = map[int32]string{ + 0: "DataTypeUnknown", + 1: "DataTypeFP8E5M2", + 2: "DataTypeFP8E4M3", + 3: "DataTypeFP16", + 4: "DataTypeFP32", + 5: "DataTypeFP64", + 6: "DataTypeInt8", + 7: "DataTypeInt16", + 8: "DataTypeInt32", + 9: "DataTypeInt64", + 10: "DataTypeUint8", + 11: "DataTypeUint16", + 12: "DataTypeUint32", + 13: "DataTypeUint64", + 14: "DataTypeString", + 15: "DataTypeBool", + 16: "DataTypeFP8E5M2Vector", + 17: "DataTypeFP8E4M3Vector", + 18: "DataTypeFP16Vector", + 19: "DataTypeFP32Vector", + 20: "DataTypeFP64Vector", + 21: "DataTypeInt8Vector", + 22: "DataTypeInt16Vector", + 23: "DataTypeInt32Vector", + 24: "DataTypeInt64Vector", + 25: "DataTypeUint8Vector", + 26: "DataTypeUint16Vector", + 27: "DataTypeUint32Vector", + 28: "DataTypeUint64Vector", + 29: "DataTypeStringVector", + 30: "DataTypeBoolVector", + } + DataType_value = map[string]int32{ + "DataTypeUnknown": 0, + "DataTypeFP8E5M2": 1, + "DataTypeFP8E4M3": 2, + "DataTypeFP16": 3, + "DataTypeFP32": 4, + "DataTypeFP64": 5, + "DataTypeInt8": 6, + "DataTypeInt16": 7, + "DataTypeInt32": 8, + "DataTypeInt64": 9, + "DataTypeUint8": 10, + "DataTypeUint16": 11, + "DataTypeUint32": 12, + "DataTypeUint64": 13, + "DataTypeString": 14, + "DataTypeBool": 15, + "DataTypeFP8E5M2Vector": 16, + "DataTypeFP8E4M3Vector": 17, + "DataTypeFP16Vector": 18, + "DataTypeFP32Vector": 19, + "DataTypeFP64Vector": 20, + "DataTypeInt8Vector": 21, + "DataTypeInt16Vector": 22, + "DataTypeInt32Vector": 23, + "DataTypeInt64Vector": 24, + "DataTypeUint8Vector": 25, + "DataTypeUint16Vector": 26, + "DataTypeUint32Vector": 27, + "DataTypeUint64Vector": 28, + "DataTypeStringVector": 29, + "DataTypeBoolVector": 30, + } +) + +func (x DataType) Enum() *DataType { + p := new(DataType) + *p = x + return p +} + +func (x DataType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (DataType) Descriptor() protoreflect.EnumDescriptor { + return file_predict_proto_enumTypes[0].Descriptor() +} + +func (DataType) Type() protoreflect.EnumType { + return &file_predict_proto_enumTypes[0] +} + +func (x DataType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use DataType.Descriptor instead. +func (DataType) EnumDescriptor() ([]byte, []int) { + return file_predict_proto_rawDescGZIP(), []int{0} +} + +// Schema definition for a feature column +type FeatureSchema struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + DataType DataType `protobuf:"varint,2,opt,name=data_type,json=dataType,proto3,enum=DataType" json:"data_type,omitempty"` + VectorDim int32 `protobuf:"varint,3,opt,name=vector_dim,json=vectorDim,proto3" json:"vector_dim,omitempty"` // 0 = scalar, >0 = fixed vector length + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FeatureSchema) Reset() { + *x = FeatureSchema{} + mi := &file_predict_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FeatureSchema) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FeatureSchema) ProtoMessage() {} + +func (x *FeatureSchema) ProtoReflect() protoreflect.Message { + mi := &file_predict_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FeatureSchema.ProtoReflect.Descriptor instead. +func (*FeatureSchema) Descriptor() ([]byte, []int) { + return file_predict_proto_rawDescGZIP(), []int{0} +} + +func (x *FeatureSchema) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *FeatureSchema) GetDataType() DataType { + if x != nil { + return x.DataType + } + return DataType_DataTypeUnknown +} + +func (x *FeatureSchema) GetVectorDim() int32 { + if x != nil { + return x.VectorDim + } + return 0 +} + +// A request-level context feature (user, session, device, etc.) +type ContextFeature struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + DataType DataType `protobuf:"varint,3,opt,name=data_type,json=dataType,proto3,enum=DataType" json:"data_type,omitempty"` + VectorDim int32 `protobuf:"varint,4,opt,name=vector_dim,json=vectorDim,proto3" json:"vector_dim,omitempty"` // 0 = scalar, >0 = fixed vector length + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ContextFeature) Reset() { + *x = ContextFeature{} + mi := &file_predict_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ContextFeature) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ContextFeature) ProtoMessage() {} + +func (x *ContextFeature) ProtoReflect() protoreflect.Message { + mi := &file_predict_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ContextFeature.ProtoReflect.Descriptor instead. +func (*ContextFeature) Descriptor() ([]byte, []int) { + return file_predict_proto_rawDescGZIP(), []int{1} +} + +func (x *ContextFeature) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ContextFeature) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +func (x *ContextFeature) GetDataType() DataType { + if x != nil { + return x.DataType + } + return DataType_DataTypeUnknown +} + +func (x *ContextFeature) GetVectorDim() int32 { + if x != nil { + return x.VectorDim + } + return 0 +} + +// A single entity to be scored/ranked +type Target struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + FeatureValues [][]byte `protobuf:"bytes,2,rep,name=feature_values,json=featureValues,proto3" json:"feature_values,omitempty"` // aligned with target_input_schema + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Target) Reset() { + *x = Target{} + mi := &file_predict_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Target) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Target) ProtoMessage() {} + +func (x *Target) ProtoReflect() protoreflect.Message { + mi := &file_predict_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Target.ProtoReflect.Descriptor instead. +func (*Target) Descriptor() ([]byte, []int) { + return file_predict_proto_rawDescGZIP(), []int{2} +} + +func (x *Target) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Target) GetFeatureValues() [][]byte { + if x != nil { + return x.FeatureValues + } + return nil +} + +type TargetScore struct { + state protoimpl.MessageState `protogen:"open.v1"` + Error string `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"` + OutputValues [][]byte `protobuf:"bytes,2,rep,name=output_values,json=outputValues,proto3" json:"output_values,omitempty"` // aligned with target_output_schema + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TargetScore) Reset() { + *x = TargetScore{} + mi := &file_predict_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TargetScore) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TargetScore) ProtoMessage() {} + +func (x *TargetScore) ProtoReflect() protoreflect.Message { + mi := &file_predict_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TargetScore.ProtoReflect.Descriptor instead. +func (*TargetScore) Descriptor() ([]byte, []int) { + return file_predict_proto_rawDescGZIP(), []int{3} +} + +func (x *TargetScore) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +func (x *TargetScore) GetOutputValues() [][]byte { + if x != nil { + return x.OutputValues + } + return nil +} + +type PointWiseRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ModelConfigId string `protobuf:"bytes,1,opt,name=model_config_id,json=modelConfigId,proto3" json:"model_config_id,omitempty"` + TrackingId string `protobuf:"bytes,2,opt,name=tracking_id,json=trackingId,proto3" json:"tracking_id,omitempty"` + ContextFeatures []*ContextFeature `protobuf:"bytes,3,rep,name=context_features,json=contextFeatures,proto3" json:"context_features,omitempty"` + TargetInputSchema []*FeatureSchema `protobuf:"bytes,4,rep,name=target_input_schema,json=targetInputSchema,proto3" json:"target_input_schema,omitempty"` + Targets []*Target `protobuf:"bytes,5,rep,name=targets,proto3" json:"targets,omitempty"` + TenantId string `protobuf:"bytes,6,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PointWiseRequest) Reset() { + *x = PointWiseRequest{} + mi := &file_predict_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PointWiseRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PointWiseRequest) ProtoMessage() {} + +func (x *PointWiseRequest) ProtoReflect() protoreflect.Message { + mi := &file_predict_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PointWiseRequest.ProtoReflect.Descriptor instead. +func (*PointWiseRequest) Descriptor() ([]byte, []int) { + return file_predict_proto_rawDescGZIP(), []int{4} +} + +func (x *PointWiseRequest) GetModelConfigId() string { + if x != nil { + return x.ModelConfigId + } + return "" +} + +func (x *PointWiseRequest) GetTrackingId() string { + if x != nil { + return x.TrackingId + } + return "" +} + +func (x *PointWiseRequest) GetContextFeatures() []*ContextFeature { + if x != nil { + return x.ContextFeatures + } + return nil +} + +func (x *PointWiseRequest) GetTargetInputSchema() []*FeatureSchema { + if x != nil { + return x.TargetInputSchema + } + return nil +} + +func (x *PointWiseRequest) GetTargets() []*Target { + if x != nil { + return x.Targets + } + return nil +} + +func (x *PointWiseRequest) GetTenantId() string { + if x != nil { + return x.TenantId + } + return "" +} + +type PointWiseResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + TargetOutputSchema []*FeatureSchema `protobuf:"bytes,1,rep,name=target_output_schema,json=targetOutputSchema,proto3" json:"target_output_schema,omitempty"` + TargetScores []*TargetScore `protobuf:"bytes,2,rep,name=target_scores,json=targetScores,proto3" json:"target_scores,omitempty"` + RequestError string `protobuf:"bytes,3,opt,name=request_error,json=requestError,proto3" json:"request_error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PointWiseResponse) Reset() { + *x = PointWiseResponse{} + mi := &file_predict_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PointWiseResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PointWiseResponse) ProtoMessage() {} + +func (x *PointWiseResponse) ProtoReflect() protoreflect.Message { + mi := &file_predict_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PointWiseResponse.ProtoReflect.Descriptor instead. +func (*PointWiseResponse) Descriptor() ([]byte, []int) { + return file_predict_proto_rawDescGZIP(), []int{5} +} + +func (x *PointWiseResponse) GetTargetOutputSchema() []*FeatureSchema { + if x != nil { + return x.TargetOutputSchema + } + return nil +} + +func (x *PointWiseResponse) GetTargetScores() []*TargetScore { + if x != nil { + return x.TargetScores + } + return nil +} + +func (x *PointWiseResponse) GetRequestError() string { + if x != nil { + return x.RequestError + } + return "" +} + +type TargetPair struct { + state protoimpl.MessageState `protogen:"open.v1"` + FirstTargetIndex int32 `protobuf:"varint,1,opt,name=first_target_index,json=firstTargetIndex,proto3" json:"first_target_index,omitempty"` + SecondTargetIndex int32 `protobuf:"varint,2,opt,name=second_target_index,json=secondTargetIndex,proto3" json:"second_target_index,omitempty"` + FeatureValues [][]byte `protobuf:"bytes,3,rep,name=feature_values,json=featureValues,proto3" json:"feature_values,omitempty"` // aligned with pair_input_schema + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TargetPair) Reset() { + *x = TargetPair{} + mi := &file_predict_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TargetPair) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TargetPair) ProtoMessage() {} + +func (x *TargetPair) ProtoReflect() protoreflect.Message { + mi := &file_predict_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TargetPair.ProtoReflect.Descriptor instead. +func (*TargetPair) Descriptor() ([]byte, []int) { + return file_predict_proto_rawDescGZIP(), []int{6} +} + +func (x *TargetPair) GetFirstTargetIndex() int32 { + if x != nil { + return x.FirstTargetIndex + } + return 0 +} + +func (x *TargetPair) GetSecondTargetIndex() int32 { + if x != nil { + return x.SecondTargetIndex + } + return 0 +} + +func (x *TargetPair) GetFeatureValues() [][]byte { + if x != nil { + return x.FeatureValues + } + return nil +} + +type PairWiseRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ModelConfigId string `protobuf:"bytes,1,opt,name=model_config_id,json=modelConfigId,proto3" json:"model_config_id,omitempty"` + TrackingId string `protobuf:"bytes,2,opt,name=tracking_id,json=trackingId,proto3" json:"tracking_id,omitempty"` + ContextFeatures []*ContextFeature `protobuf:"bytes,3,rep,name=context_features,json=contextFeatures,proto3" json:"context_features,omitempty"` + TargetInputSchema []*FeatureSchema `protobuf:"bytes,4,rep,name=target_input_schema,json=targetInputSchema,proto3" json:"target_input_schema,omitempty"` + PairInputSchema []*FeatureSchema `protobuf:"bytes,5,rep,name=pair_input_schema,json=pairInputSchema,proto3" json:"pair_input_schema,omitempty"` + Pairs []*TargetPair `protobuf:"bytes,6,rep,name=pairs,proto3" json:"pairs,omitempty"` + Targets []*Target `protobuf:"bytes,7,rep,name=targets,proto3" json:"targets,omitempty"` + TenantId string `protobuf:"bytes,8,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PairWiseRequest) Reset() { + *x = PairWiseRequest{} + mi := &file_predict_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PairWiseRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PairWiseRequest) ProtoMessage() {} + +func (x *PairWiseRequest) ProtoReflect() protoreflect.Message { + mi := &file_predict_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PairWiseRequest.ProtoReflect.Descriptor instead. +func (*PairWiseRequest) Descriptor() ([]byte, []int) { + return file_predict_proto_rawDescGZIP(), []int{7} +} + +func (x *PairWiseRequest) GetModelConfigId() string { + if x != nil { + return x.ModelConfigId + } + return "" +} + +func (x *PairWiseRequest) GetTrackingId() string { + if x != nil { + return x.TrackingId + } + return "" +} + +func (x *PairWiseRequest) GetContextFeatures() []*ContextFeature { + if x != nil { + return x.ContextFeatures + } + return nil +} + +func (x *PairWiseRequest) GetTargetInputSchema() []*FeatureSchema { + if x != nil { + return x.TargetInputSchema + } + return nil +} + +func (x *PairWiseRequest) GetPairInputSchema() []*FeatureSchema { + if x != nil { + return x.PairInputSchema + } + return nil +} + +func (x *PairWiseRequest) GetPairs() []*TargetPair { + if x != nil { + return x.Pairs + } + return nil +} + +func (x *PairWiseRequest) GetTargets() []*Target { + if x != nil { + return x.Targets + } + return nil +} + +func (x *PairWiseRequest) GetTenantId() string { + if x != nil { + return x.TenantId + } + return "" +} + +type PairScore struct { + state protoimpl.MessageState `protogen:"open.v1"` + Error string `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"` + OutputValues [][]byte `protobuf:"bytes,2,rep,name=output_values,json=outputValues,proto3" json:"output_values,omitempty"` // aligned with pair_output_schema + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PairScore) Reset() { + *x = PairScore{} + mi := &file_predict_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PairScore) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PairScore) ProtoMessage() {} + +func (x *PairScore) ProtoReflect() protoreflect.Message { + mi := &file_predict_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PairScore.ProtoReflect.Descriptor instead. +func (*PairScore) Descriptor() ([]byte, []int) { + return file_predict_proto_rawDescGZIP(), []int{8} +} + +func (x *PairScore) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +func (x *PairScore) GetOutputValues() [][]byte { + if x != nil { + return x.OutputValues + } + return nil +} + +type PairWiseResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + PairScores []*PairScore `protobuf:"bytes,1,rep,name=pair_scores,json=pairScores,proto3" json:"pair_scores,omitempty"` + TargetScores []*TargetScore `protobuf:"bytes,2,rep,name=target_scores,json=targetScores,proto3" json:"target_scores,omitempty"` + TargetOutputSchema []*FeatureSchema `protobuf:"bytes,3,rep,name=target_output_schema,json=targetOutputSchema,proto3" json:"target_output_schema,omitempty"` + PairOutputSchema []*FeatureSchema `protobuf:"bytes,4,rep,name=pair_output_schema,json=pairOutputSchema,proto3" json:"pair_output_schema,omitempty"` + RequestError string `protobuf:"bytes,5,opt,name=request_error,json=requestError,proto3" json:"request_error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PairWiseResponse) Reset() { + *x = PairWiseResponse{} + mi := &file_predict_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PairWiseResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PairWiseResponse) ProtoMessage() {} + +func (x *PairWiseResponse) ProtoReflect() protoreflect.Message { + mi := &file_predict_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PairWiseResponse.ProtoReflect.Descriptor instead. +func (*PairWiseResponse) Descriptor() ([]byte, []int) { + return file_predict_proto_rawDescGZIP(), []int{9} +} + +func (x *PairWiseResponse) GetPairScores() []*PairScore { + if x != nil { + return x.PairScores + } + return nil +} + +func (x *PairWiseResponse) GetTargetScores() []*TargetScore { + if x != nil { + return x.TargetScores + } + return nil +} + +func (x *PairWiseResponse) GetTargetOutputSchema() []*FeatureSchema { + if x != nil { + return x.TargetOutputSchema + } + return nil +} + +func (x *PairWiseResponse) GetPairOutputSchema() []*FeatureSchema { + if x != nil { + return x.PairOutputSchema + } + return nil +} + +func (x *PairWiseResponse) GetRequestError() string { + if x != nil { + return x.RequestError + } + return "" +} + +type TargetSlate struct { + state protoimpl.MessageState `protogen:"open.v1"` + TargetIndices []int32 `protobuf:"varint,1,rep,packed,name=target_indices,json=targetIndices,proto3" json:"target_indices,omitempty"` + FeatureValues [][]byte `protobuf:"bytes,2,rep,name=feature_values,json=featureValues,proto3" json:"feature_values,omitempty"` // aligned with slate_input_schema + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TargetSlate) Reset() { + *x = TargetSlate{} + mi := &file_predict_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TargetSlate) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TargetSlate) ProtoMessage() {} + +func (x *TargetSlate) ProtoReflect() protoreflect.Message { + mi := &file_predict_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TargetSlate.ProtoReflect.Descriptor instead. +func (*TargetSlate) Descriptor() ([]byte, []int) { + return file_predict_proto_rawDescGZIP(), []int{10} +} + +func (x *TargetSlate) GetTargetIndices() []int32 { + if x != nil { + return x.TargetIndices + } + return nil +} + +func (x *TargetSlate) GetFeatureValues() [][]byte { + if x != nil { + return x.FeatureValues + } + return nil +} + +type SlateScore struct { + state protoimpl.MessageState `protogen:"open.v1"` + Error string `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"` + OutputValues [][]byte `protobuf:"bytes,2,rep,name=output_values,json=outputValues,proto3" json:"output_values,omitempty"` // aligned with slate_output_schema + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SlateScore) Reset() { + *x = SlateScore{} + mi := &file_predict_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SlateScore) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SlateScore) ProtoMessage() {} + +func (x *SlateScore) ProtoReflect() protoreflect.Message { + mi := &file_predict_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SlateScore.ProtoReflect.Descriptor instead. +func (*SlateScore) Descriptor() ([]byte, []int) { + return file_predict_proto_rawDescGZIP(), []int{11} +} + +func (x *SlateScore) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +func (x *SlateScore) GetOutputValues() [][]byte { + if x != nil { + return x.OutputValues + } + return nil +} + +type SlateWiseRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ModelConfigId string `protobuf:"bytes,1,opt,name=model_config_id,json=modelConfigId,proto3" json:"model_config_id,omitempty"` + TrackingId string `protobuf:"bytes,2,opt,name=tracking_id,json=trackingId,proto3" json:"tracking_id,omitempty"` + ContextFeatures []*ContextFeature `protobuf:"bytes,3,rep,name=context_features,json=contextFeatures,proto3" json:"context_features,omitempty"` + TargetInputSchema []*FeatureSchema `protobuf:"bytes,4,rep,name=target_input_schema,json=targetInputSchema,proto3" json:"target_input_schema,omitempty"` + SlateInputSchema []*FeatureSchema `protobuf:"bytes,5,rep,name=slate_input_schema,json=slateInputSchema,proto3" json:"slate_input_schema,omitempty"` + Slates []*TargetSlate `protobuf:"bytes,6,rep,name=slates,proto3" json:"slates,omitempty"` + Targets []*Target `protobuf:"bytes,7,rep,name=targets,proto3" json:"targets,omitempty"` + TenantId string `protobuf:"bytes,8,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SlateWiseRequest) Reset() { + *x = SlateWiseRequest{} + mi := &file_predict_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SlateWiseRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SlateWiseRequest) ProtoMessage() {} + +func (x *SlateWiseRequest) ProtoReflect() protoreflect.Message { + mi := &file_predict_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SlateWiseRequest.ProtoReflect.Descriptor instead. +func (*SlateWiseRequest) Descriptor() ([]byte, []int) { + return file_predict_proto_rawDescGZIP(), []int{12} +} + +func (x *SlateWiseRequest) GetModelConfigId() string { + if x != nil { + return x.ModelConfigId + } + return "" +} + +func (x *SlateWiseRequest) GetTrackingId() string { + if x != nil { + return x.TrackingId + } + return "" +} + +func (x *SlateWiseRequest) GetContextFeatures() []*ContextFeature { + if x != nil { + return x.ContextFeatures + } + return nil +} + +func (x *SlateWiseRequest) GetTargetInputSchema() []*FeatureSchema { + if x != nil { + return x.TargetInputSchema + } + return nil +} + +func (x *SlateWiseRequest) GetSlateInputSchema() []*FeatureSchema { + if x != nil { + return x.SlateInputSchema + } + return nil +} + +func (x *SlateWiseRequest) GetSlates() []*TargetSlate { + if x != nil { + return x.Slates + } + return nil +} + +func (x *SlateWiseRequest) GetTargets() []*Target { + if x != nil { + return x.Targets + } + return nil +} + +func (x *SlateWiseRequest) GetTenantId() string { + if x != nil { + return x.TenantId + } + return "" +} + +type SlateWiseResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + SlateScores []*SlateScore `protobuf:"bytes,1,rep,name=slate_scores,json=slateScores,proto3" json:"slate_scores,omitempty"` + TargetScores []*TargetScore `protobuf:"bytes,2,rep,name=target_scores,json=targetScores,proto3" json:"target_scores,omitempty"` + TargetOutputSchema []*FeatureSchema `protobuf:"bytes,3,rep,name=target_output_schema,json=targetOutputSchema,proto3" json:"target_output_schema,omitempty"` + SlateOutputSchema []*FeatureSchema `protobuf:"bytes,4,rep,name=slate_output_schema,json=slateOutputSchema,proto3" json:"slate_output_schema,omitempty"` + RequestError string `protobuf:"bytes,5,opt,name=request_error,json=requestError,proto3" json:"request_error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SlateWiseResponse) Reset() { + *x = SlateWiseResponse{} + mi := &file_predict_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SlateWiseResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SlateWiseResponse) ProtoMessage() {} + +func (x *SlateWiseResponse) ProtoReflect() protoreflect.Message { + mi := &file_predict_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SlateWiseResponse.ProtoReflect.Descriptor instead. +func (*SlateWiseResponse) Descriptor() ([]byte, []int) { + return file_predict_proto_rawDescGZIP(), []int{13} +} + +func (x *SlateWiseResponse) GetSlateScores() []*SlateScore { + if x != nil { + return x.SlateScores + } + return nil +} + +func (x *SlateWiseResponse) GetTargetScores() []*TargetScore { + if x != nil { + return x.TargetScores + } + return nil +} + +func (x *SlateWiseResponse) GetTargetOutputSchema() []*FeatureSchema { + if x != nil { + return x.TargetOutputSchema + } + return nil +} + +func (x *SlateWiseResponse) GetSlateOutputSchema() []*FeatureSchema { + if x != nil { + return x.SlateOutputSchema + } + return nil +} + +func (x *SlateWiseResponse) GetRequestError() string { + if x != nil { + return x.RequestError + } + return "" +} + +var File_predict_proto protoreflect.FileDescriptor + +const file_predict_proto_rawDesc = "" + + "\n" + + "\rpredict.proto\"j\n" + + "\rFeatureSchema\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12&\n" + + "\tdata_type\x18\x02 \x01(\x0e2\t.DataTypeR\bdataType\x12\x1d\n" + + "\n" + + "vector_dim\x18\x03 \x01(\x05R\tvectorDim\"\x81\x01\n" + + "\x0eContextFeature\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" + + "\x05value\x18\x02 \x01(\fR\x05value\x12&\n" + + "\tdata_type\x18\x03 \x01(\x0e2\t.DataTypeR\bdataType\x12\x1d\n" + + "\n" + + "vector_dim\x18\x04 \x01(\x05R\tvectorDim\"?\n" + + "\x06Target\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12%\n" + + "\x0efeature_values\x18\x02 \x03(\fR\rfeatureValues\"H\n" + + "\vTargetScore\x12\x14\n" + + "\x05error\x18\x01 \x01(\tR\x05error\x12#\n" + + "\routput_values\x18\x02 \x03(\fR\foutputValues\"\x97\x02\n" + + "\x10PointWiseRequest\x12&\n" + + "\x0fmodel_config_id\x18\x01 \x01(\tR\rmodelConfigId\x12\x1f\n" + + "\vtracking_id\x18\x02 \x01(\tR\n" + + "trackingId\x12:\n" + + "\x10context_features\x18\x03 \x03(\v2\x0f.ContextFeatureR\x0fcontextFeatures\x12>\n" + + "\x13target_input_schema\x18\x04 \x03(\v2\x0e.FeatureSchemaR\x11targetInputSchema\x12!\n" + + "\atargets\x18\x05 \x03(\v2\a.TargetR\atargets\x12\x1b\n" + + "\ttenant_id\x18\x06 \x01(\tR\btenantId\"\xad\x01\n" + + "\x11PointWiseResponse\x12@\n" + + "\x14target_output_schema\x18\x01 \x03(\v2\x0e.FeatureSchemaR\x12targetOutputSchema\x121\n" + + "\rtarget_scores\x18\x02 \x03(\v2\f.TargetScoreR\ftargetScores\x12#\n" + + "\rrequest_error\x18\x03 \x01(\tR\frequestError\"\x91\x01\n" + + "\n" + + "TargetPair\x12,\n" + + "\x12first_target_index\x18\x01 \x01(\x05R\x10firstTargetIndex\x12.\n" + + "\x13second_target_index\x18\x02 \x01(\x05R\x11secondTargetIndex\x12%\n" + + "\x0efeature_values\x18\x03 \x03(\fR\rfeatureValues\"\xf5\x02\n" + + "\x0fPairWiseRequest\x12&\n" + + "\x0fmodel_config_id\x18\x01 \x01(\tR\rmodelConfigId\x12\x1f\n" + + "\vtracking_id\x18\x02 \x01(\tR\n" + + "trackingId\x12:\n" + + "\x10context_features\x18\x03 \x03(\v2\x0f.ContextFeatureR\x0fcontextFeatures\x12>\n" + + "\x13target_input_schema\x18\x04 \x03(\v2\x0e.FeatureSchemaR\x11targetInputSchema\x12:\n" + + "\x11pair_input_schema\x18\x05 \x03(\v2\x0e.FeatureSchemaR\x0fpairInputSchema\x12!\n" + + "\x05pairs\x18\x06 \x03(\v2\v.TargetPairR\x05pairs\x12!\n" + + "\atargets\x18\a \x03(\v2\a.TargetR\atargets\x12\x1b\n" + + "\ttenant_id\x18\b \x01(\tR\btenantId\"F\n" + + "\tPairScore\x12\x14\n" + + "\x05error\x18\x01 \x01(\tR\x05error\x12#\n" + + "\routput_values\x18\x02 \x03(\fR\foutputValues\"\x97\x02\n" + + "\x10PairWiseResponse\x12+\n" + + "\vpair_scores\x18\x01 \x03(\v2\n" + + ".PairScoreR\n" + + "pairScores\x121\n" + + "\rtarget_scores\x18\x02 \x03(\v2\f.TargetScoreR\ftargetScores\x12@\n" + + "\x14target_output_schema\x18\x03 \x03(\v2\x0e.FeatureSchemaR\x12targetOutputSchema\x12<\n" + + "\x12pair_output_schema\x18\x04 \x03(\v2\x0e.FeatureSchemaR\x10pairOutputSchema\x12#\n" + + "\rrequest_error\x18\x05 \x01(\tR\frequestError\"[\n" + + "\vTargetSlate\x12%\n" + + "\x0etarget_indices\x18\x01 \x03(\x05R\rtargetIndices\x12%\n" + + "\x0efeature_values\x18\x02 \x03(\fR\rfeatureValues\"G\n" + + "\n" + + "SlateScore\x12\x14\n" + + "\x05error\x18\x01 \x01(\tR\x05error\x12#\n" + + "\routput_values\x18\x02 \x03(\fR\foutputValues\"\xfb\x02\n" + + "\x10SlateWiseRequest\x12&\n" + + "\x0fmodel_config_id\x18\x01 \x01(\tR\rmodelConfigId\x12\x1f\n" + + "\vtracking_id\x18\x02 \x01(\tR\n" + + "trackingId\x12:\n" + + "\x10context_features\x18\x03 \x03(\v2\x0f.ContextFeatureR\x0fcontextFeatures\x12>\n" + + "\x13target_input_schema\x18\x04 \x03(\v2\x0e.FeatureSchemaR\x11targetInputSchema\x12<\n" + + "\x12slate_input_schema\x18\x05 \x03(\v2\x0e.FeatureSchemaR\x10slateInputSchema\x12$\n" + + "\x06slates\x18\x06 \x03(\v2\f.TargetSlateR\x06slates\x12!\n" + + "\atargets\x18\a \x03(\v2\a.TargetR\atargets\x12\x1b\n" + + "\ttenant_id\x18\b \x01(\tR\btenantId\"\x9d\x02\n" + + "\x11SlateWiseResponse\x12.\n" + + "\fslate_scores\x18\x01 \x03(\v2\v.SlateScoreR\vslateScores\x121\n" + + "\rtarget_scores\x18\x02 \x03(\v2\f.TargetScoreR\ftargetScores\x12@\n" + + "\x14target_output_schema\x18\x03 \x03(\v2\x0e.FeatureSchemaR\x12targetOutputSchema\x12>\n" + + "\x13slate_output_schema\x18\x04 \x03(\v2\x0e.FeatureSchemaR\x11slateOutputSchema\x12#\n" + + "\rrequest_error\x18\x05 \x01(\tR\frequestError*\xb9\x05\n" + + "\bDataType\x12\x13\n" + + "\x0fDataTypeUnknown\x10\x00\x12\x13\n" + + "\x0fDataTypeFP8E5M2\x10\x01\x12\x13\n" + + "\x0fDataTypeFP8E4M3\x10\x02\x12\x10\n" + + "\fDataTypeFP16\x10\x03\x12\x10\n" + + "\fDataTypeFP32\x10\x04\x12\x10\n" + + "\fDataTypeFP64\x10\x05\x12\x10\n" + + "\fDataTypeInt8\x10\x06\x12\x11\n" + + "\rDataTypeInt16\x10\a\x12\x11\n" + + "\rDataTypeInt32\x10\b\x12\x11\n" + + "\rDataTypeInt64\x10\t\x12\x11\n" + + "\rDataTypeUint8\x10\n" + + "\x12\x12\n" + + "\x0eDataTypeUint16\x10\v\x12\x12\n" + + "\x0eDataTypeUint32\x10\f\x12\x12\n" + + "\x0eDataTypeUint64\x10\r\x12\x12\n" + + "\x0eDataTypeString\x10\x0e\x12\x10\n" + + "\fDataTypeBool\x10\x0f\x12\x19\n" + + "\x15DataTypeFP8E5M2Vector\x10\x10\x12\x19\n" + + "\x15DataTypeFP8E4M3Vector\x10\x11\x12\x16\n" + + "\x12DataTypeFP16Vector\x10\x12\x12\x16\n" + + "\x12DataTypeFP32Vector\x10\x13\x12\x16\n" + + "\x12DataTypeFP64Vector\x10\x14\x12\x16\n" + + "\x12DataTypeInt8Vector\x10\x15\x12\x17\n" + + "\x13DataTypeInt16Vector\x10\x16\x12\x17\n" + + "\x13DataTypeInt32Vector\x10\x17\x12\x17\n" + + "\x13DataTypeInt64Vector\x10\x18\x12\x17\n" + + "\x13DataTypeUint8Vector\x10\x19\x12\x18\n" + + "\x14DataTypeUint16Vector\x10\x1a\x12\x18\n" + + "\x14DataTypeUint32Vector\x10\x1b\x12\x18\n" + + "\x14DataTypeUint64Vector\x10\x1c\x12\x18\n" + + "\x14DataTypeStringVector\x10\x1d\x12\x16\n" + + "\x12DataTypeBoolVector\x10\x1e2\xb7\x01\n" + + "\aPredict\x129\n" + + "\x0eInferPointWise\x12\x11.PointWiseRequest\x1a\x12.PointWiseResponse\"\x00\x126\n" + + "\rInferPairWise\x12\x10.PairWiseRequest\x1a\x11.PairWiseResponse\"\x00\x129\n" + + "\x0eInferSlateWise\x12\x11.SlateWiseRequest\x1a\x12.SlateWiseResponse\"\x00B\x11Z\x0f../grpc/predictb\x06proto3" + +var ( + file_predict_proto_rawDescOnce sync.Once + file_predict_proto_rawDescData []byte +) + +func file_predict_proto_rawDescGZIP() []byte { + file_predict_proto_rawDescOnce.Do(func() { + file_predict_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_predict_proto_rawDesc), len(file_predict_proto_rawDesc))) + }) + return file_predict_proto_rawDescData +} + +var file_predict_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_predict_proto_msgTypes = make([]protoimpl.MessageInfo, 14) +var file_predict_proto_goTypes = []any{ + (DataType)(0), // 0: DataType + (*FeatureSchema)(nil), // 1: FeatureSchema + (*ContextFeature)(nil), // 2: ContextFeature + (*Target)(nil), // 3: Target + (*TargetScore)(nil), // 4: TargetScore + (*PointWiseRequest)(nil), // 5: PointWiseRequest + (*PointWiseResponse)(nil), // 6: PointWiseResponse + (*TargetPair)(nil), // 7: TargetPair + (*PairWiseRequest)(nil), // 8: PairWiseRequest + (*PairScore)(nil), // 9: PairScore + (*PairWiseResponse)(nil), // 10: PairWiseResponse + (*TargetSlate)(nil), // 11: TargetSlate + (*SlateScore)(nil), // 12: SlateScore + (*SlateWiseRequest)(nil), // 13: SlateWiseRequest + (*SlateWiseResponse)(nil), // 14: SlateWiseResponse +} +var file_predict_proto_depIdxs = []int32{ + 0, // 0: FeatureSchema.data_type:type_name -> DataType + 0, // 1: ContextFeature.data_type:type_name -> DataType + 2, // 2: PointWiseRequest.context_features:type_name -> ContextFeature + 1, // 3: PointWiseRequest.target_input_schema:type_name -> FeatureSchema + 3, // 4: PointWiseRequest.targets:type_name -> Target + 1, // 5: PointWiseResponse.target_output_schema:type_name -> FeatureSchema + 4, // 6: PointWiseResponse.target_scores:type_name -> TargetScore + 2, // 7: PairWiseRequest.context_features:type_name -> ContextFeature + 1, // 8: PairWiseRequest.target_input_schema:type_name -> FeatureSchema + 1, // 9: PairWiseRequest.pair_input_schema:type_name -> FeatureSchema + 7, // 10: PairWiseRequest.pairs:type_name -> TargetPair + 3, // 11: PairWiseRequest.targets:type_name -> Target + 9, // 12: PairWiseResponse.pair_scores:type_name -> PairScore + 4, // 13: PairWiseResponse.target_scores:type_name -> TargetScore + 1, // 14: PairWiseResponse.target_output_schema:type_name -> FeatureSchema + 1, // 15: PairWiseResponse.pair_output_schema:type_name -> FeatureSchema + 2, // 16: SlateWiseRequest.context_features:type_name -> ContextFeature + 1, // 17: SlateWiseRequest.target_input_schema:type_name -> FeatureSchema + 1, // 18: SlateWiseRequest.slate_input_schema:type_name -> FeatureSchema + 11, // 19: SlateWiseRequest.slates:type_name -> TargetSlate + 3, // 20: SlateWiseRequest.targets:type_name -> Target + 12, // 21: SlateWiseResponse.slate_scores:type_name -> SlateScore + 4, // 22: SlateWiseResponse.target_scores:type_name -> TargetScore + 1, // 23: SlateWiseResponse.target_output_schema:type_name -> FeatureSchema + 1, // 24: SlateWiseResponse.slate_output_schema:type_name -> FeatureSchema + 5, // 25: Predict.InferPointWise:input_type -> PointWiseRequest + 8, // 26: Predict.InferPairWise:input_type -> PairWiseRequest + 13, // 27: Predict.InferSlateWise:input_type -> SlateWiseRequest + 6, // 28: Predict.InferPointWise:output_type -> PointWiseResponse + 10, // 29: Predict.InferPairWise:output_type -> PairWiseResponse + 14, // 30: Predict.InferSlateWise:output_type -> SlateWiseResponse + 28, // [28:31] is the sub-list for method output_type + 25, // [25:28] is the sub-list for method input_type + 25, // [25:25] is the sub-list for extension type_name + 25, // [25:25] is the sub-list for extension extendee + 0, // [0:25] is the sub-list for field type_name +} + +func init() { file_predict_proto_init() } +func file_predict_proto_init() { + if File_predict_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_predict_proto_rawDesc), len(file_predict_proto_rawDesc)), + NumEnums: 1, + NumMessages: 14, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_predict_proto_goTypes, + DependencyIndexes: file_predict_proto_depIdxs, + EnumInfos: file_predict_proto_enumTypes, + MessageInfos: file_predict_proto_msgTypes, + }.Build() + File_predict_proto = out.File + file_predict_proto_goTypes = nil + file_predict_proto_depIdxs = nil +} diff --git a/go-sdk/pkg/clients/inferflow/client/grpc/predict_grpc.pb.go b/go-sdk/pkg/clients/inferflow/client/grpc/predict_grpc.pb.go new file mode 100644 index 00000000..267954e5 --- /dev/null +++ b/go-sdk/pkg/clients/inferflow/client/grpc/predict_grpc.pb.go @@ -0,0 +1,197 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.1 +// - protoc v5.28.3 +// source: predict.proto + +package grpc + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + Predict_InferPointWise_FullMethodName = "/Predict/InferPointWise" + Predict_InferPairWise_FullMethodName = "/Predict/InferPairWise" + Predict_InferSlateWise_FullMethodName = "/Predict/InferSlateWise" +) + +// PredictClient is the client API for Predict service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type PredictClient interface { + InferPointWise(ctx context.Context, in *PointWiseRequest, opts ...grpc.CallOption) (*PointWiseResponse, error) + InferPairWise(ctx context.Context, in *PairWiseRequest, opts ...grpc.CallOption) (*PairWiseResponse, error) + InferSlateWise(ctx context.Context, in *SlateWiseRequest, opts ...grpc.CallOption) (*SlateWiseResponse, error) +} + +type predictClient struct { + cc grpc.ClientConnInterface +} + +func NewPredictClient(cc grpc.ClientConnInterface) PredictClient { + return &predictClient{cc} +} + +func (c *predictClient) InferPointWise(ctx context.Context, in *PointWiseRequest, opts ...grpc.CallOption) (*PointWiseResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(PointWiseResponse) + err := c.cc.Invoke(ctx, Predict_InferPointWise_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *predictClient) InferPairWise(ctx context.Context, in *PairWiseRequest, opts ...grpc.CallOption) (*PairWiseResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(PairWiseResponse) + err := c.cc.Invoke(ctx, Predict_InferPairWise_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *predictClient) InferSlateWise(ctx context.Context, in *SlateWiseRequest, opts ...grpc.CallOption) (*SlateWiseResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SlateWiseResponse) + err := c.cc.Invoke(ctx, Predict_InferSlateWise_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// PredictServer is the server API for Predict service. +// All implementations must embed UnimplementedPredictServer +// for forward compatibility. +type PredictServer interface { + InferPointWise(context.Context, *PointWiseRequest) (*PointWiseResponse, error) + InferPairWise(context.Context, *PairWiseRequest) (*PairWiseResponse, error) + InferSlateWise(context.Context, *SlateWiseRequest) (*SlateWiseResponse, error) + mustEmbedUnimplementedPredictServer() +} + +// UnimplementedPredictServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedPredictServer struct{} + +func (UnimplementedPredictServer) InferPointWise(context.Context, *PointWiseRequest) (*PointWiseResponse, error) { + return nil, status.Error(codes.Unimplemented, "method InferPointWise not implemented") +} +func (UnimplementedPredictServer) InferPairWise(context.Context, *PairWiseRequest) (*PairWiseResponse, error) { + return nil, status.Error(codes.Unimplemented, "method InferPairWise not implemented") +} +func (UnimplementedPredictServer) InferSlateWise(context.Context, *SlateWiseRequest) (*SlateWiseResponse, error) { + return nil, status.Error(codes.Unimplemented, "method InferSlateWise not implemented") +} +func (UnimplementedPredictServer) mustEmbedUnimplementedPredictServer() {} +func (UnimplementedPredictServer) testEmbeddedByValue() {} + +// UnsafePredictServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to PredictServer will +// result in compilation errors. +type UnsafePredictServer interface { + mustEmbedUnimplementedPredictServer() +} + +func RegisterPredictServer(s grpc.ServiceRegistrar, srv PredictServer) { + // If the following call panics, it indicates UnimplementedPredictServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&Predict_ServiceDesc, srv) +} + +func _Predict_InferPointWise_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PointWiseRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PredictServer).InferPointWise(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Predict_InferPointWise_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PredictServer).InferPointWise(ctx, req.(*PointWiseRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Predict_InferPairWise_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PairWiseRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PredictServer).InferPairWise(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Predict_InferPairWise_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PredictServer).InferPairWise(ctx, req.(*PairWiseRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Predict_InferSlateWise_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SlateWiseRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PredictServer).InferSlateWise(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Predict_InferSlateWise_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PredictServer).InferSlateWise(ctx, req.(*SlateWiseRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Predict_ServiceDesc is the grpc.ServiceDesc for Predict service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Predict_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "Predict", + HandlerType: (*PredictServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "InferPointWise", + Handler: _Predict_InferPointWise_Handler, + }, + { + MethodName: "InferPairWise", + Handler: _Predict_InferPairWise_Handler, + }, + { + MethodName: "InferSlateWise", + Handler: _Predict_InferSlateWise_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "predict.proto", +} diff --git a/go-sdk/pkg/clients/inferflow/client/proto/predict.proto b/go-sdk/pkg/clients/inferflow/client/proto/predict.proto new file mode 100644 index 00000000..0cb7f38f --- /dev/null +++ b/go-sdk/pkg/clients/inferflow/client/proto/predict.proto @@ -0,0 +1,148 @@ +syntax = "proto3"; + +option go_package = "../grpc"; + +enum DataType { + DataTypeUnknown = 0; + DataTypeFP8E5M2 = 1; + DataTypeFP8E4M3 = 2; + DataTypeFP16 = 3; + DataTypeFP32 = 4; + DataTypeFP64 = 5; + DataTypeInt8 = 6; + DataTypeInt16 = 7; + DataTypeInt32 = 8; + DataTypeInt64 = 9; + DataTypeUint8 = 10; + DataTypeUint16 = 11; + DataTypeUint32 = 12; + DataTypeUint64 = 13; + DataTypeString = 14; + DataTypeBool = 15; + DataTypeFP8E5M2Vector = 16; + DataTypeFP8E4M3Vector = 17; + DataTypeFP16Vector = 18; + DataTypeFP32Vector = 19; + DataTypeFP64Vector = 20; + DataTypeInt8Vector = 21; + DataTypeInt16Vector = 22; + DataTypeInt32Vector = 23; + DataTypeInt64Vector = 24; + DataTypeUint8Vector = 25; + DataTypeUint16Vector = 26; + DataTypeUint32Vector = 27; + DataTypeUint64Vector = 28; + DataTypeStringVector = 29; + DataTypeBoolVector = 30; +} + +message FeatureSchema { + string name = 1; + DataType data_type = 2; + int32 vector_dim = 3; +} + +message ContextFeature { + string name = 1; + bytes value = 2; + DataType data_type = 3; + int32 vector_dim = 4; +} + +message Target { + string id = 1; + repeated bytes feature_values = 2; +} + +// --- PointWise --- + +message TargetScore { + string error = 1; + repeated bytes output_values = 2; +} + +message PointWiseRequest { + string model_config_id = 1; + string tracking_id = 2; + repeated ContextFeature context_features = 3; + repeated FeatureSchema target_input_schema = 4; + repeated Target targets = 5; + string tenant_id = 6; +} + +message PointWiseResponse { + repeated FeatureSchema target_output_schema = 1; + repeated TargetScore target_scores = 2; + string request_error = 3; +} + +// --- PairWise --- + +message TargetPair { + int32 first_target_index = 1; + int32 second_target_index = 2; + repeated bytes feature_values = 3; +} + +message PairWiseRequest { + string model_config_id = 1; + string tracking_id = 2; + repeated ContextFeature context_features = 3; + repeated FeatureSchema target_input_schema = 4; + repeated FeatureSchema pair_input_schema = 5; + repeated TargetPair pairs = 6; + repeated Target targets = 7; + string tenant_id = 8; +} + +message PairScore { + string error = 1; + repeated bytes output_values = 2; +} + +message PairWiseResponse { + repeated PairScore pair_scores = 1; + repeated TargetScore target_scores = 2; + repeated FeatureSchema target_output_schema = 3; + repeated FeatureSchema pair_output_schema = 4; + string request_error = 5; +} + +// --- SlateWise --- + +message TargetSlate { + repeated int32 target_indices = 1; + repeated bytes feature_values = 2; +} + +message SlateScore { + string error = 1; + repeated bytes output_values = 2; +} + +message SlateWiseRequest { + string model_config_id = 1; + string tracking_id = 2; + repeated ContextFeature context_features = 3; + repeated FeatureSchema target_input_schema = 4; + repeated FeatureSchema slate_input_schema = 5; + repeated TargetSlate slates = 6; + repeated Target targets = 7; + string tenant_id = 8; +} + +message SlateWiseResponse { + repeated SlateScore slate_scores = 1; + repeated TargetScore target_scores = 2; + repeated FeatureSchema target_output_schema = 3; + repeated FeatureSchema slate_output_schema = 4; + string request_error = 5; +} + +// --- Service --- + +service Predict { + rpc InferPointWise(PointWiseRequest) returns (PointWiseResponse) {}; + rpc InferPairWise(PairWiseRequest) returns (PairWiseResponse) {}; + rpc InferSlateWise(SlateWiseRequest) returns (SlateWiseResponse) {}; +} diff --git a/go-sdk/pkg/clients/inferflow/conf.go b/go-sdk/pkg/clients/inferflow/conf.go new file mode 100644 index 00000000..81720396 --- /dev/null +++ b/go-sdk/pkg/clients/inferflow/conf.go @@ -0,0 +1,76 @@ +package inferflow + +import ( + "fmt" + "github.com/spf13/viper" +) + +const ( + Host = "HOST" + Port = "PORT" + DeadlineMS = "DEADLINE_MS" + PlainText = "PLAINTEXT" + AuthToken = "AUTH_TOKEN" + DefaultHost = "" + DefaultPort = "8080" + DefaultDeadlineMS = 200 + DefaultPlainText = true + DefaultAuthToken = "" +) + +type ClientConfig struct { + Host string + Port string + DeadlineExceedMS int + PlainText bool + AuthToken string +} + +func getClientConfigs(prefix string) (*ClientConfig, error) { + host := DefaultHost + port := DefaultPort + deadline := DefaultDeadlineMS + plaintext := DefaultPlainText + authToken := DefaultAuthToken + + if viper.IsSet(prefix + Host) { + host = viper.GetString(prefix + Host) + } + if viper.IsSet(prefix + Port) { + port = viper.GetString(prefix + Port) + } + if viper.IsSet(prefix + DeadlineMS) { + deadline = viper.GetInt(prefix + DeadlineMS) + } + if viper.IsSet(prefix + PlainText) { + plaintext = viper.GetBool(prefix + PlainText) + } + if viper.IsSet(prefix + AuthToken) { + authToken = viper.GetString(prefix + AuthToken) + } + conf := &ClientConfig{ + Host: host, + Port: port, + DeadlineExceedMS: deadline, + PlainText: plaintext, + AuthToken: authToken, + } + if valid, err := validConfigs(conf); !valid { + return nil, err + } + return conf, nil +} + +func validConfigs(configs *ClientConfig) (bool, error) { + if configs.Host == "" { + return false, fmt.Errorf("inferflow service host is invalid, configured value: %v", configs.Host) + } + if configs.Port == "" { + return false, fmt.Errorf("inferflow service port is invalid, configured value: %v", configs.Port) + } + if configs.DeadlineExceedMS <= 0 { + return false, fmt.Errorf("inferflow service deadline exceed timeout is invalid, configured value: %v", + configs.DeadlineExceedMS) + } + return true, nil +} diff --git a/go-sdk/pkg/clients/inferflow/inferflow.go b/go-sdk/pkg/clients/inferflow/inferflow.go new file mode 100644 index 00000000..e7775660 --- /dev/null +++ b/go-sdk/pkg/clients/inferflow/inferflow.go @@ -0,0 +1,12 @@ +package inferflow + +import ( + "github.com/Meesho/BharatMLStack/go-sdk/pkg/clients/inferflow/client/grpc" +) + +// InferflowClient exposes the Predict service APIs: PointWise, PairWise, and SlateWise. +type InferflowClient interface { + InferPointWise(request *grpc.PointWiseRequest) (*grpc.PointWiseResponse, error) + InferPairWise(request *grpc.PairWiseRequest) (*grpc.PairWiseResponse, error) + InferSlateWise(request *grpc.SlateWiseRequest) (*grpc.SlateWiseResponse, error) +} diff --git a/go-sdk/pkg/clients/inferflow/init.go b/go-sdk/pkg/clients/inferflow/init.go new file mode 100644 index 00000000..92d58001 --- /dev/null +++ b/go-sdk/pkg/clients/inferflow/init.go @@ -0,0 +1,19 @@ +package inferflow + +func GetInferflowClient(version int) InferflowClient { + switch version { + case 1: + return InitV1Client() + default: + return nil + } +} + +func GetInferflowClientFromConfig(version int, conf ClientConfig, callerId string) InferflowClient { + switch version { + case 1: + return InitV1ClientFromConfig(conf, callerId) + default: + return nil + } +} diff --git a/go-sdk/pkg/clients/inferflow/models.go b/go-sdk/pkg/clients/inferflow/models.go new file mode 100644 index 00000000..8b9460ea --- /dev/null +++ b/go-sdk/pkg/clients/inferflow/models.go @@ -0,0 +1,8 @@ +package inferflow + +import "github.com/Meesho/BharatMLStack/go-sdk/pkg/grpcclient" + +type ClientV1 struct { + ClientConfigs *ClientConfig + GrpcClient *grpcclient.GRPCClient +} diff --git a/go-sdk/pkg/clients/inferflow/v1.go b/go-sdk/pkg/clients/inferflow/v1.go new file mode 100644 index 00000000..6695fa20 --- /dev/null +++ b/go-sdk/pkg/clients/inferflow/v1.go @@ -0,0 +1,154 @@ +package inferflow + +import ( + "context" + "fmt" + "sync" + "time" + + grpc2 "github.com/Meesho/BharatMLStack/go-sdk/pkg/clients/inferflow/client/grpc" + "github.com/Meesho/BharatMLStack/go-sdk/pkg/grpcclient" + "github.com/rs/zerolog/log" + "github.com/spf13/viper" + "google.golang.org/grpc/metadata" +) + +var ( + client *ClientV1 + onceEnv sync.Once + onceConfig sync.Once + headers metadata.MD +) + +const ( + V1Prefix = "INFERFLOW_CLIENT_V1_" + CallerIDMetadata = "inferflow-caller-id" + AuthMetadata = "inferflow-auth-token" +) + +// InitV1Client initializes the client from environment variables (via viper). +// Safe to call multiple times; initialization runs once. +func InitV1Client() InferflowClient { + onceEnv.Do(func() { + clientConfig, err := getClientConfigs(V1Prefix) + if err != nil { + log.Panic().Err(err).Msgf("Invalid Inferflow client configs: %#v", clientConfig) + } + grpcClient, grpcErr := getGrpcClient(clientConfig) + if grpcErr != nil { + log.Panic().Err(grpcErr).Msgf("Error creating inferflow service grpc client, client: %#v", grpcClient) + } + headers = getMetadata(clientConfig.AuthToken) + client = &ClientV1{ + ClientConfigs: clientConfig, + GrpcClient: grpcClient, + } + }) + return client +} + +// InitV1ClientFromConfig initializes the client from an explicit config. +// Uses a separate sync.Once so it is not blocked by a prior InitV1Client call. +func InitV1ClientFromConfig(conf ClientConfig, callerId string) InferflowClient { + onceConfig.Do(func() { + grpcClient, grpcErr := getGrpcClient(&conf) + if grpcErr != nil { + log.Panic().Err(grpcErr).Msgf("Error creating inferflow service grpc client, client: %#v", grpcClient) + } + headers = metadata.New(map[string]string{ + CallerIDMetadata: callerId, + AuthMetadata: conf.AuthToken, + }) + client = &ClientV1{ + ClientConfigs: &conf, + GrpcClient: grpcClient, + } + }) + return client +} + +func getGrpcClient(conf *ClientConfig) (*grpcclient.GRPCClient, error) { + var client *grpcclient.GRPCClient + var err error + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("panic creating grpc client from prefix: %v", r) + } + }() + client = grpcclient.NewConnFromConfig(&grpcclient.Config{ + Host: conf.Host, + Port: conf.Port, + DeadLine: conf.DeadlineExceedMS, + LoadBalancingPolicy: "round_robin", + PlainText: conf.PlainText, + }, V1Prefix) + return client, err +} + +func (c *ClientV1) InferPointWise(req *grpc2.PointWiseRequest) (*grpc2.PointWiseResponse, error) { + predictClient := grpc2.NewPredictClient(c.GrpcClient) + timeout := time.Duration(c.ClientConfigs.DeadlineExceedMS) * time.Millisecond + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + ctx = metadata.NewOutgoingContext(ctx, headers) + protoResponse, err := predictClient.InferPointWise(ctx, req) + if err != nil { + log.Error().Msgf("Error while calling InferPointWise on inferflow service, err: %v", err) + return nil, err + } else if protoResponse == nil { + log.Error().Msgf("Empty response from inferflow InferPointWise") + return nil, fmt.Errorf("empty response from inferflow InferPointWise") + } + return protoResponse, nil +} + +func (c *ClientV1) InferPairWise(req *grpc2.PairWiseRequest) (*grpc2.PairWiseResponse, error) { + predictClient := grpc2.NewPredictClient(c.GrpcClient) + timeout := time.Duration(c.ClientConfigs.DeadlineExceedMS) * time.Millisecond + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + ctx = metadata.NewOutgoingContext(ctx, headers) + protoResponse, err := predictClient.InferPairWise(ctx, req) + if err != nil { + log.Error().Msgf("Error while calling InferPairWise on inferflow service, err: %v", err) + return nil, err + } else if protoResponse == nil { + log.Error().Msgf("Empty response from inferflow InferPairWise") + return nil, fmt.Errorf("empty response from inferflow InferPairWise") + } + return protoResponse, nil +} + +func (c *ClientV1) InferSlateWise(req *grpc2.SlateWiseRequest) (*grpc2.SlateWiseResponse, error) { + predictClient := grpc2.NewPredictClient(c.GrpcClient) + timeout := time.Duration(c.ClientConfigs.DeadlineExceedMS) * time.Millisecond + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + ctx = metadata.NewOutgoingContext(ctx, headers) + protoResponse, err := predictClient.InferSlateWise(ctx, req) + if err != nil { + log.Error().Msgf("Error while calling InferSlateWise on inferflow service, err: %v", err) + return nil, err + } else if protoResponse == nil { + log.Error().Msgf("Empty response from inferflow InferSlateWise") + return nil, fmt.Errorf("empty response from inferflow InferSlateWise") + } + return protoResponse, nil +} + +func getMetadata(authToken string) metadata.MD { + callerId := viper.GetString("INFERFLOW_CALLER_ID") + if callerId == "" { + log.Panic().Msgf("INFERFLOW_CALLER_ID not set!") + } + md := metadata.New(map[string]string{ + CallerIDMetadata: callerId, + }) + if authToken != "" { + md.Set(AuthMetadata, authToken) + } + return md +} diff --git a/go-sdk/pkg/clients/inferflow/v1_test.go b/go-sdk/pkg/clients/inferflow/v1_test.go new file mode 100644 index 00000000..6cdd09ca --- /dev/null +++ b/go-sdk/pkg/clients/inferflow/v1_test.go @@ -0,0 +1,144 @@ +package inferflow + +import ( + "testing" + + "github.com/spf13/viper" +) + +func TestGetClientConfigs_Valid(t *testing.T) { + viper.Set("INFERFLOW_CLIENT_V1_HOST", "localhost") + viper.Set("INFERFLOW_CLIENT_V1_PORT", "8080") + viper.Set("INFERFLOW_CLIENT_V1_DEADLINE_MS", 500) + viper.Set("INFERFLOW_CLIENT_V1_PLAINTEXT", true) + defer viper.Reset() + + conf, err := getClientConfigs(V1Prefix) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if conf.Host != "localhost" { + t.Errorf("expected host localhost, got %s", conf.Host) + } + if conf.Port != "8080" { + t.Errorf("expected port 8080, got %s", conf.Port) + } + if conf.DeadlineExceedMS != 500 { + t.Errorf("expected deadline 500, got %d", conf.DeadlineExceedMS) + } + if !conf.PlainText { + t.Error("expected plaintext true") + } +} + +func TestGetClientConfigs_MissingHost(t *testing.T) { + viper.Reset() + viper.Set("INFERFLOW_CLIENT_V1_PORT", "8080") + viper.Set("INFERFLOW_CLIENT_V1_DEADLINE_MS", 500) + defer viper.Reset() + + _, err := getClientConfigs(V1Prefix) + if err == nil { + t.Fatal("expected error for missing host, got nil") + } +} + +func TestGetClientConfigs_EmptyPort(t *testing.T) { + viper.Reset() + viper.Set("INFERFLOW_CLIENT_V1_HOST", "localhost") + viper.Set("INFERFLOW_CLIENT_V1_PORT", "") + viper.Set("INFERFLOW_CLIENT_V1_DEADLINE_MS", 500) + defer viper.Reset() + + _, err := getClientConfigs(V1Prefix) + if err == nil { + t.Fatal("expected error for empty port, got nil") + } +} + +func TestGetClientConfigs_InvalidDeadline(t *testing.T) { + viper.Set("INFERFLOW_CLIENT_V1_HOST", "localhost") + viper.Set("INFERFLOW_CLIENT_V1_PORT", "8080") + viper.Set("INFERFLOW_CLIENT_V1_DEADLINE_MS", 0) + defer viper.Reset() + + _, err := getClientConfigs(V1Prefix) + if err == nil { + t.Fatal("expected error for zero deadline, got nil") + } +} + +func TestGetClientConfigs_Defaults(t *testing.T) { + viper.Set("INFERFLOW_CLIENT_V1_HOST", "inferflow.svc") + defer viper.Reset() + + conf, err := getClientConfigs(V1Prefix) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if conf.Port != DefaultPort { + t.Errorf("expected default port %s, got %s", DefaultPort, conf.Port) + } + if conf.DeadlineExceedMS != DefaultDeadlineMS { + t.Errorf("expected default deadline %d, got %d", DefaultDeadlineMS, conf.DeadlineExceedMS) + } + if conf.PlainText != DefaultPlainText { + t.Errorf("expected default plaintext %v, got %v", DefaultPlainText, conf.PlainText) + } +} + +func TestValidConfigs(t *testing.T) { + tests := []struct { + name string + config *ClientConfig + wantOK bool + }{ + { + name: "valid config", + config: &ClientConfig{Host: "localhost", Port: "8080", DeadlineExceedMS: 200}, + wantOK: true, + }, + { + name: "empty host", + config: &ClientConfig{Host: "", Port: "8080", DeadlineExceedMS: 200}, + wantOK: false, + }, + { + name: "empty port", + config: &ClientConfig{Host: "localhost", Port: "", DeadlineExceedMS: 200}, + wantOK: false, + }, + { + name: "zero deadline", + config: &ClientConfig{Host: "localhost", Port: "8080", DeadlineExceedMS: 0}, + wantOK: false, + }, + { + name: "negative deadline", + config: &ClientConfig{Host: "localhost", Port: "8080", DeadlineExceedMS: -1}, + wantOK: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ok, err := validConfigs(tt.config) + if ok != tt.wantOK { + t.Errorf("validConfigs() = %v, want %v, err: %v", ok, tt.wantOK, err) + } + }) + } +} + +func TestGetInferflowClient_InvalidVersion(t *testing.T) { + c := GetInferflowClient(99) + if c != nil { + t.Error("expected nil for unsupported version") + } +} + +func TestGetInferflowClientFromConfig_InvalidVersion(t *testing.T) { + c := GetInferflowClientFromConfig(99, ClientConfig{}, "test") + if c != nil { + t.Error("expected nil for unsupported version") + } +} diff --git a/go-sdk/pkg/clients/numerix/adaptor.go b/go-sdk/pkg/clients/numerix/adaptor.go new file mode 100644 index 00000000..3f066cca --- /dev/null +++ b/go-sdk/pkg/clients/numerix/adaptor.go @@ -0,0 +1,153 @@ +package numerix + +import ( + "strings" + + "github.com/rs/zerolog/log" + + "github.com/Meesho/BharatMLStack/go-sdk/pkg/clients/numerix/client/grpc" + "github.com/Meesho/BharatMLStack/go-sdk/pkg/datatypeconverter/typeconverter" +) + +type IAdapter interface { + MapRequestToProto(request *NumerixRequest) *grpc.NumerixRequestProto + MapProtoToResponse(protoResponse *grpc.NumerixResponseProto) *NumerixResponse +} + +type Adapter struct { + IAdapter +} + +func (a *Adapter) ScoreDataToFP32Bytes(request *NumerixRequest) { + mapOfInputType := make(map[int]string) + for i := range request.EntityScoreData.Schema { + schemaParts := strings.Split(request.EntityScoreData.Schema[i], "@") + if i == 0 { + request.EntityScoreData.Schema[i] = schemaParts[0] + continue + } + mapOfInputType[i] = schemaParts[1] + request.EntityScoreData.Schema[i] = schemaParts[0] + } + + for i := range request.EntityScoreData.Data { + for j := range request.EntityScoreData.Data[i] { + if j == 0 { + continue + } + converted, err := typeconverter.ConvertBytesToBytes(request.EntityScoreData.Data[i][j], mapOfInputType[j], "datatypefp32") + if err != nil { + log.Error().Err(err).Msgf("Error converting bytes at index i=%d, j=%d, from type=%s to fp32", i, j, mapOfInputType[j]) + return + } + request.EntityScoreData.Data[i][j] = converted + } + } +} + +func (a *Adapter) MapRequestToProto(request *NumerixRequest) *grpc.NumerixRequestProto { + if request.EntityScoreData.Data == nil && request.EntityScoreData.StringData == nil { + log.Warn().Msg("EntityScoreData Data and StringData is nil; cannot map request.") + return nil + } + var ( + protoScores []*grpc.Score + newSchema []string + dataType = "fp32" + ) + + switch { + case request.EntityScoreData.Data != nil: + protoScores = make([]*grpc.Score, len(request.EntityScoreData.Data)) + a.ScoreDataToFP32Bytes(request) + + for rowIdx, row := range request.EntityScoreData.Data { + protoScores[rowIdx] = &grpc.Score{ + MatrixFormat: &grpc.Score_ByteData{ + ByteData: &grpc.ByteList{Values: row}, + }, + } + } + newSchema = request.EntityScoreData.Schema + + case request.EntityScoreData.StringData != nil: + protoScores = make([]*grpc.Score, len(request.EntityScoreData.StringData)) + for rowIdx, row := range request.EntityScoreData.StringData { + protoScores[rowIdx] = &grpc.Score{ + MatrixFormat: &grpc.Score_StringData{ + StringData: &grpc.StringList{Values: row}, + }, + } + } + newSchema = request.EntityScoreData.Schema + + default: + log.Warn().Msg("No valid score data found in EntityScoreData.") + return nil + } + + return &grpc.NumerixRequestProto{ + EntityScoreData: &grpc.EntityScoreData{ + Schema: newSchema, + EntityScores: protoScores, + ComputeId: request.EntityScoreData.ComputeID, + DataType: &dataType, + }, + } +} + +func (a *Adapter) MapProtoToResponse(protoResponse *grpc.NumerixResponseProto) *NumerixResponse { + if errProto := protoResponse.GetError(); errProto != nil { + log.Warn().Msgf("Received error in proto response error: %s", errProto.GetMessage()) + return &NumerixResponse{} + } + + compDataProto := protoResponse.GetComputationScoreData() + if compDataProto == nil { + return &NumerixResponse{} + } + + computationScores := compDataProto.GetComputationScores() + + inputSize := len(computationScores) + data := make([][][]byte, inputSize) + var stringData [][]string + + for i, scoreProto := range computationScores { + switch T := scoreProto.GetMatrixFormat().(type) { + case *grpc.Score_ByteData: + if T.ByteData != nil && T.ByteData.GetValues() != nil { + data[i] = T.ByteData.GetValues() + } + case *grpc.Score_StringData: + if T.StringData != nil && T.StringData.GetValues() != nil { + stringData = append(stringData, T.StringData.GetValues()) + } + } + } + + return &NumerixResponse{ + ComputationScoreData: ComputationScoreData{ + Schema: compDataProto.GetSchema(), + Data: data, + StringData: stringData, + }, + } +} + +func (a *Adapter) ConvertScoreDataFromFP32(data [][][]byte, dataType string) error { + var err error + + for i := range data { + for j := range data[i] { + if data[i][j] == nil { + continue + } + data[i][j], err = typeconverter.ConvertBytesToBytes(data[i][j], "FP32", dataType) + if err != nil { + return err + } + } + } + return nil +} diff --git a/helix-client/pkg/clients/numerix/adaptor_test.go b/go-sdk/pkg/clients/numerix/adaptor_test.go similarity index 94% rename from helix-client/pkg/clients/numerix/adaptor_test.go rename to go-sdk/pkg/clients/numerix/adaptor_test.go index 082e9953..dd8d082e 100644 --- a/helix-client/pkg/clients/numerix/adaptor_test.go +++ b/go-sdk/pkg/clients/numerix/adaptor_test.go @@ -3,7 +3,7 @@ package numerix import ( "testing" - "github.com/Meesho/BharatMLStack/helix-client/pkg/datatypeconverter/byteorder" + "github.com/Meesho/BharatMLStack/go-sdk/pkg/datatypeconverter/byteorder" "github.com/stretchr/testify/assert" ) diff --git a/helix-client/pkg/clients/numerix/client/grpc/numerix.pb.go b/go-sdk/pkg/clients/numerix/client/grpc/numerix.pb.go similarity index 100% rename from helix-client/pkg/clients/numerix/client/grpc/numerix.pb.go rename to go-sdk/pkg/clients/numerix/client/grpc/numerix.pb.go diff --git a/helix-client/pkg/clients/numerix/client/grpc/numerix_grpc.pb.go b/go-sdk/pkg/clients/numerix/client/grpc/numerix_grpc.pb.go similarity index 100% rename from helix-client/pkg/clients/numerix/client/grpc/numerix_grpc.pb.go rename to go-sdk/pkg/clients/numerix/client/grpc/numerix_grpc.pb.go diff --git a/helix-client/pkg/clients/numerix/client/proto/numerix.proto b/go-sdk/pkg/clients/numerix/client/proto/numerix.proto similarity index 100% rename from helix-client/pkg/clients/numerix/client/proto/numerix.proto rename to go-sdk/pkg/clients/numerix/client/proto/numerix.proto diff --git a/helix-client/pkg/clients/numerix/conf.go b/go-sdk/pkg/clients/numerix/conf.go similarity index 100% rename from helix-client/pkg/clients/numerix/conf.go rename to go-sdk/pkg/clients/numerix/conf.go diff --git a/helix-client/pkg/clients/numerix/init.go b/go-sdk/pkg/clients/numerix/init.go similarity index 100% rename from helix-client/pkg/clients/numerix/init.go rename to go-sdk/pkg/clients/numerix/init.go diff --git a/go-sdk/pkg/clients/numerix/models.go b/go-sdk/pkg/clients/numerix/models.go new file mode 100644 index 00000000..3983180c --- /dev/null +++ b/go-sdk/pkg/clients/numerix/models.go @@ -0,0 +1,29 @@ +package numerix + +import "github.com/Meesho/BharatMLStack/go-sdk/pkg/clients/numerix/client/grpc" + +type NumerixRequest struct { + EntityScoreData EntityScoreData `json:"entity_score_data"` +} + +type EntityScoreData struct { + Schema []string `json:"schema"` + Data [][][]byte `json:"data"` + StringData [][]string `json:"string_data"` + ComputeID string `json:"compute_id"` + DataType string `json:"data_type"` +} + +type NumerixResponse struct { + ComputationScoreData ComputationScoreData `json:"computation_score_data"` +} + +type ComputationScoreData struct { + Schema []string `json:"schema"` + Data [][][]byte `json:"data"` + StringData [][]string `json:"string_data"` +} + +type NumerixRequestWrapper struct { + RequestProto *grpc.NumerixRequestProto +} diff --git a/helix-client/pkg/clients/numerix/numerix.go b/go-sdk/pkg/clients/numerix/numerix.go similarity index 100% rename from helix-client/pkg/clients/numerix/numerix.go rename to go-sdk/pkg/clients/numerix/numerix.go diff --git a/go-sdk/pkg/clients/numerix/v1.go b/go-sdk/pkg/clients/numerix/v1.go new file mode 100644 index 00000000..6bc22500 --- /dev/null +++ b/go-sdk/pkg/clients/numerix/v1.go @@ -0,0 +1,280 @@ +package numerix + +import ( + "context" + "fmt" + "sync" + "time" + + "github.com/Meesho/BharatMLStack/go-sdk/pkg/clients/numerix/client/grpc" + "github.com/Meesho/BharatMLStack/go-sdk/pkg/datatypeconverter/byteorder" + "github.com/Meesho/BharatMLStack/go-sdk/pkg/grpcclient" + "github.com/rs/zerolog/log" + metadata "google.golang.org/grpc/metadata" +) + +type ClientV1 struct { + ClientConfigs *ClientConfig + GrpcClient *grpcclient.GRPCClient + numerixClient grpc.NumerixClient + Adapter Adapter +} + +var ( + client *ClientV1 + once sync.Once + headers metadata.MD +) + +const ( + V1Prefix = "numerix_CLIENT_V1_" + numerix_CALLER_ID = "numerix-CALLER-ID" +) + +func InitV1Client(configBytes []byte) NumerixClient { + if client == nil { + once.Do(func() { + byteorder.Init() + + clientConfig, err := getClientConfigs(configBytes) + if err != nil { + log.Panic().Err(err).Msgf("Invalid numerix client configs: %#v", clientConfig) + } + headers = metadata.New(map[string]string{ + numerix_CALLER_ID: clientConfig.CallerId, + }) + + grpcClient, grpcErr := getGrpcClient(clientConfig) + if grpcErr != nil { + log.Panic().Err(grpcErr).Msgf("Error creating numerix service grpc client, client: %#v", grpcClient) + } + + numerixClient := grpc.NewNumerixClient(grpcClient) + client = &ClientV1{ + ClientConfigs: clientConfig, + GrpcClient: grpcClient, + numerixClient: numerixClient, + Adapter: Adapter{}, + } + }) + } + return client +} + +func getGrpcClient(conf *ClientConfig) (*grpcclient.GRPCClient, error) { + var client *grpcclient.GRPCClient + var err error + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("panic creating grpc client from prefix: %v", r) + } + }() + client = grpcclient.NewConnFromConfig(&grpcclient.Config{ + Host: conf.Host, + Port: conf.Port, + DeadLine: conf.DeadlineExceedMS, + LoadBalancingPolicy: "round_robin", + PlainText: conf.PlainText, + }, V1Prefix) + return client, err +} + +type BatchInfo struct { + StartIndex int + EndIndex int +} + +type batchResult struct { + index int + response *grpc.NumerixResponseProto + err error +} + +func (c *ClientV1) RetrieveScore(req *NumerixRequest) (*NumerixResponse, error) { + if req == nil { + return nil, fmt.Errorf("numerix request cannot be nil") + } + + err := validateRequest(req) + if err != nil { + return nil, err + } + + numInputs := len(req.EntityScoreData.Data) + batchSize := c.ClientConfigs.BatchSize + + protoReq := c.Adapter.MapRequestToProto(req) + if protoReq == nil { + return nil, fmt.Errorf("failed to map request to proto") + } + + // If data is smaller than batch size, process directly + if numInputs <= batchSize { + return c.processRequest(protoReq) + } + + batches := c.getBatchIndices(numInputs, batchSize) + numBatches := len(batches) + + resultChan := make(chan batchResult, numBatches) + + finalResponse := &NumerixResponse{ + ComputationScoreData: ComputationScoreData{ + Schema: protoReq.EntityScoreData.Schema, + Data: make([][][]byte, numInputs), + StringData: make([][]string, 0), + }, + } + + wg := sync.WaitGroup{} + // Process each batch in parallel + for i, batch := range batches { + wg.Add(1) + go func(batch BatchInfo, batchIndex int) { + defer wg.Done() + defer func() { + if r := recover(); r != nil { + log.Warn(). + Interface("panic", r). + Int("batch_index", batchIndex). + Msg("Panic occurred while processing numerix batch") + resultChan <- batchResult{index: batchIndex, response: nil, err: fmt.Errorf("panic in batch processing: %v", r)} + } + }() + + protoResp, err := c.processBatchByIndex(protoReq, batch, batchIndex) + if err != nil { + resultChan <- batchResult{index: batchIndex, response: nil, err: err} + return + } + + resultChan <- batchResult{index: batchIndex, response: protoResp, err: nil} + }(batch, i) + } + + go func() { + wg.Wait() + close(resultChan) + }() + + for result := range resultChan { + if result.err != nil { + log.Warn().Err(result.err).Msgf("Error processing batch %d", result.index) + } + batch := batches[result.index] + c.fillResponseFromBatch(finalResponse, result.response, batch) + } + + return finalResponse, nil +} + +func (c *ClientV1) fillResponseFromBatch(finalResponse *NumerixResponse, batchProtoResp *grpc.NumerixResponseProto, batch BatchInfo) { + if errProto := batchProtoResp.GetError(); errProto != nil { + log.Warn().Msgf("Received error in proto response error: %s", errProto.GetMessage()) + return + } + + compDataProto := batchProtoResp.GetComputationScoreData() + if compDataProto == nil { + return + } + + computationScores := compDataProto.GetComputationScores() + batchSize := batch.EndIndex - batch.StartIndex + + for i, scoreProto := range computationScores { + if i >= batchSize { + break + } + + targetIndex := batch.StartIndex + i + switch T := scoreProto.GetMatrixFormat().(type) { + case *grpc.Score_ByteData: + if T.ByteData != nil && T.ByteData.GetValues() != nil { + finalResponse.ComputationScoreData.Data[targetIndex] = T.ByteData.GetValues() + } + + case *grpc.Score_StringData: + if T.StringData != nil && T.StringData.GetValues() != nil { + finalResponse.ComputationScoreData.StringData = append(finalResponse.ComputationScoreData.StringData, T.StringData.GetValues()) + } + } + + } +} + +func (c *ClientV1) getBatchIndices(numElements, batchSize int) []BatchInfo { + numBatches := (numElements + batchSize - 1) / batchSize + batches := make([]BatchInfo, numBatches) + + for i := 0; i < numElements; i += batchSize { + end := i + batchSize + if end > numElements { + end = numElements + } + batches[i/batchSize] = BatchInfo{i, end} + } + return batches +} + +func (c *ClientV1) processBatchByIndex(protoReq *grpc.NumerixRequestProto, batch BatchInfo, batchIndex int) (*grpc.NumerixResponseProto, error) { + batchReq := &grpc.NumerixRequestProto{ + EntityScoreData: &grpc.EntityScoreData{ + ComputeId: protoReq.EntityScoreData.ComputeId, + DataType: protoReq.EntityScoreData.DataType, + Schema: protoReq.EntityScoreData.Schema, + EntityScores: protoReq.EntityScoreData.EntityScores[batch.StartIndex:batch.EndIndex], + }, + } + + response, err := c.callGrpcService(batchReq) + if err != nil { + log.Warn().Err(err). + Int("batch_index", batchIndex). + Str("compute_id", batchReq.EntityScoreData.ComputeId). + Msg("Failed to get score from numerix service") + return nil, err + } + + return response, nil +} + +func (c *ClientV1) processRequest(protoReq *grpc.NumerixRequestProto) (*NumerixResponse, error) { + response, err := c.callGrpcService(protoReq) + if err != nil { + return nil, err + } + + resp := c.Adapter.MapProtoToResponse(response) + if resp == nil { + return nil, fmt.Errorf("failed to map proto to response") + } + return resp, nil +} + +func validateRequest(req *NumerixRequest) error { + if len(req.EntityScoreData.Schema) == 0 { + return fmt.Errorf("schema is required") + } + if (req.EntityScoreData.Data == nil && req.EntityScoreData.StringData == nil) || (len(req.EntityScoreData.Data) == 0 && len(req.EntityScoreData.StringData) == 0) { + return fmt.Errorf("data is required") + } + if req.EntityScoreData.ComputeID == "" { + return fmt.Errorf("compute_id is required") + } + if req.EntityScoreData.DataType == "" { + return fmt.Errorf("data_type is required") + } + return nil +} + +func (c *ClientV1) callGrpcService(req *grpc.NumerixRequestProto) (*grpc.NumerixResponseProto, error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(c.ClientConfigs.DeadlineExceedMS)*time.Millisecond) + defer cancel() + ctx = metadata.NewOutgoingContext(ctx, headers) + response, err := c.numerixClient.Compute(ctx, req) + if err != nil { + return nil, err + } + return response, nil +} diff --git a/helix-client/pkg/clients/predator/README.md b/go-sdk/pkg/clients/predator/README.md similarity index 100% rename from helix-client/pkg/clients/predator/README.md rename to go-sdk/pkg/clients/predator/README.md diff --git a/go-sdk/pkg/clients/predator/adaptor.go b/go-sdk/pkg/clients/predator/adaptor.go new file mode 100644 index 00000000..4e292040 --- /dev/null +++ b/go-sdk/pkg/clients/predator/adaptor.go @@ -0,0 +1,690 @@ +package predator + +import ( + "encoding/binary" + "fmt" + "math" + "strconv" + + "strings" + + triton "github.com/Meesho/BharatMLStack/go-sdk/pkg/clients/predator/client/grpc" + "github.com/Meesho/BharatMLStack/go-sdk/pkg/datatypeconverter/types" + "github.com/Meesho/BharatMLStack/go-sdk/pkg/utils" + "github.com/rs/zerolog/log" +) + +const ( + datatypeBytes = "BYTES" +) + +type IAdapter interface { + MapPredatorRequestToProto(predatorRequest *PredatorRequest, predatorDataType PredatorDataType, batches []BatchInfo) []*triton.ModelInferRequest + MapProtoToPredatorResponse(tritonResponse *triton.ModelInferResponse, requestedOutputs []Output, predatorDataType PredatorDataType) *PredatorResponse +} + +type Adapter struct { + IAdapter +} + +// PredatorDataType represents the type of data in a predator input +type PredatorDataType int + +const ( + PredatorDataTypeNone PredatorDataType = iota + PredatorDataTypeBytes + PredatorDataTypeString +) + +// getInputDataForProcessing returns the byte data for processing, converting from string if necessary +func getInputDataForProcessing(input Input, predatorDataType PredatorDataType) ([][][]byte, error) { + switch predatorDataType { + case PredatorDataTypeString: + return convertStringToBytes(input.StringData, input.DataType) + case PredatorDataTypeBytes: + return input.Data, nil + default: + return nil, fmt.Errorf("input %s has no data", input.Name) + } +} + +func (a *Adapter) initializeBatchRequest(batches []BatchInfo, batchRequests []*triton.ModelInferRequest, predatorRequest *PredatorRequest, inferOutputs []*triton.ModelInferRequest_InferRequestedOutputTensor, parameters map[string]*triton.InferParameter) { + inferInputs := a.createInferInputTensors(predatorRequest.Inputs, batches[0].EndIndex-batches[0].StartIndex) + + for i := range batchRequests { + if i == len(batchRequests)-1 { + inferInputs = a.createInferInputTensors(predatorRequest.Inputs, batches[i].EndIndex-batches[i].StartIndex) + } + batchRequests[i] = &triton.ModelInferRequest{ + ModelName: predatorRequest.ModelName, + ModelVersion: predatorRequest.ModelVersion, + Inputs: inferInputs, + Outputs: inferOutputs, + Parameters: parameters, + } + batchRequests[i].RawInputContents = make([][]byte, len(inferInputs)) + } +} + +func (a *Adapter) flatten3DByteSlice(batches []BatchInfo, data [][][]byte, datatype string, batchSize int, batchRequests []*triton.ModelInferRequest, inputIdx int, predatorRequest *PredatorRequest, inferOutputs []*triton.ModelInferRequest_InferRequestedOutputTensor, parameters map[string]*triton.InferParameter) { + rowCount := len(data) + if rowCount == 0 || len(data[0]) == 0 { + return + } + + numFeatures := len(data[0]) + + if inputIdx == 0 { + a.initializeBatchRequest(batches, batchRequests, predatorRequest, inferOutputs, parameters) + } + + if datatype == datatypeBytes { + totalSize := make([]int, len(batchRequests)) + totalByteSize := 0 + for rowIdx := 0; rowIdx < rowCount; rowIdx++ { + for featureIdx := 0; featureIdx < numFeatures; featureIdx++ { + featureSize := len(data[rowIdx][featureIdx]) + totalSize[rowIdx/batchSize] += 4 + featureSize + totalByteSize += 4 + featureSize + } + } + + buffer := make([]byte, totalByteSize) + offset := 0 + + for i := range batchRequests { + batchRequests[i].RawInputContents[inputIdx] = buffer[offset : offset+totalSize[i]] + offset += totalSize[i] + } + + lengthBytes := make([]byte, 4) + + count := 0 + for rowIdx := 0; rowIdx < rowCount; rowIdx++ { + if rowIdx%batchSize == 0 { + count = 0 + } + for featureIdx := 0; featureIdx < numFeatures; featureIdx++ { + stringData := data[rowIdx][featureIdx] + binary.LittleEndian.PutUint32(lengthBytes, uint32(len(stringData))) + batchRequests[rowIdx/batchSize].RawInputContents[inputIdx][count] = lengthBytes[0] + batchRequests[rowIdx/batchSize].RawInputContents[inputIdx][count+1] = lengthBytes[1] + batchRequests[rowIdx/batchSize].RawInputContents[inputIdx][count+2] = lengthBytes[2] + batchRequests[rowIdx/batchSize].RawInputContents[inputIdx][count+3] = lengthBytes[3] + count += 4 + for i := range stringData { + batchRequests[rowIdx/batchSize].RawInputContents[inputIdx][count] = stringData[i] + count++ + } + } + } + } else { + totalSize := make([]int, len(batchRequests)) + totalByteSize := 0 + for rowIdx := 0; rowIdx < rowCount; rowIdx++ { + for featureIdx := 0; featureIdx < numFeatures; featureIdx++ { + featureSize := len(data[rowIdx][featureIdx]) + totalSize[rowIdx/batchSize] += featureSize + totalByteSize += featureSize + } + } + + buffer := make([]byte, totalByteSize) + offset := 0 + + for i := range batchRequests { + batchRequests[i].RawInputContents[inputIdx] = buffer[offset : offset+totalSize[i]] + offset += totalSize[i] + } + + count := 0 + for rowIdx := 0; rowIdx < rowCount; rowIdx++ { + if rowIdx%batchSize == 0 { + count = 0 + } + for featureIdx := 0; featureIdx < numFeatures; featureIdx++ { + for i := range data[rowIdx][featureIdx] { + batchRequests[rowIdx/batchSize].RawInputContents[inputIdx][count] = data[rowIdx][featureIdx][i] + count++ + } + } + } + } +} + +// Element size lookup table for better performance +var elementSizeMap = map[string]int{ + "FP32": 4, + "BF16": 2, + "FP16": 2, + "INT64": 8, + "INT32": 4, + "INT16": 2, + "INT8": 1, + "BOOL": 1, + "BYTES": -1, // Variable length, needs special handling +} + +func getElementSize(datatype string) int { + if size, ok := elementSizeMap[datatype]; ok { + return size + } + return -1 // default +} + +func (a *Adapter) createInferInputTensors(inputs []Input, batchSize int) []*triton.ModelInferRequest_InferInputTensor { + inferInputs := make([]*triton.ModelInferRequest_InferInputTensor, len(inputs)) + for i, input := range inputs { + shape := make([]int64, len(input.Dims)+1) + shape[0] = int64(batchSize) + for j, dim := range input.Dims { + shape[j+1] = int64(dim) + } + inferInputs[i] = &triton.ModelInferRequest_InferInputTensor{ + Name: input.Name, + Datatype: input.DataType, + Shape: shape, + } + } + return inferInputs +} + +func (a *Adapter) createInferOutputTensors(outputs []Output) []*triton.ModelInferRequest_InferRequestedOutputTensor { + inferOutputs := make([]*triton.ModelInferRequest_InferRequestedOutputTensor, len(outputs)) + for i, output := range outputs { + inferOutputs[i] = &triton.ModelInferRequest_InferRequestedOutputTensor{ + Name: output.Name, + } + } + return inferOutputs +} + +func (a *Adapter) createSequenceParameters(predatorRequest *PredatorRequest) map[string]*triton.InferParameter { + parameters := make(map[string]*triton.InferParameter) + + if predatorRequest.SequenceId != nil { + parameters["sequence_id"] = &triton.InferParameter{ + ParameterChoice: &triton.InferParameter_StringParam{ + StringParam: *predatorRequest.SequenceId, + }, + } + } + if predatorRequest.SequenceStart != nil { + parameters["sequence_start"] = &triton.InferParameter{ + ParameterChoice: &triton.InferParameter_BoolParam{ + BoolParam: *predatorRequest.SequenceStart, + }, + } + } + if predatorRequest.SequenceEnd != nil { + parameters["sequence_end"] = &triton.InferParameter{ + ParameterChoice: &triton.InferParameter_BoolParam{ + BoolParam: *predatorRequest.SequenceEnd, + }, + } + } + + return parameters +} + +func (a *Adapter) MapPredatorRequestToProto(predatorRequest *PredatorRequest, predatorDataType PredatorDataType, batches []BatchInfo) []*triton.ModelInferRequest { + if len(batches) == 0 { + return nil + } + + inferOutputs := a.createInferOutputTensors(predatorRequest.Outputs) + parameters := a.createSequenceParameters(predatorRequest) + + batchRequests := make([]*triton.ModelInferRequest, len(batches)) + + for i, input := range predatorRequest.Inputs { + inputData, err := getInputDataForProcessing(input, predatorDataType) + if err != nil { + log.Error().Err(err). + Str("input_name", input.Name). + Msg("Failed to process input data") + continue + } + + a.flatten3DByteSlice(batches, inputData, input.DataType, predatorRequest.BatchSize, batchRequests, i, predatorRequest, inferOutputs, parameters) + } + a.InferAndSetInputShapes(predatorRequest, batchRequests, batches) + return batchRequests +} + +func (a *Adapter) MapProtoToPredatorResponse(tritonResponse *triton.ModelInferResponse, requestedOutputs []Output, predatorDataType PredatorDataType) *PredatorResponse { + if utils.IsNilPointer(tritonResponse) { + log.Warn().Msg("Received nil triton response") + return nil + } + + tritonOutputIndex := make(map[string]int) + for i, output := range tritonResponse.Outputs { + tritonOutputIndex[output.Name] = i + } + + // Validate that we have outputs before accessing them + if len(tritonResponse.Outputs) == 0 { + log.Warn().Msg("Received triton response with no outputs") + return nil + } + + // Validate that the first output has a valid shape + if len(tritonResponse.Outputs[0].Shape) == 0 { + log.Warn().Msg("Received triton response with output having no shape") + return nil + } + + // Calculate rowCount once, assuming all outputs have the same batch size + rowCount := int(tritonResponse.Outputs[0].Shape[0]) + + // Pre-calculate total number of outputs for better memory allocation + totalOutputs := 0 + for _, reqOut := range requestedOutputs { + if _, ok := tritonOutputIndex[reqOut.Name]; ok { + totalOutputs += len(reqOut.ModelScores) + } + } + outputs := make([]ResponseOutput, 0, totalOutputs) + + for _, reqOut := range requestedOutputs { + tritonIdx, ok := tritonOutputIndex[reqOut.Name] + if !ok { + continue + } + // Validate bounds for Outputs and RawOutputContents + if tritonIdx >= len(tritonResponse.Outputs) { + log.Warn(). + Int("triton_idx", tritonIdx). + Int("outputs_len", len(tritonResponse.Outputs)). + Str("output_name", reqOut.Name). + Msg("Triton output index out of bounds") + continue + } + if tritonIdx >= len(tritonResponse.RawOutputContents) { + log.Warn(). + Int("triton_idx", tritonIdx). + Int("raw_output_contents_len", len(tritonResponse.RawOutputContents)). + Str("output_name", reqOut.Name). + Msg("Triton raw output contents index out of bounds") + continue + } + tritonOut := tritonResponse.Outputs[tritonIdx] + tritonData := tritonResponse.RawOutputContents[tritonIdx] + + datatype := tritonOut.Datatype + numScores := len(reqOut.ModelScores) + elementSize := getElementSize(datatype) + + // For BYTES, parse the length-prefixed format + if datatype == "BYTES" { + // Parse all strings sequentially first + totalStrings := rowCount * numScores + allStrings := make([][]byte, totalStrings) // Initialize with nil values + offset := 0 + parsedCount := 0 + + // Parse as many strings as available in the data + for parsedCount < totalStrings && offset+4 <= len(tritonData) { + // Read 4-byte length prefix + strLen := int(binary.LittleEndian.Uint32(tritonData[offset : offset+4])) + + // Extract length prefix + string content + totalLen := 4 + strLen + if offset+totalLen > len(tritonData) { + break + } + allStrings[parsedCount] = tritonData[offset+4 : offset+totalLen] + offset += totalLen + parsedCount++ + } + + // Group by score + for scoreIdx, scoreName := range reqOut.ModelScores { + data := make([][]byte, rowCount) + for b := 0; b < rowCount; b++ { + stringIdx := b*numScores + scoreIdx + if stringIdx < len(allStrings) && allStrings[stringIdx] != nil { + data[b] = allStrings[stringIdx] + } + // data[b] remains nil for missing batches + } + + // Construct shape: batch_size + dimensions for this specific score + var shape []int64 + shape = append(shape, int64(rowCount)) + if scoreIdx < len(reqOut.Dims) { + for _, dim := range reqOut.Dims[scoreIdx] { + shape = append(shape, int64(dim)) + } + } + + // Conditionally populate StringData or Data based on original input type + var stringData [][]string + if predatorDataType == PredatorDataTypeString { + // Only convert to strings if original input was string data + convertedStringData, err := convertBytesToStringWithNils(data, datatype) + if err != nil { + log.Error().Err(err). + Str("output_name", scoreName). + Str("datatype", datatype). + Msg("Failed to convert bytes to string for output") + } else { + stringData = convertedStringData + } + } + + outputs = append(outputs, ResponseOutput{ + Name: scoreName, + DataType: datatype, + Shape: shape, + Data: data, // Always include byte data (needed for processing) + StringData: stringData, // Only populated if original input was string + }) + } + continue + } + + // Calculate individual score sizes based on their specific dimensions + scoreSizes := make([]int, numScores) + totalSizePerBatch := 0 + + for scoreIdx := 0; scoreIdx < numScores; scoreIdx++ { + elements := 1 + if scoreIdx < len(reqOut.Dims) && len(reqOut.Dims[scoreIdx]) > 0 { + for _, dim := range reqOut.Dims[scoreIdx] { + elements *= dim + } + } + scoreSize := elementSize * elements + scoreSizes[scoreIdx] = scoreSize + totalSizePerBatch += scoreSize + } + + // Calculate expected total size and actual available size + expectedTotalSize := rowCount * totalSizePerBatch + availableSize := len(tritonData) + + // Calculate how many complete batches we have + completeBatches := availableSize / totalSizePerBatch + if completeBatches > rowCount { + completeBatches = rowCount + } + + // Log warning if some data is missing + if availableSize < expectedTotalSize { + missingBatches := rowCount - completeBatches + log.Warn(). + Int("expected_size", expectedTotalSize). + Int("available_size", availableSize). + Int("expected_batches", rowCount). + Int("complete_batches", completeBatches). + Int("missing_batches", missingBatches). + Msg("Some batch data is missing, filling with nil values") + } + + // Pre-calculate cumulative offsets to avoid O(n²) calculation + cumulativeOffsets := make([]int, numScores) + if numScores > 0 { + cumulativeOffsets[0] = 0 + for scoreIdx := 1; scoreIdx < numScores; scoreIdx++ { + cumulativeOffsets[scoreIdx] = cumulativeOffsets[scoreIdx-1] + scoreSizes[scoreIdx-1] + } + } + + for scoreIdx, scoreName := range reqOut.ModelScores { + scoreSize := scoreSizes[scoreIdx] + data := make([][]byte, rowCount) + + scoreOffsetInBatch := cumulativeOffsets[scoreIdx] + for b := 0; b < rowCount; b++ { + // Calculate offset: batch offset + score offset within batch + offset := b*totalSizePerBatch + scoreOffsetInBatch + endOffset := offset + scoreSize + + // Only extract data if we have enough data for this batch + if endOffset <= availableSize { + data[b] = tritonData[offset:endOffset] + } + // data[b] remains nil for missing batches + } + + // Construct shape: batch_size + dimensions for this specific score + var shape []int64 + shape = append(shape, int64(rowCount)) + if scoreIdx < len(reqOut.Dims) { + for _, dim := range reqOut.Dims[scoreIdx] { + shape = append(shape, int64(dim)) + } + } + + // Conditionally populate StringData or Data based on original input type + var stringData [][]string + if predatorDataType == PredatorDataTypeString { + // Only convert to strings if original input was string data + convertedStringData, err := convertBytesToStringWithNils(data, datatype) + if err != nil { + log.Error().Err(err). + Str("output_name", scoreName). + Str("datatype", datatype). + Msg("Failed to convert bytes to string for output") + } else { + stringData = convertedStringData + } + } + + outputs = append(outputs, ResponseOutput{ + Name: scoreName, + DataType: datatype, + Shape: shape, + Data: data, // Always include byte data (needed for processing) + StringData: stringData, // Only populated if original input was string + }) + } + } + + return &PredatorResponse{ + ModelName: tritonResponse.ModelName, + ModelVersion: tritonResponse.ModelVersion, + Outputs: outputs, + } +} + +// convertStringToBytes converts string data to bytes based on the specified data type +func convertStringToBytes(stringData [][][]string, dataType string) ([][][]byte, error) { + byteData := make([][][]byte, len(stringData)) + + for batchIdx, batch := range stringData { + byteData[batchIdx] = make([][]byte, len(batch)) + for featureIdx, feature := range batch { + switch dataType { + case "FP32": + bytes := make([]byte, len(feature)*4) + for i, str := range feature { + val, err := strconv.ParseFloat(str, 32) + if err != nil { + return nil, fmt.Errorf("failed to parse float32 from string %s: %v", str, err) + } + binary.LittleEndian.PutUint32(bytes[i*4:(i+1)*4], math.Float32bits(float32(val))) + } + byteData[batchIdx][featureIdx] = bytes + case "INT32": + bytes := make([]byte, len(feature)*4) + for i, str := range feature { + val, err := strconv.ParseInt(str, 10, 32) + if err != nil { + return nil, fmt.Errorf("failed to parse int32 from string %s: %v", str, err) + } + binary.LittleEndian.PutUint32(bytes[i*4:(i+1)*4], uint32(val)) + } + byteData[batchIdx][featureIdx] = bytes + case "INT64": + bytes := make([]byte, len(feature)*8) + for i, str := range feature { + val, err := strconv.ParseInt(str, 10, 64) + if err != nil { + return nil, fmt.Errorf("failed to parse int64 from string %s: %v", str, err) + } + binary.LittleEndian.PutUint64(bytes[i*8:(i+1)*8], uint64(val)) + } + byteData[batchIdx][featureIdx] = bytes + case "BOOL": + bytes := make([]byte, len(feature)) + for i, str := range feature { + val, err := strconv.ParseBool(str) + if err != nil { + return nil, fmt.Errorf("failed to parse bool from string %s: %v", str, err) + } + if val { + bytes[i] = 1 + } else { + bytes[i] = 0 + } + } + byteData[batchIdx][featureIdx] = bytes + case "BYTES": + // For BYTES type, just convert string to bytes + byteData[batchIdx][featureIdx] = []byte(feature[0]) + default: + return nil, fmt.Errorf("unsupported data type for string conversion: %s", dataType) + } + } + } + + return byteData, nil +} + +// convertBytesToStringWithNils converts byte data to strings, handling nil byte slices +func convertBytesToStringWithNils(byteData [][]byte, dataType string) ([][]string, error) { + stringData := make([][]string, len(byteData)) + + for batchIdx, batch := range byteData { + if batch == nil { + // For nil batches, create nil string slice + stringData[batchIdx] = nil + continue + } + + switch dataType { + case "FP32": + strings := make([]string, len(batch)/4) + for i := 0; i < len(strings); i++ { + bits := binary.LittleEndian.Uint32(batch[i*4 : (i+1)*4]) + val := math.Float32frombits(bits) + strings[i] = strconv.FormatFloat(float64(val), 'f', -1, 32) + } + stringData[batchIdx] = strings + case "INT32": + strings := make([]string, len(batch)/4) + for i := 0; i < len(strings); i++ { + val := int32(binary.LittleEndian.Uint32(batch[i*4 : (i+1)*4])) + strings[i] = strconv.FormatInt(int64(val), 10) + } + stringData[batchIdx] = strings + case "INT64": + strings := make([]string, len(batch)/8) + for i := 0; i < len(strings); i++ { + val := int64(binary.LittleEndian.Uint64(batch[i*8 : (i+1)*8])) + strings[i] = strconv.FormatInt(val, 10) + } + stringData[batchIdx] = strings + case "BOOL": + strings := make([]string, len(batch)) + for i, b := range batch { + strings[i] = strconv.FormatBool(b != 0) + } + stringData[batchIdx] = strings + case "BYTES": + // For BYTES type, just convert bytes to string + stringData[batchIdx] = []string{string(batch)} + default: + return nil, fmt.Errorf("unsupported data type for byte conversion: %s", dataType) + } + } + + return stringData, nil +} + +func (a *Adapter) InferAndSetInputShapes(predatorRequest *PredatorRequest, batchReqs []*triton.ModelInferRequest, batches []BatchInfo) { + for batchIdx := range batches { + for inputIdx := range predatorRequest.Inputs { + totalByteSize := len(batchReqs[batchIdx].RawInputContents[inputIdx]) + inputDataType := GetFeatureStoreTypeFromPredator(predatorRequest.Inputs[inputIdx].DataType) + inputByteSize := 0 + if inputDataType == datatypeBytes { + inputByteSize = 4 + featureLen := binary.LittleEndian.Uint32(batchReqs[batchIdx].RawInputContents[inputIdx][0:4]) + inputByteSize += int(featureLen) + } else { + if !strings.Contains(inputDataType, "DataType") { + inputDataType = "DataType" + inputDataType + } + parsedInputDataType, err := types.ParseDataType(inputDataType) + if err != nil { + log.Error().Err(err). + Str("input_name", predatorRequest.Inputs[inputIdx].Name). + Msg("Failed to parse input data type") + continue + } + inputByteSize = parsedInputDataType.Size() + } + if len(batchReqs[batchIdx].Inputs[inputIdx].Shape) > 2 { + unknownIdx := -1 + knownSizes := 1 + for i, dim := range batchReqs[batchIdx].Inputs[inputIdx].Shape { + if dim == -1 { + unknownIdx = i + } else { + knownSizes *= int(dim) + } + } + if unknownIdx != -1 { + if inputByteSize == 0 { + log.Error(). + Str("input_name", predatorRequest.Inputs[inputIdx].Name). + Msg("Input data type is not supported") + continue + } + batchReqs[batchIdx].Inputs[inputIdx].Shape[unknownIdx] = int64(totalByteSize / (knownSizes * inputByteSize)) + } + } else { + if batchReqs[batchIdx].Inputs[inputIdx].Shape[1] == -1 { + if inputByteSize == 0 { + log.Error(). + Str("input_name", predatorRequest.Inputs[inputIdx].Name). + Msg("Input data type is not supported") + continue + } + batchReqs[batchIdx].Inputs[inputIdx].Shape[1] = int64(totalByteSize / (int(batchReqs[batchIdx].Inputs[inputIdx].Shape[0]) * inputByteSize)) + } + } + } + } +} + +func GetFeatureStoreTypeFromPredator(predatorDataType string) string { + switch predatorDataType { + case "INT8": + return "Int8" + case "INT16": + return "Int16" + case "INT32": + return "Int32" + case "INT64": + return "Int64" + case "UINT8": + return "Uint8" + case "UINT16": + return "Uint16" + case "UINT32": + return "Uint32" + case "UINT64": + return "Uint64" + case "STRING": + return "String" + case "BOOL": + return "Bool" + default: + return predatorDataType + } +} diff --git a/helix-client/pkg/clients/predator/client.go b/go-sdk/pkg/clients/predator/client.go similarity index 100% rename from helix-client/pkg/clients/predator/client.go rename to go-sdk/pkg/clients/predator/client.go diff --git a/helix-client/pkg/clients/predator/client/grpc/grpc_service.pb.go b/go-sdk/pkg/clients/predator/client/grpc/grpc_service.pb.go similarity index 100% rename from helix-client/pkg/clients/predator/client/grpc/grpc_service.pb.go rename to go-sdk/pkg/clients/predator/client/grpc/grpc_service.pb.go diff --git a/helix-client/pkg/clients/predator/client/grpc/grpc_service_grpc.pb.go b/go-sdk/pkg/clients/predator/client/grpc/grpc_service_grpc.pb.go similarity index 100% rename from helix-client/pkg/clients/predator/client/grpc/grpc_service_grpc.pb.go rename to go-sdk/pkg/clients/predator/client/grpc/grpc_service_grpc.pb.go diff --git a/helix-client/pkg/clients/predator/client/grpc/health.pb.go b/go-sdk/pkg/clients/predator/client/grpc/health.pb.go similarity index 100% rename from helix-client/pkg/clients/predator/client/grpc/health.pb.go rename to go-sdk/pkg/clients/predator/client/grpc/health.pb.go diff --git a/helix-client/pkg/clients/predator/client/grpc/health_grpc.pb.go b/go-sdk/pkg/clients/predator/client/grpc/health_grpc.pb.go similarity index 100% rename from helix-client/pkg/clients/predator/client/grpc/health_grpc.pb.go rename to go-sdk/pkg/clients/predator/client/grpc/health_grpc.pb.go diff --git a/helix-client/pkg/clients/predator/client/grpc/model_config.pb.go b/go-sdk/pkg/clients/predator/client/grpc/model_config.pb.go similarity index 100% rename from helix-client/pkg/clients/predator/client/grpc/model_config.pb.go rename to go-sdk/pkg/clients/predator/client/grpc/model_config.pb.go diff --git a/helix-client/pkg/clients/predator/client/proto/grpc_service.proto b/go-sdk/pkg/clients/predator/client/proto/grpc_service.proto similarity index 100% rename from helix-client/pkg/clients/predator/client/proto/grpc_service.proto rename to go-sdk/pkg/clients/predator/client/proto/grpc_service.proto diff --git a/helix-client/pkg/clients/predator/client/proto/health.proto b/go-sdk/pkg/clients/predator/client/proto/health.proto similarity index 100% rename from helix-client/pkg/clients/predator/client/proto/health.proto rename to go-sdk/pkg/clients/predator/client/proto/health.proto diff --git a/helix-client/pkg/clients/predator/client/proto/model_config.proto b/go-sdk/pkg/clients/predator/client/proto/model_config.proto similarity index 100% rename from helix-client/pkg/clients/predator/client/proto/model_config.proto rename to go-sdk/pkg/clients/predator/client/proto/model_config.proto diff --git a/helix-client/pkg/clients/predator/config.go b/go-sdk/pkg/clients/predator/config.go similarity index 100% rename from helix-client/pkg/clients/predator/config.go rename to go-sdk/pkg/clients/predator/config.go diff --git a/helix-client/pkg/clients/predator/init.go b/go-sdk/pkg/clients/predator/init.go similarity index 100% rename from helix-client/pkg/clients/predator/init.go rename to go-sdk/pkg/clients/predator/init.go diff --git a/helix-client/pkg/clients/predator/models.go b/go-sdk/pkg/clients/predator/models.go similarity index 100% rename from helix-client/pkg/clients/predator/models.go rename to go-sdk/pkg/clients/predator/models.go diff --git a/go-sdk/pkg/clients/predator/v1.go b/go-sdk/pkg/clients/predator/v1.go new file mode 100644 index 00000000..c7169550 --- /dev/null +++ b/go-sdk/pkg/clients/predator/v1.go @@ -0,0 +1,559 @@ +package predator + +import ( + "context" + "fmt" + "time" + + triton "github.com/Meesho/BharatMLStack/go-sdk/pkg/clients/predator/client/grpc" + "github.com/Meesho/BharatMLStack/go-sdk/pkg/grpcclient" + "github.com/Meesho/BharatMLStack/go-sdk/pkg/metric" + "github.com/rs/zerolog/log" + "google.golang.org/grpc/metadata" +) + +const ( + v1Prefix = "externalservicepredator_" + + // Header keys for authentication + headerCallerID = "PREDATOR-CALLER-ID" + headerCallerToken = "PREDATOR-AUTH-TOKEN" + predatorServiceName = "predator" +) + +type ClientV1 struct { + adapter Adapter + callerId string + callerToken string + conn *grpcclient.GRPCClient + grpcClient triton.GRPCInferenceServiceClient +} + +// NewClientV1 creates a new instance of the Orion client (v1) +func NewClientV1(config *Config) *ClientV1 { + validateConfig(config) + + conn := grpcclient.NewConnFromConfig(&grpcclient.Config{ + Host: config.Host, + Port: config.Port, + DeadLine: config.DeadLine, + PlainText: config.PlainText, + }, v1Prefix) + + // Create the gRPC client once during initialization + grpcClient := triton.NewGRPCInferenceServiceClient(conn) + + return &ClientV1{ + adapter: Adapter{}, + callerId: config.CallerId, + callerToken: config.CallerToken, + conn: conn, + grpcClient: grpcClient, + } +} + +func validateConfig(config *Config) { + if config == nil { + log.Panic().Msg("Configuration is nil. Please provide a valid config.") + return + } + if len(config.Host) == 0 { + log.Panic().Msg("Configuration error: Host is empty. Please provide a valid host.") + } + if len(config.Port) == 0 { + log.Panic().Msg("Configuration error: Port is empty. Please provide a valid port.") + } + if len(config.CallerId) == 0 { + log.Panic().Msg("Configuration error: Caller ID is empty. Please provide a valid caller ID.") + } + if len(config.CallerToken) == 0 { + log.Panic().Msg("Configuration error: Caller token is empty. Please provide a valid caller token.") + } +} + +func getRequestDataType(req *PredatorRequest) PredatorDataType { + if len(req.Inputs[0].StringData) > 0 { + return PredatorDataTypeString + } + if len(req.Inputs[0].Data) > 0 { + return PredatorDataTypeBytes + } + return PredatorDataTypeNone +} + +type BatchInfo struct { + StartIndex int + EndIndex int +} + +type preparedRequest struct { + requestedOutputs []Output + predatorDataType PredatorDataType + deadline int64 + numInputs int + batches []BatchInfo + batchProtoReqs []*triton.ModelInferRequest +} + +type batchResult struct { + index int + response *triton.ModelInferResponse + err error +} + +// prepareInferenceRequest prepares the initial request components +func (c *ClientV1) prepareInferenceRequest(req *PredatorRequest) (*preparedRequest, error) { + err := validatePredatorRequest(req) + if err != nil { + return nil, err + } + + requestedOutputs := req.Outputs + predatorDataType := getRequestDataType(req) + batchSize := req.BatchSize + deadline := req.Deadline + + var numInputs int + if predatorDataType == PredatorDataTypeString { + numInputs = len(req.Inputs[0].StringData) + } else { + numInputs = len(req.Inputs[0].Data) + } + + batches := c.getBatchIndices(numInputs, batchSize) + batchProtoReqs := c.adapter.MapPredatorRequestToProto(req, predatorDataType, batches) + + if batchProtoReqs == nil { + return nil, fmt.Errorf("failed to map request to proto") + } + + return &preparedRequest{ + requestedOutputs: requestedOutputs, + predatorDataType: predatorDataType, + deadline: deadline, + numInputs: numInputs, + batches: batches, + batchProtoReqs: batchProtoReqs, + }, nil +} + +// processBatchInParallel processes all batches in parallel and returns results +func (c *ClientV1) processBatchInParallel(req *preparedRequest) []batchResult { + resultChan := make(chan batchResult, len(req.batches)) + results := make([]batchResult, len(req.batches)) + + for i := range req.batches { + go c.processSingleBatch(i, req.batchProtoReqs[i], req.deadline, resultChan) + } + + for i := 0; i < len(req.batches); i++ { + indexedResult := <-resultChan + results[indexedResult.index] = indexedResult + } + + return results +} + +// processSingleBatch processes a single batch request +func (c *ClientV1) processSingleBatch(batchIndex int, batchProtoReq *triton.ModelInferRequest, deadline int64, resultChan chan batchResult) { + defer func() { + if r := recover(); r != nil { + log.Warn(). + Interface("panic", r). + Int("batch_index", batchIndex). + Msg("Panic occurred while processing batch") + resultChan <- batchResult{index: batchIndex, response: nil, err: fmt.Errorf("panic in batch processing: %v", r)} + } + }() + + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(deadline)*time.Millisecond) + defer cancel() + + md := getMetadata(c.callerId, c.callerToken) + ctx = metadata.NewOutgoingContext(ctx, md) + client := c.grpcClient + response, err := client.ModelInfer(ctx, batchProtoReq) + if err != nil { + log.Warn().Err(err). + Int("batch_index", batchIndex). + Str("model_name", batchProtoReq.ModelName). + Str("model_version", batchProtoReq.ModelVersion). + Msg("Failed to get inference from Triton server") + resultChan <- batchResult{index: batchIndex, response: nil, err: err} + return + } + + resultChan <- batchResult{index: batchIndex, response: response, err: nil} +} + +func (c *ClientV1) GetInferenceScore(req *PredatorRequest) (*PredatorResponse, error) { + // Prepare request components + preparedReq, err := c.prepareInferenceRequest(req) + if err != nil { + return nil, err + } + + // Process batches in parallel + results := c.processBatchInParallel(preparedReq) + + // Convert each proto response to PredatorResponse and merge + finalResponse, err := c.convertAndMergeResponses(req, preparedReq, results) + if err != nil { + return nil, err + } + + return finalResponse, nil +} + +func (c *ClientV1) GetInferenceScoreV2(req *PredatorRequest) (*triton.ModelInferResponse, error) { + // Prepare request components + preparedReq, err := c.prepareInferenceRequest(req) + if err != nil { + return nil, err + } + + // Process batches in parallel + results := c.processBatchInParallel(preparedReq) + + // Convert each proto response to PredatorResponse and merge + finalResponse, err := c.convertAndMergeResponsesV2(req, preparedReq, results) + if err != nil { + return nil, err + } + + return finalResponse, nil +} + +// convertAndMergeResponses converts proto responses to PredatorResponses and merges them +func (c *ClientV1) convertAndMergeResponses(predatorReq *PredatorRequest, preparedReq *preparedRequest, results []batchResult) (*PredatorResponse, error) { + var predatorResponses []*PredatorResponse + errorCount := 0 + + // Convert each batch result to PredatorResponse + for i, result := range results { + if result.err != nil { + errorCount++ + log.Warn().Err(result.err). + Int("batch_index", i). + Msg("Batch processing failed") + continue + } + + if result.response == nil { + errorCount++ + log.Warn().Int("batch_index", i).Msg("Batch returned nil response") + continue + } + + // Use adapter to convert proto response to PredatorResponse + predatorResp := c.adapter.MapProtoToPredatorResponse(result.response, preparedReq.requestedOutputs, preparedReq.predatorDataType) + if predatorResp == nil { + errorCount++ + log.Error().Int("batch_index", i).Msg("Failed to map triton response to predator response") + continue + } + + predatorResponses = append(predatorResponses, predatorResp) + } + + // If all batches failed, return an error + if len(predatorResponses) == 0 { + if errorCount > 0 { + return nil, fmt.Errorf("all batch processing failed: %v", errorCount) + } + return nil, fmt.Errorf("all batch processing failed") + } + + // If some batches failed, log warning but continue with successful ones + if errorCount > 0 { + log.Warn(). + Int("failed_batches", errorCount). + Int("successful_batches", len(predatorResponses)). + Int("total_batches", len(results)). + Msg("Some batches failed but continuing with successful results") + metricTags := c.buildMetricTags(predatorReq.ModelName, "batch_processing_error") + metric.Count(metric.ExternalApiRequestCount, int64(errorCount), metricTags) + } + + // Merge all successful responses + mergedResponse := c.mergeResponses(predatorResponses) + if mergedResponse == nil { + return nil, fmt.Errorf("failed to merge batch responses") + } + + return mergedResponse, nil +} + +// convertAndMergeResponsesV2 merges proto responses directly and returns merged ModelInferResponse +func (c *ClientV1) convertAndMergeResponsesV2(predatorReq *PredatorRequest, preparedReq *preparedRequest, results []batchResult) (*triton.ModelInferResponse, error) { + var successfulResponses []*triton.ModelInferResponse + errorCount := 0 + successfulIndices := make([]int, 0) + + // Collect successful batch responses + for i, result := range results { + if result.err != nil { + errorCount++ + log.Warn().Err(result.err). + Int("batch_index", i). + Msg("Batch processing failed") + continue + } + + if result.response == nil { + errorCount++ + log.Warn().Int("batch_index", i).Msg("Batch returned nil response") + continue + } + + successfulResponses = append(successfulResponses, result.response) + successfulIndices = append(successfulIndices, i) + } + + // If all batches failed, return an error + if len(successfulResponses) == 0 { + if errorCount > 0 { + return nil, fmt.Errorf("all batch processing failed: %v", errorCount) + } + return nil, fmt.Errorf("all batch processing failed") + } + + // If some batches failed, log warning but continue with successful ones + if errorCount > 0 { + log.Warn(). + Int("failed_batches", errorCount). + Int("successful_batches", len(successfulResponses)). + Int("total_batches", len(results)). + Msg("Some batches failed but continuing with successful results") + metricTags := c.buildMetricTags(predatorReq.ModelName, "batch_processing_error") + metric.Count(metric.ExternalApiRequestCount, int64(errorCount), metricTags) + } + + // If there's only one successful response, return it directly + if len(successfulResponses) == 1 { + return successfulResponses[0], nil + } + + // Create final proto response using first successful response as base + firstResponse := successfulResponses[0] + finalProto := &triton.ModelInferResponse{ + Outputs: make([]*triton.ModelInferResponse_InferOutputTensor, len(firstResponse.Outputs)), + RawOutputContents: make([][]byte, len(firstResponse.RawOutputContents)), + } + + // Initialize output tensor structures + for i := range firstResponse.Outputs { + finalProto.Outputs[i] = &triton.ModelInferResponse_InferOutputTensor{} + } + + // Pre-allocate sizes using first batch + c.preAllocateFinalSizes(finalProto, firstResponse, preparedReq.numInputs, preparedReq.batches) + + // Set metadata from first batch + c.setProtoMetadataFromBatch(finalProto, firstResponse, preparedReq.numInputs) + + // Set output names (not set by setProtoMetadataFromBatch) + for i, output := range firstResponse.Outputs { + if i < len(finalProto.Outputs) { + finalProto.Outputs[i].Name = output.Name + } + } + + // Copy data from all successful batches + for idx, response := range successfulResponses { + batchIndex := successfulIndices[idx] + if batchIndex < len(preparedReq.batches) { + c.copyBatchResponseToProto(finalProto, response, preparedReq.batches[batchIndex]) + } + } + + return finalProto, nil +} + +// mergeResponses combines multiple PredatorResponse objects into a single response +func (c *ClientV1) mergeResponses(responses []*PredatorResponse) *PredatorResponse { + if len(responses) == 0 { + log.Warn().Msg("Attempting to merge empty response list") + return nil + } + + // If there's only one response, return it directly + if len(responses) == 1 { + return responses[0] + } + + // Create merged response using the first response as base + mergedResp := &PredatorResponse{ + ModelName: responses[0].ModelName, + ModelVersion: responses[0].ModelVersion, + Outputs: make([]ResponseOutput, len(responses[0].Outputs)), + } + + // Merge output data for each output + for i, output := range responses[0].Outputs { + var combinedData [][]byte + var combinedStringData [][]string + + // Calculate total shape for the merged output + totalShape := make([]int64, len(output.Shape)) + copy(totalShape, output.Shape) + if len(totalShape) > 0 { + totalShape[0] = 0 // Will be calculated based on combined data + } + + for j, resp := range responses { + if resp == nil { + log.Warn(). + Int("response_index", j). + Msg("Encountered nil response during merge") + continue + } + + if len(resp.Outputs) <= i { + log.Warn(). + Int("response_index", j). + Int("output_index", i). + Int("available_outputs", len(resp.Outputs)). + Msg("Response has fewer outputs than expected") + continue + } + + // Combine byte data + if len(resp.Outputs[i].Data) > 0 { + combinedData = append(combinedData, resp.Outputs[i].Data...) + if len(totalShape) > 0 { + totalShape[0] += resp.Outputs[i].Shape[0] + } + } + // Combine string data + if len(resp.Outputs[i].StringData) > 0 { + combinedStringData = append(combinedStringData, resp.Outputs[i].StringData...) + } + } + + mergedResp.Outputs[i] = ResponseOutput{ + Name: output.Name, + DataType: output.DataType, + Shape: totalShape, + Data: combinedData, + StringData: combinedStringData, + } + } + + return mergedResp +} + +func (c *ClientV1) getBatchIndices(numElements, batchSize int) []BatchInfo { + numBatches := (numElements + batchSize - 1) / batchSize + batches := make([]BatchInfo, numBatches) + + for i := 0; i < numElements; i += batchSize { + end := i + batchSize + if end > numElements { + end = numElements + } + batches[i/batchSize] = BatchInfo{StartIndex: i, EndIndex: end} + } + return batches +} + +func (c *ClientV1) setProtoMetadataFromBatch(finalProto, batchProto *triton.ModelInferResponse, totalInputs int) { + finalProto.ModelName = batchProto.ModelName + finalProto.ModelVersion = batchProto.ModelVersion + finalProto.Id = batchProto.Id + finalProto.Parameters = batchProto.Parameters + + for i, batchOutput := range batchProto.Outputs { + if i < len(finalProto.Outputs) { + finalProto.Outputs[i].Datatype = batchOutput.Datatype + finalProto.Outputs[i].Shape = make([]int64, len(batchOutput.Shape)) + copy(finalProto.Outputs[i].Shape, batchOutput.Shape) + + if len(finalProto.Outputs[i].Shape) > 0 { + finalProto.Outputs[i].Shape[0] = int64(totalInputs) + } + + finalProto.Outputs[i].Parameters = batchOutput.Parameters + } + } +} + +func (c *ClientV1) preAllocateFinalSizes(finalProto, batchProto *triton.ModelInferResponse, totalInputs int, batches []BatchInfo) { + for i := range batchProto.Outputs { + if i < len(finalProto.RawOutputContents) { + batchSize := batches[0].EndIndex - batches[0].StartIndex + if len(batchProto.RawOutputContents) > i { + sizePerElement := len(batchProto.RawOutputContents[i]) / batchSize + totalExpectedSize := sizePerElement * totalInputs + finalProto.RawOutputContents[i] = make([]byte, totalExpectedSize) + } + } + } +} + +func (c *ClientV1) copyBatchResponseToProto(finalProto, batchProto *triton.ModelInferResponse, batch BatchInfo) { + batchSize := batch.EndIndex - batch.StartIndex + + for i, batchRawOutput := range batchProto.RawOutputContents { + if i < len(finalProto.RawOutputContents) { + if len(batchRawOutput) > 0 && batchSize > 0 { + bytesPerElement := len(batchRawOutput) / batchSize + startBytePos := batch.StartIndex * bytesPerElement + endBytePos := batch.EndIndex * bytesPerElement + + if endBytePos <= len(finalProto.RawOutputContents[i]) { + copy(finalProto.RawOutputContents[i][startBytePos:endBytePos], batchRawOutput) + } else { + finalProto.RawOutputContents[i] = append(finalProto.RawOutputContents[i], batchRawOutput...) + } + } + } + } +} + +// Keep the existing mergeResponses method as is +// Remove the old splitIntoBatches method since we're using getBatchIndices now + +func getMetadata(callerId string, callerToken string) metadata.MD { + md := metadata.New(nil) + md.Set(headerCallerID, callerId) + md.Set(headerCallerToken, callerToken) + return md +} + +func validatePredatorRequest(req *PredatorRequest) error { + if req == nil { + err := fmt.Errorf("predator request cannot be nil") + log.Error().Msg("Received nil predator request") + return err + } + + if len(req.Inputs) == 0 { + err := fmt.Errorf("predator request must contain at least one input") + log.Error().Msg("Predator request contains no inputs") + return err + } + + // Validate required request parameters + if req.BatchSize <= 0 { + err := fmt.Errorf("batch_size must be greater than 0, got: %d", req.BatchSize) + log.Error().Int("batch_size", req.BatchSize).Msg("Invalid batch size in request") + return err + } + + if req.Deadline <= 0 { + err := fmt.Errorf("deadline must be greater than 0, got: %d", req.Deadline) + log.Error().Int64("deadline", req.Deadline).Msg("Invalid deadline in request") + return err + } + + return nil +} + +func (c *ClientV1) buildMetricTags(modelName string, errorType string) []string { + return metric.BuildTag( + metric.NewTag("model-name", modelName), + metric.NewTag("caller-id", c.callerId), + metric.NewTag("error_type", errorType), + ) +} diff --git a/helix-client/pkg/clients/skye/client/grpc/skye.pb.go b/go-sdk/pkg/clients/skye/client/grpc/skye.pb.go similarity index 100% rename from helix-client/pkg/clients/skye/client/grpc/skye.pb.go rename to go-sdk/pkg/clients/skye/client/grpc/skye.pb.go diff --git a/helix-client/pkg/clients/skye/client/grpc/skye_grpc.pb.go b/go-sdk/pkg/clients/skye/client/grpc/skye_grpc.pb.go similarity index 100% rename from helix-client/pkg/clients/skye/client/grpc/skye_grpc.pb.go rename to go-sdk/pkg/clients/skye/client/grpc/skye_grpc.pb.go diff --git a/helix-client/pkg/clients/skye/client/proto/skye.proto b/go-sdk/pkg/clients/skye/client/proto/skye.proto similarity index 100% rename from helix-client/pkg/clients/skye/client/proto/skye.proto rename to go-sdk/pkg/clients/skye/client/proto/skye.proto diff --git a/helix-client/pkg/clients/skye/conf.go b/go-sdk/pkg/clients/skye/conf.go similarity index 100% rename from helix-client/pkg/clients/skye/conf.go rename to go-sdk/pkg/clients/skye/conf.go diff --git a/go-sdk/pkg/clients/skye/init.go b/go-sdk/pkg/clients/skye/init.go new file mode 100644 index 00000000..df2fd52c --- /dev/null +++ b/go-sdk/pkg/clients/skye/init.go @@ -0,0 +1,19 @@ +package skye + +func GetSkyeClient(version int) SkyeClient { + switch version { + case 1: + return InitV1Client() + default: + return nil + } +} + +func GetSkyeClientFromConfig(version int, conf ClientConfig, callerId string) SkyeClient { + switch version { + case 1: + return InitV1ClientFromConfig(conf, callerId) + default: + return nil + } +} diff --git a/go-sdk/pkg/clients/skye/models.go b/go-sdk/pkg/clients/skye/models.go new file mode 100644 index 00000000..ffca910e --- /dev/null +++ b/go-sdk/pkg/clients/skye/models.go @@ -0,0 +1,8 @@ +package skye + +import "github.com/Meesho/BharatMLStack/go-sdk/pkg/grpcclient" + +type ClientV1 struct { + ClientConfigs *ClientConfig + GrpcClient *grpcclient.GRPCClient +} diff --git a/go-sdk/pkg/clients/skye/skye.go b/go-sdk/pkg/clients/skye/skye.go new file mode 100644 index 00000000..c02c7d22 --- /dev/null +++ b/go-sdk/pkg/clients/skye/skye.go @@ -0,0 +1,11 @@ +package skye + +import ( + "github.com/Meesho/BharatMLStack/go-sdk/pkg/clients/skye/client/grpc" +) + +type SkyeClient interface { + GetSimilarCandidates(request *grpc.SkyeRequest) (*grpc.SkyeResponse, error) + GetEmbeddingsForCandidateIds(request *grpc.SkyeBulkEmbeddingRequest) (*grpc.SkyeBulkEmbeddingResponse, error) + GetDotProductOfCandidatesForEmbedding(request *grpc.EmbeddingDotProductRequest) (*grpc.EmbeddingDotProductResponse, error) +} diff --git a/go-sdk/pkg/clients/skye/v1.go b/go-sdk/pkg/clients/skye/v1.go new file mode 100644 index 00000000..ca5f93ea --- /dev/null +++ b/go-sdk/pkg/clients/skye/v1.go @@ -0,0 +1,156 @@ +package skye + +import ( + "context" + "fmt" + "sync" + "time" + + grpc2 "github.com/Meesho/BharatMLStack/go-sdk/pkg/clients/skye/client/grpc" + "github.com/Meesho/BharatMLStack/go-sdk/pkg/grpcclient" + "github.com/rs/zerolog/log" + "github.com/spf13/viper" + "google.golang.org/grpc/metadata" +) + +var ( + client *ClientV1 + once sync.Once + headers metadata.MD +) + +const ( + V1Prefix = "SKYE_CLIENT_V1_" + CallerIDMetadata = "skye-caller-id" + AuthMetadata = "skye-auth-token" +) + +func InitV1Client() SkyeClient { + if client == nil { + once.Do(func() { + clientConfig, err := getClientConfigs(V1Prefix) + if err != nil { + log.Panic().Err(err).Msgf("Invalid Skye client configs: %#v", clientConfig) + } + grpcClient, grpcErr := getGrpcClient(clientConfig) + headers = getMetadata(clientConfig.AuthToken) + if grpcErr != nil { + log.Panic().Err(grpcErr).Msgf("Error creating skye service grpc client, client: %#v", grpcClient) + } + client = &ClientV1{ + ClientConfigs: clientConfig, + GrpcClient: grpcClient, + } + }) + } + return client +} + +func InitV1ClientFromConfig(conf ClientConfig, callerId string) SkyeClient { + if client == nil { + once.Do(func() { + grpcClient, grpcErr := getGrpcClient(&conf) + if grpcErr != nil { + log.Panic().Err(grpcErr).Msgf("Error creating skye service grpc client, client: %#v", grpcClient) + } + headers = metadata.New(map[string]string{ + CallerIDMetadata: callerId, + AuthMetadata: conf.AuthToken, + }) + client = &ClientV1{ + ClientConfigs: &conf, + GrpcClient: grpcClient, + } + }) + } + return client +} + +func getGrpcClient(conf *ClientConfig) (*grpcclient.GRPCClient, error) { + var client *grpcclient.GRPCClient + var err error + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("panic creating grpc client from prefix: %v", r) + } + }() + client = grpcclient.NewConnFromConfig(&grpcclient.Config{ + Host: conf.Host, + Port: conf.Port, + DeadLine: conf.DeadlineExceedMS, + LoadBalancingPolicy: "round_robin", + PlainText: conf.PlainText, + }, V1Prefix) + return client, err +} + +func (c *ClientV1) GetSimilarCandidates(req *grpc2.SkyeRequest) (*grpc2.SkyeResponse, error) { + skyeClient := grpc2.NewSkyeSimilarCandidateServiceClient(c.GrpcClient) + timeout := time.Duration(c.ClientConfigs.DeadlineExceedMS) * time.Millisecond + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + //get metadata headers + ctx = metadata.NewOutgoingContext(ctx, headers) + // call grpc method + protoResponse, err := skyeClient.GetSimilarCandidates(ctx, req) + if err != nil { + log.Error().Msgf("Error while fetching similar candidates from skye service, err: %v", err) + return nil, err + } else if protoResponse == nil { + log.Error().Msgf("Empty response from skye service") + return nil, fmt.Errorf("empty response from skye service") + } + return protoResponse, nil +} + +func (c *ClientV1) GetEmbeddingsForCandidateIds(request *grpc2.SkyeBulkEmbeddingRequest) (*grpc2.SkyeBulkEmbeddingResponse, error) { + skyeClient := grpc2.NewSkyeEmbeddingServiceClient(c.GrpcClient) + timeout := time.Duration(c.ClientConfigs.DeadlineExceedMS) * time.Millisecond + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + //get metadata headers + ctx = metadata.NewOutgoingContext(ctx, headers) + // call grpc method + protoResponse, err := skyeClient.GetEmbeddingsForCandidates(ctx, request) + if err != nil { + log.Error().Msgf("Error while fetching bulk embeddings from skye service, err: %v", err) + return nil, err + } else if protoResponse == nil { + log.Error().Msgf("Empty response from skye service") + return nil, fmt.Errorf("empty response from skye service") + } + return protoResponse, nil +} + +func (c *ClientV1) GetDotProductOfCandidatesForEmbedding(request *grpc2.EmbeddingDotProductRequest) (*grpc2.EmbeddingDotProductResponse, error) { + skyeClient := grpc2.NewSkyeEmbeddingServiceClient(c.GrpcClient) + timeout := time.Duration(c.ClientConfigs.DeadlineExceedMS) * time.Millisecond + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + //get metadata headers + ctx = metadata.NewOutgoingContext(ctx, headers) + // call grpc method + protoResponse, err := skyeClient.GetCandidateEmbeddingScores(ctx, request) + if err != nil { + log.Error().Msgf("Error while fetching bulk embeddings from skye service, err: %v", err) + return nil, err + } else if protoResponse == nil { + log.Error().Msgf("Empty response from skye service") + return nil, fmt.Errorf("empty response from skye service") + } + return protoResponse, nil +} + +func getMetadata(authToken string) metadata.MD { + callerId := viper.GetString("APP_NAME") + if callerId == "" { + log.Panic().Msgf("APP_NAME not set!") + } + return metadata.New(map[string]string{ + CallerIDMetadata: callerId, + AuthMetadata: authToken, + }) +} diff --git a/go-sdk/pkg/clients/skye/v1_test.go b/go-sdk/pkg/clients/skye/v1_test.go new file mode 100644 index 00000000..603cb9ce --- /dev/null +++ b/go-sdk/pkg/clients/skye/v1_test.go @@ -0,0 +1,214 @@ +package skye + +// TODO need to refactor test cases post request response changes in client + +//func TestClientV1_GetSimilarCandidatesPreProd(t *testing.T) { +// viper.Set("SKYE_CLIENT_V1_HOST", "skye-serving.int.meesho.int") +// viper.Set("SKYE_CLIENT_V1_PORT", "80") +// viper.Set("SKYE_CLIENT_V1_DEADLINE_MS", 5000) +// viper.Set("SKYE_CLIENT_V1_PLAINTEXT", true) +// testcases := []struct { +// name string +// request *SimilarCandidateRequest +// expectedResponse *SimilarCandidatesResponse +// }{ +// {"successBasicRequest", +// &SimilarCandidateRequest{ +// Entity: "catalog", +// ModelName: "attribute_model", +// Variant: "non_gst", +// //CandidateIds: []string{"1161"}, +// Embeddings: [][]float32{ +// {0.20856647, 0.0192536, -0.17532587, -0.12793553, 0.011362862, -0.040895768, -0.11143229, -0.007844001, 0.0065192855, 0.42926165, -0.2711144, 0.2584112, 0.03707704, 0.07465805, -0.16700841, -0.2937192, -0.09601066, 0.11477651, 0.23940851, 0.037067287, 0.11799186, -0.101745635, 0.08189786, -0.29900208, 0.17568949, -0.159322, -0.1352676, 0.09871266, 0.3169443, 0.16222087, 0.0934057, 0.16670953}, +// }, +// Limit: 2, +// }, +// &SimilarCandidatesResponse{SimilarCandidates: [][]*SimilarCandidate{ +// { +// &SimilarCandidate{Id: "1161", Meta: Meta{Score: float32(1), AttributesMap: nil}}, +// &SimilarCandidate{Id: "104394136", Meta: Meta{Score: float32(0.9909016), AttributesMap: nil}}, +// }, +// }}, +// }, +// } +// for _, tc := range testcases { +// t.Run(tc.name, func(t *testing.T) { +// client := GetSkyeClient(1) +// res, _ := client.GetSimilarCandidates(tc.request) +// if !reflect.DeepEqual(res, tc.expectedResponse) { +// t.Errorf("GetSimilarCandidates: %+v, expected response: %+v", res, tc.expectedResponse) +// } +// }) +// } +//} +// +//func TestClientV1_GetSimilarCandidates(t *testing.T) { +// viper.Set("SKYE_CLIENT_V1_HOST", "skye-serving.int.meesho.int") +// viper.Set("SKYE_CLIENT_V1_PORT", "80") +// viper.Set("SKYE_CLIENT_V1_DEADLINE_MS", 50000) +// viper.Set("SKYE_CLIENT_V1_AUTH_TOKEN", "pre-prod") +// viper.Set("APP_NAME", "skye-go-sdk") +// testcases := []struct { +// name string +// request *SimilarCandidateRequest +// expectedResponse *SimilarCandidatesResponse +// }{ +// {"successBasicRequest", +// &SimilarCandidateRequest{ +// Entity: "catalog", +// ModelName: "test_model_v1", +// Variant: "ad", +// CandidateIds: []string{"4", "5"}, +// Limit: 2, +// }, +// &SimilarCandidatesResponse{SimilarCandidates: [][]*SimilarCandidate{ +// { +// &SimilarCandidate{Id: "15", Meta: Meta{Score: float32(0.15000030398368835), AttributesMap: nil}}, +// &SimilarCandidate{Id: "14", Meta: Meta{Score: float32(0.3202500343322754), AttributesMap: nil}}, +// }, +// { +// &SimilarCandidate{Id: "15", Meta: Meta{Score: float32(0.15000030398368835), AttributesMap: nil}}, +// &SimilarCandidate{Id: "14", Meta: Meta{Score: float32(0.3202500343322754), AttributesMap: nil}}, +// }, +// }}, +// }, +// {"successRequestWithFilters", +// &SimilarCandidateRequest{ +// Entity: "catalog", +// ModelName: "test_model_v1", +// Variant: "ad", +// CandidateIds: []string{"4", "5"}, +// Limit: 2, +// Filters: [][]Filter{ +// { +// {"sscat", FilterOperator(0), []string{"34", "0"}}, +// }, +// { +// {"sscat", FilterOperator(1), []string{"3"}}, +// }, +// }, +// }, +// &SimilarCandidatesResponse{SimilarCandidates: [][]*SimilarCandidate{ +// { +// &SimilarCandidate{Id: "15", Meta: Meta{Score: float32(0.15000030398368835), AttributesMap: nil}}, +// &SimilarCandidate{Id: "14", Meta: Meta{Score: float32(0.3202500343322754), AttributesMap: nil}}, +// }, +// { +// &SimilarCandidate{Id: "15", Meta: Meta{Score: float32(0.15000030398368835), AttributesMap: nil}}, +// &SimilarCandidate{Id: "14", Meta: Meta{Score: float32(0.3202500343322754), AttributesMap: nil}}, +// }, +// }}, +// }, +// {"successRequestWithGlobalFilter", +// &SimilarCandidateRequest{ +// Entity: "catalog", +// ModelName: "test_model_v1", +// Variant: "ad", +// CandidateIds: []string{"4", "5"}, +// Limit: 2, +// GlobalFilters: []Filter{ +// {"sscat", FilterOperator(0), []string{"34", "0"}}, +// }, +// }, +// &SimilarCandidatesResponse{SimilarCandidates: [][]*SimilarCandidate{ +// { +// &SimilarCandidate{Id: "15", Meta: Meta{Score: float32(0.15000030398368835), AttributesMap: nil}}, +// &SimilarCandidate{Id: "14", Meta: Meta{Score: float32(0.3202500343322754), AttributesMap: nil}}, +// }, +// { +// &SimilarCandidate{Id: "15", Meta: Meta{Score: float32(0.15000030398368835), AttributesMap: nil}}, +// &SimilarCandidate{Id: "14", Meta: Meta{Score: float32(0.3202500343322754), AttributesMap: nil}}, +// }, +// }}, +// }, +// {"successRequestWithEmptyResponse", +// &SimilarCandidateRequest{ +// Entity: "catalog", +// ModelName: "test_model_v1", +// Variant: "ad", +// CandidateIds: []string{"4"}, +// Limit: 2, +// GlobalFilters: []Filter{ +// {"sscat", FilterOperator(1), []string{"34", "0"}}, +// }, +// }, +// &SimilarCandidatesResponse{SimilarCandidates: [][]*SimilarCandidate{{}}}, +// }, +// } +// for _, tc := range testcases { +// t.Run(tc.name, func(t *testing.T) { +// client := GetSkyeClient(1) +// res, _ := client.GetSimilarCandidates(tc.request) +// if !reflect.DeepEqual(res, tc.expectedResponse) { +// t.Errorf("GetSimilarCandidates: %+v, expected response: %+v", res, tc.expectedResponse) +// } +// }) +// } +//} +// +//func TestClientV1_GetEmbeddingsForCandidateIds(t *testing.T) { +// viper.Set("SKYE_CLIENT_V1_HOST", "localhost") +// viper.Set("SKYE_CLIENT_V1_PORT", "8083") +// viper.Set("SKYE_CLIENT_V1_DEADLINE_MS", 500) +// testcases := []struct { +// name string +// request *EmbeddingsRequest +// expectedResponse *EmbeddingsResponse +// }{ +// {"success", &EmbeddingsRequest{ +// Entity: "catalog", +// ModelName: "test_model_v1", +// Variant: "ad", +// CandidateIds: []string{"4", "5"}, +// }, +// &EmbeddingsResponse{EmbeddingResponse: []*CandidateEmbedding{ +// {Id: "4", Embedding: []float32{-0.16042101383209229, -0.005233884789049625, 0.24357753992080688, 0.040812645107507706, -0.06298056244850159, -0.07097796350717545, 0.06337567418813705, 0.011232693679630756, 0.011302603408694267, 0.09978130459785461, 0.033062200993299484, -0.15565651655197144, -0.010328262113034725, -0.04471661522984505, 0.09161527454853058, 0.08096695691347122, -0.007991013117134571, -0.05886241793632507, 0.07680786401033401, -0.0514046885073185, 0.06688099354505539, -0.09121483564376831, -0.06651730835437775, 0.11291711032390594, 0.008980179205536842, -0.023628132417798042, 0.0035034094471484423, -0.05262045934796333, 0.0734158530831337, -0.04664924368262291, -0.13142922520637512, 0.06616105139255524, 0.08684184402227402, -0.031942326575517654, 0.004450879525393248, -0.06188590079545975, -0.07829581946134567, 0.0070262388326227665, 0.017191657796502113, 0.015088655985891819, -0.000056192118790932, -0.07053299248218536, -0.055713992565870285, 0.06138134002685547, -0.07968553900718689, 0.03809307515621185, 0.0651206374168396, -0.08329790830612183, -0.06742016971111298, 0.06040041148662567, -0.1867527812719345, 0.05807913467288017, 0.03365928679704666, -0.032647714018821716, -0.011843480169773102, -0.01642605848610401, 0.004641900770366192, -0.12394584715366364, -0.08457022905349731, 0.1559869796037674, -0.11442684382200241, -0.10122374445199966, 0.11411477625370026, -0.10065338760614395, -0.0356115996837616, -0.13335444033145905, -0.0261673741042614, 0.0756693109869957, 0.08628286421298981, -0.08219288289546967, -0.01646547205746174, -0.17476244270801544, 0.036117203533649445, -0.017218975350260735, 0.0658968985080719, 0.03999672085046768, 0.061865001916885376, 0.04729278013110161, 0.03358926996588707, -0.1880149096250534, 0.22793036699295044, 0.03482583165168762, 0.012042579241096973, 0.1847819834947586, 0.13918107748031616, 0.04905824735760689, 0.1171952560544014, -0.07494692504405975, 0.2372165322303772, 0.000022831834940006956, 0.08518408983945847, 0.08484339714050293, -0.016785187646746635, 0.007275485433638096, 0.09114666283130646, 0.15295326709747314, 0.09629631787538528, 0.03109036013484001, 0.010699527338147163, 0.0587799996137619, -0.03909625858068466, -0.004016770515590906, -0.057834841310977936, 0.06676746904850006, -0.05982685089111328, -0.08484537899494171, -0.08463805168867111, -0.04263322427868843, 0.11217007786035538, -0.17225229740142822, -0.11567119508981705, 0.17956873774528503, -0.004632641561329365, 0.042749278247356415, -0.015895918011665344, 0.04075197875499725, -0.17386670410633087, -0.05067210644483566, -0.03147958591580391, -0.06619491428136826, -0.1809258908033371, 0.044331956654787064, 0.06498933583498001, 0.03429732844233513, -0.05037061870098114, 0.06775150448083878, -0.01995770074427128, -0.0913449302315712}, SearchEmbedding: []float32{-0.16042101383209229, -0.005233884789049625, 0.24357753992080688, 0.040812645107507706, -0.06298056244850159, -0.07097796350717545, 0.06337567418813705, 0.011232693679630756, 0.011302603408694267, 0.09978130459785461, 0.033062200993299484, -0.15565651655197144, -0.010328262113034725, -0.04471661522984505, 0.09161527454853058, 0.08096695691347122, -0.007991013117134571, -0.05886241793632507, 0.07680786401033401, -0.0514046885073185, 0.06688099354505539, -0.09121483564376831, -0.06651730835437775, 0.11291711032390594, 0.008980179205536842, -0.023628132417798042, 0.0035034094471484423, -0.05262045934796333, 0.0734158530831337, -0.04664924368262291, -0.13142922520637512, 0.06616105139255524, 0.08684184402227402, -0.031942326575517654, 0.004450879525393248, -0.06188590079545975, -0.07829581946134567, 0.0070262388326227665, 0.017191657796502113, 0.015088655985891819, -0.000056192118790932, -0.07053299248218536, -0.055713992565870285, 0.06138134002685547, -0.07968553900718689, 0.03809307515621185, 0.0651206374168396, -0.08329790830612183, -0.06742016971111298, 0.06040041148662567, -0.1867527812719345, 0.05807913467288017, 0.03365928679704666, -0.032647714018821716, -0.011843480169773102, -0.01642605848610401, 0.004641900770366192, -0.12394584715366364, -0.08457022905349731, 0.1559869796037674, -0.11442684382200241, -0.10122374445199966, 0.11411477625370026, -0.10065338760614395, -0.0356115996837616, -0.13335444033145905, -0.0261673741042614, 0.0756693109869957, 0.08628286421298981, -0.08219288289546967, -0.01646547205746174, -0.17476244270801544, 0.036117203533649445, -0.017218975350260735, 0.0658968985080719, 0.03999672085046768, 0.061865001916885376, 0.04729278013110161, 0.03358926996588707, -0.1880149096250534, 0.22793036699295044, 0.03482583165168762, 0.012042579241096973, 0.1847819834947586, 0.13918107748031616, 0.04905824735760689, 0.1171952560544014, -0.07494692504405975, 0.2372165322303772, 0.000022831834940006956, 0.08518408983945847, 0.08484339714050293, -0.016785187646746635, 0.007275485433638096, 0.09114666283130646, 0.15295326709747314, 0.09629631787538528, 0.03109036013484001, 0.010699527338147163, 0.0587799996137619, -0.03909625858068466, -0.004016770515590906, -0.057834841310977936, 0.06676746904850006, -0.05982685089111328, -0.08484537899494171, -0.08463805168867111, -0.04263322427868843, 0.11217007786035538, -0.17225229740142822, -0.11567119508981705, 0.17956873774528503, -0.004632641561329365, 0.042749278247356415, -0.015895918011665344, 0.04075197875499725, -0.17386670410633087, -0.05067210644483566, -0.03147958591580391, -0.06619491428136826, -0.1809258908033371, 0.044331956654787064, 0.06498933583498001, 0.03429732844233513, -0.05037061870098114, 0.06775150448083878, -0.01995770074427128, -0.0913449302315712}}, +// {Id: "5", Embedding: []float32{-0.16042101383209229, -0.005233884789049625, 0.24357753992080688, 0.040812645107507706, -0.06298056244850159, -0.07097796350717545, 0.06337567418813705, 0.011232693679630756, 0.011302603408694267, 0.09978130459785461, 0.033062200993299484, -0.15565651655197144, -0.010328262113034725, -0.04471661522984505, 0.09161527454853058, 0.08096695691347122, -0.007991013117134571, -0.05886241793632507, 0.07680786401033401, -0.0514046885073185, 0.06688099354505539, -0.09121483564376831, -0.06651730835437775, 0.11291711032390594, 0.008980179205536842, -0.023628132417798042, 0.0035034094471484423, -0.05262045934796333, 0.0734158530831337, -0.04664924368262291, -0.13142922520637512, 0.06616105139255524, 0.08684184402227402, -0.031942326575517654, 0.004450879525393248, -0.06188590079545975, -0.07829581946134567, 0.0070262388326227665, 0.017191657796502113, 0.015088655985891819, -0.000056192118790932, -0.07053299248218536, -0.055713992565870285, 0.06138134002685547, -0.07968553900718689, 0.03809307515621185, 0.0651206374168396, -0.08329790830612183, -0.06742016971111298, 0.06040041148662567, -0.1867527812719345, 0.05807913467288017, 0.03365928679704666, -0.032647714018821716, -0.011843480169773102, -0.01642605848610401, 0.004641900770366192, -0.12394584715366364, -0.08457022905349731, 0.1559869796037674, -0.11442684382200241, -0.10122374445199966, 0.11411477625370026, -0.10065338760614395, -0.0356115996837616, -0.13335444033145905, -0.0261673741042614, 0.0756693109869957, 0.08628286421298981, -0.08219288289546967, -0.01646547205746174, -0.17476244270801544, 0.036117203533649445, -0.017218975350260735, 0.0658968985080719, 0.03999672085046768, 0.061865001916885376, 0.04729278013110161, 0.03358926996588707, -0.1880149096250534, 0.22793036699295044, 0.03482583165168762, 0.012042579241096973, 0.1847819834947586, 0.13918107748031616, 0.04905824735760689, 0.1171952560544014, -0.07494692504405975, 0.2372165322303772, 0.000022831834940006956, 0.08518408983945847, 0.08484339714050293, -0.016785187646746635, 0.007275485433638096, 0.09114666283130646, 0.15295326709747314, 0.09629631787538528, 0.03109036013484001, 0.010699527338147163, 0.0587799996137619, -0.03909625858068466, -0.004016770515590906, -0.057834841310977936, 0.06676746904850006, -0.05982685089111328, -0.08484537899494171, -0.08463805168867111, -0.04263322427868843, 0.11217007786035538, -0.17225229740142822, -0.11567119508981705, 0.17956873774528503, -0.004632641561329365, 0.042749278247356415, -0.015895918011665344, 0.04075197875499725, -0.17386670410633087, -0.05067210644483566, -0.03147958591580391, -0.06619491428136826, -0.1809258908033371, 0.044331956654787064, 0.06498933583498001, 0.03429732844233513, -0.05037061870098114, 0.06775150448083878, -0.01995770074427128, -0.0913449302315712}, SearchEmbedding: []float32{-0.16042101383209229, -0.005233884789049625, 0.24357753992080688, 0.040812645107507706, -0.06298056244850159, -0.07097796350717545, 0.06337567418813705, 0.011232693679630756, 0.011302603408694267, 0.09978130459785461, 0.033062200993299484, -0.15565651655197144, -0.010328262113034725, -0.04471661522984505, 0.09161527454853058, 0.08096695691347122, -0.007991013117134571, -0.05886241793632507, 0.07680786401033401, -0.0514046885073185, 0.06688099354505539, -0.09121483564376831, -0.06651730835437775, 0.11291711032390594, 0.008980179205536842, -0.023628132417798042, 0.0035034094471484423, -0.05262045934796333, 0.0734158530831337, -0.04664924368262291, -0.13142922520637512, 0.06616105139255524, 0.08684184402227402, -0.031942326575517654, 0.004450879525393248, -0.06188590079545975, -0.07829581946134567, 0.0070262388326227665, 0.017191657796502113, 0.015088655985891819, -0.000056192118790932, -0.07053299248218536, -0.055713992565870285, 0.06138134002685547, -0.07968553900718689, 0.03809307515621185, 0.0651206374168396, -0.08329790830612183, -0.06742016971111298, 0.06040041148662567, -0.1867527812719345, 0.05807913467288017, 0.03365928679704666, -0.032647714018821716, -0.011843480169773102, -0.01642605848610401, 0.004641900770366192, -0.12394584715366364, -0.08457022905349731, 0.1559869796037674, -0.11442684382200241, -0.10122374445199966, 0.11411477625370026, -0.10065338760614395, -0.0356115996837616, -0.13335444033145905, -0.0261673741042614, 0.0756693109869957, 0.08628286421298981, -0.08219288289546967, -0.01646547205746174, -0.17476244270801544, 0.036117203533649445, -0.017218975350260735, 0.0658968985080719, 0.03999672085046768, 0.061865001916885376, 0.04729278013110161, 0.03358926996588707, -0.1880149096250534, 0.22793036699295044, 0.03482583165168762, 0.012042579241096973, 0.1847819834947586, 0.13918107748031616, 0.04905824735760689, 0.1171952560544014, -0.07494692504405975, 0.2372165322303772, 0.000022831834940006956, 0.08518408983945847, 0.08484339714050293, -0.016785187646746635, 0.007275485433638096, 0.09114666283130646, 0.15295326709747314, 0.09629631787538528, 0.03109036013484001, 0.010699527338147163, 0.0587799996137619, -0.03909625858068466, -0.004016770515590906, -0.057834841310977936, 0.06676746904850006, -0.05982685089111328, -0.08484537899494171, -0.08463805168867111, -0.04263322427868843, 0.11217007786035538, -0.17225229740142822, -0.11567119508981705, 0.17956873774528503, -0.004632641561329365, 0.042749278247356415, -0.015895918011665344, 0.04075197875499725, -0.17386670410633087, -0.05067210644483566, -0.03147958591580391, -0.06619491428136826, -0.1809258908033371, 0.044331956654787064, 0.06498933583498001, 0.03429732844233513, -0.05037061870098114, 0.06775150448083878, -0.01995770074427128, -0.0913449302315712}}}, +// }, +// }, +// } +// for _, tc := range testcases { +// t.Run(tc.name, func(t *testing.T) { +// client := GetSkyeClient(1) +// res, _ := client.GetEmbeddingsForCandidateIds(tc.request) +// if !reflect.DeepEqual(res, tc.expectedResponse) { +// t.Errorf("GetEmbeddingsForCandidateIds: %+v, expected response: %+v", res, tc.expectedResponse) +// } +// }) +// } +//} +// +//func TestClientV1_GetDotProductOfCandidatesForEmbedding(t *testing.T) { +// viper.Set("SKYE_CLIENT_V1_HOST", "localhost") +// viper.Set("SKYE_CLIENT_V1_PORT", "8083") +// viper.Set("SKYE_CLIENT_V1_DEADLINE_MS", 500) +// testcases := []struct { +// name string +// request *DotProductRequest +// expectedResponse *DotProductResponse +// }{ +// {"success", +// &DotProductRequest{ +// Entity: "catalog", +// ModelName: "test_model_v1", +// Variant: "ad", +// CandidateIds: []string{"4", "5"}, +// SourceEmbedding: []float32{-0.16042101383209229, -0.005233884789049625, 0.24357753992080688, 0.040812645107507706, -0.06298056244850159, -0.07097796350717545, 0.06337567418813705, 0.011232693679630756, 0.011302603408694267, 0.09978130459785461, 0.033062200993299484, -0.15565651655197144, -0.010328262113034725, -0.04471661522984505, 0.09161527454853058, 0.08096695691347122, -0.007991013117134571, -0.05886241793632507, 0.07680786401033401, -0.0514046885073185, 0.06688099354505539, -0.09121483564376831, -0.06651730835437775, 0.11291711032390594, 0.008980179205536842, -0.023628132417798042, 0.0035034094471484423, -0.05262045934796333, 0.0734158530831337, -0.04664924368262291, -0.13142922520637512, 0.06616105139255524, 0.08684184402227402, -0.031942326575517654, 0.004450879525393248, -0.06188590079545975, -0.07829581946134567, 0.0070262388326227665, 0.017191657796502113, 0.015088655985891819, -0.000056192118790932, -0.07053299248218536, -0.055713992565870285, 0.06138134002685547, -0.07968553900718689, 0.03809307515621185, 0.0651206374168396, -0.08329790830612183, -0.06742016971111298, 0.06040041148662567, -0.1867527812719345, 0.05807913467288017, 0.03365928679704666, -0.032647714018821716, -0.011843480169773102, -0.01642605848610401, 0.004641900770366192, -0.12394584715366364, -0.08457022905349731, 0.1559869796037674, -0.11442684382200241, -0.10122374445199966, 0.11411477625370026, -0.10065338760614395, -0.0356115996837616, -0.13335444033145905, -0.0261673741042614, 0.0756693109869957, 0.08628286421298981, -0.08219288289546967, -0.01646547205746174, -0.17476244270801544, 0.036117203533649445, -0.017218975350260735, 0.0658968985080719, 0.03999672085046768, 0.061865001916885376, 0.04729278013110161, 0.03358926996588707, -0.1880149096250534, 0.22793036699295044, 0.03482583165168762, 0.012042579241096973, 0.1847819834947586, 0.13918107748031616, 0.04905824735760689, 0.1171952560544014, -0.07494692504405975, 0.2372165322303772, 0.000022831834940006956, 0.08518408983945847, 0.08484339714050293, -0.016785187646746635, 0.007275485433638096, 0.09114666283130646, 0.15295326709747314, 0.09629631787538528, 0.03109036013484001, 0.010699527338147163, 0.0587799996137619, -0.03909625858068466, -0.004016770515590906, -0.057834841310977936, 0.06676746904850006, -0.05982685089111328, -0.08484537899494171, -0.08463805168867111, -0.04263322427868843, 0.11217007786035538, -0.17225229740142822, -0.11567119508981705, 0.17956873774528503, -0.004632641561329365, 0.042749278247356415, -0.015895918011665344, 0.04075197875499725, -0.17386670410633087, -0.05067210644483566, -0.03147958591580391, -0.06619491428136826, -0.1809258908033371, 0.044331956654787064, 0.06498933583498001, 0.03429732844233513, -0.05037061870098114, 0.06775150448083878, -0.01995770074427128, -0.0913449302315712}, +// }, +// &DotProductResponse{ +// CandidateScores: []*CandidateDotProduct{ +// {CandidateId: "4", Score: float32(0.9999998211860657)}, +// {CandidateId: "5", Score: float32(0.9999998211860657)}}, +// }, +// }, +// } +// for _, tc := range testcases { +// t.Run(tc.name, func(t *testing.T) { +// client := GetSkyeClient(1) +// res, _ := client.GetDotProductOfCandidatesForEmbedding(tc.request) +// if !reflect.DeepEqual(res, tc.expectedResponse) { +// t.Errorf("GetDotProductOfCandidatesForEmbedding: %+v, expected response: %+v", res, tc.expectedResponse) +// } +// }) +// } +//} diff --git a/helix-client/pkg/datatypeconverter/byteorder/pack_test.go b/go-sdk/pkg/datatypeconverter/byteorder/pack_test.go similarity index 100% rename from helix-client/pkg/datatypeconverter/byteorder/pack_test.go rename to go-sdk/pkg/datatypeconverter/byteorder/pack_test.go diff --git a/go-sdk/pkg/datatypeconverter/byteorder/system.go b/go-sdk/pkg/datatypeconverter/byteorder/system.go new file mode 100644 index 00000000..87e640d6 --- /dev/null +++ b/go-sdk/pkg/datatypeconverter/byteorder/system.go @@ -0,0 +1,827 @@ +package byteorder + +import ( + "encoding/binary" + "fmt" + + // "github.com/Meesho/orion/pkg/proto/persist" + "math" + "unsafe" + + "github.com/Meesho/BharatMLStack/go-sdk/pkg/datatypeconverter/float8" + "github.com/Meesho/BharatMLStack/go-sdk/pkg/datatypeconverter/types" + "github.com/x448/float16" +) + +var ByteOrder *CustomByteOrder + +type CustomByteOrder struct { + binary.ByteOrder +} + +/** + * Extensions for Uint8/16 + */ +func (c *CustomByteOrder) PutUint8FromUint32(b []byte, v uint32) { + _ = b[0] // bounds check hint to compiler; see golang.org/issue/14808 + b[0] = uint8(v) +} + +func (c *CustomByteOrder) PutUint16FromUint32(b []byte, v uint32) { + c.ByteOrder.PutUint16(b, uint16(v)) +} + +func (c *CustomByteOrder) Uint8(b []byte) uint8 { + _ = b[0] // bounds check hint to compiler; see golang.org/issue/14808 + return b[0] +} + +func (c *CustomByteOrder) Uint8AsUint32(b []byte) uint32 { + _ = b[0] // bounds check hint to compiler; see golang.org/issue/14808 + return uint32(b[0]) +} + +func (c *CustomByteOrder) Uint16AsUint32(b []byte) uint32 { + return uint32(c.ByteOrder.Uint16(b)) +} + +func (c *CustomByteOrder) Uint32Vector(b []byte) []uint32 { + if len(b)%4 != 0 { + panic("invalid byte slice length: must be a multiple of 4") + } + result := make([]uint32, len(b)/4) + for i := 0; i < len(result); i++ { + result[i] = c.Uint32(b[i*4 : i*4+4]) + } + return result +} + +func (c *CustomByteOrder) Uint8AsUint32Vector(b []byte) []uint32 { + result := make([]uint32, len(b)) + for i, byteValue := range b { + result[i] = c.Uint8AsUint32([]byte{byteValue}) + } + return result +} + +func (c *CustomByteOrder) Uint16AsUint32Vector(b []byte) []uint32 { + if len(b)%2 != 0 { + panic("invalid byte slice length: must be a multiple of 2") + } + result := make([]uint32, len(b)/2) + for i := 0; i < len(result); i++ { + result[i] = c.Uint16AsUint32(b[i*2 : i*2+2]) + } + return result +} + +func (c *CustomByteOrder) Uint64Vector(b []byte) []uint64 { + if len(b)%8 != 0 { + panic("invalid byte slice length: must be a multiple of 8") + } + result := make([]uint64, len(b)/8) + for i := 0; i < len(result); i++ { + result[i] = c.Uint64(b[i*8 : i*8+8]) + } + return result +} + +/** + * Extensions for Int8/16/32/64 + */ +func (c *CustomByteOrder) PutInt8FromInt32(b []byte, v int32) { + _ = b[0] // bounds check hint to compiler; see golang.org/issue/14808 + b[0] = uint8(v) +} + +func (c *CustomByteOrder) PutInt16FromInt32(b []byte, v int32) { + c.ByteOrder.PutUint16(b, uint16(v)) +} + +func (c *CustomByteOrder) PutInt32(b []byte, v int32) { + c.PutUint32(b, uint32(v)) +} + +func (c *CustomByteOrder) PutInt64(b []byte, v int64) { + c.PutUint64(b, uint64(v)) +} + +func (c *CustomByteOrder) Int8(b []byte) int8 { + _ = b[0] // bounds check hint to compiler; see golang.org/issue/14808 + return int8(b[0]) +} + +func (c *CustomByteOrder) Int8AsInt32(b []byte) int32 { + _ = b[0] // bounds check hint to compiler; see golang.org/issue/14808 + return int32(int8(b[0])) +} + +func (c *CustomByteOrder) Int16(b []byte) int16 { + return int16(c.ByteOrder.Uint16(b)) +} + +func (c *CustomByteOrder) Int16AsInt32(b []byte) int32 { + return int32(int16(c.ByteOrder.Uint16(b))) +} + +func (c *CustomByteOrder) Int32(b []byte) int32 { + return int32(c.Uint32(b)) +} + +func (c *CustomByteOrder) Int64(b []byte) int64 { + return int64(c.Uint64(b)) +} + +func (c *CustomByteOrder) String(b []byte) string { + var decodedString string + + // Iterate over each byte + for _, b := range b { + // Ignore padding (zeroes) + if b != 0 { + // Convert the byte to a character and append to the decoded string + decodedString += string(b) + } + } + + return decodedString + +} + +func (c *CustomByteOrder) Bool(b []byte) bool { + if len(b) < 1 { + return false + } + return b[0] != 0 +} + +/** + * Extensions for Float8/16 + */ +func (c *CustomByteOrder) PutFloat8E5M2FromFP32(b []byte, v float32) { + b[0] = uint8(float8.FP8E5M2FromFP32Value(v)) +} + +func (c *CustomByteOrder) PutFloat8E4M3FromFP32(b []byte, v float32) { + b[0] = uint8(float8.FP8E4M3FromFP32Value(v)) +} + +func (c *CustomByteOrder) PutFloat16FromFP32(b []byte, v float32) { + fp16 := float16.Fromfloat32(v) + c.ByteOrder.PutUint16(b, fp16.Bits()) +} + +func (c *CustomByteOrder) PutFloat32(b []byte, v float32) { + c.PutUint32(b, math.Float32bits(v)) +} + +func (c *CustomByteOrder) PutFloat64(b []byte, v float64) { + c.PutUint64(b, math.Float64bits(v)) +} + +func (c *CustomByteOrder) Float8E5M2AsFP32(b []byte) float32 { + return float8.FP8E5M2ToFP32Value(float8.Float8e5m2(b[0])) +} + +func (c *CustomByteOrder) Float8E4M3AsFP32(b []byte) float32 { + return float8.FP8E4M3ToFP32Value(float8.Float8e4m3(b[0])) +} + +func (c *CustomByteOrder) Float16AsFP32(b []byte) float32 { + return float16.Frombits(c.ByteOrder.Uint16(b)).Float32() +} + +func (c *CustomByteOrder) Float32(b []byte) float32 { + return math.Float32frombits(c.Uint32(b)) +} + +func (c *CustomByteOrder) Float32Vector(b []byte) []float32 { + if len(b)%4 != 0 { + panic("invalid byte slice length: must be a multiple of 4") + } + n := len(b) / 4 + result := make([]float32, n) + + for i := 0; i < n; i++ { + offset := i * 4 + result[i] = math.Float32frombits(c.Uint32(b[offset : offset+4])) + } + + return result +} + +func (c *CustomByteOrder) FP8E5M2Vector(b []byte) []float32 { + if len(b) == 0 { + return nil + } + + result := make([]float32, len(b)) // Each byte represents one FP8E5M2 + for i, byteValue := range b { + result[i] = c.Float8E5M2AsFP32([]byte{byteValue}) // Use a custom decoding function + } + + return result +} + +func (c *CustomByteOrder) FP8E4M3Vector(b []byte) []float32 { + if len(b) == 0 { + return nil + } + + result := make([]float32, len(b)) + for i, byteValue := range b { + result[i] = c.Float8E4M3AsFP32([]byte{byteValue}) // Use a custom decoding function + } + + return result +} + +func (c *CustomByteOrder) FP16Vector(b []byte) []float32 { + if len(b)%2 != 0 { + panic("invalid byte slice length: must be a multiple of 2") + } + + n := len(b) / 2 // Number of FP16 elements + result := make([]float32, n) + + for i := 0; i < n; i++ { + offset := i * 2 + result[i] = c.Float16AsFP32(b[offset : offset+2]) + } + + return result +} + +func (c *CustomByteOrder) Float64Vector(b []byte) []float64 { + if len(b)%8 != 0 { + panic("invalid byte slice length: must be a multiple of 8") + } + + n := len(b) / 8 + result := make([]float64, n) + + for i := 0; i < n; i++ { + offset := i * 8 + result[i] = math.Float64frombits(c.Uint64(b[offset : offset+8])) + } + + return result +} + +func (c *CustomByteOrder) Int8Vector(b []byte) []int8 { + result := make([]int8, len(b)) + for i, byteValue := range b { + result[i] = c.Int8([]byte{byteValue}) + } + return result +} + +func (c *CustomByteOrder) Uint8Vector(b []byte) []uint8 { + result := make([]uint8, len(b)) + for i, byteValue := range b { + result[i] = c.Uint8([]byte{byteValue}) + } + return result +} + +func (c *CustomByteOrder) Int8AsInt32Vector(b []byte) []int32 { + result := make([]int32, len(b)) + for i, byteValue := range b { + result[i] = c.Int8AsInt32([]byte{byteValue}) + } + return result +} + +func (c *CustomByteOrder) Int16Vector(b []byte) []int16 { + if len(b)%2 != 0 { + panic("invalid byte slice length: must be a multiple of 2") + } + result := make([]int16, len(b)/2) + for i := 0; i < len(result); i++ { + result[i] = c.Int16(b[i*2 : i*2+2]) + } + return result +} + +func (c *CustomByteOrder) Int16AsInt32Vector(b []byte) []int32 { + if len(b)%2 != 0 { + panic("invalid byte slice length: must be a multiple of 2") + } + result := make([]int32, len(b)/2) + for i := 0; i < len(result); i++ { + result[i] = c.Int16AsInt32(b[i*2 : i*2+2]) + } + return result +} + +func (c *CustomByteOrder) Int32Vector(b []byte) []int32 { + if len(b)%4 != 0 { + panic("invalid byte slice length: must be a multiple of 4") + } + result := make([]int32, len(b)/4) + for i := 0; i < len(result); i++ { + result[i] = c.Int32(b[i*4 : i*4+4]) + } + return result +} + +func (c *CustomByteOrder) Int64Vector(b []byte) []int64 { + if len(b)%8 != 0 { + panic("invalid byte slice length: must be a multiple of 8") + } + result := make([]int64, len(b)/8) + for i := 0; i < len(result); i++ { + result[i] = c.Int64(b[i*8 : i*8+8]) + } + return result +} + +func (c *CustomByteOrder) BoolVector(encodedValue []byte, vectorLength int) []bool { + // Allocate a bool slice for the output + result := make([]bool, vectorLength) + + // Iterate over each bit in the byte array + for i := 0; i < vectorLength; i++ { + // Determine which byte and bit within the byte to read + byteIndex := i / 8 + bitIndex := i % 8 + + // Extract the bit: shift the byte and mask it + bit := (encodedValue[byteIndex] >> bitIndex) & 1 + + // Convert the bit to a bool and store it + result[i] = bit == 1 + } + + return result +} + +func (c *CustomByteOrder) StringVector(encodedValue []byte, vectorLength int, stringLength int) []string { + // Allocate a slice for the output strings + result := make([]string, vectorLength) + + // Iterate over the vector length to decode each string + for i := 0; i < vectorLength; i++ { + // Calculate the start and end of the current string slice + start := i * stringLength + end := start + stringLength + + // Ensure we don't go out of bounds + if end > len(encodedValue) { + break + } + + // Extract the string slice and decode it using the String method + decodedString := c.String(encodedValue[start:end]) + result[i] = decodedString + } + + return result +} + +func (c *CustomByteOrder) Float64(b []byte) float64 { + return math.Float64frombits(c.Uint64(b)) +} + +const ( + ByteLow5bitsMask uint8 = 0x1F + ByteLow3bitsMask uint8 = 0x07 + ByteHigh3bitsMask uint8 = 0xE0 + MaxUint8 uint8 = 0xFF + MinUint8 uint8 = 0x00 + MaxUint16 uint16 = 0xFFFF + MinUint16 uint16 = 0x0000 + MaxUint32 uint32 = 0xFFFFFFFF + MinUint32 uint32 = 0x00000000 + MaxUint64 uint64 = 0xFFFFFFFFFFFFFFFF + MinUint64 uint64 = 0x0000000000000000 +) + +func Init() { + loadByteOrder() +} + +func loadByteOrder() { + buf := [2]byte{} + *(*uint16)(unsafe.Pointer(&buf[0])) = uint16(0xABCD) + + switch buf { + case [2]byte{0xCD, 0xAB}: + ByteOrder = &CustomByteOrder{binary.LittleEndian} + case [2]byte{0xAB, 0xCD}: + ByteOrder = &CustomByteOrder{binary.BigEndian} + default: + panic("Could not determine endianness.") + } +} + +func GetToByteFP32AndLess(dType types.DataType) (func([]byte, float32), error) { + switch dType { + case types.DataTypeFP8E5M2, types.DataTypeFP8E5M2Vector: + return ByteOrder.PutFloat8E5M2FromFP32, nil + case types.DataTypeFP8E4M3, types.DataTypeFP8E4M3Vector: + return ByteOrder.PutFloat8E4M3FromFP32, nil + case types.DataTypeFP16, types.DataTypeFP16Vector: + return ByteOrder.PutFloat16FromFP32, nil + case types.DataTypeFP32, types.DataTypeFP32Vector: + return ByteOrder.PutFloat32, nil + default: + return nil, fmt.Errorf("unsupported data type: %s", dType) + } +} + +func GetFromByteFP32AndLess(dType types.DataType) (func([]byte) float32, error) { + switch dType { + case types.DataTypeFP8E5M2, types.DataTypeFP8E5M2Vector: + return ByteOrder.Float8E5M2AsFP32, nil + case types.DataTypeFP8E4M3, types.DataTypeFP8E4M3Vector: + return ByteOrder.Float8E4M3AsFP32, nil + case types.DataTypeFP16, types.DataTypeFP16Vector: + return ByteOrder.Float16AsFP32, nil + case types.DataTypeFP32, types.DataTypeFP32Vector: + return ByteOrder.Float32, nil + default: + return nil, fmt.Errorf("unsupported data type: %s", dType) + } +} + +func GetToByteInt32AndLess(dType types.DataType) (func([]byte, int32), error) { + switch dType { + case types.DataTypeInt8, types.DataTypeInt8Vector: + return ByteOrder.PutInt8FromInt32, nil + case types.DataTypeInt16, types.DataTypeInt16Vector: + return ByteOrder.PutInt16FromInt32, nil + case types.DataTypeInt32, types.DataTypeInt32Vector: + return ByteOrder.PutInt32, nil + default: + return nil, fmt.Errorf("unsupported data type: %s", dType) + } +} + +func GetFromByteInt32AndLess(dType types.DataType) (func([]byte) int32, error) { + switch dType { + case types.DataTypeInt8, types.DataTypeInt8Vector: + return ByteOrder.Int8AsInt32, nil + case types.DataTypeInt16, types.DataTypeInt16Vector: + return ByteOrder.Int16AsInt32, nil + case types.DataTypeInt32, types.DataTypeInt32Vector: + return ByteOrder.Int32, nil + default: + return nil, fmt.Errorf("unsupported data type: %s", dType) + } +} + +func GetToByteUint32AndLess(dType types.DataType) (func([]byte, uint32), error) { + switch dType { + case types.DataTypeUint8, types.DataTypeUint8Vector: + return ByteOrder.PutUint8FromUint32, nil + case types.DataTypeUint16, types.DataTypeUint16Vector: + return ByteOrder.PutUint16FromUint32, nil + case types.DataTypeUint32, types.DataTypeUint32Vector: + return ByteOrder.PutUint32, nil + default: + return nil, fmt.Errorf("unsupported data type: %s", dType) + } +} + +func GetFromByteUint32AndLess(dType types.DataType) (func([]byte) uint32, error) { + switch dType { + case types.DataTypeUint8, types.DataTypeUint8Vector: + return ByteOrder.Uint8AsUint32, nil + case types.DataTypeUint16, types.DataTypeUint16Vector: + return ByteOrder.Uint16AsUint32, nil + case types.DataTypeUint32, types.DataTypeUint32Vector: + return ByteOrder.Uint32, nil + default: + return nil, fmt.Errorf("unsupported data type: %s", dType) + } +} + +func BinPackUint32InUint64(high, low uint32) uint64 { + return uint64(high)<<32 | uint64(low) +} + +func UnpackUint64InUint32(highLow uint64) (uint32, uint32) { + return uint32(highLow >> 32), uint32(highLow) +} + +func BinPackUint16InUint32(high, low uint16) uint32 { + return uint32(high)<<16 | uint32(low) +} + +func UnpackUint32InUint16(highLow uint32) (uint16, uint16) { + return uint16(highLow >> 16), uint16(highLow) +} + +func BinPackUint8InUint16(high, low uint8) uint16 { + return uint16(high)<<8 | uint16(low) +} + +func UnpackUint16InUint8(highLow uint16) (uint8, uint8) { + return uint8(highLow >> 8), uint8(highLow) +} + +// func ParseFeatureValue(featureLabels []string, features *persist.FeatureValues, dataType types.DataType, featureMeta map[string]config.FeatureMeta) (interface{}, error) { +// switch dataType { +// case types.DataTypeInt8, types.DataTypeInt16, types.DataTypeInt32: +// return GetInt32(featureLabels, features, featureMeta) +// case types.DataTypeUint8, types.DataTypeUint16, types.DataTypeUint32: +// return GetUInt32(featureLabels, features, featureMeta) +// case types.DataTypeInt64: +// return GetInt64(featureLabels, features, featureMeta) +// case types.DataTypeUint64: +// return GetUInt64(featureLabels, features, featureMeta) +// case types.DataTypeFP8E5M2, types.DataTypeFP8E4M3, types.DataTypeFP16, types.DataTypeFP32: +// return GetFP32(featureLabels, features, featureMeta) +// case types.DataTypeFP64: +// return GetFP64(featureLabels, features, featureMeta) +// case types.DataTypeBool: +// return GetUInt8(featureLabels, features, featureMeta) +// case types.DataTypeString: +// return GetString(featureLabels, features, featureMeta) +// case types.DataTypeInt8Vector, types.DataTypeInt16Vector, types.DataTypeInt32Vector: +// return GetInt32Vector(featureLabels, features, featureMeta) +// case types.DataTypeInt64Vector: +// return GetInt64Vector(featureLabels, features, featureMeta) +// case types.DataTypeUint8Vector, types.DataTypeUint16Vector, types.DataTypeUint32Vector: +// return GetUInt32Vector(featureLabels, features, featureMeta) +// case types.DataTypeUint64Vector: +// return GetUInt64Vector(featureLabels, features, featureMeta) +// case types.DataTypeFP8E5M2Vector, types.DataTypeFP8E4M3Vector, types.DataTypeFP16Vector, types.DataTypeFP32Vector: +// return GetFP32Vector(featureLabels, features, featureMeta) +// case types.DataTypeFP64Vector: +// return GetFP64Vector(featureLabels, features, featureMeta) +// case types.DataTypeBoolVector: +// return GetBoolVector(featureLabels, features, featureMeta) +// case types.DataTypeStringVector: +// return GetStringVector(featureLabels, features, featureMeta) +// default: +// return nil, fmt.Errorf("unknown Data type: %d", dataType) +// } +// } + +// func GetInt32(featureLabels []string, featureValues *persist.FeatureValues, featureMeta map[string]config.FeatureMeta) ([]int32, error) { +// int32Array := make([]int32, len(featureMeta)) +// labelExists := make(map[string]bool, len(featureLabels)) +// for index, label := range featureLabels { +// labelExists[label] = true +// int32Array[featureMeta[label].Sequence] = featureValues.GetValues().Int32Values[index] +// } + +// for label, meta := range featureMeta { +// if !labelExists[label] { +// int32Array[meta.Sequence] = ByteOrder.Int32(meta.DefaultValuesInBytes) +// } +// } +// return int32Array, nil +// } + +// func GetUInt32(featureLabels []string, featureValues *persist.FeatureValues, featureMeta map[string]config.FeatureMeta) ([]uint32, error) { +// uint32Array := make([]uint32, len(featureMeta)) +// labelExists := make(map[string]bool, len(featureLabels)) +// for index, label := range featureLabels { +// labelExists[label] = true +// uint32Array[featureMeta[label].Sequence] = uint32(featureValues.GetValues().Uint32Values[index]) +// } + +// for label, meta := range featureMeta { +// if !labelExists[label] { +// uint32Array[meta.Sequence] = ByteOrder.Uint32(meta.DefaultValuesInBytes) +// } +// } +// return uint32Array, nil +// } + +// func GetInt64(featureLabels []string, featureValues *persist.FeatureValues, featureMeta map[string]config.FeatureMeta) ([]int64, error) { +// int64Array := make([]int64, len(featureMeta)) +// labelExists := make(map[string]bool, len(featureLabels)) +// for index, label := range featureLabels { +// labelExists[label] = true +// int64Array[featureMeta[label].Sequence] = int64(featureValues.GetValues().Int64Values[index]) +// } + +// for label, meta := range featureMeta { +// if !labelExists[label] { +// int64Array[meta.Sequence] = ByteOrder.Int64(meta.DefaultValuesInBytes) +// } +// } + +// return int64Array, nil +// } + +// func GetUInt64(featureLabels []string, featureValues *persist.FeatureValues, featureMeta map[string]config.FeatureMeta) ([]uint64, error) { +// uint64Array := make([]uint64, len(featureMeta)) +// labelExists := make(map[string]bool, len(featureLabels)) +// for index, label := range featureLabels { +// labelExists[label] = true +// uint64Array[featureMeta[label].Sequence] = featureValues.GetValues().Uint64Values[index] +// } + +// for label, meta := range featureMeta { +// if !labelExists[label] { +// uint64Array[meta.Sequence] = ByteOrder.Uint64(meta.DefaultValuesInBytes) +// } +// } +// return uint64Array, nil +// } + +// func GetFP32(featureLabels []string, featureValues *persist.FeatureValues, featureMeta map[string]config.FeatureMeta) ([]float32, error) { +// fp32Array := make([]float32, len(featureMeta)) +// labelExists := make(map[string]bool, len(featureLabels)) +// for index, label := range featureLabels { +// labelExists[label] = true +// fp32Array[featureMeta[label].Sequence] = float32(featureValues.GetValues().Fp32Values[index]) +// } + +// for label, meta := range featureMeta { +// if !labelExists[label] { +// fp32Array[meta.Sequence] = ByteOrder.Float32(meta.DefaultValuesInBytes) +// } +// } +// return fp32Array, nil +// } + +// func GetFP64(featureLabels []string, featureValues *persist.FeatureValues, featureMeta map[string]config.FeatureMeta) ([]float64, error) { +// fp64Array := make([]float64, len(featureMeta)) +// labelExists := make(map[string]bool, len(featureLabels)) +// for index, label := range featureLabels { +// labelExists[label] = true +// fp64Array[featureMeta[label].Sequence] = featureValues.GetValues().Fp64Values[index] +// } + +// for label, meta := range featureMeta { +// if !labelExists[label] { +// fp64Array[meta.Sequence] = ByteOrder.Float64(meta.DefaultValuesInBytes) +// } +// } +// return fp64Array, nil +// } + +// func GetUInt8(featureLabels []string, featureValues *persist.FeatureValues, featureMeta map[string]config.FeatureMeta) ([]uint8, error) { +// uint8Array := make([]uint8, len(featureMeta)) +// labelExists := make(map[string]bool, len(featureLabels)) +// for index, label := range featureLabels { +// labelExists[label] = true +// uint8Array[featureMeta[label].Sequence] = func(b bool) uint8 { +// if b { +// return 1 +// } +// return 0 +// }(featureValues.GetValues().BoolValues[index]) +// } + +// for label, meta := range featureMeta { +// if !labelExists[label] { +// uint8Array[meta.Sequence] = ByteOrder.Uint8(meta.DefaultValuesInBytes) +// } +// } +// return uint8Array, nil +// } + +// func GetString(featureLabels []string, featureValues *persist.FeatureValues, featureMeta map[string]config.FeatureMeta) ([]string, error) { +// stringArray := make([]string, len(featureMeta)) +// labelExists := make(map[string]bool, len(featureLabels)) +// for index, label := range featureLabels { +// labelExists[label] = true +// stringArray[featureMeta[label].Sequence] = featureValues.GetValues().StringValues[index] +// } + +// for label, meta := range featureMeta { +// if !labelExists[label] { +// stringArray[meta.Sequence] = ByteOrder.String(meta.DefaultValuesInBytes) +// } +// } +// return stringArray, nil +// } + +// func GetInt32Vector(featureLabels []string, featureValues *persist.FeatureValues, featureMeta map[string]config.FeatureMeta) ([][]int32, error) { +// int32Vectors := make([][]int32, len(featureMeta)) +// labelExists := make(map[string]bool, len(featureLabels)) +// for index, label := range featureLabels { +// labelExists[label] = true +// int32Vectors[featureMeta[label].Sequence] = featureValues.GetValues().Vector[index].Values.Int32Values +// } + +// for label, meta := range featureMeta { +// if !labelExists[label] { +// int32Vectors[meta.Sequence] = ByteOrder.Int32Vector(meta.DefaultValuesInBytes) +// } +// } +// return int32Vectors, nil +// } + +// func GetInt64Vector(featureLabels []string, featureValues *persist.FeatureValues, featureMeta map[string]config.FeatureMeta) ([][]int64, error) { +// int64Vectors := make([][]int64, len(featureMeta)) +// labelExists := make(map[string]bool, len(featureLabels)) +// for index, label := range featureLabels { +// labelExists[label] = true +// int64Vectors[featureMeta[label].Sequence] = featureValues.GetValues().Vector[index].Values.Int64Values +// } + +// for label, meta := range featureMeta { +// if !labelExists[label] { +// int64Vectors[meta.Sequence] = ByteOrder.Int64Vector(meta.DefaultValuesInBytes) +// } +// } +// return int64Vectors, nil +// } + +// func GetUInt32Vector(featureLabels []string, featureValues *persist.FeatureValues, featureMeta map[string]config.FeatureMeta) ([][]uint32, error) { +// uint32Vectors := make([][]uint32, len(featureMeta)) +// labelExists := make(map[string]bool, len(featureLabels)) +// for index, label := range featureLabels { +// labelExists[label] = true +// uint32Vectors[featureMeta[label].Sequence] = featureValues.GetValues().Vector[index].Values.Uint32Values +// } + +// for label, meta := range featureMeta { +// if !labelExists[label] { +// uint32Vectors[meta.Sequence] = ByteOrder.Uint32Vector(meta.DefaultValuesInBytes) +// } +// } +// return uint32Vectors, nil +// } + +// func GetUInt64Vector(featureLabels []string, featureValues *persist.FeatureValues, featureMeta map[string]config.FeatureMeta) ([][]uint64, error) { +// uint64Vectors := make([][]uint64, len(featureMeta)) +// labelExists := make(map[string]bool, len(featureLabels)) +// for index, label := range featureLabels { +// labelExists[label] = true +// uint64Vectors[featureMeta[label].Sequence] = featureValues.GetValues().Vector[index].Values.Uint64Values +// } + +// for label, meta := range featureMeta { +// if !labelExists[label] { +// uint64Vectors[meta.Sequence] = ByteOrder.Uint64Vector(meta.DefaultValuesInBytes) +// } +// } +// return uint64Vectors, nil +// } + +// func GetFP32Vector(featureLabels []string, featureValues *persist.FeatureValues, featureMeta map[string]config.FeatureMeta) ([][]float32, error) { +// fp32Vectors := make([][]float32, len(featureMeta)) +// labelExists := make(map[string]bool, len(featureLabels)) +// for index, label := range featureLabels { +// labelExists[label] = true +// fp32FeatureValues := []float32{} +// for _, value := range featureValues.GetValues().Vector[index].Values.Fp32Values { +// fp32FeatureValues = append(fp32FeatureValues, float32(value)) +// } +// fp32Vectors[featureMeta[label].Sequence] = fp32FeatureValues +// } + +// for label, meta := range featureMeta { +// if !labelExists[label] { +// fp32Vectors[meta.Sequence] = ByteOrder.FP16Vector(meta.DefaultValuesInBytes) +// } +// } +// return fp32Vectors, nil +// } + +// func GetFP64Vector(featureLabels []string, featureValues *persist.FeatureValues, featureMeta map[string]config.FeatureMeta) ([][]float64, error) { +// fp64Vectors := make([][]float64, len(featureMeta)) +// labelExists := make(map[string]bool, len(featureLabels)) +// for index, label := range featureLabels { +// labelExists[label] = true +// fp64Vectors[featureMeta[label].Sequence] = featureValues.GetValues().Vector[index].Values.Fp64Values +// } + +// for label, meta := range featureMeta { +// if !labelExists[label] { +// fp64Vectors[meta.Sequence] = ByteOrder.Float64Vector(meta.DefaultValuesInBytes) +// } +// } +// return fp64Vectors, nil +// } + +// func GetBoolVector(featureLabels []string, featureValues *persist.FeatureValues, featureMeta map[string]config.FeatureMeta) ([][]bool, error) { +// boolVectors := make([][]bool, len(featureMeta)) +// labelExists := make(map[string]bool, len(featureLabels)) +// for index, label := range featureLabels { +// labelExists[label] = true +// boolVectors[featureMeta[label].Sequence] = featureValues.GetValues().Vector[index].Values.BoolValues +// } + +// for label, meta := range featureMeta { +// if !labelExists[label] { +// boolVectors[meta.Sequence] = ByteOrder.BoolVector(meta.DefaultValuesInBytes, int(meta.VectorLength)) +// } +// } +// return boolVectors, nil +// } + +// func GetStringVector(featureLabels []string, featureValues *persist.FeatureValues, featureMeta map[string]config.FeatureMeta) ([][]string, error) { +// stringVectors := make([][]string, len(featureMeta)) +// labelExists := make(map[string]bool, len(featureLabels)) +// for index, label := range featureLabels { +// labelExists[label] = true +// stringVectors[featureMeta[label].Sequence] = featureValues.GetValues().Vector[index].Values.StringValues +// } + +// for label, meta := range featureMeta { +// if !labelExists[label] { +// stringVectors[meta.Sequence] = ByteOrder.StringVector(meta.DefaultValuesInBytes, int(meta.VectorLength), int(meta.StringLength)) +// } +// } +// return stringVectors, nil +// } diff --git a/helix-client/pkg/datatypeconverter/byteorder/system_test.go b/go-sdk/pkg/datatypeconverter/byteorder/system_test.go similarity index 100% rename from helix-client/pkg/datatypeconverter/byteorder/system_test.go rename to go-sdk/pkg/datatypeconverter/byteorder/system_test.go diff --git a/helix-client/pkg/datatypeconverter/byteorder/vector_test.go b/go-sdk/pkg/datatypeconverter/byteorder/vector_test.go similarity index 100% rename from helix-client/pkg/datatypeconverter/byteorder/vector_test.go rename to go-sdk/pkg/datatypeconverter/byteorder/vector_test.go diff --git a/helix-client/pkg/datatypeconverter/float8/float8_e4m3.go b/go-sdk/pkg/datatypeconverter/float8/float8_e4m3.go similarity index 100% rename from helix-client/pkg/datatypeconverter/float8/float8_e4m3.go rename to go-sdk/pkg/datatypeconverter/float8/float8_e4m3.go diff --git a/helix-client/pkg/datatypeconverter/float8/float8_e4m3_bench_test.go b/go-sdk/pkg/datatypeconverter/float8/float8_e4m3_bench_test.go similarity index 100% rename from helix-client/pkg/datatypeconverter/float8/float8_e4m3_bench_test.go rename to go-sdk/pkg/datatypeconverter/float8/float8_e4m3_bench_test.go diff --git a/helix-client/pkg/datatypeconverter/float8/float8_e4m3_test.go b/go-sdk/pkg/datatypeconverter/float8/float8_e4m3_test.go similarity index 100% rename from helix-client/pkg/datatypeconverter/float8/float8_e4m3_test.go rename to go-sdk/pkg/datatypeconverter/float8/float8_e4m3_test.go diff --git a/helix-client/pkg/datatypeconverter/float8/float8_e5m2.go b/go-sdk/pkg/datatypeconverter/float8/float8_e5m2.go similarity index 100% rename from helix-client/pkg/datatypeconverter/float8/float8_e5m2.go rename to go-sdk/pkg/datatypeconverter/float8/float8_e5m2.go diff --git a/helix-client/pkg/datatypeconverter/float8/float8_e5m2_bench_test.go b/go-sdk/pkg/datatypeconverter/float8/float8_e5m2_bench_test.go similarity index 100% rename from helix-client/pkg/datatypeconverter/float8/float8_e5m2_bench_test.go rename to go-sdk/pkg/datatypeconverter/float8/float8_e5m2_bench_test.go diff --git a/helix-client/pkg/datatypeconverter/float8/float8_e5m2_test.go b/go-sdk/pkg/datatypeconverter/float8/float8_e5m2_test.go similarity index 100% rename from helix-client/pkg/datatypeconverter/float8/float8_e5m2_test.go rename to go-sdk/pkg/datatypeconverter/float8/float8_e5m2_test.go diff --git a/helix-client/pkg/datatypeconverter/typeconverter/tyeconverter.go b/go-sdk/pkg/datatypeconverter/typeconverter/tyeconverter.go similarity index 99% rename from helix-client/pkg/datatypeconverter/typeconverter/tyeconverter.go rename to go-sdk/pkg/datatypeconverter/typeconverter/tyeconverter.go index 3ca8df3f..2599c26e 100644 --- a/helix-client/pkg/datatypeconverter/typeconverter/tyeconverter.go +++ b/go-sdk/pkg/datatypeconverter/typeconverter/tyeconverter.go @@ -5,8 +5,8 @@ import ( "strconv" "strings" - "github.com/Meesho/BharatMLStack/helix-client/pkg/datatypeconverter/byteorder" - "github.com/Meesho/BharatMLStack/helix-client/pkg/datatypeconverter/types" + "github.com/Meesho/BharatMLStack/go-sdk/pkg/datatypeconverter/byteorder" + "github.com/Meesho/BharatMLStack/go-sdk/pkg/datatypeconverter/types" ) // BytesToString converts byte data to string based on the specified data type diff --git a/helix-client/pkg/datatypeconverter/typeconverter/tyeconverter_test.go b/go-sdk/pkg/datatypeconverter/typeconverter/tyeconverter_test.go similarity index 99% rename from helix-client/pkg/datatypeconverter/typeconverter/tyeconverter_test.go rename to go-sdk/pkg/datatypeconverter/typeconverter/tyeconverter_test.go index 2180e999..bafc2ebe 100644 --- a/helix-client/pkg/datatypeconverter/typeconverter/tyeconverter_test.go +++ b/go-sdk/pkg/datatypeconverter/typeconverter/tyeconverter_test.go @@ -6,7 +6,7 @@ import ( "strconv" "testing" - "github.com/Meesho/BharatMLStack/helix-client/pkg/datatypeconverter/byteorder" + "github.com/Meesho/BharatMLStack/go-sdk/pkg/datatypeconverter/byteorder" ) func TestMain(m *testing.M) { diff --git a/helix-client/pkg/datatypeconverter/types/data_type.go b/go-sdk/pkg/datatypeconverter/types/data_type.go similarity index 100% rename from helix-client/pkg/datatypeconverter/types/data_type.go rename to go-sdk/pkg/datatypeconverter/types/data_type.go diff --git a/helix-client/pkg/datatypeconverter/types/data_type_test.go b/go-sdk/pkg/datatypeconverter/types/data_type_test.go similarity index 100% rename from helix-client/pkg/datatypeconverter/types/data_type_test.go rename to go-sdk/pkg/datatypeconverter/types/data_type_test.go diff --git a/helix-client/pkg/enums/client_type.go b/go-sdk/pkg/enums/client_type.go similarity index 100% rename from helix-client/pkg/enums/client_type.go rename to go-sdk/pkg/enums/client_type.go diff --git a/helix-client/pkg/enums/user_context.go b/go-sdk/pkg/enums/user_context.go similarity index 100% rename from helix-client/pkg/enums/user_context.go rename to go-sdk/pkg/enums/user_context.go diff --git a/go-sdk/pkg/grpc/grpcframework.go b/go-sdk/pkg/grpc/grpcframework.go new file mode 100644 index 00000000..811cd66e --- /dev/null +++ b/go-sdk/pkg/grpc/grpcframework.go @@ -0,0 +1,132 @@ +package grpc + +import ( + "net" + "net/http" + "strconv" + "sync" + + "github.com/Meesho/BharatMLStack/go-sdk/pkg/httpframework" + "github.com/Meesho/BharatMLStack/go-sdk/pkg/middleware" + "github.com/gin-gonic/gin" + "github.com/rs/zerolog/log" + "github.com/soheilhy/cmux" + "github.com/spf13/viper" + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" +) + +type Server struct { + GRPCServer *grpc.Server + HTTPHandler *gin.Engine +} + +var ( + server *Server + once sync.Once +) + +// Init initializes the gRPC and HTTP server with the given middlewares if any +func Init(interceptors ...grpc.UnaryServerInterceptor) { + once.Do(func() { + // Create a gRPC server with the logger and recovery middleware + interceptors = append(interceptors, middleware.GRPCRecovery, middleware.GRPCLogger) + grpcServer := grpc.NewServer( + grpc.ChainUnaryInterceptor(interceptors...), + ) + + // Create a Gin router + httpframework.Init() + router := httpframework.Instance() + + // Create HTTP routes and handlers using Gin + router.GET("/health/self", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"message": "true"}) + }) + + server = &Server{ + GRPCServer: grpcServer, + HTTPHandler: router, + } + }) +} + +// Run starts the cmux multiplexer (Mux) to handle incoming connections and route them to the appropriate servers +func (server *Server) Run() error { + if !viper.IsSet("APP_PORT") { + log.Panic().Msgf("Failed to start the application - APP_PORT is not set") + } + port := viper.GetInt("APP_PORT") + + listener, err := net.Listen("tcp", ":"+strconv.Itoa(port)) + if err != nil { + log.Panic().Msgf("Failed to start the application - Failed to listen: %v", err) + } + + // Create a cmux multiplexer that will multiplex 2 protocols on same port + mux := cmux.New(listener) + + // Create a cmux listener for HTTP connections + httpListener := mux.Match(cmux.HTTP1Fast()) + // Create a cmux listener for gRPC connections + grpcListener := mux.Match(cmux.HTTP2(), cmux.HTTP2HeaderField("content-type", "application/grpc"), cmux.Any()) + + reflection.Register(server.GRPCServer) + // Start listeners for each protocol + // Start the gRPC server in a separate goroutine + go func() { + if err := server.GRPCServer.Serve(grpcListener); err != nil { + log.Panic().Msgf("Failed to serve gRPC server: %v", err) + } + }() + // Start the HTTP server in a separate goroutine + go func() { + if err := http.Serve(httpListener, server.HTTPHandler); err != nil { + log.Panic().Msgf("Failed to serve HTTP server: %v", err) + } + }() + + return mux.Serve() +} + +// InitWithServerOptions is a new initialization function that accepts grpc.ServerOption +// It initializes the gRPC and HTTP server with the given server options and middlewares. +func InitWithServerOptions(serverOptions []grpc.ServerOption, interceptors ...grpc.UnaryServerInterceptor) { + once.Do(func() { + if serverOptions == nil { + log.Info().Msg("Server options are nil") + } + // Append default middlewares + interceptors = append(interceptors, middleware.GRPCRecovery, middleware.GRPCLogger) + + // Combine provided server options with the interceptor chain + allServerOptions := append(serverOptions, grpc.ChainUnaryInterceptor(interceptors...)) + + // Create a gRPC server with the specified options + grpcServer := grpc.NewServer( + allServerOptions..., + ) + + // Create a Gin router + httpframework.Init() + router := httpframework.Instance() + + // Create HTTP routes and handlers using Gin + router.GET("/health/self", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"message": "true"}) + }) + + server = &Server{ + GRPCServer: grpcServer, + HTTPHandler: router, + } + }) +} + +// Instance returns the grpc instance +func Instance() *Server { + if server == nil { + log.Panic().Msg("Server not initialized, call Init first") + } + return server +} diff --git a/go-sdk/pkg/grpc/grpcframework_test.go b/go-sdk/pkg/grpc/grpcframework_test.go new file mode 100644 index 00000000..96ae3b87 --- /dev/null +++ b/go-sdk/pkg/grpc/grpcframework_test.go @@ -0,0 +1,399 @@ +package grpc + +import ( + "context" + "net/http" + "os" + "sync" + "testing" + "time" + + "github.com/Meesho/BharatMLStack/go-sdk/pkg/httpframework" + + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +// resetGlobalState resets the global state for testing +func resetGlobalState() { + server = nil + once = sync.Once{} + httpframework.ResetForTesting() +} + +// Helper function to wait for server to be ready +func waitForServer(port string) bool { + maxRetries := 10 + for i := 0; i < maxRetries; i++ { + resp, err := http.Get("http://localhost:" + port + "/health/self") + if err == nil { + resp.Body.Close() + return true + } + time.Sleep(100 * time.Millisecond) + } + return false +} + +func TestRunWithoutAppPort(t *testing.T) { + os.Unsetenv("APP_PORT") + viper.Set("APP_NAME", "test-grpc-app") + viper.AutomaticEnv() + + Init() + instance := Instance() + assert.Panics(t, func() { + err := instance.Run() + if err != nil { + return + } + }) +} + +func TestInitAndRun(t *testing.T) { + resetGlobalState() + + os.Setenv("APP_PORT", "8080") + viper.Set("APP_NAME", "test-grpc-app") + viper.AutomaticEnv() + + // Call the function under test in a goroutine, as it blocks + Init() + instance := Instance() + + // Assert that the server is initialized + assert.NotNil(t, instance.HTTPHandler) + + // Assert that the server's GRPCServer is not nil + assert.NotNil(t, instance.GRPCServer) + + // Test Run method + go instance.Run() + + // Wait for the server to start + assert.True(t, waitForServer("8080"), "Server did not start within timeout") + + // Send a GET request to the /health/self endpoint + resp, err := http.Get("http://localhost:8080/health/self") + assert.NoError(t, err) + defer resp.Body.Close() + + //make grpc call to the server + conn, err := grpc.NewClient("localhost:8080", grpc.WithTransportCredentials(insecure.NewCredentials())) + assert.NoError(t, err) + defer conn.Close() + + // Check the response status code + assert.Equal(t, http.StatusOK, resp.StatusCode) +} + +func TestInitWithServerOptions(t *testing.T) { + resetGlobalState() + + os.Setenv("APP_PORT", "8081") + viper.AutomaticEnv() + + serverOpts := []grpc.ServerOption{ + grpc.MaxRecvMsgSize(1024 * 1024 * 4), // 4MB max message size + } + + // Call the function under test in a goroutine, as it blocks + InitWithServerOptions(serverOpts) + instance := Instance() + + // Assert that the server is initialized + assert.NotNil(t, instance.HTTPHandler) + + // Assert that the server's GRPCServer is not nil + assert.NotNil(t, instance.GRPCServer) + + // Test Run method + go instance.Run() + + // Wait for the server to start + assert.True(t, waitForServer("8081"), "Server did not start within timeout") + + // Send a GET request to the /health/self endpoint + resp, err := http.Get("http://localhost:8081/health/self") + assert.NoError(t, err) + defer resp.Body.Close() + + //make grpc call to the server + conn, err := grpc.NewClient("localhost:8081", grpc.WithTransportCredentials(insecure.NewCredentials())) + assert.NoError(t, err) + defer conn.Close() + + // Check the response status code + assert.Equal(t, http.StatusOK, resp.StatusCode) +} + +func TestInitWithoutServerOptions(t *testing.T) { + resetGlobalState() + + os.Setenv("APP_PORT", "8082") + viper.AutomaticEnv() + + // Call the function under test in a goroutine, as it blocks + InitWithServerOptions(nil) + instance := Instance() + + // Assert that the server is initialized + assert.NotNil(t, instance.HTTPHandler) + + // Assert that the server's GRPCServer is not nil + assert.NotNil(t, instance.GRPCServer) + + // Test Run method + go instance.Run() + + // Wait for the server to start + assert.True(t, waitForServer("8082"), "Server did not start within timeout") + + // Send a GET request to the /health/self endpoint + resp, err := http.Get("http://localhost:8082/health/self") + assert.NoError(t, err) + defer resp.Body.Close() + + //make grpc call to the server + conn, err := grpc.NewClient("localhost:8082", grpc.WithTransportCredentials(insecure.NewCredentials())) + assert.NoError(t, err) + defer conn.Close() + + // Check the response status code + assert.Equal(t, http.StatusOK, resp.StatusCode) +} + +func TestInstanceWithoutInit(t *testing.T) { + resetGlobalState() + + assert.Panics(t, func() { Instance() }) +} + +// New comprehensive tests for InitWithServerOptions + +func TestInitWithServerOptionsCustomInterceptors(t *testing.T) { + resetGlobalState() + + os.Setenv("APP_PORT", "8083") + viper.AutomaticEnv() + + // Custom interceptor for testing + customInterceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + // This is a simple pass-through interceptor for testing + return handler(ctx, req) + } + + serverOpts := []grpc.ServerOption{ + grpc.MaxRecvMsgSize(1024 * 1024 * 2), // 2MB max message size + } + + // Call with custom interceptor + InitWithServerOptions(serverOpts, customInterceptor) + instance := Instance() + + // Assert that the server is initialized + assert.NotNil(t, instance.HTTPHandler) + assert.NotNil(t, instance.GRPCServer) + + // Test Run method + go instance.Run() + + // Wait for the server to start + assert.True(t, waitForServer("8083"), "Server did not start within timeout") + + // Send a GET request to the /health/self endpoint + resp, err := http.Get("http://localhost:8083/health/self") + assert.NoError(t, err) + defer resp.Body.Close() + + // Check the response status code + assert.Equal(t, http.StatusOK, resp.StatusCode) +} + +func TestInitWithServerOptionsEmptyServerOptions(t *testing.T) { + resetGlobalState() + + os.Setenv("APP_PORT", "8084") + viper.AutomaticEnv() + + // Test with empty server options slice + serverOpts := []grpc.ServerOption{} + + InitWithServerOptions(serverOpts) + instance := Instance() + + // Assert that the server is initialized + assert.NotNil(t, instance.HTTPHandler) + assert.NotNil(t, instance.GRPCServer) + + // Test Run method + go instance.Run() + + // Wait for the server to start + assert.True(t, waitForServer("8084"), "Server did not start within timeout") + + // Send a GET request to the /health/self endpoint + resp, err := http.Get("http://localhost:8084/health/self") + assert.NoError(t, err) + defer resp.Body.Close() + + // Check the response status code + assert.Equal(t, http.StatusOK, resp.StatusCode) +} + +func TestInitWithServerOptionsMultipleOptions(t *testing.T) { + resetGlobalState() + + os.Setenv("APP_PORT", "8085") + viper.AutomaticEnv() + + serverOpts := []grpc.ServerOption{ + grpc.MaxRecvMsgSize(1024 * 1024 * 8), // 8MB max message size + grpc.MaxSendMsgSize(1024 * 1024 * 8), // 8MB max send message size + grpc.MaxConcurrentStreams(1000), + } + + InitWithServerOptions(serverOpts) + instance := Instance() + + // Assert that the server is initialized + assert.NotNil(t, instance.HTTPHandler) + assert.NotNil(t, instance.GRPCServer) + + // Test Run method + go instance.Run() + + // Wait for the server to start + assert.True(t, waitForServer("8085"), "Server did not start within timeout") + + // Send a GET request to the /health/self endpoint + resp, err := http.Get("http://localhost:8085/health/self") + assert.NoError(t, err) + defer resp.Body.Close() + + // Check the response status code + assert.Equal(t, http.StatusOK, resp.StatusCode) +} + +func TestInitWithServerOptionsOnlyCalledOnce(t *testing.T) { + resetGlobalState() + + os.Setenv("APP_PORT", "8086") + viper.AutomaticEnv() + + serverOpts1 := []grpc.ServerOption{ + grpc.MaxRecvMsgSize(1024 * 1024 * 4), // 4MB max message size + } + + serverOpts2 := []grpc.ServerOption{ + grpc.MaxRecvMsgSize(1024 * 1024 * 8), // 8MB max message size + } + + // Call InitWithServerOptions twice - only first should take effect + InitWithServerOptions(serverOpts1) + instance1 := Instance() + + InitWithServerOptions(serverOpts2) // This should be ignored due to sync.Once + instance2 := Instance() + + // Both instances should be the same + assert.Equal(t, instance1, instance2) + assert.NotNil(t, instance1.HTTPHandler) + assert.NotNil(t, instance1.GRPCServer) +} + +func TestInitWithServerOptionsNilLogging(t *testing.T) { + resetGlobalState() + + os.Setenv("APP_PORT", "8087") + viper.AutomaticEnv() + + // Test that nil server options are handled gracefully + InitWithServerOptions(nil) + instance := Instance() + + // Assert that the server is initialized + assert.NotNil(t, instance.HTTPHandler) + assert.NotNil(t, instance.GRPCServer) + + // Test Run method + go instance.Run() + + // Wait for the server to start + assert.True(t, waitForServer("8087"), "Server did not start within timeout") + + // Send a GET request to the /health/self endpoint + resp, err := http.Get("http://localhost:8087/health/self") + assert.NoError(t, err) + defer resp.Body.Close() + + // Check the response status code + assert.Equal(t, http.StatusOK, resp.StatusCode) +} + +// Test to verify interceptor ordering in InitWithServerOptions +func TestInitWithServerOptionsInterceptorOrdering(t *testing.T) { + resetGlobalState() + + os.Setenv("APP_PORT", "8088") + viper.AutomaticEnv() + + // Track the order of interceptor calls + var callOrder []string + + // First custom interceptor + interceptor1 := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + callOrder = append(callOrder, "interceptor1") + return handler(ctx, req) + } + + // Second custom interceptor + interceptor2 := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + callOrder = append(callOrder, "interceptor2") + return handler(ctx, req) + } + + serverOpts := []grpc.ServerOption{ + grpc.MaxRecvMsgSize(1024 * 1024 * 4), // 4MB max message size + } + + // Call with multiple custom interceptors + InitWithServerOptions(serverOpts, interceptor1, interceptor2) + instance := Instance() + + // Assert that the server is initialized + assert.NotNil(t, instance.HTTPHandler) + assert.NotNil(t, instance.GRPCServer) + + // Note: Testing actual interceptor call order would require setting up a real gRPC service + // For now, we verify that the server was created successfully with multiple interceptors + // The ordering test would need to be done in an integration test with actual gRPC calls +} + +// Test to verify that both Init and InitWithServerOptions work similarly +func TestInitVsInitWithServerOptions(t *testing.T) { + t.Run("Init creates server properly", func(t *testing.T) { + resetGlobalState() + os.Setenv("APP_PORT", "8089") + viper.Set("APP_NAME", "test-grpc-app") + viper.AutomaticEnv() + + Init() + instance := Instance() + assert.NotNil(t, instance.HTTPHandler) + assert.NotNil(t, instance.GRPCServer) + }) + + t.Run("InitWithServerOptions creates server properly", func(t *testing.T) { + resetGlobalState() + os.Setenv("APP_PORT", "8090") + viper.AutomaticEnv() + + InitWithServerOptions(nil) + instance := Instance() + assert.NotNil(t, instance.HTTPHandler) + assert.NotNil(t, instance.GRPCServer) + }) +} diff --git a/helix-client/pkg/grpc/options_util.go b/go-sdk/pkg/grpc/options_util.go similarity index 100% rename from helix-client/pkg/grpc/options_util.go rename to go-sdk/pkg/grpc/options_util.go diff --git a/go-sdk/pkg/grpcclient/grpc.go b/go-sdk/pkg/grpcclient/grpc.go new file mode 100644 index 00000000..7490d1d6 --- /dev/null +++ b/go-sdk/pkg/grpcclient/grpc.go @@ -0,0 +1,140 @@ +package grpcclient + +import ( + "crypto/tls" + "errors" + "time" + + "github.com/Meesho/BharatMLStack/go-sdk/pkg/metric" + "github.com/rs/zerolog/log" + "github.com/spf13/viper" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/resolver" + "google.golang.org/grpc/status" +) + +const ( + ResolverDefaultScheme = "dns" +) + +type Config struct { + Host string + Port string + DeadLine int + LoadBalancingPolicy string + PlainText bool +} + +type GRPCClient struct { + Conn *grpc.ClientConn + DeadLine int64 + envPrefix string +} + +func NewConnFromConfig(config *Config, envPrefix string) *GRPCClient { + conn, err := getGRPCConnections(*config) + if err != nil { + log.Panic().Msgf("error while GRPC connection initialization from config - %#v. error - %s", config, err) + } + conn.envPrefix = envPrefix + return conn +} + +func NewConn(envPrefix string) *GRPCClient { + config := newConfig(envPrefix) + conn, err := getGRPCConnections(*config) + if err != nil { + log.Panic().Msgf("error while %s GRPC connection initialization. %s", envPrefix, err) + } + conn.envPrefix = envPrefix + return conn +} + +func newConfig(envPrefix string) *Config { + config := &Config{ + DeadLine: 200_000, + PlainText: false, + } + if !viper.IsSet(envPrefix + "_HOST") { + log.Panic().Msg(envPrefix + "_HOST not set") + } + if !viper.IsSet(envPrefix + "_PORT") { + log.Panic().Msg(envPrefix + "_PORT not set") + } + if !viper.IsSet(envPrefix + "_TIMEOUT_IN_MS") { + log.Panic().Msg(envPrefix + "_TIMEOUT_IN_MS not set") + } + if viper.IsSet(envPrefix + "_GRPC_PLAIN_TEXT") { + config.PlainText = viper.GetBool(envPrefix + "_GRPC_PLAIN_TEXT") + } + if viper.IsSet(envPrefix + "_LOAD_BALANCING_POLICY") { + config.LoadBalancingPolicy = viper.GetString(envPrefix + "_LOAD_BALANCING_POLICY") + } else { + config.LoadBalancingPolicy = "round_robin" + } + config.Host = viper.GetString(envPrefix + "_HOST") + config.Port = viper.GetString(envPrefix + "_PORT") + config.DeadLine = viper.GetInt(envPrefix + "_TIMEOUT_IN_MS") + return config +} + +func getGRPCConnections(config Config) (*GRPCClient, error) { + resolver.SetDefaultScheme(ResolverDefaultScheme) + var gConn *grpc.ClientConn + var err error + if config.Host == "" { + return nil, errors.New("host is not set") + } + if config.Port == "" { + return nil, errors.New("port is not set") + } + if config.DeadLine <= 0 { + return nil, errors.New("deadline is not set or is negative") + } + + if config.LoadBalancingPolicy == "" { + log.Warn().Msgf("Load balancing policy is not set for %s. Setting it to round robin", config.Host) + config.LoadBalancingPolicy = "round_robin" + } + if config.PlainText { + gConn, err = grpc.NewClient(config.Host+":"+config.Port, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"`+config.LoadBalancingPolicy+`"}`), + ) + } else { + creds := credentials.NewTLS(&tls.Config{InsecureSkipVerify: true}) + gConn, err = grpc.NewClient(config.Host+":"+config.Port, + grpc.WithTransportCredentials(creds), + grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"`+config.LoadBalancingPolicy+`"}`), + ) + } + if err != nil { + return nil, err + } + return &GRPCClient{Conn: gConn, DeadLine: int64(config.DeadLine)}, nil +} + +// Invoke is a wrapper around grpc.ClientConn.Invoke and capable to generate metric for external grpc service +func (c *GRPCClient) Invoke(ctx context.Context, method string, args any, reply any, opts ...grpc.CallOption) error { + startTime := time.Now() + err := c.Conn.Invoke(ctx, method, args, reply, opts...) + var code uint32 = 0 + if err != nil { + if e, ok := status.FromError(err); ok { + code = uint32(e.Code()) + } + } + latency := time.Since(startTime) + latencyTags := metric.BuildExternalGRPCServiceLatencyTags(c.envPrefix, method, int(code)) + countTags := metric.BuildExternalGRPCServiceCountTags(c.envPrefix, method, int(code)) + metric.Timing(metric.ExternalApiRequestLatency, latency, latencyTags) + metric.Incr(metric.ExternalApiRequestCount, countTags) + return err +} + +func (c *GRPCClient) NewStream(ctx context.Context, desc *grpc.StreamDesc, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) { + return c.Conn.NewStream(ctx, desc, method, opts...) +} diff --git a/helix-client/pkg/grpcclient/grpc_test.go b/go-sdk/pkg/grpcclient/grpc_test.go similarity index 100% rename from helix-client/pkg/grpcclient/grpc_test.go rename to go-sdk/pkg/grpcclient/grpc_test.go diff --git a/go-sdk/pkg/httpframework/httpframework.go b/go-sdk/pkg/httpframework/httpframework.go new file mode 100644 index 00000000..e88a1129 --- /dev/null +++ b/go-sdk/pkg/httpframework/httpframework.go @@ -0,0 +1,49 @@ +package httpframework + +import ( + "os" + "sync" + + "github.com/Meesho/BharatMLStack/go-sdk/pkg/middleware" + "github.com/gin-gonic/gin" + "github.com/rs/zerolog/log" + "github.com/spf13/viper" +) + +var ( + router *gin.Engine + once sync.Once +) + +// Init initializes gin engine with the given middlewares +// It sets the gin mode to release if the environment is production and use the middleware logger and recovery +func Init(middlewares ...gin.HandlerFunc) { + once.Do(func() { + env := os.Getenv("APP_ENV") + if env == "prod" || env == "production" { + gin.SetMode(gin.ReleaseMode) + } + appName := viper.GetString("APP_NAME") + if appName == "" { + log.Fatal().Msg("APP_NAME cannot be empty!!!") + } + router = gin.New() + middlewares = append(middlewares, middleware.HTTPLogger(), middleware.HTTPRecovery()) + router.Use(middlewares...) + }) +} + +// Instance returns the httpframework instance +func Instance() *gin.Engine { + if router == nil { + log.Fatal().Msg("Router not initialized") + } + return router +} + +// ResetForTesting resets the global state for testing purposes +// This function should only be used in tests +func ResetForTesting() { + router = nil + once = sync.Once{} +} diff --git a/helix-client/pkg/httpframework/httpframework_test.go b/go-sdk/pkg/httpframework/httpframework_test.go similarity index 100% rename from helix-client/pkg/httpframework/httpframework_test.go rename to go-sdk/pkg/httpframework/httpframework_test.go diff --git a/go-sdk/pkg/interaction-store/adapter.go b/go-sdk/pkg/interaction-store/adapter.go new file mode 100644 index 00000000..116d8497 --- /dev/null +++ b/go-sdk/pkg/interaction-store/adapter.go @@ -0,0 +1,175 @@ +package interactionstore + +import ( + pb "github.com/Meesho/BharatMLStack/go-sdk/pkg/proto/interaction-store/timeseries" +) + +// Adapter handles conversion between SDK models and proto messages +type Adapter struct{} + +// ConvertToPersistClickDataProto converts SDK request to proto request +func (a *Adapter) ConvertToPersistClickDataProto(request *PersistClickDataRequest) *pb.PersistClickDataRequest { + if request == nil { + return nil + } + + protoData := make([]*pb.ClickData, len(request.Data)) + for i, d := range request.Data { + protoData[i] = &pb.ClickData{ + CatalogId: d.CatalogId, + ProductId: d.ProductId, + Timestamp: d.Timestamp, + Metadata: d.Metadata, + } + } + + return &pb.PersistClickDataRequest{ + UserId: request.UserId, + Data: protoData, + } +} + +// ConvertToPersistOrderDataProto converts SDK request to proto request +func (a *Adapter) ConvertToPersistOrderDataProto(request *PersistOrderDataRequest) *pb.PersistOrderDataRequest { + if request == nil { + return nil + } + + protoData := make([]*pb.OrderData, len(request.Data)) + for i, d := range request.Data { + protoData[i] = &pb.OrderData{ + CatalogId: d.CatalogId, + ProductId: d.ProductId, + SubOrderNum: d.SubOrderNum, + Timestamp: d.Timestamp, + Metadata: d.Metadata, + } + } + + return &pb.PersistOrderDataRequest{ + UserId: request.UserId, + Data: protoData, + } +} + +// ConvertToRetrieveDataProto converts SDK request to proto request +func (a *Adapter) ConvertToRetrieveDataProto(request *RetrieveDataRequest) *pb.RetrieveDataRequest { + if request == nil { + return nil + } + + return &pb.RetrieveDataRequest{ + UserId: request.UserId, + StartTimestamp: request.StartTimestamp, + EndTimestamp: request.EndTimestamp, + Limit: request.Limit, + } +} + +// ConvertToRetrieveInteractionsProto converts SDK request to proto request +func (a *Adapter) ConvertToRetrieveInteractionsProto(request *RetrieveInteractionsRequest) *pb.RetrieveInteractionsRequest { + if request == nil { + return nil + } + + interactionTypes := make([]pb.InteractionType, len(request.InteractionTypes)) + for i, t := range request.InteractionTypes { + interactionTypes[i] = pb.InteractionType(t) + } + + return &pb.RetrieveInteractionsRequest{ + UserId: request.UserId, + InteractionTypes: interactionTypes, + StartTimestamp: request.StartTimestamp, + EndTimestamp: request.EndTimestamp, + Limit: request.Limit, + } +} + +// ConvertFromPersistDataResponse converts proto response to SDK response +func (a *Adapter) ConvertFromPersistDataResponse(response *pb.PersistDataResponse) *PersistDataResponse { + if response == nil { + return nil + } + + return &PersistDataResponse{ + Message: response.Message, + } +} + +// ConvertFromRetrieveClickDataResponse converts proto response to SDK response +func (a *Adapter) ConvertFromRetrieveClickDataResponse(response *pb.RetrieveClickDataResponse) *RetrieveClickDataResponse { + if response == nil || len(response.Data) == 0 { + return &RetrieveClickDataResponse{Data: []ClickEvent{}} + } + + events := make([]ClickEvent, len(response.Data)) + for i, e := range response.Data { + events[i] = ClickEvent{ + CatalogId: e.CatalogId, + ProductId: e.ProductId, + Timestamp: e.Timestamp, + Metadata: e.Metadata, + } + } + + return &RetrieveClickDataResponse{Data: events} +} + +// ConvertFromRetrieveOrderDataResponse converts proto response to SDK response +func (a *Adapter) ConvertFromRetrieveOrderDataResponse(response *pb.RetrieveOrderDataResponse) *RetrieveOrderDataResponse { + if response == nil || len(response.Data) == 0 { + return &RetrieveOrderDataResponse{Data: []OrderEvent{}} + } + + events := make([]OrderEvent, len(response.Data)) + for i, e := range response.Data { + events[i] = OrderEvent{ + CatalogId: e.CatalogId, + ProductId: e.ProductId, + SubOrderNum: e.SubOrderNum, + Timestamp: e.Timestamp, + Metadata: e.Metadata, + } + } + + return &RetrieveOrderDataResponse{Data: events} +} + +// ConvertFromRetrieveInteractionsResponse converts proto response to SDK response +func (a *Adapter) ConvertFromRetrieveInteractionsResponse(response *pb.RetrieveInteractionsResponse) *RetrieveInteractionsResponse { + if response == nil || len(response.Data) == 0 { + return &RetrieveInteractionsResponse{Data: make(map[string]InteractionData)} + } + + data := make(map[string]InteractionData) + for key, val := range response.Data { + clickEvents := make([]ClickEvent, len(val.ClickEvents)) + for i, e := range val.ClickEvents { + clickEvents[i] = ClickEvent{ + CatalogId: e.CatalogId, + ProductId: e.ProductId, + Timestamp: e.Timestamp, + Metadata: e.Metadata, + } + } + + orderEvents := make([]OrderEvent, len(val.OrderEvents)) + for i, e := range val.OrderEvents { + orderEvents[i] = OrderEvent{ + CatalogId: e.CatalogId, + ProductId: e.ProductId, + SubOrderNum: e.SubOrderNum, + Timestamp: e.Timestamp, + Metadata: e.Metadata, + } + } + + data[key] = InteractionData{ + ClickEvents: clickEvents, + OrderEvents: orderEvents, + } + } + + return &RetrieveInteractionsResponse{Data: data} +} diff --git a/go-sdk/pkg/interaction-store/adapter_test.go b/go-sdk/pkg/interaction-store/adapter_test.go new file mode 100644 index 00000000..4a191ad3 --- /dev/null +++ b/go-sdk/pkg/interaction-store/adapter_test.go @@ -0,0 +1,357 @@ +package interactionstore + +import ( + "testing" + + pb "github.com/Meesho/BharatMLStack/go-sdk/pkg/proto/interaction-store/timeseries" + "github.com/stretchr/testify/assert" +) + +func TestAdapter_ConvertToPersistClickDataProto(t *testing.T) { + adapter := Adapter{} + + tests := []struct { + name string + request *PersistClickDataRequest + want *pb.PersistClickDataRequest + }{ + { + name: "nil request", + request: nil, + want: nil, + }, + { + name: "valid request", + request: &PersistClickDataRequest{ + UserId: "user123", + Data: []ClickData{ + {CatalogId: 100, ProductId: 200, Timestamp: 1704067200000, Metadata: "meta1"}, + {CatalogId: 101, ProductId: 201, Timestamp: 1704067201000, Metadata: "meta2"}, + }, + }, + want: &pb.PersistClickDataRequest{ + UserId: "user123", + Data: []*pb.ClickData{ + {CatalogId: 100, ProductId: 200, Timestamp: 1704067200000, Metadata: "meta1"}, + {CatalogId: 101, ProductId: 201, Timestamp: 1704067201000, Metadata: "meta2"}, + }, + }, + }, + { + name: "empty data", + request: &PersistClickDataRequest{ + UserId: "user123", + Data: []ClickData{}, + }, + want: &pb.PersistClickDataRequest{ + UserId: "user123", + Data: []*pb.ClickData{}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := adapter.ConvertToPersistClickDataProto(tt.request) + if tt.want == nil { + assert.Nil(t, got) + } else { + assert.Equal(t, tt.want.UserId, got.UserId) + assert.Len(t, got.Data, len(tt.want.Data)) + } + }) + } +} + +func TestAdapter_ConvertToPersistOrderDataProto(t *testing.T) { + adapter := Adapter{} + + tests := []struct { + name string + request *PersistOrderDataRequest + want *pb.PersistOrderDataRequest + }{ + { + name: "nil request", + request: nil, + want: nil, + }, + { + name: "valid request", + request: &PersistOrderDataRequest{ + UserId: "user123", + Data: []OrderData{ + {CatalogId: 100, ProductId: 200, SubOrderNum: "SUB001", Timestamp: 1704067200000, Metadata: "meta1"}, + }, + }, + want: &pb.PersistOrderDataRequest{ + UserId: "user123", + Data: []*pb.OrderData{ + {CatalogId: 100, ProductId: 200, SubOrderNum: "SUB001", Timestamp: 1704067200000, Metadata: "meta1"}, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := adapter.ConvertToPersistOrderDataProto(tt.request) + if tt.want == nil { + assert.Nil(t, got) + } else { + assert.Equal(t, tt.want.UserId, got.UserId) + assert.Len(t, got.Data, len(tt.want.Data)) + } + }) + } +} + +func TestAdapter_ConvertToRetrieveDataProto(t *testing.T) { + adapter := Adapter{} + + tests := []struct { + name string + request *RetrieveDataRequest + want *pb.RetrieveDataRequest + }{ + { + name: "nil request", + request: nil, + want: nil, + }, + { + name: "valid request", + request: &RetrieveDataRequest{ + UserId: "user123", + StartTimestamp: 1704067200000, + EndTimestamp: 1704153600000, + Limit: 100, + }, + want: &pb.RetrieveDataRequest{ + UserId: "user123", + StartTimestamp: 1704067200000, + EndTimestamp: 1704153600000, + Limit: 100, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := adapter.ConvertToRetrieveDataProto(tt.request) + if tt.want == nil { + assert.Nil(t, got) + } else { + assert.Equal(t, tt.want.UserId, got.UserId) + assert.Equal(t, tt.want.StartTimestamp, got.StartTimestamp) + assert.Equal(t, tt.want.EndTimestamp, got.EndTimestamp) + assert.Equal(t, tt.want.Limit, got.Limit) + } + }) + } +} + +func TestAdapter_ConvertToRetrieveInteractionsProto(t *testing.T) { + adapter := Adapter{} + + tests := []struct { + name string + request *RetrieveInteractionsRequest + want *pb.RetrieveInteractionsRequest + }{ + { + name: "nil request", + request: nil, + want: nil, + }, + { + name: "valid request with multiple interaction types", + request: &RetrieveInteractionsRequest{ + UserId: "user123", + InteractionTypes: []InteractionType{InteractionTypeClick, InteractionTypeOrder}, + StartTimestamp: 1704067200000, + EndTimestamp: 1704153600000, + Limit: 100, + }, + want: &pb.RetrieveInteractionsRequest{ + UserId: "user123", + InteractionTypes: []pb.InteractionType{pb.InteractionType_CLICK, pb.InteractionType_ORDER}, + StartTimestamp: 1704067200000, + EndTimestamp: 1704153600000, + Limit: 100, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := adapter.ConvertToRetrieveInteractionsProto(tt.request) + if tt.want == nil { + assert.Nil(t, got) + } else { + assert.Equal(t, tt.want.UserId, got.UserId) + assert.Len(t, got.InteractionTypes, len(tt.want.InteractionTypes)) + } + }) + } +} + +func TestAdapter_ConvertFromPersistDataResponse(t *testing.T) { + adapter := Adapter{} + + tests := []struct { + name string + response *pb.PersistDataResponse + want *PersistDataResponse + }{ + { + name: "nil response", + response: nil, + want: nil, + }, + { + name: "valid response", + response: &pb.PersistDataResponse{ + Message: "success", + }, + want: &PersistDataResponse{ + Message: "success", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := adapter.ConvertFromPersistDataResponse(tt.response) + if tt.want == nil { + assert.Nil(t, got) + } else { + assert.Equal(t, tt.want.Message, got.Message) + } + }) + } +} + +func TestAdapter_ConvertFromRetrieveClickDataResponse(t *testing.T) { + adapter := Adapter{} + + tests := []struct { + name string + response *pb.RetrieveClickDataResponse + want *RetrieveClickDataResponse + }{ + { + name: "nil response", + response: nil, + want: &RetrieveClickDataResponse{Data: []ClickEvent{}}, + }, + { + name: "empty data", + response: &pb.RetrieveClickDataResponse{Data: nil}, + want: &RetrieveClickDataResponse{Data: []ClickEvent{}}, + }, + { + name: "valid response", + response: &pb.RetrieveClickDataResponse{ + Data: []*pb.ClickEvent{ + {CatalogId: 100, ProductId: 200, Timestamp: 1704067200000, Metadata: "meta1"}, + {CatalogId: 101, ProductId: 201, Timestamp: 1704067201000, Metadata: "meta2"}, + }, + }, + want: &RetrieveClickDataResponse{ + Data: []ClickEvent{ + {CatalogId: 100, ProductId: 200, Timestamp: 1704067200000, Metadata: "meta1"}, + {CatalogId: 101, ProductId: 201, Timestamp: 1704067201000, Metadata: "meta2"}, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := adapter.ConvertFromRetrieveClickDataResponse(tt.response) + assert.Len(t, got.Data, len(tt.want.Data)) + for i, e := range got.Data { + assert.Equal(t, tt.want.Data[i].CatalogId, e.CatalogId) + assert.Equal(t, tt.want.Data[i].ProductId, e.ProductId) + } + }) + } +} + +func TestAdapter_ConvertFromRetrieveOrderDataResponse(t *testing.T) { + adapter := Adapter{} + + tests := []struct { + name string + response *pb.RetrieveOrderDataResponse + want *RetrieveOrderDataResponse + }{ + { + name: "nil response", + response: nil, + want: &RetrieveOrderDataResponse{Data: []OrderEvent{}}, + }, + { + name: "valid response", + response: &pb.RetrieveOrderDataResponse{ + Data: []*pb.OrderEvent{ + {CatalogId: 100, ProductId: 200, SubOrderNum: "SUB001", Timestamp: 1704067200000, Metadata: "meta1"}, + }, + }, + want: &RetrieveOrderDataResponse{ + Data: []OrderEvent{ + {CatalogId: 100, ProductId: 200, SubOrderNum: "SUB001", Timestamp: 1704067200000, Metadata: "meta1"}, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := adapter.ConvertFromRetrieveOrderDataResponse(tt.response) + assert.Len(t, got.Data, len(tt.want.Data)) + }) + } +} + +func TestAdapter_ConvertFromRetrieveInteractionsResponse(t *testing.T) { + adapter := Adapter{} + + tests := []struct { + name string + response *pb.RetrieveInteractionsResponse + wantLen int + }{ + { + name: "nil response", + response: nil, + wantLen: 0, + }, + { + name: "empty data", + response: &pb.RetrieveInteractionsResponse{Data: nil}, + wantLen: 0, + }, + { + name: "valid response", + response: &pb.RetrieveInteractionsResponse{ + Data: map[string]*pb.InteractionData{ + "clicks": { + ClickEvents: []*pb.ClickEvent{ + {CatalogId: 100, ProductId: 200, Timestamp: 1704067200000}, + }, + }, + }, + }, + wantLen: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := adapter.ConvertFromRetrieveInteractionsResponse(tt.response) + assert.Len(t, got.Data, tt.wantLen) + }) + } +} diff --git a/go-sdk/pkg/interaction-store/client.go b/go-sdk/pkg/interaction-store/client.go new file mode 100644 index 00000000..9324d864 --- /dev/null +++ b/go-sdk/pkg/interaction-store/client.go @@ -0,0 +1,207 @@ +package interactionstore + +import ( + "context" + "errors" + "fmt" + "time" + + pb "github.com/Meesho/BharatMLStack/go-sdk/pkg/proto/interaction-store/timeseries" + "github.com/rs/zerolog/log" + "google.golang.org/grpc/metadata" +) + +const ( + headerCallerID = "interaction-store-caller-id" +) + +// Client defines the interface for interaction-store operations +type Client interface { + // PersistClickData persists click interaction data + PersistClickData(ctx context.Context, request *PersistClickDataRequest) (*PersistDataResponse, error) + // PersistOrderData persists order interaction data + PersistOrderData(ctx context.Context, request *PersistOrderDataRequest) (*PersistDataResponse, error) + // RetrieveClickInteractions retrieves click interactions for a user + RetrieveClickInteractions(ctx context.Context, request *RetrieveDataRequest) (*RetrieveClickDataResponse, error) + // RetrieveOrderInteractions retrieves order interactions for a user + RetrieveOrderInteractions(ctx context.Context, request *RetrieveDataRequest) (*RetrieveOrderDataResponse, error) + // RetrieveInteractions retrieves multiple interaction types for a user + RetrieveInteractions(ctx context.Context, request *RetrieveInteractionsRequest) (*RetrieveInteractionsResponse, error) +} + +// ClientV1 represents version 1 of the interaction-store client +type ClientV1 struct { + grpcClient *GRPCClient + client pb.InteractionStoreTimeSeriesServiceClient + adapter Adapter + callerId string +} + +// NewClientV1 creates a new instance of the interaction-store client (v1) +func NewClientV1(config *Config, timing func(name string, value time.Duration, tags []string), count func(name string, value int64, tags []string)) *ClientV1 { + validateConfig(config) + + conn := NewConnFromConfig(config, "interaction-store", timing, count) + + return &ClientV1{ + grpcClient: conn, + client: pb.NewInteractionStoreTimeSeriesServiceClient(conn), + adapter: Adapter{}, + callerId: config.CallerId, + } +} + +// PersistClickData persists click interaction data +func (c *ClientV1) PersistClickData(ctx context.Context, request *PersistClickDataRequest) (*PersistDataResponse, error) { + if ctx == nil { + ctx = context.Background() + } + + protoRequest := c.adapter.ConvertToPersistClickDataProto(request) + if protoRequest == nil { + return nil, errors.New("failed to convert persist click data request to proto format") + } + + ctx = c.withAuthMetadata(ctx) + ctx, cancel := c.withTimeout(ctx) + defer cancel() + + response, err := c.client.PersistClickData(ctx, protoRequest) + if err != nil { + return nil, fmt.Errorf("failed to persist click data: %w", err) + } + + if response == nil { + return nil, errors.New("empty response from server") + } + + return c.adapter.ConvertFromPersistDataResponse(response), nil +} + +// PersistOrderData persists order interaction data +func (c *ClientV1) PersistOrderData(ctx context.Context, request *PersistOrderDataRequest) (*PersistDataResponse, error) { + if ctx == nil { + ctx = context.Background() + } + + protoRequest := c.adapter.ConvertToPersistOrderDataProto(request) + if protoRequest == nil { + return nil, errors.New("failed to convert persist order data request to proto format") + } + + ctx = c.withAuthMetadata(ctx) + ctx, cancel := c.withTimeout(ctx) + defer cancel() + + response, err := c.client.PersistOrderData(ctx, protoRequest) + if err != nil { + return nil, fmt.Errorf("failed to persist order data: %w", err) + } + + if response == nil { + return nil, errors.New("empty response from server") + } + + return c.adapter.ConvertFromPersistDataResponse(response), nil +} + +// RetrieveClickInteractions retrieves click interactions for a user +func (c *ClientV1) RetrieveClickInteractions(ctx context.Context, request *RetrieveDataRequest) (*RetrieveClickDataResponse, error) { + if ctx == nil { + ctx = context.Background() + } + + protoRequest := c.adapter.ConvertToRetrieveDataProto(request) + if protoRequest == nil { + return nil, errors.New("failed to convert retrieve click data request to proto format") + } + + ctx = c.withAuthMetadata(ctx) + ctx, cancel := c.withTimeout(ctx) + defer cancel() + + response, err := c.client.RetrieveClickInteractions(ctx, protoRequest) + if err != nil { + log.Error().Err(err).Msg("failed to retrieve click interactions") + return nil, fmt.Errorf("failed to retrieve click interactions: %w", err) + } + + return c.adapter.ConvertFromRetrieveClickDataResponse(response), nil +} + +// RetrieveOrderInteractions retrieves order interactions for a user +func (c *ClientV1) RetrieveOrderInteractions(ctx context.Context, request *RetrieveDataRequest) (*RetrieveOrderDataResponse, error) { + if ctx == nil { + ctx = context.Background() + } + + protoRequest := c.adapter.ConvertToRetrieveDataProto(request) + if protoRequest == nil { + return nil, errors.New("failed to convert retrieve order data request to proto format") + } + + ctx = c.withAuthMetadata(ctx) + ctx, cancel := c.withTimeout(ctx) + defer cancel() + + response, err := c.client.RetrieveOrderInteractions(ctx, protoRequest) + if err != nil { + log.Error().Err(err).Msg("failed to retrieve order interactions") + return nil, fmt.Errorf("failed to retrieve order interactions: %w", err) + } + + return c.adapter.ConvertFromRetrieveOrderDataResponse(response), nil +} + +// RetrieveInteractions retrieves multiple interaction types for a user +func (c *ClientV1) RetrieveInteractions(ctx context.Context, request *RetrieveInteractionsRequest) (*RetrieveInteractionsResponse, error) { + if ctx == nil { + ctx = context.Background() + } + + protoRequest := c.adapter.ConvertToRetrieveInteractionsProto(request) + if protoRequest == nil { + return nil, errors.New("failed to convert retrieve interactions request to proto format") + } + + ctx = c.withAuthMetadata(ctx) + ctx, cancel := c.withTimeout(ctx) + defer cancel() + + response, err := c.client.RetrieveInteractions(ctx, protoRequest) + if err != nil { + log.Error().Err(err).Msg("failed to retrieve interactions") + return nil, fmt.Errorf("failed to retrieve interactions: %w", err) + } + + return c.adapter.ConvertFromRetrieveInteractionsResponse(response), nil +} + +// withAuthMetadata adds authentication metadata to context +func (c *ClientV1) withAuthMetadata(ctx context.Context) context.Context { + md := metadata.New(map[string]string{ + headerCallerID: c.callerId, + }) + return metadata.NewOutgoingContext(ctx, md) +} + +// withTimeout adds timeout to context +func (c *ClientV1) withTimeout(ctx context.Context) (context.Context, context.CancelFunc) { + timeout := time.Duration(c.grpcClient.DeadLine) * time.Millisecond + return context.WithTimeout(ctx, timeout) +} + +func validateConfig(config *Config) { + if config == nil { + panic("Configuration is nil. Please provide a valid config.") + } + if len(config.Host) == 0 { + panic("Configuration error: Host is empty. Please provide a valid host.") + } + if len(config.Port) == 0 { + panic("Configuration error: Port is empty. Please provide a valid port.") + } + if len(config.CallerId) == 0 { + panic("Configuration error: Caller ID is empty. Please provide a valid caller ID.") + } +} diff --git a/go-sdk/pkg/interaction-store/client_test.go b/go-sdk/pkg/interaction-store/client_test.go new file mode 100644 index 00000000..b5217290 --- /dev/null +++ b/go-sdk/pkg/interaction-store/client_test.go @@ -0,0 +1,468 @@ +package interactionstore + +import ( + "context" + "errors" + "testing" + + pb "github.com/Meesho/BharatMLStack/go-sdk/pkg/proto/interaction-store/timeseries" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "google.golang.org/grpc" +) + +// MockInteractionStoreClient is a mock implementation of InteractionStoreTimeSeriesServiceClient +type MockInteractionStoreClient struct { + mock.Mock +} + +func (m *MockInteractionStoreClient) PersistClickData(ctx context.Context, in *pb.PersistClickDataRequest, opts ...grpc.CallOption) (*pb.PersistDataResponse, error) { + args := m.Called(ctx, in, opts) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*pb.PersistDataResponse), args.Error(1) +} + +func (m *MockInteractionStoreClient) PersistOrderData(ctx context.Context, in *pb.PersistOrderDataRequest, opts ...grpc.CallOption) (*pb.PersistDataResponse, error) { + args := m.Called(ctx, in, opts) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*pb.PersistDataResponse), args.Error(1) +} + +func (m *MockInteractionStoreClient) RetrieveClickInteractions(ctx context.Context, in *pb.RetrieveDataRequest, opts ...grpc.CallOption) (*pb.RetrieveClickDataResponse, error) { + args := m.Called(ctx, in, opts) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*pb.RetrieveClickDataResponse), args.Error(1) +} + +func (m *MockInteractionStoreClient) RetrieveOrderInteractions(ctx context.Context, in *pb.RetrieveDataRequest, opts ...grpc.CallOption) (*pb.RetrieveOrderDataResponse, error) { + args := m.Called(ctx, in, opts) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*pb.RetrieveOrderDataResponse), args.Error(1) +} + +func (m *MockInteractionStoreClient) RetrieveInteractions(ctx context.Context, in *pb.RetrieveInteractionsRequest, opts ...grpc.CallOption) (*pb.RetrieveInteractionsResponse, error) { + args := m.Called(ctx, in, opts) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*pb.RetrieveInteractionsResponse), args.Error(1) +} + +func TestNewClientV1(t *testing.T) { + tests := []struct { + name string + config *Config + }{ + { + name: "success", + config: &Config{ + Host: "localhost", + Port: "50051", + DeadLine: 1000, + PlainText: true, + CallerId: "test-caller", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := NewClientV1(tt.config, nil, nil) + assert.Equal(t, tt.config.CallerId, got.callerId) + assert.NotNil(t, got.grpcClient) + assert.NotNil(t, got.client) + }) + } +} + +func TestClientV1_PersistClickData(t *testing.T) { + mockClient := &MockInteractionStoreClient{} + client := &ClientV1{ + grpcClient: &GRPCClient{DeadLine: 1000}, + client: mockClient, + adapter: Adapter{}, + callerId: "test-caller", + } + + tests := []struct { + name string + request *PersistClickDataRequest + mockResult *pb.PersistDataResponse + mockError error + wantErr bool + }{ + { + name: "success", + request: &PersistClickDataRequest{ + UserId: "user123", + Data: []ClickData{ + {CatalogId: 100, ProductId: 200, Timestamp: 1704067200000}, + }, + }, + mockResult: &pb.PersistDataResponse{Message: "success"}, + mockError: nil, + wantErr: false, + }, + { + name: "server error", + request: &PersistClickDataRequest{ + UserId: "user123", + Data: []ClickData{ + {CatalogId: 100, ProductId: 200, Timestamp: 1704067200000}, + }, + }, + mockResult: nil, + mockError: errors.New("server error"), + wantErr: true, + }, + { + name: "nil request", + request: nil, + mockResult: nil, + mockError: nil, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.request != nil { + mockClient.On("PersistClickData", mock.Anything, mock.Anything, mock.Anything). + Return(tt.mockResult, tt.mockError).Once() + } + + result, err := client.PersistClickData(context.Background(), tt.request) + + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.NotNil(t, result) + assert.Equal(t, tt.mockResult.Message, result.Message) + } + }) + } +} + +func TestClientV1_PersistOrderData(t *testing.T) { + mockClient := &MockInteractionStoreClient{} + client := &ClientV1{ + grpcClient: &GRPCClient{DeadLine: 1000}, + client: mockClient, + adapter: Adapter{}, + callerId: "test-caller", + } + + tests := []struct { + name string + request *PersistOrderDataRequest + mockResult *pb.PersistDataResponse + mockError error + wantErr bool + }{ + { + name: "success", + request: &PersistOrderDataRequest{ + UserId: "user123", + Data: []OrderData{ + {CatalogId: 100, ProductId: 200, SubOrderNum: "SUB001", Timestamp: 1704067200000}, + }, + }, + mockResult: &pb.PersistDataResponse{Message: "success"}, + mockError: nil, + wantErr: false, + }, + { + name: "server error", + request: &PersistOrderDataRequest{ + UserId: "user123", + Data: []OrderData{ + {CatalogId: 100, ProductId: 200, SubOrderNum: "SUB001", Timestamp: 1704067200000}, + }, + }, + mockResult: nil, + mockError: errors.New("server error"), + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockClient.On("PersistOrderData", mock.Anything, mock.Anything, mock.Anything). + Return(tt.mockResult, tt.mockError).Once() + + result, err := client.PersistOrderData(context.Background(), tt.request) + + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.NotNil(t, result) + } + }) + } +} + +func TestClientV1_RetrieveClickInteractions(t *testing.T) { + mockClient := &MockInteractionStoreClient{} + client := &ClientV1{ + grpcClient: &GRPCClient{DeadLine: 1000}, + client: mockClient, + adapter: Adapter{}, + callerId: "test-caller", + } + + tests := []struct { + name string + request *RetrieveDataRequest + mockResult *pb.RetrieveClickDataResponse + mockError error + wantErr bool + }{ + { + name: "success", + request: &RetrieveDataRequest{ + UserId: "user123", + StartTimestamp: 1704067200000, + EndTimestamp: 1704153600000, + Limit: 100, + }, + mockResult: &pb.RetrieveClickDataResponse{ + Data: []*pb.ClickEvent{ + {CatalogId: 100, ProductId: 200, Timestamp: 1704067200000}, + }, + }, + mockError: nil, + wantErr: false, + }, + { + name: "server error", + request: &RetrieveDataRequest{ + UserId: "user123", + StartTimestamp: 1704067200000, + EndTimestamp: 1704153600000, + Limit: 100, + }, + mockResult: nil, + mockError: errors.New("server error"), + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockClient.On("RetrieveClickInteractions", mock.Anything, mock.Anything, mock.Anything). + Return(tt.mockResult, tt.mockError).Once() + + result, err := client.RetrieveClickInteractions(context.Background(), tt.request) + + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.NotNil(t, result) + assert.Len(t, result.Data, len(tt.mockResult.Data)) + } + }) + } +} + +func TestClientV1_RetrieveOrderInteractions(t *testing.T) { + mockClient := &MockInteractionStoreClient{} + client := &ClientV1{ + grpcClient: &GRPCClient{DeadLine: 1000}, + client: mockClient, + adapter: Adapter{}, + callerId: "test-caller", + } + + tests := []struct { + name string + request *RetrieveDataRequest + mockResult *pb.RetrieveOrderDataResponse + mockError error + wantErr bool + }{ + { + name: "success", + request: &RetrieveDataRequest{ + UserId: "user123", + StartTimestamp: 1704067200000, + EndTimestamp: 1704153600000, + Limit: 100, + }, + mockResult: &pb.RetrieveOrderDataResponse{ + Data: []*pb.OrderEvent{ + {CatalogId: 100, ProductId: 200, SubOrderNum: "SUB001", Timestamp: 1704067200000}, + }, + }, + mockError: nil, + wantErr: false, + }, + { + name: "server error", + request: &RetrieveDataRequest{ + UserId: "user123", + StartTimestamp: 1704067200000, + EndTimestamp: 1704153600000, + Limit: 100, + }, + mockResult: nil, + mockError: errors.New("server error"), + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockClient.On("RetrieveOrderInteractions", mock.Anything, mock.Anything, mock.Anything). + Return(tt.mockResult, tt.mockError).Once() + + result, err := client.RetrieveOrderInteractions(context.Background(), tt.request) + + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.NotNil(t, result) + } + }) + } +} + +func TestClientV1_RetrieveInteractions(t *testing.T) { + mockClient := &MockInteractionStoreClient{} + client := &ClientV1{ + grpcClient: &GRPCClient{DeadLine: 1000}, + client: mockClient, + adapter: Adapter{}, + callerId: "test-caller", + } + + tests := []struct { + name string + request *RetrieveInteractionsRequest + mockResult *pb.RetrieveInteractionsResponse + mockError error + wantErr bool + }{ + { + name: "success", + request: &RetrieveInteractionsRequest{ + UserId: "user123", + InteractionTypes: []InteractionType{InteractionTypeClick, InteractionTypeOrder}, + StartTimestamp: 1704067200000, + EndTimestamp: 1704153600000, + Limit: 100, + }, + mockResult: &pb.RetrieveInteractionsResponse{ + Data: map[string]*pb.InteractionData{ + "clicks": { + ClickEvents: []*pb.ClickEvent{ + {CatalogId: 100, ProductId: 200, Timestamp: 1704067200000}, + }, + }, + }, + }, + mockError: nil, + wantErr: false, + }, + { + name: "server error", + request: &RetrieveInteractionsRequest{ + UserId: "user123", + InteractionTypes: []InteractionType{InteractionTypeClick}, + StartTimestamp: 1704067200000, + EndTimestamp: 1704153600000, + Limit: 100, + }, + mockResult: nil, + mockError: errors.New("server error"), + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockClient.On("RetrieveInteractions", mock.Anything, mock.Anything, mock.Anything). + Return(tt.mockResult, tt.mockError).Once() + + result, err := client.RetrieveInteractions(context.Background(), tt.request) + + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.NotNil(t, result) + } + }) + } +} + +func TestValidateConfig(t *testing.T) { + tests := []struct { + name string + config *Config + shouldPanic bool + }{ + { + name: "nil config", + config: nil, + shouldPanic: true, + }, + { + name: "empty host", + config: &Config{ + Host: "", + Port: "50051", + CallerId: "test", + }, + shouldPanic: true, + }, + { + name: "empty port", + config: &Config{ + Host: "localhost", + Port: "", + CallerId: "test", + }, + shouldPanic: true, + }, + { + name: "empty caller id", + config: &Config{ + Host: "localhost", + Port: "50051", + CallerId: "", + }, + shouldPanic: true, + }, + { + name: "valid config", + config: &Config{ + Host: "localhost", + Port: "50051", + CallerId: "test", + }, + shouldPanic: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.shouldPanic { + assert.Panics(t, func() { validateConfig(tt.config) }) + } else { + assert.NotPanics(t, func() { validateConfig(tt.config) }) + } + }) + } +} diff --git a/go-sdk/pkg/interaction-store/grpc.go b/go-sdk/pkg/interaction-store/grpc.go new file mode 100644 index 00000000..92c0c7f5 --- /dev/null +++ b/go-sdk/pkg/interaction-store/grpc.go @@ -0,0 +1,109 @@ +package interactionstore + +import ( + "context" + "crypto/tls" + "errors" + "strconv" + "time" + + "github.com/rs/zerolog/log" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/resolver" + "google.golang.org/grpc/status" +) + +const ( + ResolverDefaultScheme = "dns" +) + +// GRPCClient wraps a gRPC client connection with metrics support +type GRPCClient struct { + Conn *grpc.ClientConn + DeadLine int64 + externalServiceName string + timing func(name string, value time.Duration, tags []string) + count func(name string, value int64, tags []string) +} + +// NewConnFromConfig creates a new gRPC connection from the provided configuration +func NewConnFromConfig(config *Config, externalServiceName string, timing func(name string, value time.Duration, tags []string), count func(name string, value int64, tags []string)) *GRPCClient { + conn, err := getGRPCConnections(*config) + if err != nil { + log.Panic().Msgf("error while GRPC connection initialization. %s", err) + } + conn.externalServiceName = externalServiceName + conn.timing = timing + conn.count = count + return conn +} + +func getGRPCConnections(config Config) (*GRPCClient, error) { + resolver.SetDefaultScheme(ResolverDefaultScheme) + var gConn *grpc.ClientConn + var err error + if config.PlainText { + gConn, err = grpc.NewClient(config.Host+":"+config.Port, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`), + ) + } else { + creds := credentials.NewTLS(&tls.Config{InsecureSkipVerify: true}) + gConn, err = grpc.NewClient(config.Host+":"+config.Port, + grpc.WithTransportCredentials(creds), + grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`)) + } + if err != nil { + return nil, err + } + return &GRPCClient{Conn: gConn, DeadLine: int64(config.DeadLine)}, nil +} + +// Invoke is a wrapper around grpc.ClientConn.Invoke with metrics support +func (c *GRPCClient) Invoke(ctx context.Context, method string, args any, reply any, opts ...grpc.CallOption) error { + startTime := time.Now() + err := c.Conn.Invoke(ctx, method, args, reply, opts...) + var code uint32 = 0 + if err != nil { + if e, ok := status.FromError(err); ok { + code = uint32(e.Code()) + } + } + latency := time.Since(startTime) + latencyTags := BuildExternalGRPCServiceLatencyTags(c.externalServiceName, method, int(code)) + countTags := BuildExternalGRPCServiceCountTags(c.externalServiceName, method, int(code)) + if c.timing != nil { + c.timing("interaction-store.grpc.invoke.latency", latency, latencyTags) + } + if c.count != nil { + c.count("interaction-store.grpc.invoke.count", 1, countTags) + } + return err +} + +// BuildExternalGRPCServiceLatencyTags builds tags for latency metrics +func BuildExternalGRPCServiceLatencyTags(service, method string, statusCode int) []string { + return []string{ + "communication_protocol:grpc", + "external_service:" + service, + "method:" + method, + "grpc_status_code:" + strconv.Itoa(statusCode), + } +} + +// BuildExternalGRPCServiceCountTags builds tags for count metrics +func BuildExternalGRPCServiceCountTags(service, method string, statusCode int) []string { + return []string{ + "communication_protocol:grpc", + "external_service:" + service, + "method:" + method, + "grpc_status_code:" + strconv.Itoa(statusCode), + } +} + +// NewStream is not implemented for this client +func (c *GRPCClient) NewStream(ctx context.Context, desc *grpc.StreamDesc, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) { + return nil, errors.New("NewStream is not implemented") +} diff --git a/go-sdk/pkg/interaction-store/init.go b/go-sdk/pkg/interaction-store/init.go new file mode 100644 index 00000000..d96e8c46 --- /dev/null +++ b/go-sdk/pkg/interaction-store/init.go @@ -0,0 +1,45 @@ +package interactionstore + +import ( + "sync" + "time" + + "github.com/rs/zerolog/log" +) + +const ( + Version1 = 1 +) + +var ( + registry = make(map[int]Client) + mut sync.Mutex +) + +func InitClient(version int, conf *Config, timing func(name string, value time.Duration, tags []string), count func(name string, value int64, tags []string)) Client { + mut.Lock() + defer mut.Unlock() + if registry[version] != nil { + log.Panic().Msgf("Client for version %d already initialised", version) + } + switch version { + case Version1: + registry[version] = NewClientV1(conf, timing, count) + default: + log.Panic().Msgf("Unsupported client version: %d", version) + } + return registry[version] +} + +func GetInstance(version int) Client { + if registry[version] == nil { + log.Panic().Msgf("Client for version %d not initialised", version) + } + return registry[version] +} + +func ResetRegistry() { + mut.Lock() + defer mut.Unlock() + registry = make(map[int]Client) +} diff --git a/go-sdk/pkg/interaction-store/models.go b/go-sdk/pkg/interaction-store/models.go new file mode 100644 index 00000000..704598a1 --- /dev/null +++ b/go-sdk/pkg/interaction-store/models.go @@ -0,0 +1,116 @@ +package interactionstore + +import ( + pb "github.com/Meesho/BharatMLStack/go-sdk/pkg/proto/interaction-store/timeseries" +) + +type Config struct { + Host string + Port string + DeadLine int + PlainText bool + CallerId string +} + +type InteractionType int + +const ( + InteractionTypeClick InteractionType = 0 + InteractionTypeOrder InteractionType = 1 +) + +type PersistClickDataRequest struct { + UserId string `json:"user_id"` + Data []ClickData `json:"data"` +} + +type ClickData struct { + CatalogId int32 `json:"catalog_id"` + ProductId int32 `json:"product_id"` + Timestamp int64 `json:"timestamp"` + Metadata string `json:"metadata"` +} + +type PersistOrderDataRequest struct { + UserId string `json:"user_id"` + Data []OrderData `json:"data"` +} + +type OrderData struct { + CatalogId int32 `json:"catalog_id"` + ProductId int32 `json:"product_id"` + SubOrderNum string `json:"sub_order_num"` + Timestamp int64 `json:"timestamp"` + Metadata string `json:"metadata"` +} + +type PersistDataResponse struct { + Message string `json:"message"` +} + +type RetrieveDataRequest struct { + UserId string `json:"user_id"` + StartTimestamp int64 `json:"start_timestamp"` + EndTimestamp int64 `json:"end_timestamp"` + Limit int32 `json:"limit"` +} + +type RetrieveInteractionsRequest struct { + UserId string `json:"user_id"` + InteractionTypes []InteractionType `json:"interaction_types"` + StartTimestamp int64 `json:"start_timestamp"` + EndTimestamp int64 `json:"end_timestamp"` + Limit int32 `json:"limit"` +} + +type ClickEvent struct { + CatalogId int32 `json:"catalog_id"` + ProductId int32 `json:"product_id"` + Timestamp int64 `json:"timestamp"` + Metadata string `json:"metadata"` +} + +type OrderEvent struct { + CatalogId int32 `json:"catalog_id"` + ProductId int32 `json:"product_id"` + SubOrderNum string `json:"sub_order_num"` + Timestamp int64 `json:"timestamp"` + Metadata string `json:"metadata"` +} + +type RetrieveClickDataResponse struct { + Data []ClickEvent `json:"data"` +} + +type RetrieveOrderDataResponse struct { + Data []OrderEvent `json:"data"` +} + +type InteractionData struct { + ClickEvents []ClickEvent `json:"click_events"` + OrderEvents []OrderEvent `json:"order_events"` +} + +type RetrieveInteractionsResponse struct { + Data map[string]InteractionData `json:"data"` +} + +type Response struct { + Err string `json:"err"` + Resp any `json:"resp"` +} + +type ClickResponse struct { + Err string `json:"err"` + Resp *pb.RetrieveClickDataResponse `json:"resp"` +} + +type OrderResponse struct { + Err string `json:"err"` + Resp *pb.RetrieveOrderDataResponse `json:"resp"` +} + +type InteractionsResponse struct { + Err string `json:"err"` + Resp *pb.RetrieveInteractionsResponse `json:"resp"` +} diff --git a/helix-client/pkg/metric/metric.go b/go-sdk/pkg/metric/metric.go similarity index 100% rename from helix-client/pkg/metric/metric.go rename to go-sdk/pkg/metric/metric.go diff --git a/helix-client/pkg/metric/tag.go b/go-sdk/pkg/metric/tag.go similarity index 100% rename from helix-client/pkg/metric/tag.go rename to go-sdk/pkg/metric/tag.go diff --git a/helix-client/pkg/metric/tag_test.go b/go-sdk/pkg/metric/tag_test.go similarity index 100% rename from helix-client/pkg/metric/tag_test.go rename to go-sdk/pkg/metric/tag_test.go diff --git a/go-sdk/pkg/middleware/grpclogger.go b/go-sdk/pkg/middleware/grpclogger.go new file mode 100644 index 00000000..7a362aab --- /dev/null +++ b/go-sdk/pkg/middleware/grpclogger.go @@ -0,0 +1,72 @@ +package middleware + +import ( + "context" + "encoding/json" + "strconv" + "strings" + "time" + + "github.com/Meesho/BharatMLStack/go-sdk/pkg/metric" + "github.com/rs/zerolog/log" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +const ( + MetadataUserContext = "USER-CONTEXT" +) + +func GRPCLogger(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, + handler grpc.UnaryHandler) (resp interface{}, err error) { + startTime := time.Now() + + // Extract metadata from the context + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + md = metadata.MD{} + } + + // Extract relevant information from the request + userContext := md.Get(MetadataUserContext) + requestHeaders, _ := json.Marshal(md) + + // Call the gRPC handler to handle the request + resp, err = handler(ctx, req) + statusCode := codes.OK + if err != nil { + statusCode = status.Code(err) + } + // Extract relevant information from the response + responseTime := time.Since(startTime) + + // Log the request and response information + logMessage := strings.Join([]string{ + info.FullMethod, + strconv.Itoa(int(statusCode)), + responseTime.String(), + string(requestHeaders), + }, " | ") + if err != nil { + log.Error().Err(err).Msg(logMessage) + } else { + log.Info().Msg(logMessage) + } + telemetryMiddleware(info, responseTime, statusCode, userContext) + return resp, err +} + +func telemetryMiddleware(info *grpc.UnaryServerInfo, responseTime time.Duration, + statusCode codes.Code, userContext []string) { + metricTags := metric.BuildTag( + metric.NewTag(metric.TagPath, info.FullMethod), + metric.NewTag(metric.TagGrpcStatusCode, strconv.Itoa(int(statusCode))), + ) + if len(userContext) != 0 { + metricTags = append(metricTags, metric.TagAsString(metric.TagUserContext, userContext[0])) + } + metric.Timing(metric.ApiRequestLatency, responseTime, metricTags) + metric.Incr(metric.ApiRequestCount, metricTags) +} diff --git a/helix-client/pkg/middleware/grpcrecovery.go b/go-sdk/pkg/middleware/grpcrecovery.go similarity index 100% rename from helix-client/pkg/middleware/grpcrecovery.go rename to go-sdk/pkg/middleware/grpcrecovery.go diff --git a/go-sdk/pkg/middleware/httplogger.go b/go-sdk/pkg/middleware/httplogger.go new file mode 100644 index 00000000..160742fc --- /dev/null +++ b/go-sdk/pkg/middleware/httplogger.go @@ -0,0 +1,49 @@ +package middleware + +import ( + "strconv" + "time" + + "github.com/gin-gonic/gin" + "github.com/rs/zerolog/log" + + "github.com/Meesho/BharatMLStack/go-sdk/pkg/api/http" + "github.com/Meesho/BharatMLStack/go-sdk/pkg/metric" +) + +// HTTPLogger logs the request +func HTTPLogger() gin.HandlerFunc { + return func(c *gin.Context) { + startTime := time.Now() + c.Next() + endTime := time.Now() + + latency := endTime.Sub(startTime) + + // Example: + // r.GET("/users/:id", func(c *gin.Context) { + // // Request: GET /users/42 + // route := c.FullPath() // "/users/:id" + // }) + route := c.FullPath() + if route == "" { + route = "unknown" + } + + clientIP := c.ClientIP() + method := c.Request.Method + statusCode := c.Writer.Status() + userContext := c.Request.Header.Get(http.HeaderUserContext) + + metricTags := metric.BuildTag( + metric.NewTag(metric.TagPath, route), + metric.NewTag(metric.TagMethod, method), + metric.NewTag(metric.TagHttpStatusCode, strconv.Itoa(statusCode)), + metric.NewTag(metric.TagHttpStatusCode, strconv.Itoa(statusCode)), // TODO: Remove this support in future + metric.NewTag(metric.TagUserContext, userContext), + ) + metric.Incr(metric.ApiRequestCount, metricTags) + metric.Timing(metric.ApiRequestLatency, latency, metricTags) // TODO: Remove this support in future + log.Info().Msgf("[access] [%s] %s %s %d %v", clientIP, method, route, statusCode, latency) + } +} diff --git a/go-sdk/pkg/middleware/httplogger_test.go b/go-sdk/pkg/middleware/httplogger_test.go new file mode 100644 index 00000000..887eb516 --- /dev/null +++ b/go-sdk/pkg/middleware/httplogger_test.go @@ -0,0 +1,109 @@ +package middleware + +import ( + "bytes" + "net/http" + "net/http/httptest" + "testing" + "time" + + http2 "github.com/Meesho/BharatMLStack/go-sdk/pkg/api/http" + "github.com/gin-gonic/gin" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "github.com/stretchr/testify/assert" +) + +func TestHTTPLogger(t *testing.T) { + gin.SetMode(gin.TestMode) + + var logBuffer bytes.Buffer + log.Logger = zerolog.New(&logBuffer).With().Timestamp().Logger() + + t.Run("logs successful request with all parameters", func(t *testing.T) { + logBuffer.Reset() + router := gin.New() + router.Use(HTTPLogger()) + router.GET("/users/:id", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"message": "success"}) + }) + + req := httptest.NewRequest("GET", "/users/123", nil) + req.Header.Set(http2.HeaderUserContext, "anonymous") + w := httptest.NewRecorder() + + router.ServeHTTP(w, req) + assert.Equal(t, http.StatusOK, w.Code) + + logOutput := logBuffer.String() + assert.Contains(t, logOutput, "[access]") + assert.Contains(t, logOutput, "GET") + assert.Contains(t, logOutput, "/users/:id") + assert.Contains(t, logOutput, "200") + assert.Contains(t, logOutput, "192.0.2.1") + }) + + t.Run("handles error response", func(t *testing.T) { + logBuffer.Reset() + router := gin.New() + router.Use(HTTPLogger()) + router.GET("/error", func(c *gin.Context) { + c.JSON(http.StatusInternalServerError, gin.H{"error": "internal error"}) + }) + + req := httptest.NewRequest("GET", "/error", nil) + req.Header.Set(http2.HeaderUserContext, "anonymous") + w := httptest.NewRecorder() + + router.ServeHTTP(w, req) + assert.Equal(t, http.StatusInternalServerError, w.Code) + + logOutput := logBuffer.String() + assert.Contains(t, logOutput, "[access]") + assert.Contains(t, logOutput, "GET") + assert.Contains(t, logOutput, "/error") + assert.Contains(t, logOutput, "500") + }) + + t.Run("handles unknown route", func(t *testing.T) { + logBuffer.Reset() + router := gin.New() + router.Use(HTTPLogger()) + req := httptest.NewRequest("GET", "/*any", nil) + w := httptest.NewRecorder() + + router.ServeHTTP(w, req) + assert.Equal(t, http.StatusNotFound, w.Code) + + logOutput := logBuffer.String() + assert.Contains(t, logOutput, "[access]") + assert.Contains(t, logOutput, "GET") + assert.Contains(t, logOutput, "unknown") + assert.Contains(t, logOutput, "404") + }) + + t.Run("measures request latency", func(t *testing.T) { + logBuffer.Reset() + router := gin.New() + router.Use(HTTPLogger()) + router.GET("/slow", func(c *gin.Context) { + time.Sleep(5 * time.Millisecond) + c.JSON(http.StatusOK, gin.H{"message": "slow response"}) + }) + + req := httptest.NewRequest("GET", "/slow", nil) + w := httptest.NewRecorder() + + start := time.Now() + router.ServeHTTP(w, req) + elapsed := time.Since(start) + assert.Equal(t, http.StatusOK, w.Code) + + assert.GreaterOrEqual(t, elapsed, 5*time.Millisecond) + + logOutput := logBuffer.String() + assert.Contains(t, logOutput, "[access]") + assert.Contains(t, logOutput, "/slow") + assert.Regexp(t, `\d+(\.\d+)?(ms|µs|ns)`, logOutput) + }) +} diff --git a/go-sdk/pkg/middleware/httprecovery.go b/go-sdk/pkg/middleware/httprecovery.go new file mode 100644 index 00000000..384e8987 --- /dev/null +++ b/go-sdk/pkg/middleware/httprecovery.go @@ -0,0 +1,32 @@ +package middleware + +import ( + "fmt" + "runtime/debug" + + "github.com/Meesho/BharatMLStack/go-sdk/pkg/api" + "github.com/gin-gonic/gin" + "github.com/rs/zerolog/log" +) + +// HTTPRecovery handles context errors/panics and sets response code accordingly +func HTTPRecovery() gin.HandlerFunc { + return func(c *gin.Context) { + defer func() { + if len(c.Errors) > 0 { + err := c.Errors.Last().Err + if apiErr, ok := err.(*api.Error); ok { + c.JSON(apiErr.StatusCode, gin.H{"error": apiErr.Message}) + c.Abort() + } + } + if err := recover(); err != nil { + log.Error().Msgf("Panic occurred: %v\n%s", err, debug.Stack()) + errorMsg := fmt.Sprintf("%v", err) + c.JSON(500, gin.H{"error": errorMsg}) + c.Abort() + } + }() + c.Next() + } +} diff --git a/go-sdk/pkg/proto/interaction-store/time_series.proto b/go-sdk/pkg/proto/interaction-store/time_series.proto new file mode 100644 index 00000000..2bb89f76 --- /dev/null +++ b/go-sdk/pkg/proto/interaction-store/time_series.proto @@ -0,0 +1,93 @@ +syntax = "proto3"; +package proto; + +option go_package = "github.com/Meesho/BharatMLStack/go-sdk/pkg/proto/interaction-store/timeseries"; + +enum InteractionType { + CLICK = 0; + ORDER = 1; +} + +message PersistClickDataRequest { + string user_id = 1; + repeated ClickData data = 2; +} + +message ClickData { + int32 catalog_id = 1; + int32 product_id = 2; + int64 timestamp = 3; + string metadata = 4; +} + +message PersistOrderDataRequest { + string user_id = 1; + repeated OrderData data = 2; +} + +message OrderData { + int32 catalog_id = 1; + int32 product_id = 2; + string sub_order_num = 3; + int64 timestamp = 4; + string metadata = 5; +} + +message PersistDataResponse { + string message = 1; +} + +message RetrieveDataRequest { + string user_id = 1; + int64 start_timestamp = 2; + int64 end_timestamp = 3; + int32 limit = 4; +} + +message RetrieveInteractionsRequest { + string user_id = 1; + repeated InteractionType interaction_types = 2; + int64 start_timestamp = 3; + int64 end_timestamp = 4; + int32 limit = 5; +} + +message RetrieveInteractionsResponse { + map data = 1; +} + +message InteractionData { + repeated ClickEvent click_events = 1; + repeated OrderEvent order_events = 2; +} + +message RetrieveClickDataResponse { + repeated ClickEvent data = 1; +} + +message ClickEvent { + int32 catalog_id = 2; + int32 product_id = 3; + int64 timestamp = 4; + string metadata = 5; +} + +message RetrieveOrderDataResponse { + repeated OrderEvent data = 1; +} + +message OrderEvent { + int32 catalog_id = 2; + int32 product_id = 3; + string sub_order_num = 4; + int64 timestamp = 5; + string metadata = 6; +} + +service InteractionStoreTimeSeriesService { + rpc PersistClickData(PersistClickDataRequest) returns (PersistDataResponse) {}; + rpc PersistOrderData(PersistOrderDataRequest) returns (PersistDataResponse) {}; + rpc RetrieveClickInteractions(RetrieveDataRequest) returns (RetrieveClickDataResponse) {}; + rpc RetrieveOrderInteractions(RetrieveDataRequest) returns (RetrieveOrderDataResponse) {}; + rpc RetrieveInteractions(RetrieveInteractionsRequest) returns (RetrieveInteractionsResponse) {}; +} diff --git a/go-sdk/pkg/proto/interaction-store/timeseries/time_series.pb.go b/go-sdk/pkg/proto/interaction-store/timeseries/time_series.pb.go new file mode 100644 index 00000000..97fe8117 --- /dev/null +++ b/go-sdk/pkg/proto/interaction-store/timeseries/time_series.pb.go @@ -0,0 +1,993 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v6.33.0 +// source: time_series.proto + +package timeseries + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type InteractionType int32 + +const ( + InteractionType_CLICK InteractionType = 0 + InteractionType_ORDER InteractionType = 1 +) + +// Enum value maps for InteractionType. +var ( + InteractionType_name = map[int32]string{ + 0: "CLICK", + 1: "ORDER", + } + InteractionType_value = map[string]int32{ + "CLICK": 0, + "ORDER": 1, + } +) + +func (x InteractionType) Enum() *InteractionType { + p := new(InteractionType) + *p = x + return p +} + +func (x InteractionType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (InteractionType) Descriptor() protoreflect.EnumDescriptor { + return file_time_series_proto_enumTypes[0].Descriptor() +} + +func (InteractionType) Type() protoreflect.EnumType { + return &file_time_series_proto_enumTypes[0] +} + +func (x InteractionType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use InteractionType.Descriptor instead. +func (InteractionType) EnumDescriptor() ([]byte, []int) { + return file_time_series_proto_rawDescGZIP(), []int{0} +} + +type PersistClickDataRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Data []*ClickData `protobuf:"bytes,2,rep,name=data,proto3" json:"data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PersistClickDataRequest) Reset() { + *x = PersistClickDataRequest{} + mi := &file_time_series_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PersistClickDataRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PersistClickDataRequest) ProtoMessage() {} + +func (x *PersistClickDataRequest) ProtoReflect() protoreflect.Message { + mi := &file_time_series_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PersistClickDataRequest.ProtoReflect.Descriptor instead. +func (*PersistClickDataRequest) Descriptor() ([]byte, []int) { + return file_time_series_proto_rawDescGZIP(), []int{0} +} + +func (x *PersistClickDataRequest) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *PersistClickDataRequest) GetData() []*ClickData { + if x != nil { + return x.Data + } + return nil +} + +type ClickData struct { + state protoimpl.MessageState `protogen:"open.v1"` + CatalogId int32 `protobuf:"varint,1,opt,name=catalog_id,json=catalogId,proto3" json:"catalog_id,omitempty"` + ProductId int32 `protobuf:"varint,2,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"` + Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Metadata string `protobuf:"bytes,4,opt,name=metadata,proto3" json:"metadata,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClickData) Reset() { + *x = ClickData{} + mi := &file_time_series_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClickData) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClickData) ProtoMessage() {} + +func (x *ClickData) ProtoReflect() protoreflect.Message { + mi := &file_time_series_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClickData.ProtoReflect.Descriptor instead. +func (*ClickData) Descriptor() ([]byte, []int) { + return file_time_series_proto_rawDescGZIP(), []int{1} +} + +func (x *ClickData) GetCatalogId() int32 { + if x != nil { + return x.CatalogId + } + return 0 +} + +func (x *ClickData) GetProductId() int32 { + if x != nil { + return x.ProductId + } + return 0 +} + +func (x *ClickData) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +func (x *ClickData) GetMetadata() string { + if x != nil { + return x.Metadata + } + return "" +} + +type PersistOrderDataRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Data []*OrderData `protobuf:"bytes,2,rep,name=data,proto3" json:"data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PersistOrderDataRequest) Reset() { + *x = PersistOrderDataRequest{} + mi := &file_time_series_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PersistOrderDataRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PersistOrderDataRequest) ProtoMessage() {} + +func (x *PersistOrderDataRequest) ProtoReflect() protoreflect.Message { + mi := &file_time_series_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PersistOrderDataRequest.ProtoReflect.Descriptor instead. +func (*PersistOrderDataRequest) Descriptor() ([]byte, []int) { + return file_time_series_proto_rawDescGZIP(), []int{2} +} + +func (x *PersistOrderDataRequest) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *PersistOrderDataRequest) GetData() []*OrderData { + if x != nil { + return x.Data + } + return nil +} + +type OrderData struct { + state protoimpl.MessageState `protogen:"open.v1"` + CatalogId int32 `protobuf:"varint,1,opt,name=catalog_id,json=catalogId,proto3" json:"catalog_id,omitempty"` + ProductId int32 `protobuf:"varint,2,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"` + SubOrderNum string `protobuf:"bytes,3,opt,name=sub_order_num,json=subOrderNum,proto3" json:"sub_order_num,omitempty"` + Timestamp int64 `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Metadata string `protobuf:"bytes,5,opt,name=metadata,proto3" json:"metadata,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *OrderData) Reset() { + *x = OrderData{} + mi := &file_time_series_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *OrderData) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OrderData) ProtoMessage() {} + +func (x *OrderData) ProtoReflect() protoreflect.Message { + mi := &file_time_series_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OrderData.ProtoReflect.Descriptor instead. +func (*OrderData) Descriptor() ([]byte, []int) { + return file_time_series_proto_rawDescGZIP(), []int{3} +} + +func (x *OrderData) GetCatalogId() int32 { + if x != nil { + return x.CatalogId + } + return 0 +} + +func (x *OrderData) GetProductId() int32 { + if x != nil { + return x.ProductId + } + return 0 +} + +func (x *OrderData) GetSubOrderNum() string { + if x != nil { + return x.SubOrderNum + } + return "" +} + +func (x *OrderData) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +func (x *OrderData) GetMetadata() string { + if x != nil { + return x.Metadata + } + return "" +} + +type PersistDataResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PersistDataResponse) Reset() { + *x = PersistDataResponse{} + mi := &file_time_series_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PersistDataResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PersistDataResponse) ProtoMessage() {} + +func (x *PersistDataResponse) ProtoReflect() protoreflect.Message { + mi := &file_time_series_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PersistDataResponse.ProtoReflect.Descriptor instead. +func (*PersistDataResponse) Descriptor() ([]byte, []int) { + return file_time_series_proto_rawDescGZIP(), []int{4} +} + +func (x *PersistDataResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +type RetrieveDataRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + StartTimestamp int64 `protobuf:"varint,2,opt,name=start_timestamp,json=startTimestamp,proto3" json:"start_timestamp,omitempty"` + EndTimestamp int64 `protobuf:"varint,3,opt,name=end_timestamp,json=endTimestamp,proto3" json:"end_timestamp,omitempty"` + Limit int32 `protobuf:"varint,4,opt,name=limit,proto3" json:"limit,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RetrieveDataRequest) Reset() { + *x = RetrieveDataRequest{} + mi := &file_time_series_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RetrieveDataRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RetrieveDataRequest) ProtoMessage() {} + +func (x *RetrieveDataRequest) ProtoReflect() protoreflect.Message { + mi := &file_time_series_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RetrieveDataRequest.ProtoReflect.Descriptor instead. +func (*RetrieveDataRequest) Descriptor() ([]byte, []int) { + return file_time_series_proto_rawDescGZIP(), []int{5} +} + +func (x *RetrieveDataRequest) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *RetrieveDataRequest) GetStartTimestamp() int64 { + if x != nil { + return x.StartTimestamp + } + return 0 +} + +func (x *RetrieveDataRequest) GetEndTimestamp() int64 { + if x != nil { + return x.EndTimestamp + } + return 0 +} + +func (x *RetrieveDataRequest) GetLimit() int32 { + if x != nil { + return x.Limit + } + return 0 +} + +type RetrieveInteractionsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + InteractionTypes []InteractionType `protobuf:"varint,2,rep,packed,name=interaction_types,json=interactionTypes,proto3,enum=proto.InteractionType" json:"interaction_types,omitempty"` + StartTimestamp int64 `protobuf:"varint,3,opt,name=start_timestamp,json=startTimestamp,proto3" json:"start_timestamp,omitempty"` + EndTimestamp int64 `protobuf:"varint,4,opt,name=end_timestamp,json=endTimestamp,proto3" json:"end_timestamp,omitempty"` + Limit int32 `protobuf:"varint,5,opt,name=limit,proto3" json:"limit,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RetrieveInteractionsRequest) Reset() { + *x = RetrieveInteractionsRequest{} + mi := &file_time_series_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RetrieveInteractionsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RetrieveInteractionsRequest) ProtoMessage() {} + +func (x *RetrieveInteractionsRequest) ProtoReflect() protoreflect.Message { + mi := &file_time_series_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RetrieveInteractionsRequest.ProtoReflect.Descriptor instead. +func (*RetrieveInteractionsRequest) Descriptor() ([]byte, []int) { + return file_time_series_proto_rawDescGZIP(), []int{6} +} + +func (x *RetrieveInteractionsRequest) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *RetrieveInteractionsRequest) GetInteractionTypes() []InteractionType { + if x != nil { + return x.InteractionTypes + } + return nil +} + +func (x *RetrieveInteractionsRequest) GetStartTimestamp() int64 { + if x != nil { + return x.StartTimestamp + } + return 0 +} + +func (x *RetrieveInteractionsRequest) GetEndTimestamp() int64 { + if x != nil { + return x.EndTimestamp + } + return 0 +} + +func (x *RetrieveInteractionsRequest) GetLimit() int32 { + if x != nil { + return x.Limit + } + return 0 +} + +type RetrieveInteractionsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Data map[string]*InteractionData `protobuf:"bytes,1,rep,name=data,proto3" json:"data,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RetrieveInteractionsResponse) Reset() { + *x = RetrieveInteractionsResponse{} + mi := &file_time_series_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RetrieveInteractionsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RetrieveInteractionsResponse) ProtoMessage() {} + +func (x *RetrieveInteractionsResponse) ProtoReflect() protoreflect.Message { + mi := &file_time_series_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RetrieveInteractionsResponse.ProtoReflect.Descriptor instead. +func (*RetrieveInteractionsResponse) Descriptor() ([]byte, []int) { + return file_time_series_proto_rawDescGZIP(), []int{7} +} + +func (x *RetrieveInteractionsResponse) GetData() map[string]*InteractionData { + if x != nil { + return x.Data + } + return nil +} + +type InteractionData struct { + state protoimpl.MessageState `protogen:"open.v1"` + ClickEvents []*ClickEvent `protobuf:"bytes,1,rep,name=click_events,json=clickEvents,proto3" json:"click_events,omitempty"` + OrderEvents []*OrderEvent `protobuf:"bytes,2,rep,name=order_events,json=orderEvents,proto3" json:"order_events,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InteractionData) Reset() { + *x = InteractionData{} + mi := &file_time_series_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InteractionData) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InteractionData) ProtoMessage() {} + +func (x *InteractionData) ProtoReflect() protoreflect.Message { + mi := &file_time_series_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InteractionData.ProtoReflect.Descriptor instead. +func (*InteractionData) Descriptor() ([]byte, []int) { + return file_time_series_proto_rawDescGZIP(), []int{8} +} + +func (x *InteractionData) GetClickEvents() []*ClickEvent { + if x != nil { + return x.ClickEvents + } + return nil +} + +func (x *InteractionData) GetOrderEvents() []*OrderEvent { + if x != nil { + return x.OrderEvents + } + return nil +} + +type RetrieveClickDataResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Data []*ClickEvent `protobuf:"bytes,1,rep,name=data,proto3" json:"data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RetrieveClickDataResponse) Reset() { + *x = RetrieveClickDataResponse{} + mi := &file_time_series_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RetrieveClickDataResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RetrieveClickDataResponse) ProtoMessage() {} + +func (x *RetrieveClickDataResponse) ProtoReflect() protoreflect.Message { + mi := &file_time_series_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RetrieveClickDataResponse.ProtoReflect.Descriptor instead. +func (*RetrieveClickDataResponse) Descriptor() ([]byte, []int) { + return file_time_series_proto_rawDescGZIP(), []int{9} +} + +func (x *RetrieveClickDataResponse) GetData() []*ClickEvent { + if x != nil { + return x.Data + } + return nil +} + +type ClickEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + CatalogId int32 `protobuf:"varint,2,opt,name=catalog_id,json=catalogId,proto3" json:"catalog_id,omitempty"` + ProductId int32 `protobuf:"varint,3,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"` + Timestamp int64 `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Metadata string `protobuf:"bytes,5,opt,name=metadata,proto3" json:"metadata,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClickEvent) Reset() { + *x = ClickEvent{} + mi := &file_time_series_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClickEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClickEvent) ProtoMessage() {} + +func (x *ClickEvent) ProtoReflect() protoreflect.Message { + mi := &file_time_series_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClickEvent.ProtoReflect.Descriptor instead. +func (*ClickEvent) Descriptor() ([]byte, []int) { + return file_time_series_proto_rawDescGZIP(), []int{10} +} + +func (x *ClickEvent) GetCatalogId() int32 { + if x != nil { + return x.CatalogId + } + return 0 +} + +func (x *ClickEvent) GetProductId() int32 { + if x != nil { + return x.ProductId + } + return 0 +} + +func (x *ClickEvent) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +func (x *ClickEvent) GetMetadata() string { + if x != nil { + return x.Metadata + } + return "" +} + +type RetrieveOrderDataResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Data []*OrderEvent `protobuf:"bytes,1,rep,name=data,proto3" json:"data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RetrieveOrderDataResponse) Reset() { + *x = RetrieveOrderDataResponse{} + mi := &file_time_series_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RetrieveOrderDataResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RetrieveOrderDataResponse) ProtoMessage() {} + +func (x *RetrieveOrderDataResponse) ProtoReflect() protoreflect.Message { + mi := &file_time_series_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RetrieveOrderDataResponse.ProtoReflect.Descriptor instead. +func (*RetrieveOrderDataResponse) Descriptor() ([]byte, []int) { + return file_time_series_proto_rawDescGZIP(), []int{11} +} + +func (x *RetrieveOrderDataResponse) GetData() []*OrderEvent { + if x != nil { + return x.Data + } + return nil +} + +type OrderEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + CatalogId int32 `protobuf:"varint,2,opt,name=catalog_id,json=catalogId,proto3" json:"catalog_id,omitempty"` + ProductId int32 `protobuf:"varint,3,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"` + SubOrderNum string `protobuf:"bytes,4,opt,name=sub_order_num,json=subOrderNum,proto3" json:"sub_order_num,omitempty"` + Timestamp int64 `protobuf:"varint,5,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Metadata string `protobuf:"bytes,6,opt,name=metadata,proto3" json:"metadata,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *OrderEvent) Reset() { + *x = OrderEvent{} + mi := &file_time_series_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *OrderEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OrderEvent) ProtoMessage() {} + +func (x *OrderEvent) ProtoReflect() protoreflect.Message { + mi := &file_time_series_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OrderEvent.ProtoReflect.Descriptor instead. +func (*OrderEvent) Descriptor() ([]byte, []int) { + return file_time_series_proto_rawDescGZIP(), []int{12} +} + +func (x *OrderEvent) GetCatalogId() int32 { + if x != nil { + return x.CatalogId + } + return 0 +} + +func (x *OrderEvent) GetProductId() int32 { + if x != nil { + return x.ProductId + } + return 0 +} + +func (x *OrderEvent) GetSubOrderNum() string { + if x != nil { + return x.SubOrderNum + } + return "" +} + +func (x *OrderEvent) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +func (x *OrderEvent) GetMetadata() string { + if x != nil { + return x.Metadata + } + return "" +} + +var File_time_series_proto protoreflect.FileDescriptor + +const file_time_series_proto_rawDesc = "" + + "\n" + + "\x11time_series.proto\x12\x05proto\"X\n" + + "\x17PersistClickDataRequest\x12\x17\n" + + "\auser_id\x18\x01 \x01(\tR\x06userId\x12$\n" + + "\x04data\x18\x02 \x03(\v2\x10.proto.ClickDataR\x04data\"\x83\x01\n" + + "\tClickData\x12\x1d\n" + + "\n" + + "catalog_id\x18\x01 \x01(\x05R\tcatalogId\x12\x1d\n" + + "\n" + + "product_id\x18\x02 \x01(\x05R\tproductId\x12\x1c\n" + + "\ttimestamp\x18\x03 \x01(\x03R\ttimestamp\x12\x1a\n" + + "\bmetadata\x18\x04 \x01(\tR\bmetadata\"X\n" + + "\x17PersistOrderDataRequest\x12\x17\n" + + "\auser_id\x18\x01 \x01(\tR\x06userId\x12$\n" + + "\x04data\x18\x02 \x03(\v2\x10.proto.OrderDataR\x04data\"\xa7\x01\n" + + "\tOrderData\x12\x1d\n" + + "\n" + + "catalog_id\x18\x01 \x01(\x05R\tcatalogId\x12\x1d\n" + + "\n" + + "product_id\x18\x02 \x01(\x05R\tproductId\x12\"\n" + + "\rsub_order_num\x18\x03 \x01(\tR\vsubOrderNum\x12\x1c\n" + + "\ttimestamp\x18\x04 \x01(\x03R\ttimestamp\x12\x1a\n" + + "\bmetadata\x18\x05 \x01(\tR\bmetadata\"/\n" + + "\x13PersistDataResponse\x12\x18\n" + + "\amessage\x18\x01 \x01(\tR\amessage\"\x92\x01\n" + + "\x13RetrieveDataRequest\x12\x17\n" + + "\auser_id\x18\x01 \x01(\tR\x06userId\x12'\n" + + "\x0fstart_timestamp\x18\x02 \x01(\x03R\x0estartTimestamp\x12#\n" + + "\rend_timestamp\x18\x03 \x01(\x03R\fendTimestamp\x12\x14\n" + + "\x05limit\x18\x04 \x01(\x05R\x05limit\"\xdf\x01\n" + + "\x1bRetrieveInteractionsRequest\x12\x17\n" + + "\auser_id\x18\x01 \x01(\tR\x06userId\x12C\n" + + "\x11interaction_types\x18\x02 \x03(\x0e2\x16.proto.InteractionTypeR\x10interactionTypes\x12'\n" + + "\x0fstart_timestamp\x18\x03 \x01(\x03R\x0estartTimestamp\x12#\n" + + "\rend_timestamp\x18\x04 \x01(\x03R\fendTimestamp\x12\x14\n" + + "\x05limit\x18\x05 \x01(\x05R\x05limit\"\xb2\x01\n" + + "\x1cRetrieveInteractionsResponse\x12A\n" + + "\x04data\x18\x01 \x03(\v2-.proto.RetrieveInteractionsResponse.DataEntryR\x04data\x1aO\n" + + "\tDataEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12,\n" + + "\x05value\x18\x02 \x01(\v2\x16.proto.InteractionDataR\x05value:\x028\x01\"}\n" + + "\x0fInteractionData\x124\n" + + "\fclick_events\x18\x01 \x03(\v2\x11.proto.ClickEventR\vclickEvents\x124\n" + + "\forder_events\x18\x02 \x03(\v2\x11.proto.OrderEventR\vorderEvents\"B\n" + + "\x19RetrieveClickDataResponse\x12%\n" + + "\x04data\x18\x01 \x03(\v2\x11.proto.ClickEventR\x04data\"\x84\x01\n" + + "\n" + + "ClickEvent\x12\x1d\n" + + "\n" + + "catalog_id\x18\x02 \x01(\x05R\tcatalogId\x12\x1d\n" + + "\n" + + "product_id\x18\x03 \x01(\x05R\tproductId\x12\x1c\n" + + "\ttimestamp\x18\x04 \x01(\x03R\ttimestamp\x12\x1a\n" + + "\bmetadata\x18\x05 \x01(\tR\bmetadata\"B\n" + + "\x19RetrieveOrderDataResponse\x12%\n" + + "\x04data\x18\x01 \x03(\v2\x11.proto.OrderEventR\x04data\"\xa8\x01\n" + + "\n" + + "OrderEvent\x12\x1d\n" + + "\n" + + "catalog_id\x18\x02 \x01(\x05R\tcatalogId\x12\x1d\n" + + "\n" + + "product_id\x18\x03 \x01(\x05R\tproductId\x12\"\n" + + "\rsub_order_num\x18\x04 \x01(\tR\vsubOrderNum\x12\x1c\n" + + "\ttimestamp\x18\x05 \x01(\x03R\ttimestamp\x12\x1a\n" + + "\bmetadata\x18\x06 \x01(\tR\bmetadata*'\n" + + "\x0fInteractionType\x12\t\n" + + "\x05CLICK\x10\x00\x12\t\n" + + "\x05ORDER\x10\x012\xe4\x03\n" + + "!InteractionStoreTimeSeriesService\x12P\n" + + "\x10PersistClickData\x12\x1e.proto.PersistClickDataRequest\x1a\x1a.proto.PersistDataResponse\"\x00\x12P\n" + + "\x10PersistOrderData\x12\x1e.proto.PersistOrderDataRequest\x1a\x1a.proto.PersistDataResponse\"\x00\x12[\n" + + "\x19RetrieveClickInteractions\x12\x1a.proto.RetrieveDataRequest\x1a .proto.RetrieveClickDataResponse\"\x00\x12[\n" + + "\x19RetrieveOrderInteractions\x12\x1a.proto.RetrieveDataRequest\x1a .proto.RetrieveOrderDataResponse\"\x00\x12a\n" + + "\x14RetrieveInteractions\x12\".proto.RetrieveInteractionsRequest\x1a#.proto.RetrieveInteractionsResponse\"\x00BOZMgithub.com/Meesho/BharatMLStack/go-sdk/pkg/proto/interaction-store/timeseriesb\x06proto3" + +var ( + file_time_series_proto_rawDescOnce sync.Once + file_time_series_proto_rawDescData []byte +) + +func file_time_series_proto_rawDescGZIP() []byte { + file_time_series_proto_rawDescOnce.Do(func() { + file_time_series_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_time_series_proto_rawDesc), len(file_time_series_proto_rawDesc))) + }) + return file_time_series_proto_rawDescData +} + +var file_time_series_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_time_series_proto_msgTypes = make([]protoimpl.MessageInfo, 14) +var file_time_series_proto_goTypes = []any{ + (InteractionType)(0), // 0: proto.InteractionType + (*PersistClickDataRequest)(nil), // 1: proto.PersistClickDataRequest + (*ClickData)(nil), // 2: proto.ClickData + (*PersistOrderDataRequest)(nil), // 3: proto.PersistOrderDataRequest + (*OrderData)(nil), // 4: proto.OrderData + (*PersistDataResponse)(nil), // 5: proto.PersistDataResponse + (*RetrieveDataRequest)(nil), // 6: proto.RetrieveDataRequest + (*RetrieveInteractionsRequest)(nil), // 7: proto.RetrieveInteractionsRequest + (*RetrieveInteractionsResponse)(nil), // 8: proto.RetrieveInteractionsResponse + (*InteractionData)(nil), // 9: proto.InteractionData + (*RetrieveClickDataResponse)(nil), // 10: proto.RetrieveClickDataResponse + (*ClickEvent)(nil), // 11: proto.ClickEvent + (*RetrieveOrderDataResponse)(nil), // 12: proto.RetrieveOrderDataResponse + (*OrderEvent)(nil), // 13: proto.OrderEvent + nil, // 14: proto.RetrieveInteractionsResponse.DataEntry +} +var file_time_series_proto_depIdxs = []int32{ + 2, // 0: proto.PersistClickDataRequest.data:type_name -> proto.ClickData + 4, // 1: proto.PersistOrderDataRequest.data:type_name -> proto.OrderData + 0, // 2: proto.RetrieveInteractionsRequest.interaction_types:type_name -> proto.InteractionType + 14, // 3: proto.RetrieveInteractionsResponse.data:type_name -> proto.RetrieveInteractionsResponse.DataEntry + 11, // 4: proto.InteractionData.click_events:type_name -> proto.ClickEvent + 13, // 5: proto.InteractionData.order_events:type_name -> proto.OrderEvent + 11, // 6: proto.RetrieveClickDataResponse.data:type_name -> proto.ClickEvent + 13, // 7: proto.RetrieveOrderDataResponse.data:type_name -> proto.OrderEvent + 9, // 8: proto.RetrieveInteractionsResponse.DataEntry.value:type_name -> proto.InteractionData + 1, // 9: proto.InteractionStoreTimeSeriesService.PersistClickData:input_type -> proto.PersistClickDataRequest + 3, // 10: proto.InteractionStoreTimeSeriesService.PersistOrderData:input_type -> proto.PersistOrderDataRequest + 6, // 11: proto.InteractionStoreTimeSeriesService.RetrieveClickInteractions:input_type -> proto.RetrieveDataRequest + 6, // 12: proto.InteractionStoreTimeSeriesService.RetrieveOrderInteractions:input_type -> proto.RetrieveDataRequest + 7, // 13: proto.InteractionStoreTimeSeriesService.RetrieveInteractions:input_type -> proto.RetrieveInteractionsRequest + 5, // 14: proto.InteractionStoreTimeSeriesService.PersistClickData:output_type -> proto.PersistDataResponse + 5, // 15: proto.InteractionStoreTimeSeriesService.PersistOrderData:output_type -> proto.PersistDataResponse + 10, // 16: proto.InteractionStoreTimeSeriesService.RetrieveClickInteractions:output_type -> proto.RetrieveClickDataResponse + 12, // 17: proto.InteractionStoreTimeSeriesService.RetrieveOrderInteractions:output_type -> proto.RetrieveOrderDataResponse + 8, // 18: proto.InteractionStoreTimeSeriesService.RetrieveInteractions:output_type -> proto.RetrieveInteractionsResponse + 14, // [14:19] is the sub-list for method output_type + 9, // [9:14] is the sub-list for method input_type + 9, // [9:9] is the sub-list for extension type_name + 9, // [9:9] is the sub-list for extension extendee + 0, // [0:9] is the sub-list for field type_name +} + +func init() { file_time_series_proto_init() } +func file_time_series_proto_init() { + if File_time_series_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_time_series_proto_rawDesc), len(file_time_series_proto_rawDesc)), + NumEnums: 1, + NumMessages: 14, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_time_series_proto_goTypes, + DependencyIndexes: file_time_series_proto_depIdxs, + EnumInfos: file_time_series_proto_enumTypes, + MessageInfos: file_time_series_proto_msgTypes, + }.Build() + File_time_series_proto = out.File + file_time_series_proto_goTypes = nil + file_time_series_proto_depIdxs = nil +} diff --git a/go-sdk/pkg/proto/interaction-store/timeseries/time_series_grpc.pb.go b/go-sdk/pkg/proto/interaction-store/timeseries/time_series_grpc.pb.go new file mode 100644 index 00000000..7b4f3533 --- /dev/null +++ b/go-sdk/pkg/proto/interaction-store/timeseries/time_series_grpc.pb.go @@ -0,0 +1,274 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.0 +// - protoc v6.33.0 +// source: time_series.proto + +package timeseries + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + InteractionStoreTimeSeriesService_PersistClickData_FullMethodName = "/proto.InteractionStoreTimeSeriesService/PersistClickData" + InteractionStoreTimeSeriesService_PersistOrderData_FullMethodName = "/proto.InteractionStoreTimeSeriesService/PersistOrderData" + InteractionStoreTimeSeriesService_RetrieveClickInteractions_FullMethodName = "/proto.InteractionStoreTimeSeriesService/RetrieveClickInteractions" + InteractionStoreTimeSeriesService_RetrieveOrderInteractions_FullMethodName = "/proto.InteractionStoreTimeSeriesService/RetrieveOrderInteractions" + InteractionStoreTimeSeriesService_RetrieveInteractions_FullMethodName = "/proto.InteractionStoreTimeSeriesService/RetrieveInteractions" +) + +// InteractionStoreTimeSeriesServiceClient is the client API for InteractionStoreTimeSeriesService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type InteractionStoreTimeSeriesServiceClient interface { + PersistClickData(ctx context.Context, in *PersistClickDataRequest, opts ...grpc.CallOption) (*PersistDataResponse, error) + PersistOrderData(ctx context.Context, in *PersistOrderDataRequest, opts ...grpc.CallOption) (*PersistDataResponse, error) + RetrieveClickInteractions(ctx context.Context, in *RetrieveDataRequest, opts ...grpc.CallOption) (*RetrieveClickDataResponse, error) + RetrieveOrderInteractions(ctx context.Context, in *RetrieveDataRequest, opts ...grpc.CallOption) (*RetrieveOrderDataResponse, error) + RetrieveInteractions(ctx context.Context, in *RetrieveInteractionsRequest, opts ...grpc.CallOption) (*RetrieveInteractionsResponse, error) +} + +type interactionStoreTimeSeriesServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewInteractionStoreTimeSeriesServiceClient(cc grpc.ClientConnInterface) InteractionStoreTimeSeriesServiceClient { + return &interactionStoreTimeSeriesServiceClient{cc} +} + +func (c *interactionStoreTimeSeriesServiceClient) PersistClickData(ctx context.Context, in *PersistClickDataRequest, opts ...grpc.CallOption) (*PersistDataResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(PersistDataResponse) + err := c.cc.Invoke(ctx, InteractionStoreTimeSeriesService_PersistClickData_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *interactionStoreTimeSeriesServiceClient) PersistOrderData(ctx context.Context, in *PersistOrderDataRequest, opts ...grpc.CallOption) (*PersistDataResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(PersistDataResponse) + err := c.cc.Invoke(ctx, InteractionStoreTimeSeriesService_PersistOrderData_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *interactionStoreTimeSeriesServiceClient) RetrieveClickInteractions(ctx context.Context, in *RetrieveDataRequest, opts ...grpc.CallOption) (*RetrieveClickDataResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RetrieveClickDataResponse) + err := c.cc.Invoke(ctx, InteractionStoreTimeSeriesService_RetrieveClickInteractions_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *interactionStoreTimeSeriesServiceClient) RetrieveOrderInteractions(ctx context.Context, in *RetrieveDataRequest, opts ...grpc.CallOption) (*RetrieveOrderDataResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RetrieveOrderDataResponse) + err := c.cc.Invoke(ctx, InteractionStoreTimeSeriesService_RetrieveOrderInteractions_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *interactionStoreTimeSeriesServiceClient) RetrieveInteractions(ctx context.Context, in *RetrieveInteractionsRequest, opts ...grpc.CallOption) (*RetrieveInteractionsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RetrieveInteractionsResponse) + err := c.cc.Invoke(ctx, InteractionStoreTimeSeriesService_RetrieveInteractions_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// InteractionStoreTimeSeriesServiceServer is the server API for InteractionStoreTimeSeriesService service. +// All implementations must embed UnimplementedInteractionStoreTimeSeriesServiceServer +// for forward compatibility. +type InteractionStoreTimeSeriesServiceServer interface { + PersistClickData(context.Context, *PersistClickDataRequest) (*PersistDataResponse, error) + PersistOrderData(context.Context, *PersistOrderDataRequest) (*PersistDataResponse, error) + RetrieveClickInteractions(context.Context, *RetrieveDataRequest) (*RetrieveClickDataResponse, error) + RetrieveOrderInteractions(context.Context, *RetrieveDataRequest) (*RetrieveOrderDataResponse, error) + RetrieveInteractions(context.Context, *RetrieveInteractionsRequest) (*RetrieveInteractionsResponse, error) + mustEmbedUnimplementedInteractionStoreTimeSeriesServiceServer() +} + +// UnimplementedInteractionStoreTimeSeriesServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedInteractionStoreTimeSeriesServiceServer struct{} + +func (UnimplementedInteractionStoreTimeSeriesServiceServer) PersistClickData(context.Context, *PersistClickDataRequest) (*PersistDataResponse, error) { + return nil, status.Error(codes.Unimplemented, "method PersistClickData not implemented") +} +func (UnimplementedInteractionStoreTimeSeriesServiceServer) PersistOrderData(context.Context, *PersistOrderDataRequest) (*PersistDataResponse, error) { + return nil, status.Error(codes.Unimplemented, "method PersistOrderData not implemented") +} +func (UnimplementedInteractionStoreTimeSeriesServiceServer) RetrieveClickInteractions(context.Context, *RetrieveDataRequest) (*RetrieveClickDataResponse, error) { + return nil, status.Error(codes.Unimplemented, "method RetrieveClickInteractions not implemented") +} +func (UnimplementedInteractionStoreTimeSeriesServiceServer) RetrieveOrderInteractions(context.Context, *RetrieveDataRequest) (*RetrieveOrderDataResponse, error) { + return nil, status.Error(codes.Unimplemented, "method RetrieveOrderInteractions not implemented") +} +func (UnimplementedInteractionStoreTimeSeriesServiceServer) RetrieveInteractions(context.Context, *RetrieveInteractionsRequest) (*RetrieveInteractionsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method RetrieveInteractions not implemented") +} +func (UnimplementedInteractionStoreTimeSeriesServiceServer) mustEmbedUnimplementedInteractionStoreTimeSeriesServiceServer() { +} +func (UnimplementedInteractionStoreTimeSeriesServiceServer) testEmbeddedByValue() {} + +// UnsafeInteractionStoreTimeSeriesServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to InteractionStoreTimeSeriesServiceServer will +// result in compilation errors. +type UnsafeInteractionStoreTimeSeriesServiceServer interface { + mustEmbedUnimplementedInteractionStoreTimeSeriesServiceServer() +} + +func RegisterInteractionStoreTimeSeriesServiceServer(s grpc.ServiceRegistrar, srv InteractionStoreTimeSeriesServiceServer) { + // If the following call panics, it indicates UnimplementedInteractionStoreTimeSeriesServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&InteractionStoreTimeSeriesService_ServiceDesc, srv) +} + +func _InteractionStoreTimeSeriesService_PersistClickData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PersistClickDataRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(InteractionStoreTimeSeriesServiceServer).PersistClickData(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: InteractionStoreTimeSeriesService_PersistClickData_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(InteractionStoreTimeSeriesServiceServer).PersistClickData(ctx, req.(*PersistClickDataRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _InteractionStoreTimeSeriesService_PersistOrderData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PersistOrderDataRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(InteractionStoreTimeSeriesServiceServer).PersistOrderData(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: InteractionStoreTimeSeriesService_PersistOrderData_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(InteractionStoreTimeSeriesServiceServer).PersistOrderData(ctx, req.(*PersistOrderDataRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _InteractionStoreTimeSeriesService_RetrieveClickInteractions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RetrieveDataRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(InteractionStoreTimeSeriesServiceServer).RetrieveClickInteractions(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: InteractionStoreTimeSeriesService_RetrieveClickInteractions_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(InteractionStoreTimeSeriesServiceServer).RetrieveClickInteractions(ctx, req.(*RetrieveDataRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _InteractionStoreTimeSeriesService_RetrieveOrderInteractions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RetrieveDataRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(InteractionStoreTimeSeriesServiceServer).RetrieveOrderInteractions(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: InteractionStoreTimeSeriesService_RetrieveOrderInteractions_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(InteractionStoreTimeSeriesServiceServer).RetrieveOrderInteractions(ctx, req.(*RetrieveDataRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _InteractionStoreTimeSeriesService_RetrieveInteractions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RetrieveInteractionsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(InteractionStoreTimeSeriesServiceServer).RetrieveInteractions(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: InteractionStoreTimeSeriesService_RetrieveInteractions_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(InteractionStoreTimeSeriesServiceServer).RetrieveInteractions(ctx, req.(*RetrieveInteractionsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// InteractionStoreTimeSeriesService_ServiceDesc is the grpc.ServiceDesc for InteractionStoreTimeSeriesService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var InteractionStoreTimeSeriesService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "proto.InteractionStoreTimeSeriesService", + HandlerType: (*InteractionStoreTimeSeriesServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "PersistClickData", + Handler: _InteractionStoreTimeSeriesService_PersistClickData_Handler, + }, + { + MethodName: "PersistOrderData", + Handler: _InteractionStoreTimeSeriesService_PersistOrderData_Handler, + }, + { + MethodName: "RetrieveClickInteractions", + Handler: _InteractionStoreTimeSeriesService_RetrieveClickInteractions_Handler, + }, + { + MethodName: "RetrieveOrderInteractions", + Handler: _InteractionStoreTimeSeriesService_RetrieveOrderInteractions_Handler, + }, + { + MethodName: "RetrieveInteractions", + Handler: _InteractionStoreTimeSeriesService_RetrieveInteractions_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "time_series.proto", +} diff --git a/helix-client/pkg/system/global.go b/go-sdk/pkg/system/global.go similarity index 100% rename from helix-client/pkg/system/global.go rename to go-sdk/pkg/system/global.go diff --git a/helix-client/pkg/utils/byte_utils.go b/go-sdk/pkg/utils/byte_utils.go similarity index 98% rename from helix-client/pkg/utils/byte_utils.go rename to go-sdk/pkg/utils/byte_utils.go index fd32d5e0..8415d329 100644 --- a/helix-client/pkg/utils/byte_utils.go +++ b/go-sdk/pkg/utils/byte_utils.go @@ -6,7 +6,7 @@ import ( "errors" "reflect" - "github.com/Meesho/BharatMLStack/helix-client/pkg/system" + "github.com/Meesho/BharatMLStack/go-sdk/pkg/system" "github.com/rs/zerolog/log" ) diff --git a/helix-client/pkg/utils/byte_utils_test.go b/go-sdk/pkg/utils/byte_utils_test.go similarity index 100% rename from helix-client/pkg/utils/byte_utils_test.go rename to go-sdk/pkg/utils/byte_utils_test.go diff --git a/helix-client/pkg/utils/empty_utils.go b/go-sdk/pkg/utils/empty_utils.go similarity index 100% rename from helix-client/pkg/utils/empty_utils.go rename to go-sdk/pkg/utils/empty_utils.go diff --git a/helix-client/pkg/utils/empty_utils_test.go b/go-sdk/pkg/utils/empty_utils_test.go similarity index 100% rename from helix-client/pkg/utils/empty_utils_test.go rename to go-sdk/pkg/utils/empty_utils_test.go diff --git a/helix-client/pkg/utils/slice_utils.go b/go-sdk/pkg/utils/slice_utils.go similarity index 100% rename from helix-client/pkg/utils/slice_utils.go rename to go-sdk/pkg/utils/slice_utils.go diff --git a/helix-client/pkg/utils/slice_utils_test.go b/go-sdk/pkg/utils/slice_utils_test.go similarity index 100% rename from helix-client/pkg/utils/slice_utils_test.go rename to go-sdk/pkg/utils/slice_utils_test.go diff --git a/helix-client/README.md b/helix-client/README.md deleted file mode 100644 index 8e7b2a3f..00000000 --- a/helix-client/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# helix-clients - -### Environment Variables -```shell -HELIX_CLIENT_ENABLED=true -HELIX_CLIENT_API_AUTH_TOKEN= -HELIX_CLIENT_CONFIG_REFRESH_INTERVAL_IN_MINUTES=10 -HELIX_CLIENT_CONFIG_INIT_HARD_FAILURE=false -HELIX_CLIENT_ENVIRONMENT=PROD - - -HELIX_CLIENT_CONN_CONF_COUNT=1 - -HELIX_CLIENT_CONN_CONF_ID=1 -HELIX_CLIENT_CONN_CONF_1_CONFIG_ID=pdp-organic-1,pdp-organic-2 -HELIX_CLIENT_CONN_CONF_1_SERVICE=MODEL_PROXY -HELIX_CLIENT_CONN_CONF_1_CONN_PROTOCOL=HTTP1 -HELIX_CLIENT_CONN_CONF_1_TIMEOUT=200 -HELIX_CLIENT_CONN_CONF_1_MAX_IDLE_CONN=200 -HELIX_CLIENT_CONN_CONF_1_MAX_IDLE_CONN_PER_HOST=200 -HELIX_CLIENT_CONN_CONF_1_IDLE_CONN_TIMEOUT=200 -OR -HELIX_CLIENT_CONN_CONF_ID=1 -HELIX_CLIENT_CONN_CONF_1_CONFIG_ID=pdp-organic-3,pdp-organic-4 -HELIX_CLIENT_CONN_CONF_1_SERVICE=MODEL_PROXY -HELIX_CLIENT_CONN_CONF_1_CONN_PROTOCOL=GRPC -HELIX_CLIENT_CONN_CONF_1_DEADLINE=200 -HELIX_CLIENT_CONN_CONF_1_PLAINTEXT=true -HELIX_CLIENT_CONN_CONF_1_CHAN_ALGO=round-robin -HELIX_CLIENT_CONN_CONF_1_KEEP_ALIVE_TIME=200 -HELIX_CLIENT_CONN_CONF_1_KEEP_ALIVE_WITHOUT_CALLS=true -HELIX_CLIENT_CONN_CONF_1_IDLE_CONN_TIME_OUT=200 -``` diff --git a/helix-client/VERSION b/helix-client/VERSION deleted file mode 100644 index 60453e69..00000000 --- a/helix-client/VERSION +++ /dev/null @@ -1 +0,0 @@ -v1.0.0 \ No newline at end of file diff --git a/helix-client/go.mod b/helix-client/go.mod deleted file mode 100644 index cfd1c694..00000000 --- a/helix-client/go.mod +++ /dev/null @@ -1,68 +0,0 @@ -module github.com/Meesho/BharatMLStack/helix-client - -go 1.24.4 - -require github.com/rs/zerolog v1.33.0 - -require ( - github.com/DataDog/datadog-go/v5 v5.5.0 - github.com/gin-gonic/gin v1.11.0 - github.com/soheilhy/cmux v0.1.5 - github.com/spf13/viper v1.16.0 - github.com/stretchr/testify v1.11.1 - github.com/x448/float16 v0.8.4 - golang.org/x/net v0.43.0 - google.golang.org/grpc v1.75.0 - google.golang.org/protobuf v1.36.9 -) - -require ( - github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/bytedance/sonic v1.14.0 // indirect - github.com/bytedance/sonic/loader v0.3.0 // indirect - github.com/cloudwego/base64x v0.1.6 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.8 // indirect - github.com/gin-contrib/sse v1.1.0 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.27.0 // indirect - github.com/goccy/go-json v0.10.2 // indirect - github.com/goccy/go-yaml v1.18.0 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.3.0 // indirect - github.com/leodido/go-urn v1.4.0 // indirect - github.com/magiconair/properties v1.8.7 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.2.4 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/quic-go/qpack v0.5.1 // indirect - github.com/quic-go/quic-go v0.54.0 // indirect - github.com/rogpeppe/go-internal v1.13.1 // indirect - github.com/spf13/afero v1.9.5 // indirect - github.com/spf13/cast v1.5.1 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect - github.com/subosito/gotenv v1.4.2 // indirect - github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.3.0 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect - go.uber.org/mock v0.5.0 // indirect - golang.org/x/arch v0.20.0 // indirect - golang.org/x/crypto v0.41.0 // indirect - golang.org/x/mod v0.26.0 // indirect - golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.35.0 // indirect - golang.org/x/text v0.28.0 // indirect - golang.org/x/tools v0.35.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect - gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) diff --git a/helix-client/go.sum b/helix-client/go.sum deleted file mode 100644 index 43bca6d8..00000000 --- a/helix-client/go.sum +++ /dev/null @@ -1,613 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DataDog/datadog-go/v5 v5.5.0 h1:G5KHeB8pWBNXT4Jtw0zAkhdxEAWSpWH00geHI6LDrKU= -github.com/DataDog/datadog-go/v5 v5.5.0/go.mod h1:K9kcYBlxkcPP8tvvjZZKs/m1edNAUFzBbdpTUKfCsuw= -github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= -github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= -github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= -github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= -github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= -github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= -github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= -github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= -github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= -github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= -github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= -github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= -github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= -github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= -github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= -github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= -github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= -github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= -github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= -github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= -github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= -github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= -github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= -github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= -github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= -github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= -github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= -github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= -github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= -github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= -github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= -github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= -go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= -go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= -go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= -go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= -go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= -golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= -golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= -golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= -golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= -golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= -gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= -google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= -google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/helix-client/pkg/api/context.go b/helix-client/pkg/api/context.go deleted file mode 100644 index abd61e0b..00000000 --- a/helix-client/pkg/api/context.go +++ /dev/null @@ -1,42 +0,0 @@ -package api - -import ( - netHttp "net/http" - "strconv" - - "github.com/Meesho/BharatMLStack/helix-client/pkg/api/http" - "github.com/gin-gonic/gin" -) - -// Deprecated: Use RequestContext instead of RequestMeta -type RequestMeta struct { - UserId string - UserContext string - AppVersionCode int - ClientId string - Pincode string - AppSession string - FeedSession string -} - -// Deprecated: GetRequestMeta Build RequestMeta from gin context -func GetRequestMeta(context *gin.Context) *RequestMeta { - rawAppVersionCode := context.Request.Header.Get(http.HeaderAppVersionCode) - appVersionCode, _ := strconv.Atoi(rawAppVersionCode) - return &RequestMeta{ - UserId: context.Request.Header.Get(http.HeaderUserId), - UserContext: context.Request.Header.Get(http.HeaderUserContext), - AppVersionCode: appVersionCode, - ClientId: context.Request.Header.Get(http.HeaderClientId), - Pincode: context.Request.Header.Get(http.HeaderUserPincode), - } -} - -// Deprecated: UpdateHeaders Update headers from request meta -func UpdateHeaders(header *netHttp.Header, requestMeta *RequestMeta) { - header.Set(http.HeaderUserId, requestMeta.UserId) - header.Set(http.HeaderUserContext, requestMeta.UserContext) - header.Set(http.HeaderAppVersionCode, strconv.Itoa(requestMeta.AppVersionCode)) - header.Set(http.HeaderClientId, requestMeta.ClientId) - header.Set(http.HeaderUserPincode, requestMeta.Pincode) -} diff --git a/helix-client/pkg/api/request_context.go b/helix-client/pkg/api/request_context.go deleted file mode 100644 index 2dd813d2..00000000 --- a/helix-client/pkg/api/request_context.go +++ /dev/null @@ -1,210 +0,0 @@ -package api - -import ( - "context" - "errors" - "fmt" - netHttp "net/http" - "strconv" - - "github.com/Meesho/BharatMLStack/helix-client/pkg/api/http" - enum "github.com/Meesho/BharatMLStack/helix-client/pkg/enums" - "github.com/gin-gonic/gin" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/status" -) - -type RequestContext struct { - UserId string - UserContext enum.UserContext - AppVersionCode int - ClientId string - UserStateCode string - UserPinCode string - UserCity string - AppSession string - FeedSession string - InstanceId string - SessionId string - UserLongitude string - UserLatitude string - UserAddressId string - UserCountry string - UserLanguage string - UserLocation string -} - -const ( - RequestContextValue = "REQUEST_CONTEXT" -) - -func GetRequestContextForGRPC(ctx context.Context) (*RequestContext, error) { - requestContext := RequestContext{} - mdContext, ok := metadata.FromIncomingContext(ctx) - if !ok { - return nil, status.Errorf(codes.InvalidArgument, "metadata is not provided") - } - - // get user id (mandatory) - userId, err := getMetadataValue(mdContext, http.HeaderUserId) - if err != nil { - return nil, err - } - if len(userId) == 0 { - return nil, errors.New("user id is empty in headers") - } - requestContext.UserId = userId - - // get user context (mandatory) - userContext, err := getMetadataValue(mdContext, http.HeaderUserContext) - if err != nil { - return nil, err - } - if len(userContext) == 0 { - return nil, errors.New("user context is empty in headers") - } else { - if userCtx, err := enum.ParseUserContext(userContext); err != nil { - return nil, err - } else { - requestContext.UserContext = userCtx - } - } - - // get app version code (optional) - rawAppVersionCode, err := getMetadataValue(mdContext, http.HeaderAppVersionCode) - if err != nil { - return nil, err - } - if len(rawAppVersionCode) != 0 { - if appVersionCode, err := strconv.Atoi(rawAppVersionCode); err != nil { - return nil, errors.New("app_version_code should be an integer") - } else { - requestContext.AppVersionCode = appVersionCode - } - } - - // get client id (optional) - requestContext.ClientId, _ = getMetadataValue(mdContext, http.HeaderClientId) - - // get userStateCode - requestContext.UserStateCode, _ = getMetadataValue(mdContext, http.HeaderUserStateCode) - - // get userPinCode - requestContext.UserPinCode, _ = getMetadataValue(mdContext, http.HeaderUserPincode) - - // get userCity - requestContext.UserCity, _ = getMetadataValue(mdContext, http.HeaderUserCity) - requestContext.FeedSession, _ = getMetadataValue(mdContext, http.HeaderFeedSession) - requestContext.AppSession, _ = getMetadataValue(mdContext, http.HeaderAppSession) - requestContext.InstanceId, _ = getMetadataValue(mdContext, http.HeaderInstanceId) - requestContext.SessionId, _ = getMetadataValue(mdContext, http.HeaderSessionId) - requestContext.UserLongitude, _ = getMetadataValue(mdContext, http.HeaderUserLongitude) - requestContext.UserLatitude, _ = getMetadataValue(mdContext, http.HeaderUserLatitude) - requestContext.UserAddressId, _ = getMetadataValue(mdContext, http.HeaderUserAddressId) - requestContext.UserCountry, _ = getMetadataValue(mdContext, http.HeaderCountry) - requestContext.UserLanguage, _ = getMetadataValue(mdContext, http.HeaderLanguage) - requestContext.UserLocation, _ = getMetadataValue(mdContext, http.HeaderAppUserLocation) - return &requestContext, nil -} - -func getMetadataValue(md metadata.MD, key string) (string, error) { - values := md.Get(key) - if len(values) == 0 { - return "", fmt.Errorf("metadata key '%s' is missing ", key) - } - return values[0], nil -} - -// UpdateWithHeaders Update headers from request context -func UpdateWithGRPCHeaders(headers map[string]string, context *RequestContext) { - headers[http.HeaderUserId] = context.UserId - headers[http.HeaderUserContext] = context.UserContext.String() - headers[http.HeaderAppVersionCode] = strconv.Itoa(context.AppVersionCode) - headers[http.HeaderClientId] = context.ClientId - headers[http.HeaderUserStateCode] = context.UserStateCode - headers[http.HeaderUserPincode] = context.UserPinCode - headers[http.HeaderUserCity] = context.UserCity - headers[http.HeaderInstanceId] = context.InstanceId - headers[http.HeaderSessionId] = context.SessionId - headers[http.HeaderUserLongitude] = context.UserLongitude - headers[http.HeaderUserLatitude] = context.UserLatitude - headers[http.HeaderUserAddressId] = context.UserAddressId - headers[http.HeaderCountry] = context.UserCountry - headers[http.HeaderLanguage] = context.UserLanguage -} - -// GetRequestContext Build RequestContext from gin context -func GetRequestContext(context *gin.Context) (*RequestContext, error) { - requestContext := &RequestContext{} - // get user id (mandatory) - userId := context.Request.Header.Get(http.HeaderUserId) - if len(userId) == 0 { - return nil, errors.New("user id is missing in headers") - } - - requestContext.UserId = userId - // get user context (mandatory) - userContext := context.Request.Header.Get(http.HeaderUserContext) - if len(userContext) == 0 { - return nil, errors.New("user context is missing in headers") - } else { - if userCtx, err := enum.ParseUserContext(userContext); err != nil { - return nil, err - } else { - requestContext.UserContext = userCtx - } - } - - // get app version code (optional) - rawAppVersionCode := context.Request.Header.Get(http.HeaderAppVersionCode) - if len(rawAppVersionCode) != 0 { - if appVersionCode, err := strconv.Atoi(rawAppVersionCode); err != nil { - return nil, errors.New("app_version_code should be an integer") - } else { - requestContext.AppVersionCode = appVersionCode - } - } - - // get client id (optional) - requestContext.ClientId = context.Request.Header.Get(http.HeaderClientId) - - // get userStateCode - requestContext.UserStateCode = context.Request.Header.Get(http.HeaderUserStateCode) - - // get userPinCode - requestContext.UserPinCode = context.Request.Header.Get(http.HeaderUserPincode) - - // get userCity - requestContext.UserCity = context.Request.Header.Get(http.HeaderUserCity) - - requestContext.FeedSession = context.Request.Header.Get(http.HeaderFeedSession) - requestContext.AppSession = context.Request.Header.Get(http.HeaderAppSession) - requestContext.InstanceId = context.Request.Header.Get(http.HeaderInstanceId) - requestContext.SessionId = context.Request.Header.Get(http.HeaderSessionId) - requestContext.UserLongitude = context.Request.Header.Get(http.HeaderUserLongitude) - requestContext.UserLatitude = context.Request.Header.Get(http.HeaderUserLatitude) - requestContext.UserAddressId = context.Request.Header.Get(http.HeaderUserAddressId) - requestContext.UserCountry = context.Request.Header.Get(http.HeaderCountry) - requestContext.UserLanguage = context.Request.Header.Get(http.HeaderLanguage) - requestContext.UserLocation = context.Request.Header.Get(http.HeaderAppUserLocation) - return requestContext, nil -} - -// UpdateWithHeaders Update headers from request context -func UpdateWithHeaders(header *netHttp.Header, context *RequestContext) { - header.Set(http.HeaderUserId, context.UserId) - header.Set(http.HeaderUserContext, context.UserContext.Name()) - header.Set(http.HeaderAppVersionCode, strconv.Itoa(context.AppVersionCode)) - header.Set(http.HeaderClientId, context.ClientId) - header.Set(http.HeaderUserStateCode, context.UserStateCode) - header.Set(http.HeaderUserPincode, context.UserPinCode) - header.Set(http.HeaderUserCity, context.UserCity) - header.Set(http.HeaderInstanceId, context.InstanceId) - header.Set(http.HeaderSessionId, context.SessionId) - header.Set(http.HeaderUserLongitude, context.UserLongitude) - header.Set(http.HeaderUserLatitude, context.UserLatitude) - header.Set(http.HeaderUserAddressId, context.UserAddressId) - header.Set(http.HeaderCountry, context.UserCountry) - header.Set(http.HeaderLanguage, context.UserLanguage) -} diff --git a/helix-client/pkg/api/request_context_test.go b/helix-client/pkg/api/request_context_test.go deleted file mode 100644 index ecad973a..00000000 --- a/helix-client/pkg/api/request_context_test.go +++ /dev/null @@ -1,845 +0,0 @@ -package api - -import ( - "context" - "fmt" - - httpHeaders "github.com/Meesho/BharatMLStack/helix-client/pkg/api/http" - - "net/http" - "net/http/httptest" - "testing" - - enum "github.com/Meesho/BharatMLStack/helix-client/pkg/enums" - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" - "google.golang.org/grpc/metadata" -) - -// TestGetRequestContext tests the GetRequestContext function -func TestGetRequestContextForGRPC(t *testing.T) { - - userContext, _ := enum.ParseUserContext("anonymous") - - tests := []struct { - name string - headers map[string]string - expectedResult *RequestContext - expectedError string - }{ - { - name: "Valid headers with all fields", - headers: map[string]string{ - httpHeaders.HeaderUserId: "12345", - httpHeaders.HeaderUserContext: "anonymous", - httpHeaders.HeaderAppVersionCode: "2", - httpHeaders.HeaderClientId: "client_1", - httpHeaders.HeaderUserStateCode: "state_1", - httpHeaders.HeaderUserPincode: "123456", - }, - expectedResult: &RequestContext{ - UserId: "12345", - UserContext: userContext, - AppVersionCode: 2, - ClientId: "client_1", - UserStateCode: "state_1", - UserPinCode: "123456", - }, - expectedError: "", - }, - { - name: "Missing user id", - headers: map[string]string{ - httpHeaders.HeaderUserContext: "anonymous", - httpHeaders.HeaderAppVersionCode: "2", - httpHeaders.HeaderClientId: "client_1", - httpHeaders.HeaderUserStateCode: "state_1", - httpHeaders.HeaderUserPincode: "123456", - }, - expectedResult: nil, - expectedError: "metadata key 'USER-ID' is missing ", - }, - { - name: "Empty user id", - headers: map[string]string{ - httpHeaders.HeaderUserId: "", - httpHeaders.HeaderUserContext: "anonymous", - httpHeaders.HeaderAppVersionCode: "2", - httpHeaders.HeaderClientId: "client_1", - httpHeaders.HeaderUserStateCode: "state_1", - httpHeaders.HeaderUserPincode: "123456", - }, - expectedResult: nil, - expectedError: "user id is empty in headers", - }, - { - name: "Empty user context", - headers: map[string]string{ - httpHeaders.HeaderUserId: "123", - httpHeaders.HeaderUserContext: "", - httpHeaders.HeaderAppVersionCode: "2", - httpHeaders.HeaderClientId: "client_1", - httpHeaders.HeaderUserStateCode: "state_1", - httpHeaders.HeaderUserPincode: "123456", - }, - expectedResult: nil, - expectedError: "user context is empty in headers", - }, - { - name: "Missing user context", - headers: map[string]string{ - httpHeaders.HeaderUserId: "12345", - httpHeaders.HeaderAppVersionCode: "2", - httpHeaders.HeaderClientId: "client_1", - httpHeaders.HeaderUserStateCode: "state_1", - httpHeaders.HeaderUserPincode: "123456", - }, - expectedResult: nil, - expectedError: "metadata key 'USER-CONTEXT' is missing ", - }, - { - name: "Invalid user context", - headers: map[string]string{ - httpHeaders.HeaderUserId: "12345", - httpHeaders.HeaderUserContext: "invalid_context", - httpHeaders.HeaderAppVersionCode: "2", - httpHeaders.HeaderClientId: "client_1", - httpHeaders.HeaderUserStateCode: "state_1", - httpHeaders.HeaderUserPincode: "123456", - }, - expectedResult: nil, - expectedError: fmt.Sprintf("%q is not a valid UserContext", "invalid_context"), - }, - { - name: "Non-integer app version code", - headers: map[string]string{ - httpHeaders.HeaderUserId: "12345", - httpHeaders.HeaderUserContext: "anonymous", - httpHeaders.HeaderAppVersionCode: "non_integer", - httpHeaders.HeaderClientId: "client_1", - httpHeaders.HeaderUserStateCode: "state_1", - httpHeaders.HeaderUserPincode: "123456", - }, - expectedResult: nil, - expectedError: "app_version_code should be an integer", - }, - { - name: "Missing user state code", - headers: map[string]string{ - httpHeaders.HeaderUserId: "12345", - httpHeaders.HeaderUserContext: "anonymous", - httpHeaders.HeaderAppVersionCode: "2", - httpHeaders.HeaderClientId: "client_1", - httpHeaders.HeaderUserPincode: "123456", - }, - expectedResult: &RequestContext{ - UserId: "12345", - UserContext: userContext, - AppVersionCode: 2, - ClientId: "client_1", - UserStateCode: "", - UserPinCode: "123456", - }, - expectedError: "", - }, - { - name: "Missing user pin code", - headers: map[string]string{ - httpHeaders.HeaderUserId: "12345", - httpHeaders.HeaderUserContext: "anonymous", - httpHeaders.HeaderAppVersionCode: "2", - httpHeaders.HeaderClientId: "client_1", - httpHeaders.HeaderUserStateCode: "state_1", - }, - expectedResult: &RequestContext{ - UserId: "12345", - UserContext: userContext, - AppVersionCode: 2, - ClientId: "client_1", - UserStateCode: "state_1", - UserPinCode: "", - }, - expectedError: "", - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - ctx := context.Background() - headers := make(map[string]string) - for key, value := range test.headers { - headers[key] = value - } - ctx = metadata.NewIncomingContext(ctx, metadata.New(headers)) - - result, err := GetRequestContextForGRPC(ctx) - if test.expectedError == "" { - assert.NoError(t, err) - assert.Equal(t, test.expectedResult, result) - } else { - assert.EqualError(t, err, test.expectedError) - assert.Nil(t, result) - } - }) - } -} - -func TestGetRequestContextForGrpc_error(t *testing.T) { - - t.Run("test.name", func(t *testing.T) { - ctx := context.Background() - result, err := GetRequestContextForGRPC(ctx) - assert.EqualError(t, err, "rpc error: code = InvalidArgument desc = metadata is not provided") - assert.Nil(t, result) - - }) - -} - -// TestUpdateWithHeaders tests the UpdateWithHeaders function -func TestUpdateWithGRPCHeaders(t *testing.T) { - userContext, _ := enum.ParseUserContext("anonymous") - - header := make(map[string]string) - context := &RequestContext{ - UserId: "12345", - UserContext: userContext, - AppVersionCode: 2, - ClientId: "client_1", - UserStateCode: "state_1", - UserPinCode: "123456", - UserCity: "city_1", - } - - UpdateWithGRPCHeaders(header, context) - - assert.Equal(t, "12345", header[httpHeaders.HeaderUserId]) - assert.Equal(t, "anonymous", header[httpHeaders.HeaderUserContext]) - assert.Equal(t, "2", header[httpHeaders.HeaderAppVersionCode]) - assert.Equal(t, "client_1", header[httpHeaders.HeaderClientId]) - assert.Equal(t, "state_1", header[httpHeaders.HeaderUserStateCode]) - assert.Equal(t, "123456", header[httpHeaders.HeaderUserPincode]) - assert.Equal(t, "city_1", header[httpHeaders.HeaderUserCity]) -} - -// TestGetRequestContext tests the GetRequestContext function -func TestGetRequestContext(t *testing.T) { - gin.SetMode(gin.TestMode) - - userContext, _ := enum.ParseUserContext("anonymous") - - tests := []struct { - name string - headers map[string]string - expectedResult *RequestContext - expectedError string - }{ - { - name: "Valid headers with all fields", - headers: map[string]string{ - httpHeaders.HeaderUserId: "12345", - httpHeaders.HeaderUserContext: "anonymous", - httpHeaders.HeaderAppVersionCode: "2", - httpHeaders.HeaderClientId: "client_1", - httpHeaders.HeaderUserStateCode: "state_1", - httpHeaders.HeaderUserPincode: "123456", - }, - expectedResult: &RequestContext{ - UserId: "12345", - UserContext: userContext, - AppVersionCode: 2, - ClientId: "client_1", - UserStateCode: "state_1", - UserPinCode: "123456", - }, - expectedError: "", - }, - { - name: "Missing user id", - headers: map[string]string{ - httpHeaders.HeaderUserContext: "anonymous", - httpHeaders.HeaderAppVersionCode: "2", - httpHeaders.HeaderClientId: "client_1", - httpHeaders.HeaderUserStateCode: "state_1", - httpHeaders.HeaderUserPincode: "123456", - }, - expectedResult: nil, - expectedError: "user id is missing in headers", - }, - { - name: "Missing user context", - headers: map[string]string{ - httpHeaders.HeaderUserId: "12345", - httpHeaders.HeaderAppVersionCode: "2", - httpHeaders.HeaderClientId: "client_1", - httpHeaders.HeaderUserStateCode: "state_1", - httpHeaders.HeaderUserPincode: "123456", - }, - expectedResult: nil, - expectedError: "user context is missing in headers", - }, - { - name: "Invalid user context", - headers: map[string]string{ - httpHeaders.HeaderUserId: "12345", - httpHeaders.HeaderUserContext: "invalid_context", - httpHeaders.HeaderAppVersionCode: "2", - httpHeaders.HeaderClientId: "client_1", - httpHeaders.HeaderUserStateCode: "state_1", - httpHeaders.HeaderUserPincode: "123456", - }, - expectedResult: nil, - expectedError: fmt.Sprintf("%q is not a valid UserContext", "invalid_context"), - }, - { - name: "Non-integer app version code", - headers: map[string]string{ - httpHeaders.HeaderUserId: "12345", - httpHeaders.HeaderUserContext: "anonymous", - httpHeaders.HeaderAppVersionCode: "non_integer", - httpHeaders.HeaderClientId: "client_1", - httpHeaders.HeaderUserStateCode: "state_1", - httpHeaders.HeaderUserPincode: "123456", - }, - expectedResult: nil, - expectedError: "app_version_code should be an integer", - }, - { - name: "Missing user state code", - headers: map[string]string{ - httpHeaders.HeaderUserId: "12345", - httpHeaders.HeaderUserContext: "anonymous", - httpHeaders.HeaderAppVersionCode: "2", - httpHeaders.HeaderClientId: "client_1", - httpHeaders.HeaderUserPincode: "123456", - }, - expectedResult: &RequestContext{ - UserId: "12345", - UserContext: userContext, - AppVersionCode: 2, - ClientId: "client_1", - UserStateCode: "", - UserPinCode: "123456", - }, - expectedError: "", - }, - { - name: "Missing user pin code", - headers: map[string]string{ - httpHeaders.HeaderUserId: "12345", - httpHeaders.HeaderUserContext: "anonymous", - httpHeaders.HeaderAppVersionCode: "2", - httpHeaders.HeaderClientId: "client_1", - httpHeaders.HeaderUserStateCode: "state_1", - }, - expectedResult: &RequestContext{ - UserId: "12345", - UserContext: userContext, - AppVersionCode: 2, - ClientId: "client_1", - UserStateCode: "state_1", - UserPinCode: "", - }, - expectedError: "", - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - context, _ := gin.CreateTestContext(httptest.NewRecorder()) - request, _ := http.NewRequest(http.MethodGet, "/", nil) - for key, value := range test.headers { - request.Header.Set(key, value) - } - context.Request = request - - result, err := GetRequestContext(context) - if test.expectedError == "" { - assert.NoError(t, err) - assert.Equal(t, test.expectedResult, result) - } else { - assert.EqualError(t, err, test.expectedError) - assert.Nil(t, result) - } - }) - } -} - -// TestUpdateWithHeaders tests the UpdateWithHeaders function -func TestUpdateWithHTTPHeaders(t *testing.T) { - userContext, _ := enum.ParseUserContext("anonymous") - - header := &http.Header{} - context := &RequestContext{ - UserId: "12345", - UserContext: userContext, - AppVersionCode: 2, - ClientId: "client_1", - UserStateCode: "state_1", - UserPinCode: "123456", - UserCity: "city_1", - } - - UpdateWithHeaders(header, context) - - assert.Equal(t, "12345", header.Get(httpHeaders.HeaderUserId)) - assert.Equal(t, "anonymous", header.Get(httpHeaders.HeaderUserContext)) - assert.Equal(t, "2", header.Get(httpHeaders.HeaderAppVersionCode)) - assert.Equal(t, "client_1", header.Get(httpHeaders.HeaderClientId)) - assert.Equal(t, "state_1", header.Get(httpHeaders.HeaderUserStateCode)) - assert.Equal(t, "123456", header.Get(httpHeaders.HeaderUserPincode)) - assert.Equal(t, "city_1", header.Get(httpHeaders.HeaderUserCity)) -} - -// TestGetRequestContextForGRPC_WithFeedAndAppSession tests FeedSession and AppSession fields -func TestGetRequestContextForGRPC_WithFeedAndAppSession(t *testing.T) { - userContext, _ := enum.ParseUserContext("anonymous") - - tests := []struct { - name string - headers map[string]string - expectedResult *RequestContext - expectedError string - }{ - { - name: "Valid headers with FeedSession and AppSession", - headers: map[string]string{ - httpHeaders.HeaderUserId: "12345", - httpHeaders.HeaderUserContext: "anonymous", - httpHeaders.HeaderAppVersionCode: "2", - httpHeaders.HeaderClientId: "client_1", - httpHeaders.HeaderUserCity: "city_1", - httpHeaders.HeaderFeedSession: "feed_session_123", - httpHeaders.HeaderAppSession: "app_session_456", - }, - expectedResult: &RequestContext{ - UserId: "12345", - UserContext: userContext, - AppVersionCode: 2, - ClientId: "client_1", - UserCity: "city_1", - FeedSession: "feed_session_123", - AppSession: "app_session_456", - }, - expectedError: "", - }, - { - name: "Valid headers without FeedSession and AppSession", - headers: map[string]string{ - httpHeaders.HeaderUserId: "12345", - httpHeaders.HeaderUserContext: "anonymous", - httpHeaders.HeaderAppVersionCode: "2", - httpHeaders.HeaderClientId: "client_1", - httpHeaders.HeaderUserCity: "city_1", - }, - expectedResult: &RequestContext{ - UserId: "12345", - UserContext: userContext, - AppVersionCode: 2, - ClientId: "client_1", - UserCity: "city_1", - FeedSession: "", - AppSession: "", - }, - expectedError: "", - }, - { - name: "Valid headers with empty FeedSession and AppSession", - headers: map[string]string{ - httpHeaders.HeaderUserId: "12345", - httpHeaders.HeaderUserContext: "anonymous", - httpHeaders.HeaderAppVersionCode: "2", - httpHeaders.HeaderClientId: "client_1", - httpHeaders.HeaderUserCity: "city_1", - httpHeaders.HeaderFeedSession: "", - httpHeaders.HeaderAppSession: "", - }, - expectedResult: &RequestContext{ - UserId: "12345", - UserContext: userContext, - AppVersionCode: 2, - ClientId: "client_1", - UserCity: "city_1", - FeedSession: "", - AppSession: "", - }, - expectedError: "", - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - ctx := context.Background() - headers := make(map[string]string) - for key, value := range test.headers { - headers[key] = value - } - ctx = metadata.NewIncomingContext(ctx, metadata.New(headers)) - - result, err := GetRequestContextForGRPC(ctx) - if test.expectedError == "" { - assert.NoError(t, err) - assert.Equal(t, test.expectedResult, result) - } else { - assert.EqualError(t, err, test.expectedError) - assert.Nil(t, result) - } - }) - } -} - -// TestGetRequestContext_WithFeedAndAppSession tests FeedSession and AppSession fields for HTTP requests -func TestGetRequestContext_WithFeedAndAppSession(t *testing.T) { - gin.SetMode(gin.TestMode) - userContext, _ := enum.ParseUserContext("anonymous") - - tests := []struct { - name string - headers map[string]string - expectedResult *RequestContext - expectedError string - }{ - { - name: "Valid headers with FeedSession and AppSession", - headers: map[string]string{ - httpHeaders.HeaderUserId: "12345", - httpHeaders.HeaderUserContext: "anonymous", - httpHeaders.HeaderAppVersionCode: "2", - httpHeaders.HeaderClientId: "client_1", - httpHeaders.HeaderUserCity: "city_1", - httpHeaders.HeaderFeedSession: "feed_session_123", - httpHeaders.HeaderAppSession: "app_session_456", - }, - expectedResult: &RequestContext{ - UserId: "12345", - UserContext: userContext, - AppVersionCode: 2, - ClientId: "client_1", - UserCity: "city_1", - FeedSession: "feed_session_123", - AppSession: "app_session_456", - }, - expectedError: "", - }, - { - name: "Valid headers without FeedSession and AppSession", - headers: map[string]string{ - httpHeaders.HeaderUserId: "12345", - httpHeaders.HeaderUserContext: "anonymous", - httpHeaders.HeaderAppVersionCode: "2", - httpHeaders.HeaderClientId: "client_1", - httpHeaders.HeaderUserCity: "city_1", - }, - expectedResult: &RequestContext{ - UserId: "12345", - UserContext: userContext, - AppVersionCode: 2, - ClientId: "client_1", - UserCity: "city_1", - FeedSession: "", - AppSession: "", - }, - expectedError: "", - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - context, _ := gin.CreateTestContext(httptest.NewRecorder()) - request, _ := http.NewRequest(http.MethodGet, "/", nil) - for key, value := range test.headers { - request.Header.Set(key, value) - } - context.Request = request - - result, err := GetRequestContext(context) - if test.expectedError == "" { - assert.NoError(t, err) - assert.Equal(t, test.expectedResult, result) - } else { - assert.EqualError(t, err, test.expectedError) - assert.Nil(t, result) - } - }) - } -} - -// TestUpdateWithGRPCHeaders_WithAllFields tests UpdateWithGRPCHeaders with all fields including FeedSession and AppSession -func TestUpdateWithGRPCHeaders_WithAllFields(t *testing.T) { - userContext, _ := enum.ParseUserContext("anonymous") - - header := make(map[string]string) - context := &RequestContext{ - UserId: "12345", - UserContext: userContext, - AppVersionCode: 2, - ClientId: "client_1", - UserStateCode: "state_1", - UserPinCode: "123456", - UserCity: "city_1", - FeedSession: "feed_session_123", - AppSession: "app_session_456", - } - - UpdateWithGRPCHeaders(header, context) - - assert.Equal(t, "12345", header[httpHeaders.HeaderUserId]) - assert.Equal(t, "anonymous", header[httpHeaders.HeaderUserContext]) - assert.Equal(t, "2", header[httpHeaders.HeaderAppVersionCode]) - assert.Equal(t, "client_1", header[httpHeaders.HeaderClientId]) - assert.Equal(t, "state_1", header[httpHeaders.HeaderUserStateCode]) - assert.Equal(t, "123456", header[httpHeaders.HeaderUserPincode]) - assert.Equal(t, "city_1", header[httpHeaders.HeaderUserCity]) - - // Note: FeedSession and AppSession are not included in UpdateWithGRPCHeaders function - // This is intentional as per the current implementation -} - -// TestUpdateWithHTTPHeaders_WithAllFields tests UpdateWithHeaders with all fields including FeedSession and AppSession -func TestUpdateWithHTTPHeaders_WithAllFields(t *testing.T) { - userContext, _ := enum.ParseUserContext("anonymous") - - header := &http.Header{} - context := &RequestContext{ - UserId: "12345", - UserContext: userContext, - AppVersionCode: 2, - ClientId: "client_1", - UserStateCode: "state_1", - UserPinCode: "123456", - UserCity: "city_1", - FeedSession: "feed_session_123", - AppSession: "app_session_456", - } - - UpdateWithHeaders(header, context) - - assert.Equal(t, "12345", header.Get(httpHeaders.HeaderUserId)) - assert.Equal(t, "anonymous", header.Get(httpHeaders.HeaderUserContext)) - assert.Equal(t, "2", header.Get(httpHeaders.HeaderAppVersionCode)) - assert.Equal(t, "client_1", header.Get(httpHeaders.HeaderClientId)) - assert.Equal(t, "state_1", header.Get(httpHeaders.HeaderUserStateCode)) - assert.Equal(t, "123456", header.Get(httpHeaders.HeaderUserPincode)) - assert.Equal(t, "city_1", header.Get(httpHeaders.HeaderUserCity)) - - // Note: FeedSession and AppSession are not included in UpdateWithHeaders function - // This is intentional as per the current implementation -} - -// TestGetMetadataValue tests the getMetadataValue helper function -func TestGetMetadataValue(t *testing.T) { - tests := []struct { - name string - metadata map[string]string - key string - expectedValue string - expectedError string - }{ - { - name: "Valid metadata with key", - metadata: map[string]string{ - "test-key": "test-value", - }, - key: "test-key", - expectedValue: "test-value", - expectedError: "", - }, - { - name: "Missing key in metadata", - metadata: map[string]string{ - "other-key": "other-value", - }, - key: "test-key", - expectedValue: "", - expectedError: "metadata key 'test-key' is missing ", - }, - { - name: "Empty metadata", - metadata: map[string]string{}, - key: "test-key", - expectedValue: "", - expectedError: "metadata key 'test-key' is missing ", - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - md := metadata.New(test.metadata) - value, err := getMetadataValue(md, test.key) - - if test.expectedError == "" { - assert.NoError(t, err) - assert.Equal(t, test.expectedValue, value) - } else { - assert.EqualError(t, err, test.expectedError) - assert.Equal(t, test.expectedValue, value) - } - }) - } -} - -// TestGetRequestContextForGRPC_EdgeCases tests additional edge cases -func TestGetRequestContextForGRPC_EdgeCases(t *testing.T) { - userContext, _ := enum.ParseUserContext("anonymous") - - tests := []struct { - name string - headers map[string]string - expectedResult *RequestContext - expectedError string - }{ - { - name: "Zero app version code", - headers: map[string]string{ - httpHeaders.HeaderUserId: "12345", - httpHeaders.HeaderUserContext: "anonymous", - httpHeaders.HeaderAppVersionCode: "0", - httpHeaders.HeaderClientId: "client_1", - }, - expectedResult: &RequestContext{ - UserId: "12345", - UserContext: userContext, - AppVersionCode: 0, - ClientId: "client_1", - }, - expectedError: "", - }, - { - name: "Negative app version code", - headers: map[string]string{ - httpHeaders.HeaderUserId: "12345", - httpHeaders.HeaderUserContext: "anonymous", - httpHeaders.HeaderAppVersionCode: "-1", - httpHeaders.HeaderClientId: "client_1", - }, - expectedResult: &RequestContext{ - UserId: "12345", - UserContext: userContext, - AppVersionCode: -1, - ClientId: "client_1", - }, - expectedError: "", - }, - { - name: "Empty string app version code", - headers: map[string]string{ - httpHeaders.HeaderUserId: "12345", - httpHeaders.HeaderUserContext: "anonymous", - httpHeaders.HeaderAppVersionCode: "", - httpHeaders.HeaderClientId: "client_1", - }, - expectedResult: &RequestContext{ - UserId: "12345", - UserContext: userContext, - AppVersionCode: 0, - ClientId: "client_1", - }, - expectedError: "", - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - ctx := context.Background() - headers := make(map[string]string) - for key, value := range test.headers { - headers[key] = value - } - ctx = metadata.NewIncomingContext(ctx, metadata.New(headers)) - - result, err := GetRequestContextForGRPC(ctx) - if test.expectedError == "" { - assert.NoError(t, err) - assert.Equal(t, test.expectedResult, result) - } else { - assert.EqualError(t, err, test.expectedError) - assert.Nil(t, result) - } - }) - } -} - -// TestGetRequestContext_EdgeCases tests additional edge cases for HTTP requests -func TestGetRequestContext_EdgeCases(t *testing.T) { - gin.SetMode(gin.TestMode) - userContext, _ := enum.ParseUserContext("anonymous") - - tests := []struct { - name string - headers map[string]string - expectedResult *RequestContext - expectedError string - }{ - { - name: "Zero app version code", - headers: map[string]string{ - httpHeaders.HeaderUserId: "12345", - httpHeaders.HeaderUserContext: "anonymous", - httpHeaders.HeaderAppVersionCode: "0", - httpHeaders.HeaderClientId: "client_1", - }, - expectedResult: &RequestContext{ - UserId: "12345", - UserContext: userContext, - AppVersionCode: 0, - ClientId: "client_1", - }, - expectedError: "", - }, - { - name: "Empty string app version code", - headers: map[string]string{ - httpHeaders.HeaderUserId: "12345", - httpHeaders.HeaderUserContext: "anonymous", - httpHeaders.HeaderAppVersionCode: "", - httpHeaders.HeaderClientId: "client_1", - }, - expectedResult: &RequestContext{ - UserId: "12345", - UserContext: userContext, - AppVersionCode: 0, - ClientId: "client_1", - }, - expectedError: "", - }, - { - name: "Minimum required headers only", - headers: map[string]string{ - httpHeaders.HeaderUserId: "12345", - httpHeaders.HeaderUserContext: "anonymous", - }, - expectedResult: &RequestContext{ - UserId: "12345", - UserContext: userContext, - AppVersionCode: 0, - ClientId: "", - UserStateCode: "", - UserPinCode: "", - UserCity: "", - FeedSession: "", - AppSession: "", - }, - expectedError: "", - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - context, _ := gin.CreateTestContext(httptest.NewRecorder()) - request, _ := http.NewRequest(http.MethodGet, "/", nil) - for key, value := range test.headers { - request.Header.Set(key, value) - } - context.Request = request - - result, err := GetRequestContext(context) - if test.expectedError == "" { - assert.NoError(t, err) - assert.Equal(t, test.expectedResult, result) - } else { - assert.EqualError(t, err, test.expectedError) - assert.Nil(t, result) - } - }) - } -} diff --git a/helix-client/pkg/clients/numerix/adaptor.go b/helix-client/pkg/clients/numerix/adaptor.go deleted file mode 100644 index 48e88fa7..00000000 --- a/helix-client/pkg/clients/numerix/adaptor.go +++ /dev/null @@ -1,153 +0,0 @@ -package numerix - -import ( - "strings" - - "github.com/rs/zerolog/log" - - "github.com/Meesho/BharatMLStack/helix-client/pkg/clients/numerix/client/grpc" - "github.com/Meesho/BharatMLStack/helix-client/pkg/datatypeconverter/typeconverter" -) - -type IAdapter interface { - MapRequestToProto(request *NumerixRequest) *grpc.NumerixRequestProto - MapProtoToResponse(protoResponse *grpc.NumerixResponseProto) *NumerixResponse -} - -type Adapter struct { - IAdapter -} - -func (a *Adapter) ScoreDataToFP32Bytes(request *NumerixRequest) { - mapOfInputType := make(map[int]string) - for i := range request.EntityScoreData.Schema { - schemaParts := strings.Split(request.EntityScoreData.Schema[i], "@") - if i == 0 { - request.EntityScoreData.Schema[i] = schemaParts[0] - continue - } - mapOfInputType[i] = schemaParts[1] - request.EntityScoreData.Schema[i] = schemaParts[0] - } - - for i := range request.EntityScoreData.Data { - for j := range request.EntityScoreData.Data[i] { - if j == 0 { - continue - } - converted, err := typeconverter.ConvertBytesToBytes(request.EntityScoreData.Data[i][j], mapOfInputType[j], "datatypefp32") - if err != nil { - log.Error().Err(err).Msgf("Error converting bytes at index i=%d, j=%d, from type=%s to fp32", i, j, mapOfInputType[j]) - return - } - request.EntityScoreData.Data[i][j] = converted - } - } -} - -func (a *Adapter) MapRequestToProto(request *NumerixRequest) *grpc.NumerixRequestProto { - if request.EntityScoreData.Data == nil && request.EntityScoreData.StringData == nil { - log.Warn().Msg("EntityScoreData Data and StringData is nil; cannot map request.") - return nil - } - var ( - protoScores []*grpc.Score - newSchema []string - dataType = "fp32" - ) - - switch { - case request.EntityScoreData.Data != nil: - protoScores = make([]*grpc.Score, len(request.EntityScoreData.Data)) - a.ScoreDataToFP32Bytes(request) - - for rowIdx, row := range request.EntityScoreData.Data { - protoScores[rowIdx] = &grpc.Score{ - MatrixFormat: &grpc.Score_ByteData{ - ByteData: &grpc.ByteList{Values: row}, - }, - } - } - newSchema = request.EntityScoreData.Schema - - case request.EntityScoreData.StringData != nil: - protoScores = make([]*grpc.Score, len(request.EntityScoreData.StringData)) - for rowIdx, row := range request.EntityScoreData.StringData { - protoScores[rowIdx] = &grpc.Score{ - MatrixFormat: &grpc.Score_StringData{ - StringData: &grpc.StringList{Values: row}, - }, - } - } - newSchema = request.EntityScoreData.Schema - - default: - log.Warn().Msg("No valid score data found in EntityScoreData.") - return nil - } - - return &grpc.NumerixRequestProto{ - EntityScoreData: &grpc.EntityScoreData{ - Schema: newSchema, - EntityScores: protoScores, - ComputeId: request.EntityScoreData.ComputeID, - DataType: &dataType, - }, - } -} - -func (a *Adapter) MapProtoToResponse(protoResponse *grpc.NumerixResponseProto) *NumerixResponse { - if errProto := protoResponse.GetError(); errProto != nil { - log.Warn().Msgf("Received error in proto response error: %s", errProto.GetMessage()) - return &NumerixResponse{} - } - - compDataProto := protoResponse.GetComputationScoreData() - if compDataProto == nil { - return &NumerixResponse{} - } - - computationScores := compDataProto.GetComputationScores() - - inputSize := len(computationScores) - data := make([][][]byte, inputSize) - var stringData [][]string - - for i, scoreProto := range computationScores { - switch T := scoreProto.GetMatrixFormat().(type) { - case *grpc.Score_ByteData: - if T.ByteData != nil && T.ByteData.GetValues() != nil { - data[i] = T.ByteData.GetValues() - } - case *grpc.Score_StringData: - if T.StringData != nil && T.StringData.GetValues() != nil { - stringData = append(stringData, T.StringData.GetValues()) - } - } - } - - return &NumerixResponse{ - ComputationScoreData: ComputationScoreData{ - Schema: compDataProto.GetSchema(), - Data: data, - StringData: stringData, - }, - } -} - -func (a *Adapter) ConvertScoreDataFromFP32(data [][][]byte, dataType string) error { - var err error - - for i := range data { - for j := range data[i] { - if data[i][j] == nil { - continue - } - data[i][j], err = typeconverter.ConvertBytesToBytes(data[i][j], "FP32", dataType) - if err != nil { - return err - } - } - } - return nil -} diff --git a/helix-client/pkg/clients/numerix/models.go b/helix-client/pkg/clients/numerix/models.go deleted file mode 100644 index aa70b388..00000000 --- a/helix-client/pkg/clients/numerix/models.go +++ /dev/null @@ -1,29 +0,0 @@ -package numerix - -import "github.com/Meesho/BharatMLStack/helix-client/pkg/clients/numerix/client/grpc" - -type NumerixRequest struct { - EntityScoreData EntityScoreData `json:"entity_score_data"` -} - -type EntityScoreData struct { - Schema []string `json:"schema"` - Data [][][]byte `json:"data"` - StringData [][]string `json:"string_data"` - ComputeID string `json:"compute_id"` - DataType string `json:"data_type"` -} - -type NumerixResponse struct { - ComputationScoreData ComputationScoreData `json:"computation_score_data"` -} - -type ComputationScoreData struct { - Schema []string `json:"schema"` - Data [][][]byte `json:"data"` - StringData [][]string `json:"string_data"` -} - -type NumerixRequestWrapper struct { - RequestProto *grpc.NumerixRequestProto -} diff --git a/helix-client/pkg/clients/numerix/v1.go b/helix-client/pkg/clients/numerix/v1.go deleted file mode 100644 index 055fb628..00000000 --- a/helix-client/pkg/clients/numerix/v1.go +++ /dev/null @@ -1,280 +0,0 @@ -package numerix - -import ( - "context" - "fmt" - "sync" - "time" - - "github.com/Meesho/BharatMLStack/helix-client/pkg/clients/numerix/client/grpc" - "github.com/Meesho/BharatMLStack/helix-client/pkg/datatypeconverter/byteorder" - "github.com/Meesho/BharatMLStack/helix-client/pkg/grpcclient" - "github.com/rs/zerolog/log" - metadata "google.golang.org/grpc/metadata" -) - -type ClientV1 struct { - ClientConfigs *ClientConfig - GrpcClient *grpcclient.GRPCClient - numerixClient grpc.NumerixClient - Adapter Adapter -} - -var ( - client *ClientV1 - once sync.Once - headers metadata.MD -) - -const ( - V1Prefix = "numerix_CLIENT_V1_" - numerix_CALLER_ID = "numerix-CALLER-ID" -) - -func InitV1Client(configBytes []byte) NumerixClient { - if client == nil { - once.Do(func() { - byteorder.Init() - - clientConfig, err := getClientConfigs(configBytes) - if err != nil { - log.Panic().Err(err).Msgf("Invalid numerix client configs: %#v", clientConfig) - } - headers = metadata.New(map[string]string{ - numerix_CALLER_ID: clientConfig.CallerId, - }) - - grpcClient, grpcErr := getGrpcClient(clientConfig) - if grpcErr != nil { - log.Panic().Err(grpcErr).Msgf("Error creating numerix service grpc client, client: %#v", grpcClient) - } - - numerixClient := grpc.NewNumerixClient(grpcClient) - client = &ClientV1{ - ClientConfigs: clientConfig, - GrpcClient: grpcClient, - numerixClient: numerixClient, - Adapter: Adapter{}, - } - }) - } - return client -} - -func getGrpcClient(conf *ClientConfig) (*grpcclient.GRPCClient, error) { - var client *grpcclient.GRPCClient - var err error - defer func() { - if r := recover(); r != nil { - err = fmt.Errorf("panic creating grpc client from prefix: %v", r) - } - }() - client = grpcclient.NewConnFromConfig(&grpcclient.Config{ - Host: conf.Host, - Port: conf.Port, - DeadLine: conf.DeadlineExceedMS, - LoadBalancingPolicy: "round_robin", - PlainText: conf.PlainText, - }, V1Prefix) - return client, err -} - -type BatchInfo struct { - StartIndex int - EndIndex int -} - -type batchResult struct { - index int - response *grpc.NumerixResponseProto - err error -} - -func (c *ClientV1) RetrieveScore(req *NumerixRequest) (*NumerixResponse, error) { - if req == nil { - return nil, fmt.Errorf("numerix request cannot be nil") - } - - err := validateRequest(req) - if err != nil { - return nil, err - } - - numInputs := len(req.EntityScoreData.Data) - batchSize := c.ClientConfigs.BatchSize - - protoReq := c.Adapter.MapRequestToProto(req) - if protoReq == nil { - return nil, fmt.Errorf("failed to map request to proto") - } - - // If data is smaller than batch size, process directly - if numInputs <= batchSize { - return c.processRequest(protoReq) - } - - batches := c.getBatchIndices(numInputs, batchSize) - numBatches := len(batches) - - resultChan := make(chan batchResult, numBatches) - - finalResponse := &NumerixResponse{ - ComputationScoreData: ComputationScoreData{ - Schema: protoReq.EntityScoreData.Schema, - Data: make([][][]byte, numInputs), - StringData: make([][]string, 0), - }, - } - - wg := sync.WaitGroup{} - // Process each batch in parallel - for i, batch := range batches { - wg.Add(1) - go func(batch BatchInfo, batchIndex int) { - defer wg.Done() - defer func() { - if r := recover(); r != nil { - log.Warn(). - Interface("panic", r). - Int("batch_index", batchIndex). - Msg("Panic occurred while processing numerix batch") - resultChan <- batchResult{index: batchIndex, response: nil, err: fmt.Errorf("panic in batch processing: %v", r)} - } - }() - - protoResp, err := c.processBatchByIndex(protoReq, batch, batchIndex) - if err != nil { - resultChan <- batchResult{index: batchIndex, response: nil, err: err} - return - } - - resultChan <- batchResult{index: batchIndex, response: protoResp, err: nil} - }(batch, i) - } - - go func() { - wg.Wait() - close(resultChan) - }() - - for result := range resultChan { - if result.err != nil { - log.Warn().Err(result.err).Msgf("Error processing batch %d", result.index) - } - batch := batches[result.index] - c.fillResponseFromBatch(finalResponse, result.response, batch) - } - - return finalResponse, nil -} - -func (c *ClientV1) fillResponseFromBatch(finalResponse *NumerixResponse, batchProtoResp *grpc.NumerixResponseProto, batch BatchInfo) { - if errProto := batchProtoResp.GetError(); errProto != nil { - log.Warn().Msgf("Received error in proto response error: %s", errProto.GetMessage()) - return - } - - compDataProto := batchProtoResp.GetComputationScoreData() - if compDataProto == nil { - return - } - - computationScores := compDataProto.GetComputationScores() - batchSize := batch.EndIndex - batch.StartIndex - - for i, scoreProto := range computationScores { - if i >= batchSize { - break - } - - targetIndex := batch.StartIndex + i - switch T := scoreProto.GetMatrixFormat().(type) { - case *grpc.Score_ByteData: - if T.ByteData != nil && T.ByteData.GetValues() != nil { - finalResponse.ComputationScoreData.Data[targetIndex] = T.ByteData.GetValues() - } - - case *grpc.Score_StringData: - if T.StringData != nil && T.StringData.GetValues() != nil { - finalResponse.ComputationScoreData.StringData = append(finalResponse.ComputationScoreData.StringData, T.StringData.GetValues()) - } - } - - } -} - -func (c *ClientV1) getBatchIndices(numElements, batchSize int) []BatchInfo { - numBatches := (numElements + batchSize - 1) / batchSize - batches := make([]BatchInfo, numBatches) - - for i := 0; i < numElements; i += batchSize { - end := i + batchSize - if end > numElements { - end = numElements - } - batches[i/batchSize] = BatchInfo{i, end} - } - return batches -} - -func (c *ClientV1) processBatchByIndex(protoReq *grpc.NumerixRequestProto, batch BatchInfo, batchIndex int) (*grpc.NumerixResponseProto, error) { - batchReq := &grpc.NumerixRequestProto{ - EntityScoreData: &grpc.EntityScoreData{ - ComputeId: protoReq.EntityScoreData.ComputeId, - DataType: protoReq.EntityScoreData.DataType, - Schema: protoReq.EntityScoreData.Schema, - EntityScores: protoReq.EntityScoreData.EntityScores[batch.StartIndex:batch.EndIndex], - }, - } - - response, err := c.callGrpcService(batchReq) - if err != nil { - log.Warn().Err(err). - Int("batch_index", batchIndex). - Str("compute_id", batchReq.EntityScoreData.ComputeId). - Msg("Failed to get score from numerix service") - return nil, err - } - - return response, nil -} - -func (c *ClientV1) processRequest(protoReq *grpc.NumerixRequestProto) (*NumerixResponse, error) { - response, err := c.callGrpcService(protoReq) - if err != nil { - return nil, err - } - - resp := c.Adapter.MapProtoToResponse(response) - if resp == nil { - return nil, fmt.Errorf("failed to map proto to response") - } - return resp, nil -} - -func validateRequest(req *NumerixRequest) error { - if len(req.EntityScoreData.Schema) == 0 { - return fmt.Errorf("schema is required") - } - if (req.EntityScoreData.Data == nil && req.EntityScoreData.StringData == nil) || (len(req.EntityScoreData.Data) == 0 && len(req.EntityScoreData.StringData) == 0) { - return fmt.Errorf("data is required") - } - if req.EntityScoreData.ComputeID == "" { - return fmt.Errorf("compute_id is required") - } - if req.EntityScoreData.DataType == "" { - return fmt.Errorf("data_type is required") - } - return nil -} - -func (c *ClientV1) callGrpcService(req *grpc.NumerixRequestProto) (*grpc.NumerixResponseProto, error) { - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(c.ClientConfigs.DeadlineExceedMS)*time.Millisecond) - defer cancel() - ctx = metadata.NewOutgoingContext(ctx, headers) - response, err := c.numerixClient.Compute(ctx, req) - if err != nil { - return nil, err - } - return response, nil -} diff --git a/helix-client/pkg/clients/predator/adaptor.go b/helix-client/pkg/clients/predator/adaptor.go deleted file mode 100644 index 3090519c..00000000 --- a/helix-client/pkg/clients/predator/adaptor.go +++ /dev/null @@ -1,690 +0,0 @@ -package predator - -import ( - "encoding/binary" - "fmt" - "math" - "strconv" - - "strings" - - triton "github.com/Meesho/BharatMLStack/helix-client/pkg/clients/predator/client/grpc" - "github.com/Meesho/BharatMLStack/helix-client/pkg/datatypeconverter/types" - "github.com/Meesho/BharatMLStack/helix-client/pkg/utils" - "github.com/rs/zerolog/log" -) - -const ( - datatypeBytes = "BYTES" -) - -type IAdapter interface { - MapPredatorRequestToProto(predatorRequest *PredatorRequest, predatorDataType PredatorDataType, batches []BatchInfo) []*triton.ModelInferRequest - MapProtoToPredatorResponse(tritonResponse *triton.ModelInferResponse, requestedOutputs []Output, predatorDataType PredatorDataType) *PredatorResponse -} - -type Adapter struct { - IAdapter -} - -// PredatorDataType represents the type of data in a predator input -type PredatorDataType int - -const ( - PredatorDataTypeNone PredatorDataType = iota - PredatorDataTypeBytes - PredatorDataTypeString -) - -// getInputDataForProcessing returns the byte data for processing, converting from string if necessary -func getInputDataForProcessing(input Input, predatorDataType PredatorDataType) ([][][]byte, error) { - switch predatorDataType { - case PredatorDataTypeString: - return convertStringToBytes(input.StringData, input.DataType) - case PredatorDataTypeBytes: - return input.Data, nil - default: - return nil, fmt.Errorf("input %s has no data", input.Name) - } -} - -func (a *Adapter) initializeBatchRequest(batches []BatchInfo, batchRequests []*triton.ModelInferRequest, predatorRequest *PredatorRequest, inferOutputs []*triton.ModelInferRequest_InferRequestedOutputTensor, parameters map[string]*triton.InferParameter) { - inferInputs := a.createInferInputTensors(predatorRequest.Inputs, batches[0].EndIndex-batches[0].StartIndex) - - for i := range batchRequests { - if i == len(batchRequests)-1 { - inferInputs = a.createInferInputTensors(predatorRequest.Inputs, batches[i].EndIndex-batches[i].StartIndex) - } - batchRequests[i] = &triton.ModelInferRequest{ - ModelName: predatorRequest.ModelName, - ModelVersion: predatorRequest.ModelVersion, - Inputs: inferInputs, - Outputs: inferOutputs, - Parameters: parameters, - } - batchRequests[i].RawInputContents = make([][]byte, len(inferInputs)) - } -} - -func (a *Adapter) flatten3DByteSlice(batches []BatchInfo, data [][][]byte, datatype string, batchSize int, batchRequests []*triton.ModelInferRequest, inputIdx int, predatorRequest *PredatorRequest, inferOutputs []*triton.ModelInferRequest_InferRequestedOutputTensor, parameters map[string]*triton.InferParameter) { - rowCount := len(data) - if rowCount == 0 || len(data[0]) == 0 { - return - } - - numFeatures := len(data[0]) - - if inputIdx == 0 { - a.initializeBatchRequest(batches, batchRequests, predatorRequest, inferOutputs, parameters) - } - - if datatype == datatypeBytes { - totalSize := make([]int, len(batchRequests)) - totalByteSize := 0 - for rowIdx := 0; rowIdx < rowCount; rowIdx++ { - for featureIdx := 0; featureIdx < numFeatures; featureIdx++ { - featureSize := len(data[rowIdx][featureIdx]) - totalSize[rowIdx/batchSize] += 4 + featureSize - totalByteSize += 4 + featureSize - } - } - - buffer := make([]byte, totalByteSize) - offset := 0 - - for i := range batchRequests { - batchRequests[i].RawInputContents[inputIdx] = buffer[offset : offset+totalSize[i]] - offset += totalSize[i] - } - - lengthBytes := make([]byte, 4) - - count := 0 - for rowIdx := 0; rowIdx < rowCount; rowIdx++ { - if rowIdx%batchSize == 0 { - count = 0 - } - for featureIdx := 0; featureIdx < numFeatures; featureIdx++ { - stringData := data[rowIdx][featureIdx] - binary.LittleEndian.PutUint32(lengthBytes, uint32(len(stringData))) - batchRequests[rowIdx/batchSize].RawInputContents[inputIdx][count] = lengthBytes[0] - batchRequests[rowIdx/batchSize].RawInputContents[inputIdx][count+1] = lengthBytes[1] - batchRequests[rowIdx/batchSize].RawInputContents[inputIdx][count+2] = lengthBytes[2] - batchRequests[rowIdx/batchSize].RawInputContents[inputIdx][count+3] = lengthBytes[3] - count += 4 - for i := range stringData { - batchRequests[rowIdx/batchSize].RawInputContents[inputIdx][count] = stringData[i] - count++ - } - } - } - } else { - totalSize := make([]int, len(batchRequests)) - totalByteSize := 0 - for rowIdx := 0; rowIdx < rowCount; rowIdx++ { - for featureIdx := 0; featureIdx < numFeatures; featureIdx++ { - featureSize := len(data[rowIdx][featureIdx]) - totalSize[rowIdx/batchSize] += featureSize - totalByteSize += featureSize - } - } - - buffer := make([]byte, totalByteSize) - offset := 0 - - for i := range batchRequests { - batchRequests[i].RawInputContents[inputIdx] = buffer[offset : offset+totalSize[i]] - offset += totalSize[i] - } - - count := 0 - for rowIdx := 0; rowIdx < rowCount; rowIdx++ { - if rowIdx%batchSize == 0 { - count = 0 - } - for featureIdx := 0; featureIdx < numFeatures; featureIdx++ { - for i := range data[rowIdx][featureIdx] { - batchRequests[rowIdx/batchSize].RawInputContents[inputIdx][count] = data[rowIdx][featureIdx][i] - count++ - } - } - } - } -} - -// Element size lookup table for better performance -var elementSizeMap = map[string]int{ - "FP32": 4, - "BF16": 2, - "FP16": 2, - "INT64": 8, - "INT32": 4, - "INT16": 2, - "INT8": 1, - "BOOL": 1, - "BYTES": -1, // Variable length, needs special handling -} - -func getElementSize(datatype string) int { - if size, ok := elementSizeMap[datatype]; ok { - return size - } - return -1 // default -} - -func (a *Adapter) createInferInputTensors(inputs []Input, batchSize int) []*triton.ModelInferRequest_InferInputTensor { - inferInputs := make([]*triton.ModelInferRequest_InferInputTensor, len(inputs)) - for i, input := range inputs { - shape := make([]int64, len(input.Dims)+1) - shape[0] = int64(batchSize) - for j, dim := range input.Dims { - shape[j+1] = int64(dim) - } - inferInputs[i] = &triton.ModelInferRequest_InferInputTensor{ - Name: input.Name, - Datatype: input.DataType, - Shape: shape, - } - } - return inferInputs -} - -func (a *Adapter) createInferOutputTensors(outputs []Output) []*triton.ModelInferRequest_InferRequestedOutputTensor { - inferOutputs := make([]*triton.ModelInferRequest_InferRequestedOutputTensor, len(outputs)) - for i, output := range outputs { - inferOutputs[i] = &triton.ModelInferRequest_InferRequestedOutputTensor{ - Name: output.Name, - } - } - return inferOutputs -} - -func (a *Adapter) createSequenceParameters(predatorRequest *PredatorRequest) map[string]*triton.InferParameter { - parameters := make(map[string]*triton.InferParameter) - - if predatorRequest.SequenceId != nil { - parameters["sequence_id"] = &triton.InferParameter{ - ParameterChoice: &triton.InferParameter_StringParam{ - StringParam: *predatorRequest.SequenceId, - }, - } - } - if predatorRequest.SequenceStart != nil { - parameters["sequence_start"] = &triton.InferParameter{ - ParameterChoice: &triton.InferParameter_BoolParam{ - BoolParam: *predatorRequest.SequenceStart, - }, - } - } - if predatorRequest.SequenceEnd != nil { - parameters["sequence_end"] = &triton.InferParameter{ - ParameterChoice: &triton.InferParameter_BoolParam{ - BoolParam: *predatorRequest.SequenceEnd, - }, - } - } - - return parameters -} - -func (a *Adapter) MapPredatorRequestToProto(predatorRequest *PredatorRequest, predatorDataType PredatorDataType, batches []BatchInfo) []*triton.ModelInferRequest { - if len(batches) == 0 { - return nil - } - - inferOutputs := a.createInferOutputTensors(predatorRequest.Outputs) - parameters := a.createSequenceParameters(predatorRequest) - - batchRequests := make([]*triton.ModelInferRequest, len(batches)) - - for i, input := range predatorRequest.Inputs { - inputData, err := getInputDataForProcessing(input, predatorDataType) - if err != nil { - log.Error().Err(err). - Str("input_name", input.Name). - Msg("Failed to process input data") - continue - } - - a.flatten3DByteSlice(batches, inputData, input.DataType, predatorRequest.BatchSize, batchRequests, i, predatorRequest, inferOutputs, parameters) - } - a.InferAndSetInputShapes(predatorRequest, batchRequests, batches) - return batchRequests -} - -func (a *Adapter) MapProtoToPredatorResponse(tritonResponse *triton.ModelInferResponse, requestedOutputs []Output, predatorDataType PredatorDataType) *PredatorResponse { - if utils.IsNilPointer(tritonResponse) { - log.Warn().Msg("Received nil triton response") - return nil - } - - tritonOutputIndex := make(map[string]int) - for i, output := range tritonResponse.Outputs { - tritonOutputIndex[output.Name] = i - } - - // Validate that we have outputs before accessing them - if len(tritonResponse.Outputs) == 0 { - log.Warn().Msg("Received triton response with no outputs") - return nil - } - - // Validate that the first output has a valid shape - if len(tritonResponse.Outputs[0].Shape) == 0 { - log.Warn().Msg("Received triton response with output having no shape") - return nil - } - - // Calculate rowCount once, assuming all outputs have the same batch size - rowCount := int(tritonResponse.Outputs[0].Shape[0]) - - // Pre-calculate total number of outputs for better memory allocation - totalOutputs := 0 - for _, reqOut := range requestedOutputs { - if _, ok := tritonOutputIndex[reqOut.Name]; ok { - totalOutputs += len(reqOut.ModelScores) - } - } - outputs := make([]ResponseOutput, 0, totalOutputs) - - for _, reqOut := range requestedOutputs { - tritonIdx, ok := tritonOutputIndex[reqOut.Name] - if !ok { - continue - } - // Validate bounds for Outputs and RawOutputContents - if tritonIdx >= len(tritonResponse.Outputs) { - log.Warn(). - Int("triton_idx", tritonIdx). - Int("outputs_len", len(tritonResponse.Outputs)). - Str("output_name", reqOut.Name). - Msg("Triton output index out of bounds") - continue - } - if tritonIdx >= len(tritonResponse.RawOutputContents) { - log.Warn(). - Int("triton_idx", tritonIdx). - Int("raw_output_contents_len", len(tritonResponse.RawOutputContents)). - Str("output_name", reqOut.Name). - Msg("Triton raw output contents index out of bounds") - continue - } - tritonOut := tritonResponse.Outputs[tritonIdx] - tritonData := tritonResponse.RawOutputContents[tritonIdx] - - datatype := tritonOut.Datatype - numScores := len(reqOut.ModelScores) - elementSize := getElementSize(datatype) - - // For BYTES, parse the length-prefixed format - if datatype == "BYTES" { - // Parse all strings sequentially first - totalStrings := rowCount * numScores - allStrings := make([][]byte, totalStrings) // Initialize with nil values - offset := 0 - parsedCount := 0 - - // Parse as many strings as available in the data - for parsedCount < totalStrings && offset+4 <= len(tritonData) { - // Read 4-byte length prefix - strLen := int(binary.LittleEndian.Uint32(tritonData[offset : offset+4])) - - // Extract length prefix + string content - totalLen := 4 + strLen - if offset+totalLen > len(tritonData) { - break - } - allStrings[parsedCount] = tritonData[offset+4 : offset+totalLen] - offset += totalLen - parsedCount++ - } - - // Group by score - for scoreIdx, scoreName := range reqOut.ModelScores { - data := make([][]byte, rowCount) - for b := 0; b < rowCount; b++ { - stringIdx := b*numScores + scoreIdx - if stringIdx < len(allStrings) && allStrings[stringIdx] != nil { - data[b] = allStrings[stringIdx] - } - // data[b] remains nil for missing batches - } - - // Construct shape: batch_size + dimensions for this specific score - var shape []int64 - shape = append(shape, int64(rowCount)) - if scoreIdx < len(reqOut.Dims) { - for _, dim := range reqOut.Dims[scoreIdx] { - shape = append(shape, int64(dim)) - } - } - - // Conditionally populate StringData or Data based on original input type - var stringData [][]string - if predatorDataType == PredatorDataTypeString { - // Only convert to strings if original input was string data - convertedStringData, err := convertBytesToStringWithNils(data, datatype) - if err != nil { - log.Error().Err(err). - Str("output_name", scoreName). - Str("datatype", datatype). - Msg("Failed to convert bytes to string for output") - } else { - stringData = convertedStringData - } - } - - outputs = append(outputs, ResponseOutput{ - Name: scoreName, - DataType: datatype, - Shape: shape, - Data: data, // Always include byte data (needed for processing) - StringData: stringData, // Only populated if original input was string - }) - } - continue - } - - // Calculate individual score sizes based on their specific dimensions - scoreSizes := make([]int, numScores) - totalSizePerBatch := 0 - - for scoreIdx := 0; scoreIdx < numScores; scoreIdx++ { - elements := 1 - if scoreIdx < len(reqOut.Dims) && len(reqOut.Dims[scoreIdx]) > 0 { - for _, dim := range reqOut.Dims[scoreIdx] { - elements *= dim - } - } - scoreSize := elementSize * elements - scoreSizes[scoreIdx] = scoreSize - totalSizePerBatch += scoreSize - } - - // Calculate expected total size and actual available size - expectedTotalSize := rowCount * totalSizePerBatch - availableSize := len(tritonData) - - // Calculate how many complete batches we have - completeBatches := availableSize / totalSizePerBatch - if completeBatches > rowCount { - completeBatches = rowCount - } - - // Log warning if some data is missing - if availableSize < expectedTotalSize { - missingBatches := rowCount - completeBatches - log.Warn(). - Int("expected_size", expectedTotalSize). - Int("available_size", availableSize). - Int("expected_batches", rowCount). - Int("complete_batches", completeBatches). - Int("missing_batches", missingBatches). - Msg("Some batch data is missing, filling with nil values") - } - - // Pre-calculate cumulative offsets to avoid O(n²) calculation - cumulativeOffsets := make([]int, numScores) - if numScores > 0 { - cumulativeOffsets[0] = 0 - for scoreIdx := 1; scoreIdx < numScores; scoreIdx++ { - cumulativeOffsets[scoreIdx] = cumulativeOffsets[scoreIdx-1] + scoreSizes[scoreIdx-1] - } - } - - for scoreIdx, scoreName := range reqOut.ModelScores { - scoreSize := scoreSizes[scoreIdx] - data := make([][]byte, rowCount) - - scoreOffsetInBatch := cumulativeOffsets[scoreIdx] - for b := 0; b < rowCount; b++ { - // Calculate offset: batch offset + score offset within batch - offset := b*totalSizePerBatch + scoreOffsetInBatch - endOffset := offset + scoreSize - - // Only extract data if we have enough data for this batch - if endOffset <= availableSize { - data[b] = tritonData[offset:endOffset] - } - // data[b] remains nil for missing batches - } - - // Construct shape: batch_size + dimensions for this specific score - var shape []int64 - shape = append(shape, int64(rowCount)) - if scoreIdx < len(reqOut.Dims) { - for _, dim := range reqOut.Dims[scoreIdx] { - shape = append(shape, int64(dim)) - } - } - - // Conditionally populate StringData or Data based on original input type - var stringData [][]string - if predatorDataType == PredatorDataTypeString { - // Only convert to strings if original input was string data - convertedStringData, err := convertBytesToStringWithNils(data, datatype) - if err != nil { - log.Error().Err(err). - Str("output_name", scoreName). - Str("datatype", datatype). - Msg("Failed to convert bytes to string for output") - } else { - stringData = convertedStringData - } - } - - outputs = append(outputs, ResponseOutput{ - Name: scoreName, - DataType: datatype, - Shape: shape, - Data: data, // Always include byte data (needed for processing) - StringData: stringData, // Only populated if original input was string - }) - } - } - - return &PredatorResponse{ - ModelName: tritonResponse.ModelName, - ModelVersion: tritonResponse.ModelVersion, - Outputs: outputs, - } -} - -// convertStringToBytes converts string data to bytes based on the specified data type -func convertStringToBytes(stringData [][][]string, dataType string) ([][][]byte, error) { - byteData := make([][][]byte, len(stringData)) - - for batchIdx, batch := range stringData { - byteData[batchIdx] = make([][]byte, len(batch)) - for featureIdx, feature := range batch { - switch dataType { - case "FP32": - bytes := make([]byte, len(feature)*4) - for i, str := range feature { - val, err := strconv.ParseFloat(str, 32) - if err != nil { - return nil, fmt.Errorf("failed to parse float32 from string %s: %v", str, err) - } - binary.LittleEndian.PutUint32(bytes[i*4:(i+1)*4], math.Float32bits(float32(val))) - } - byteData[batchIdx][featureIdx] = bytes - case "INT32": - bytes := make([]byte, len(feature)*4) - for i, str := range feature { - val, err := strconv.ParseInt(str, 10, 32) - if err != nil { - return nil, fmt.Errorf("failed to parse int32 from string %s: %v", str, err) - } - binary.LittleEndian.PutUint32(bytes[i*4:(i+1)*4], uint32(val)) - } - byteData[batchIdx][featureIdx] = bytes - case "INT64": - bytes := make([]byte, len(feature)*8) - for i, str := range feature { - val, err := strconv.ParseInt(str, 10, 64) - if err != nil { - return nil, fmt.Errorf("failed to parse int64 from string %s: %v", str, err) - } - binary.LittleEndian.PutUint64(bytes[i*8:(i+1)*8], uint64(val)) - } - byteData[batchIdx][featureIdx] = bytes - case "BOOL": - bytes := make([]byte, len(feature)) - for i, str := range feature { - val, err := strconv.ParseBool(str) - if err != nil { - return nil, fmt.Errorf("failed to parse bool from string %s: %v", str, err) - } - if val { - bytes[i] = 1 - } else { - bytes[i] = 0 - } - } - byteData[batchIdx][featureIdx] = bytes - case "BYTES": - // For BYTES type, just convert string to bytes - byteData[batchIdx][featureIdx] = []byte(feature[0]) - default: - return nil, fmt.Errorf("unsupported data type for string conversion: %s", dataType) - } - } - } - - return byteData, nil -} - -// convertBytesToStringWithNils converts byte data to strings, handling nil byte slices -func convertBytesToStringWithNils(byteData [][]byte, dataType string) ([][]string, error) { - stringData := make([][]string, len(byteData)) - - for batchIdx, batch := range byteData { - if batch == nil { - // For nil batches, create nil string slice - stringData[batchIdx] = nil - continue - } - - switch dataType { - case "FP32": - strings := make([]string, len(batch)/4) - for i := 0; i < len(strings); i++ { - bits := binary.LittleEndian.Uint32(batch[i*4 : (i+1)*4]) - val := math.Float32frombits(bits) - strings[i] = strconv.FormatFloat(float64(val), 'f', -1, 32) - } - stringData[batchIdx] = strings - case "INT32": - strings := make([]string, len(batch)/4) - for i := 0; i < len(strings); i++ { - val := int32(binary.LittleEndian.Uint32(batch[i*4 : (i+1)*4])) - strings[i] = strconv.FormatInt(int64(val), 10) - } - stringData[batchIdx] = strings - case "INT64": - strings := make([]string, len(batch)/8) - for i := 0; i < len(strings); i++ { - val := int64(binary.LittleEndian.Uint64(batch[i*8 : (i+1)*8])) - strings[i] = strconv.FormatInt(val, 10) - } - stringData[batchIdx] = strings - case "BOOL": - strings := make([]string, len(batch)) - for i, b := range batch { - strings[i] = strconv.FormatBool(b != 0) - } - stringData[batchIdx] = strings - case "BYTES": - // For BYTES type, just convert bytes to string - stringData[batchIdx] = []string{string(batch)} - default: - return nil, fmt.Errorf("unsupported data type for byte conversion: %s", dataType) - } - } - - return stringData, nil -} - -func (a *Adapter) InferAndSetInputShapes(predatorRequest *PredatorRequest, batchReqs []*triton.ModelInferRequest, batches []BatchInfo) { - for batchIdx := range batches { - for inputIdx := range predatorRequest.Inputs { - totalByteSize := len(batchReqs[batchIdx].RawInputContents[inputIdx]) - inputDataType := GetFeatureStoreTypeFromPredator(predatorRequest.Inputs[inputIdx].DataType) - inputByteSize := 0 - if inputDataType == datatypeBytes { - inputByteSize = 4 - featureLen := binary.LittleEndian.Uint32(batchReqs[batchIdx].RawInputContents[inputIdx][0:4]) - inputByteSize += int(featureLen) - } else { - if !strings.Contains(inputDataType, "DataType") { - inputDataType = "DataType" + inputDataType - } - parsedInputDataType, err := types.ParseDataType(inputDataType) - if err != nil { - log.Error().Err(err). - Str("input_name", predatorRequest.Inputs[inputIdx].Name). - Msg("Failed to parse input data type") - continue - } - inputByteSize = parsedInputDataType.Size() - } - if len(batchReqs[batchIdx].Inputs[inputIdx].Shape) > 2 { - unknownIdx := -1 - knownSizes := 1 - for i, dim := range batchReqs[batchIdx].Inputs[inputIdx].Shape { - if dim == -1 { - unknownIdx = i - } else { - knownSizes *= int(dim) - } - } - if unknownIdx != -1 { - if inputByteSize == 0 { - log.Error(). - Str("input_name", predatorRequest.Inputs[inputIdx].Name). - Msg("Input data type is not supported") - continue - } - batchReqs[batchIdx].Inputs[inputIdx].Shape[unknownIdx] = int64(totalByteSize / (knownSizes * inputByteSize)) - } - } else { - if batchReqs[batchIdx].Inputs[inputIdx].Shape[1] == -1 { - if inputByteSize == 0 { - log.Error(). - Str("input_name", predatorRequest.Inputs[inputIdx].Name). - Msg("Input data type is not supported") - continue - } - batchReqs[batchIdx].Inputs[inputIdx].Shape[1] = int64(totalByteSize / (int(batchReqs[batchIdx].Inputs[inputIdx].Shape[0]) * inputByteSize)) - } - } - } - } -} - -func GetFeatureStoreTypeFromPredator(predatorDataType string) string { - switch predatorDataType { - case "INT8": - return "Int8" - case "INT16": - return "Int16" - case "INT32": - return "Int32" - case "INT64": - return "Int64" - case "UINT8": - return "Uint8" - case "UINT16": - return "Uint16" - case "UINT32": - return "Uint32" - case "UINT64": - return "Uint64" - case "STRING": - return "String" - case "BOOL": - return "Bool" - default: - return predatorDataType - } -} diff --git a/helix-client/pkg/clients/predator/v1.go b/helix-client/pkg/clients/predator/v1.go deleted file mode 100644 index 7bfae8f4..00000000 --- a/helix-client/pkg/clients/predator/v1.go +++ /dev/null @@ -1,559 +0,0 @@ -package predator - -import ( - "context" - "fmt" - "time" - - triton "github.com/Meesho/BharatMLStack/helix-client/pkg/clients/predator/client/grpc" - "github.com/Meesho/BharatMLStack/helix-client/pkg/grpcclient" - "github.com/Meesho/BharatMLStack/helix-client/pkg/metric" - "github.com/rs/zerolog/log" - "google.golang.org/grpc/metadata" -) - -const ( - v1Prefix = "externalservicepredator_" - - // Header keys for authentication - headerCallerID = "PREDATOR-CALLER-ID" - headerCallerToken = "PREDATOR-AUTH-TOKEN" - predatorServiceName = "predator" -) - -type ClientV1 struct { - adapter Adapter - callerId string - callerToken string - conn *grpcclient.GRPCClient - grpcClient triton.GRPCInferenceServiceClient -} - -// NewClientV1 creates a new instance of the Orion client (v1) -func NewClientV1(config *Config) *ClientV1 { - validateConfig(config) - - conn := grpcclient.NewConnFromConfig(&grpcclient.Config{ - Host: config.Host, - Port: config.Port, - DeadLine: config.DeadLine, - PlainText: config.PlainText, - }, v1Prefix) - - // Create the gRPC client once during initialization - grpcClient := triton.NewGRPCInferenceServiceClient(conn) - - return &ClientV1{ - adapter: Adapter{}, - callerId: config.CallerId, - callerToken: config.CallerToken, - conn: conn, - grpcClient: grpcClient, - } -} - -func validateConfig(config *Config) { - if config == nil { - log.Panic().Msg("Configuration is nil. Please provide a valid config.") - return - } - if len(config.Host) == 0 { - log.Panic().Msg("Configuration error: Host is empty. Please provide a valid host.") - } - if len(config.Port) == 0 { - log.Panic().Msg("Configuration error: Port is empty. Please provide a valid port.") - } - if len(config.CallerId) == 0 { - log.Panic().Msg("Configuration error: Caller ID is empty. Please provide a valid caller ID.") - } - if len(config.CallerToken) == 0 { - log.Panic().Msg("Configuration error: Caller token is empty. Please provide a valid caller token.") - } -} - -func getRequestDataType(req *PredatorRequest) PredatorDataType { - if len(req.Inputs[0].StringData) > 0 { - return PredatorDataTypeString - } - if len(req.Inputs[0].Data) > 0 { - return PredatorDataTypeBytes - } - return PredatorDataTypeNone -} - -type BatchInfo struct { - StartIndex int - EndIndex int -} - -type preparedRequest struct { - requestedOutputs []Output - predatorDataType PredatorDataType - deadline int64 - numInputs int - batches []BatchInfo - batchProtoReqs []*triton.ModelInferRequest -} - -type batchResult struct { - index int - response *triton.ModelInferResponse - err error -} - -// prepareInferenceRequest prepares the initial request components -func (c *ClientV1) prepareInferenceRequest(req *PredatorRequest) (*preparedRequest, error) { - err := validatePredatorRequest(req) - if err != nil { - return nil, err - } - - requestedOutputs := req.Outputs - predatorDataType := getRequestDataType(req) - batchSize := req.BatchSize - deadline := req.Deadline - - var numInputs int - if predatorDataType == PredatorDataTypeString { - numInputs = len(req.Inputs[0].StringData) - } else { - numInputs = len(req.Inputs[0].Data) - } - - batches := c.getBatchIndices(numInputs, batchSize) - batchProtoReqs := c.adapter.MapPredatorRequestToProto(req, predatorDataType, batches) - - if batchProtoReqs == nil { - return nil, fmt.Errorf("failed to map request to proto") - } - - return &preparedRequest{ - requestedOutputs: requestedOutputs, - predatorDataType: predatorDataType, - deadline: deadline, - numInputs: numInputs, - batches: batches, - batchProtoReqs: batchProtoReqs, - }, nil -} - -// processBatchInParallel processes all batches in parallel and returns results -func (c *ClientV1) processBatchInParallel(req *preparedRequest) []batchResult { - resultChan := make(chan batchResult, len(req.batches)) - results := make([]batchResult, len(req.batches)) - - for i := range req.batches { - go c.processSingleBatch(i, req.batchProtoReqs[i], req.deadline, resultChan) - } - - for i := 0; i < len(req.batches); i++ { - indexedResult := <-resultChan - results[indexedResult.index] = indexedResult - } - - return results -} - -// processSingleBatch processes a single batch request -func (c *ClientV1) processSingleBatch(batchIndex int, batchProtoReq *triton.ModelInferRequest, deadline int64, resultChan chan batchResult) { - defer func() { - if r := recover(); r != nil { - log.Warn(). - Interface("panic", r). - Int("batch_index", batchIndex). - Msg("Panic occurred while processing batch") - resultChan <- batchResult{index: batchIndex, response: nil, err: fmt.Errorf("panic in batch processing: %v", r)} - } - }() - - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(deadline)*time.Millisecond) - defer cancel() - - md := getMetadata(c.callerId, c.callerToken) - ctx = metadata.NewOutgoingContext(ctx, md) - client := c.grpcClient - response, err := client.ModelInfer(ctx, batchProtoReq) - if err != nil { - log.Warn().Err(err). - Int("batch_index", batchIndex). - Str("model_name", batchProtoReq.ModelName). - Str("model_version", batchProtoReq.ModelVersion). - Msg("Failed to get inference from Triton server") - resultChan <- batchResult{index: batchIndex, response: nil, err: err} - return - } - - resultChan <- batchResult{index: batchIndex, response: response, err: nil} -} - -func (c *ClientV1) GetInferenceScore(req *PredatorRequest) (*PredatorResponse, error) { - // Prepare request components - preparedReq, err := c.prepareInferenceRequest(req) - if err != nil { - return nil, err - } - - // Process batches in parallel - results := c.processBatchInParallel(preparedReq) - - // Convert each proto response to PredatorResponse and merge - finalResponse, err := c.convertAndMergeResponses(req, preparedReq, results) - if err != nil { - return nil, err - } - - return finalResponse, nil -} - -func (c *ClientV1) GetInferenceScoreV2(req *PredatorRequest) (*triton.ModelInferResponse, error) { - // Prepare request components - preparedReq, err := c.prepareInferenceRequest(req) - if err != nil { - return nil, err - } - - // Process batches in parallel - results := c.processBatchInParallel(preparedReq) - - // Convert each proto response to PredatorResponse and merge - finalResponse, err := c.convertAndMergeResponsesV2(req, preparedReq, results) - if err != nil { - return nil, err - } - - return finalResponse, nil -} - -// convertAndMergeResponses converts proto responses to PredatorResponses and merges them -func (c *ClientV1) convertAndMergeResponses(predatorReq *PredatorRequest, preparedReq *preparedRequest, results []batchResult) (*PredatorResponse, error) { - var predatorResponses []*PredatorResponse - errorCount := 0 - - // Convert each batch result to PredatorResponse - for i, result := range results { - if result.err != nil { - errorCount++ - log.Warn().Err(result.err). - Int("batch_index", i). - Msg("Batch processing failed") - continue - } - - if result.response == nil { - errorCount++ - log.Warn().Int("batch_index", i).Msg("Batch returned nil response") - continue - } - - // Use adapter to convert proto response to PredatorResponse - predatorResp := c.adapter.MapProtoToPredatorResponse(result.response, preparedReq.requestedOutputs, preparedReq.predatorDataType) - if predatorResp == nil { - errorCount++ - log.Error().Int("batch_index", i).Msg("Failed to map triton response to predator response") - continue - } - - predatorResponses = append(predatorResponses, predatorResp) - } - - // If all batches failed, return an error - if len(predatorResponses) == 0 { - if errorCount > 0 { - return nil, fmt.Errorf("all batch processing failed: %v", errorCount) - } - return nil, fmt.Errorf("all batch processing failed") - } - - // If some batches failed, log warning but continue with successful ones - if errorCount > 0 { - log.Warn(). - Int("failed_batches", errorCount). - Int("successful_batches", len(predatorResponses)). - Int("total_batches", len(results)). - Msg("Some batches failed but continuing with successful results") - metricTags := c.buildMetricTags(predatorReq.ModelName, "batch_processing_error") - metric.Count(metric.ExternalApiRequestCount, int64(errorCount), metricTags) - } - - // Merge all successful responses - mergedResponse := c.mergeResponses(predatorResponses) - if mergedResponse == nil { - return nil, fmt.Errorf("failed to merge batch responses") - } - - return mergedResponse, nil -} - -// convertAndMergeResponsesV2 merges proto responses directly and returns merged ModelInferResponse -func (c *ClientV1) convertAndMergeResponsesV2(predatorReq *PredatorRequest, preparedReq *preparedRequest, results []batchResult) (*triton.ModelInferResponse, error) { - var successfulResponses []*triton.ModelInferResponse - errorCount := 0 - successfulIndices := make([]int, 0) - - // Collect successful batch responses - for i, result := range results { - if result.err != nil { - errorCount++ - log.Warn().Err(result.err). - Int("batch_index", i). - Msg("Batch processing failed") - continue - } - - if result.response == nil { - errorCount++ - log.Warn().Int("batch_index", i).Msg("Batch returned nil response") - continue - } - - successfulResponses = append(successfulResponses, result.response) - successfulIndices = append(successfulIndices, i) - } - - // If all batches failed, return an error - if len(successfulResponses) == 0 { - if errorCount > 0 { - return nil, fmt.Errorf("all batch processing failed: %v", errorCount) - } - return nil, fmt.Errorf("all batch processing failed") - } - - // If some batches failed, log warning but continue with successful ones - if errorCount > 0 { - log.Warn(). - Int("failed_batches", errorCount). - Int("successful_batches", len(successfulResponses)). - Int("total_batches", len(results)). - Msg("Some batches failed but continuing with successful results") - metricTags := c.buildMetricTags(predatorReq.ModelName, "batch_processing_error") - metric.Count(metric.ExternalApiRequestCount, int64(errorCount), metricTags) - } - - // If there's only one successful response, return it directly - if len(successfulResponses) == 1 { - return successfulResponses[0], nil - } - - // Create final proto response using first successful response as base - firstResponse := successfulResponses[0] - finalProto := &triton.ModelInferResponse{ - Outputs: make([]*triton.ModelInferResponse_InferOutputTensor, len(firstResponse.Outputs)), - RawOutputContents: make([][]byte, len(firstResponse.RawOutputContents)), - } - - // Initialize output tensor structures - for i := range firstResponse.Outputs { - finalProto.Outputs[i] = &triton.ModelInferResponse_InferOutputTensor{} - } - - // Pre-allocate sizes using first batch - c.preAllocateFinalSizes(finalProto, firstResponse, preparedReq.numInputs, preparedReq.batches) - - // Set metadata from first batch - c.setProtoMetadataFromBatch(finalProto, firstResponse, preparedReq.numInputs) - - // Set output names (not set by setProtoMetadataFromBatch) - for i, output := range firstResponse.Outputs { - if i < len(finalProto.Outputs) { - finalProto.Outputs[i].Name = output.Name - } - } - - // Copy data from all successful batches - for idx, response := range successfulResponses { - batchIndex := successfulIndices[idx] - if batchIndex < len(preparedReq.batches) { - c.copyBatchResponseToProto(finalProto, response, preparedReq.batches[batchIndex]) - } - } - - return finalProto, nil -} - -// mergeResponses combines multiple PredatorResponse objects into a single response -func (c *ClientV1) mergeResponses(responses []*PredatorResponse) *PredatorResponse { - if len(responses) == 0 { - log.Warn().Msg("Attempting to merge empty response list") - return nil - } - - // If there's only one response, return it directly - if len(responses) == 1 { - return responses[0] - } - - // Create merged response using the first response as base - mergedResp := &PredatorResponse{ - ModelName: responses[0].ModelName, - ModelVersion: responses[0].ModelVersion, - Outputs: make([]ResponseOutput, len(responses[0].Outputs)), - } - - // Merge output data for each output - for i, output := range responses[0].Outputs { - var combinedData [][]byte - var combinedStringData [][]string - - // Calculate total shape for the merged output - totalShape := make([]int64, len(output.Shape)) - copy(totalShape, output.Shape) - if len(totalShape) > 0 { - totalShape[0] = 0 // Will be calculated based on combined data - } - - for j, resp := range responses { - if resp == nil { - log.Warn(). - Int("response_index", j). - Msg("Encountered nil response during merge") - continue - } - - if len(resp.Outputs) <= i { - log.Warn(). - Int("response_index", j). - Int("output_index", i). - Int("available_outputs", len(resp.Outputs)). - Msg("Response has fewer outputs than expected") - continue - } - - // Combine byte data - if len(resp.Outputs[i].Data) > 0 { - combinedData = append(combinedData, resp.Outputs[i].Data...) - if len(totalShape) > 0 { - totalShape[0] += resp.Outputs[i].Shape[0] - } - } - // Combine string data - if len(resp.Outputs[i].StringData) > 0 { - combinedStringData = append(combinedStringData, resp.Outputs[i].StringData...) - } - } - - mergedResp.Outputs[i] = ResponseOutput{ - Name: output.Name, - DataType: output.DataType, - Shape: totalShape, - Data: combinedData, - StringData: combinedStringData, - } - } - - return mergedResp -} - -func (c *ClientV1) getBatchIndices(numElements, batchSize int) []BatchInfo { - numBatches := (numElements + batchSize - 1) / batchSize - batches := make([]BatchInfo, numBatches) - - for i := 0; i < numElements; i += batchSize { - end := i + batchSize - if end > numElements { - end = numElements - } - batches[i/batchSize] = BatchInfo{StartIndex: i, EndIndex: end} - } - return batches -} - -func (c *ClientV1) setProtoMetadataFromBatch(finalProto, batchProto *triton.ModelInferResponse, totalInputs int) { - finalProto.ModelName = batchProto.ModelName - finalProto.ModelVersion = batchProto.ModelVersion - finalProto.Id = batchProto.Id - finalProto.Parameters = batchProto.Parameters - - for i, batchOutput := range batchProto.Outputs { - if i < len(finalProto.Outputs) { - finalProto.Outputs[i].Datatype = batchOutput.Datatype - finalProto.Outputs[i].Shape = make([]int64, len(batchOutput.Shape)) - copy(finalProto.Outputs[i].Shape, batchOutput.Shape) - - if len(finalProto.Outputs[i].Shape) > 0 { - finalProto.Outputs[i].Shape[0] = int64(totalInputs) - } - - finalProto.Outputs[i].Parameters = batchOutput.Parameters - } - } -} - -func (c *ClientV1) preAllocateFinalSizes(finalProto, batchProto *triton.ModelInferResponse, totalInputs int, batches []BatchInfo) { - for i := range batchProto.Outputs { - if i < len(finalProto.RawOutputContents) { - batchSize := batches[0].EndIndex - batches[0].StartIndex - if len(batchProto.RawOutputContents) > i { - sizePerElement := len(batchProto.RawOutputContents[i]) / batchSize - totalExpectedSize := sizePerElement * totalInputs - finalProto.RawOutputContents[i] = make([]byte, totalExpectedSize) - } - } - } -} - -func (c *ClientV1) copyBatchResponseToProto(finalProto, batchProto *triton.ModelInferResponse, batch BatchInfo) { - batchSize := batch.EndIndex - batch.StartIndex - - for i, batchRawOutput := range batchProto.RawOutputContents { - if i < len(finalProto.RawOutputContents) { - if len(batchRawOutput) > 0 && batchSize > 0 { - bytesPerElement := len(batchRawOutput) / batchSize - startBytePos := batch.StartIndex * bytesPerElement - endBytePos := batch.EndIndex * bytesPerElement - - if endBytePos <= len(finalProto.RawOutputContents[i]) { - copy(finalProto.RawOutputContents[i][startBytePos:endBytePos], batchRawOutput) - } else { - finalProto.RawOutputContents[i] = append(finalProto.RawOutputContents[i], batchRawOutput...) - } - } - } - } -} - -// Keep the existing mergeResponses method as is -// Remove the old splitIntoBatches method since we're using getBatchIndices now - -func getMetadata(callerId string, callerToken string) metadata.MD { - md := metadata.New(nil) - md.Set(headerCallerID, callerId) - md.Set(headerCallerToken, callerToken) - return md -} - -func validatePredatorRequest(req *PredatorRequest) error { - if req == nil { - err := fmt.Errorf("predator request cannot be nil") - log.Error().Msg("Received nil predator request") - return err - } - - if len(req.Inputs) == 0 { - err := fmt.Errorf("predator request must contain at least one input") - log.Error().Msg("Predator request contains no inputs") - return err - } - - // Validate required request parameters - if req.BatchSize <= 0 { - err := fmt.Errorf("batch_size must be greater than 0, got: %d", req.BatchSize) - log.Error().Int("batch_size", req.BatchSize).Msg("Invalid batch size in request") - return err - } - - if req.Deadline <= 0 { - err := fmt.Errorf("deadline must be greater than 0, got: %d", req.Deadline) - log.Error().Int64("deadline", req.Deadline).Msg("Invalid deadline in request") - return err - } - - return nil -} - -func (c *ClientV1) buildMetricTags(modelName string, errorType string) []string { - return metric.BuildTag( - metric.NewTag("model-name", modelName), - metric.NewTag("caller-id", c.callerId), - metric.NewTag("error_type", errorType), - ) -} diff --git a/helix-client/pkg/clients/skye/init.go b/helix-client/pkg/clients/skye/init.go deleted file mode 100644 index b5b736d9..00000000 --- a/helix-client/pkg/clients/skye/init.go +++ /dev/null @@ -1,10 +0,0 @@ -package skye - -func GetSkyeClient(version int) SkyeClient { - switch version { - case 1: - return InitV1Client() - default: - return nil - } -} diff --git a/helix-client/pkg/clients/skye/models.go b/helix-client/pkg/clients/skye/models.go deleted file mode 100644 index 3ff2c304..00000000 --- a/helix-client/pkg/clients/skye/models.go +++ /dev/null @@ -1,8 +0,0 @@ -package skye - -import "github.com/Meesho/BharatMLStack/helix-client/pkg/grpcclient" - -type ClientV1 struct { - ClientConfigs *ClientConfig - GrpcClient *grpcclient.GRPCClient -} diff --git a/helix-client/pkg/clients/skye/skye.go b/helix-client/pkg/clients/skye/skye.go deleted file mode 100644 index 776aa9a8..00000000 --- a/helix-client/pkg/clients/skye/skye.go +++ /dev/null @@ -1,11 +0,0 @@ -package skye - -import ( - "github.com/Meesho/BharatMLStack/helix-client/pkg/clients/skye/client/grpc" -) - -type SkyeClient interface { - GetSimilarCandidates(request *grpc.SkyeRequest) (*grpc.SkyeResponse, error) - GetEmbeddingsForCandidateIds(request *grpc.SkyeBulkEmbeddingRequest) (*grpc.SkyeBulkEmbeddingResponse, error) - GetDotProductOfCandidatesForEmbedding(request *grpc.EmbeddingDotProductRequest) (*grpc.EmbeddingDotProductResponse, error) -} diff --git a/helix-client/pkg/clients/skye/v1.go b/helix-client/pkg/clients/skye/v1.go deleted file mode 100644 index 975f23e2..00000000 --- a/helix-client/pkg/clients/skye/v1.go +++ /dev/null @@ -1,137 +0,0 @@ -package skye - -import ( - "context" - "fmt" - "sync" - "time" - - grpc2 "github.com/Meesho/BharatMLStack/helix-client/pkg/clients/skye/client/grpc" - "github.com/Meesho/BharatMLStack/helix-client/pkg/grpcclient" - "github.com/rs/zerolog/log" - "github.com/spf13/viper" - "google.golang.org/grpc/metadata" -) - -var ( - client *ClientV1 - once sync.Once -) - -const ( - V1Prefix = "SKYE_CLIENT_V1_" - CallerIDMetadata = "skye-caller-id" - AuthMetadata = "skye-auth-token" -) - -func InitV1Client() SkyeClient { - if client == nil { - once.Do(func() { - clientConfig, err := getClientConfigs(V1Prefix) - if err != nil { - log.Panic().Err(err).Msgf("Invalid Skye client configs: %#v", clientConfig) - } - grpcClient, grpcErr := getGrpcClient(clientConfig) - if grpcErr != nil { - log.Panic().Err(grpcErr).Msgf("Error creating skye service grpc client, client: %#v", grpcClient) - } - client = &ClientV1{ - ClientConfigs: clientConfig, - GrpcClient: grpcClient, - } - }) - } - return client -} - -func getGrpcClient(conf *ClientConfig) (*grpcclient.GRPCClient, error) { - var client *grpcclient.GRPCClient - var err error - defer func() { - if r := recover(); r != nil { - err = fmt.Errorf("panic creating grpc client from prefix: %v", r) - } - }() - client = grpcclient.NewConnFromConfig(&grpcclient.Config{ - Host: conf.Host, - Port: conf.Port, - DeadLine: conf.DeadlineExceedMS, - LoadBalancingPolicy: "round_robin", - PlainText: conf.PlainText, - }, V1Prefix) - return client, err -} - -func (c *ClientV1) GetSimilarCandidates(req *grpc2.SkyeRequest) (*grpc2.SkyeResponse, error) { - skyeClient := grpc2.NewSkyeSimilarCandidateServiceClient(c.GrpcClient) - timeout := time.Duration(c.ClientConfigs.DeadlineExceedMS) * time.Millisecond - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - - //get metadata headers - md := getMetadata(c.ClientConfigs) - ctx = metadata.NewOutgoingContext(ctx, md) - // call grpc method - protoResponse, err := skyeClient.GetSimilarCandidates(ctx, req) - if err != nil { - log.Error().Msgf("Error while fetching similar candidates from skye service, err: %v", err) - return nil, err - } else if protoResponse == nil { - log.Error().Msgf("Empty response from skye service") - return nil, fmt.Errorf("empty response from skye service") - } - return protoResponse, nil -} - -func (c *ClientV1) GetEmbeddingsForCandidateIds(request *grpc2.SkyeBulkEmbeddingRequest) (*grpc2.SkyeBulkEmbeddingResponse, error) { - skyeClient := grpc2.NewSkyeEmbeddingServiceClient(c.GrpcClient) - timeout := time.Duration(c.ClientConfigs.DeadlineExceedMS) * time.Millisecond - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - - //get metadata headers - md := getMetadata(c.ClientConfigs) - ctx = metadata.NewOutgoingContext(ctx, md) - // call grpc method - protoResponse, err := skyeClient.GetEmbeddingsForCandidates(ctx, request) - if err != nil { - log.Error().Msgf("Error while fetching bulk embeddings from skye service, err: %v", err) - return nil, err - } else if protoResponse == nil { - log.Error().Msgf("Empty response from skye service") - return nil, fmt.Errorf("empty response from skye service") - } - return protoResponse, nil -} - -func (c *ClientV1) GetDotProductOfCandidatesForEmbedding(request *grpc2.EmbeddingDotProductRequest) (*grpc2.EmbeddingDotProductResponse, error) { - skyeClient := grpc2.NewSkyeEmbeddingServiceClient(c.GrpcClient) - timeout := time.Duration(c.ClientConfigs.DeadlineExceedMS) * time.Millisecond - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - - //get metadata headers - md := getMetadata(c.ClientConfigs) - ctx = metadata.NewOutgoingContext(ctx, md) - // call grpc method - protoResponse, err := skyeClient.GetCandidateEmbeddingScores(ctx, request) - if err != nil { - log.Error().Msgf("Error while fetching bulk embeddings from skye service, err: %v", err) - return nil, err - } else if protoResponse == nil { - log.Error().Msgf("Empty response from skye service") - return nil, fmt.Errorf("empty response from skye service") - } - return protoResponse, nil -} - -func getMetadata(config *ClientConfig) metadata.MD { - md := metadata.New(nil) - appName := viper.GetString("APP_NAME") - if appName == "" { - log.Panic().Msgf("APP_NAME not set!") - } - md.Append(CallerIDMetadata, appName) - md.Append(AuthMetadata, config.AuthToken) - return md -} diff --git a/helix-client/pkg/clients/skye/v1_test.go b/helix-client/pkg/clients/skye/v1_test.go deleted file mode 100644 index 098275c1..00000000 --- a/helix-client/pkg/clients/skye/v1_test.go +++ /dev/null @@ -1,214 +0,0 @@ -package skye - -// TODO need to refactor test cases post request response changes in client - -//func TestClientV1_GetSimilarCandidatesPreProd(t *testing.T) { -// viper.Set("SKYE_CLIENT_V1_HOST", "skye-serving.int.meesho.int") -// viper.Set("SKYE_CLIENT_V1_PORT", "80") -// viper.Set("SKYE_CLIENT_V1_DEADLINE_MS", 5000) -// viper.Set("SKYE_CLIENT_V1_PLAINTEXT", true) -// testcases := []struct { -// name string -// request *SimilarCandidateRequest -// expectedResponse *SimilarCandidatesResponse -// }{ -// {"successBasicRequest", -// &SimilarCandidateRequest{ -// Entity: "catalog", -// ModelName: "attribute_model", -// Variant: "non_gst", -// //CandidateIds: []string{"1161"}, -// Embeddings: [][]float32{ -// {0.20856647, 0.0192536, -0.17532587, -0.12793553, 0.011362862, -0.040895768, -0.11143229, -0.007844001, 0.0065192855, 0.42926165, -0.2711144, 0.2584112, 0.03707704, 0.07465805, -0.16700841, -0.2937192, -0.09601066, 0.11477651, 0.23940851, 0.037067287, 0.11799186, -0.101745635, 0.08189786, -0.29900208, 0.17568949, -0.159322, -0.1352676, 0.09871266, 0.3169443, 0.16222087, 0.0934057, 0.16670953}, -// }, -// Limit: 2, -// }, -// &SimilarCandidatesResponse{SimilarCandidates: [][]*SimilarCandidate{ -// { -// &SimilarCandidate{Id: "1161", Meta: Meta{Score: float32(1), AttributesMap: nil}}, -// &SimilarCandidate{Id: "104394136", Meta: Meta{Score: float32(0.9909016), AttributesMap: nil}}, -// }, -// }}, -// }, -// } -// for _, tc := range testcases { -// t.Run(tc.name, func(t *testing.T) { -// client := GetSkyeClient(1) -// res, _ := client.GetSimilarCandidates(tc.request) -// if !reflect.DeepEqual(res, tc.expectedResponse) { -// t.Errorf("GetSimilarCandidates: %+v, expected response: %+v", res, tc.expectedResponse) -// } -// }) -// } -//} -// -//func TestClientV1_GetSimilarCandidates(t *testing.T) { -// viper.Set("SKYE_CLIENT_V1_HOST", "skye-serving.int.meesho.int") -// viper.Set("SKYE_CLIENT_V1_PORT", "80") -// viper.Set("SKYE_CLIENT_V1_DEADLINE_MS", 50000) -// viper.Set("SKYE_CLIENT_V1_AUTH_TOKEN", "pre-prod") -// viper.Set("APP_NAME", "skye-helix-client") -// testcases := []struct { -// name string -// request *SimilarCandidateRequest -// expectedResponse *SimilarCandidatesResponse -// }{ -// {"successBasicRequest", -// &SimilarCandidateRequest{ -// Entity: "catalog", -// ModelName: "test_model_v1", -// Variant: "ad", -// CandidateIds: []string{"4", "5"}, -// Limit: 2, -// }, -// &SimilarCandidatesResponse{SimilarCandidates: [][]*SimilarCandidate{ -// { -// &SimilarCandidate{Id: "15", Meta: Meta{Score: float32(0.15000030398368835), AttributesMap: nil}}, -// &SimilarCandidate{Id: "14", Meta: Meta{Score: float32(0.3202500343322754), AttributesMap: nil}}, -// }, -// { -// &SimilarCandidate{Id: "15", Meta: Meta{Score: float32(0.15000030398368835), AttributesMap: nil}}, -// &SimilarCandidate{Id: "14", Meta: Meta{Score: float32(0.3202500343322754), AttributesMap: nil}}, -// }, -// }}, -// }, -// {"successRequestWithFilters", -// &SimilarCandidateRequest{ -// Entity: "catalog", -// ModelName: "test_model_v1", -// Variant: "ad", -// CandidateIds: []string{"4", "5"}, -// Limit: 2, -// Filters: [][]Filter{ -// { -// {"sscat", FilterOperator(0), []string{"34", "0"}}, -// }, -// { -// {"sscat", FilterOperator(1), []string{"3"}}, -// }, -// }, -// }, -// &SimilarCandidatesResponse{SimilarCandidates: [][]*SimilarCandidate{ -// { -// &SimilarCandidate{Id: "15", Meta: Meta{Score: float32(0.15000030398368835), AttributesMap: nil}}, -// &SimilarCandidate{Id: "14", Meta: Meta{Score: float32(0.3202500343322754), AttributesMap: nil}}, -// }, -// { -// &SimilarCandidate{Id: "15", Meta: Meta{Score: float32(0.15000030398368835), AttributesMap: nil}}, -// &SimilarCandidate{Id: "14", Meta: Meta{Score: float32(0.3202500343322754), AttributesMap: nil}}, -// }, -// }}, -// }, -// {"successRequestWithGlobalFilter", -// &SimilarCandidateRequest{ -// Entity: "catalog", -// ModelName: "test_model_v1", -// Variant: "ad", -// CandidateIds: []string{"4", "5"}, -// Limit: 2, -// GlobalFilters: []Filter{ -// {"sscat", FilterOperator(0), []string{"34", "0"}}, -// }, -// }, -// &SimilarCandidatesResponse{SimilarCandidates: [][]*SimilarCandidate{ -// { -// &SimilarCandidate{Id: "15", Meta: Meta{Score: float32(0.15000030398368835), AttributesMap: nil}}, -// &SimilarCandidate{Id: "14", Meta: Meta{Score: float32(0.3202500343322754), AttributesMap: nil}}, -// }, -// { -// &SimilarCandidate{Id: "15", Meta: Meta{Score: float32(0.15000030398368835), AttributesMap: nil}}, -// &SimilarCandidate{Id: "14", Meta: Meta{Score: float32(0.3202500343322754), AttributesMap: nil}}, -// }, -// }}, -// }, -// {"successRequestWithEmptyResponse", -// &SimilarCandidateRequest{ -// Entity: "catalog", -// ModelName: "test_model_v1", -// Variant: "ad", -// CandidateIds: []string{"4"}, -// Limit: 2, -// GlobalFilters: []Filter{ -// {"sscat", FilterOperator(1), []string{"34", "0"}}, -// }, -// }, -// &SimilarCandidatesResponse{SimilarCandidates: [][]*SimilarCandidate{{}}}, -// }, -// } -// for _, tc := range testcases { -// t.Run(tc.name, func(t *testing.T) { -// client := GetSkyeClient(1) -// res, _ := client.GetSimilarCandidates(tc.request) -// if !reflect.DeepEqual(res, tc.expectedResponse) { -// t.Errorf("GetSimilarCandidates: %+v, expected response: %+v", res, tc.expectedResponse) -// } -// }) -// } -//} -// -//func TestClientV1_GetEmbeddingsForCandidateIds(t *testing.T) { -// viper.Set("SKYE_CLIENT_V1_HOST", "localhost") -// viper.Set("SKYE_CLIENT_V1_PORT", "8083") -// viper.Set("SKYE_CLIENT_V1_DEADLINE_MS", 500) -// testcases := []struct { -// name string -// request *EmbeddingsRequest -// expectedResponse *EmbeddingsResponse -// }{ -// {"success", &EmbeddingsRequest{ -// Entity: "catalog", -// ModelName: "test_model_v1", -// Variant: "ad", -// CandidateIds: []string{"4", "5"}, -// }, -// &EmbeddingsResponse{EmbeddingResponse: []*CandidateEmbedding{ -// {Id: "4", Embedding: []float32{-0.16042101383209229, -0.005233884789049625, 0.24357753992080688, 0.040812645107507706, -0.06298056244850159, -0.07097796350717545, 0.06337567418813705, 0.011232693679630756, 0.011302603408694267, 0.09978130459785461, 0.033062200993299484, -0.15565651655197144, -0.010328262113034725, -0.04471661522984505, 0.09161527454853058, 0.08096695691347122, -0.007991013117134571, -0.05886241793632507, 0.07680786401033401, -0.0514046885073185, 0.06688099354505539, -0.09121483564376831, -0.06651730835437775, 0.11291711032390594, 0.008980179205536842, -0.023628132417798042, 0.0035034094471484423, -0.05262045934796333, 0.0734158530831337, -0.04664924368262291, -0.13142922520637512, 0.06616105139255524, 0.08684184402227402, -0.031942326575517654, 0.004450879525393248, -0.06188590079545975, -0.07829581946134567, 0.0070262388326227665, 0.017191657796502113, 0.015088655985891819, -0.000056192118790932, -0.07053299248218536, -0.055713992565870285, 0.06138134002685547, -0.07968553900718689, 0.03809307515621185, 0.0651206374168396, -0.08329790830612183, -0.06742016971111298, 0.06040041148662567, -0.1867527812719345, 0.05807913467288017, 0.03365928679704666, -0.032647714018821716, -0.011843480169773102, -0.01642605848610401, 0.004641900770366192, -0.12394584715366364, -0.08457022905349731, 0.1559869796037674, -0.11442684382200241, -0.10122374445199966, 0.11411477625370026, -0.10065338760614395, -0.0356115996837616, -0.13335444033145905, -0.0261673741042614, 0.0756693109869957, 0.08628286421298981, -0.08219288289546967, -0.01646547205746174, -0.17476244270801544, 0.036117203533649445, -0.017218975350260735, 0.0658968985080719, 0.03999672085046768, 0.061865001916885376, 0.04729278013110161, 0.03358926996588707, -0.1880149096250534, 0.22793036699295044, 0.03482583165168762, 0.012042579241096973, 0.1847819834947586, 0.13918107748031616, 0.04905824735760689, 0.1171952560544014, -0.07494692504405975, 0.2372165322303772, 0.000022831834940006956, 0.08518408983945847, 0.08484339714050293, -0.016785187646746635, 0.007275485433638096, 0.09114666283130646, 0.15295326709747314, 0.09629631787538528, 0.03109036013484001, 0.010699527338147163, 0.0587799996137619, -0.03909625858068466, -0.004016770515590906, -0.057834841310977936, 0.06676746904850006, -0.05982685089111328, -0.08484537899494171, -0.08463805168867111, -0.04263322427868843, 0.11217007786035538, -0.17225229740142822, -0.11567119508981705, 0.17956873774528503, -0.004632641561329365, 0.042749278247356415, -0.015895918011665344, 0.04075197875499725, -0.17386670410633087, -0.05067210644483566, -0.03147958591580391, -0.06619491428136826, -0.1809258908033371, 0.044331956654787064, 0.06498933583498001, 0.03429732844233513, -0.05037061870098114, 0.06775150448083878, -0.01995770074427128, -0.0913449302315712}, SearchEmbedding: []float32{-0.16042101383209229, -0.005233884789049625, 0.24357753992080688, 0.040812645107507706, -0.06298056244850159, -0.07097796350717545, 0.06337567418813705, 0.011232693679630756, 0.011302603408694267, 0.09978130459785461, 0.033062200993299484, -0.15565651655197144, -0.010328262113034725, -0.04471661522984505, 0.09161527454853058, 0.08096695691347122, -0.007991013117134571, -0.05886241793632507, 0.07680786401033401, -0.0514046885073185, 0.06688099354505539, -0.09121483564376831, -0.06651730835437775, 0.11291711032390594, 0.008980179205536842, -0.023628132417798042, 0.0035034094471484423, -0.05262045934796333, 0.0734158530831337, -0.04664924368262291, -0.13142922520637512, 0.06616105139255524, 0.08684184402227402, -0.031942326575517654, 0.004450879525393248, -0.06188590079545975, -0.07829581946134567, 0.0070262388326227665, 0.017191657796502113, 0.015088655985891819, -0.000056192118790932, -0.07053299248218536, -0.055713992565870285, 0.06138134002685547, -0.07968553900718689, 0.03809307515621185, 0.0651206374168396, -0.08329790830612183, -0.06742016971111298, 0.06040041148662567, -0.1867527812719345, 0.05807913467288017, 0.03365928679704666, -0.032647714018821716, -0.011843480169773102, -0.01642605848610401, 0.004641900770366192, -0.12394584715366364, -0.08457022905349731, 0.1559869796037674, -0.11442684382200241, -0.10122374445199966, 0.11411477625370026, -0.10065338760614395, -0.0356115996837616, -0.13335444033145905, -0.0261673741042614, 0.0756693109869957, 0.08628286421298981, -0.08219288289546967, -0.01646547205746174, -0.17476244270801544, 0.036117203533649445, -0.017218975350260735, 0.0658968985080719, 0.03999672085046768, 0.061865001916885376, 0.04729278013110161, 0.03358926996588707, -0.1880149096250534, 0.22793036699295044, 0.03482583165168762, 0.012042579241096973, 0.1847819834947586, 0.13918107748031616, 0.04905824735760689, 0.1171952560544014, -0.07494692504405975, 0.2372165322303772, 0.000022831834940006956, 0.08518408983945847, 0.08484339714050293, -0.016785187646746635, 0.007275485433638096, 0.09114666283130646, 0.15295326709747314, 0.09629631787538528, 0.03109036013484001, 0.010699527338147163, 0.0587799996137619, -0.03909625858068466, -0.004016770515590906, -0.057834841310977936, 0.06676746904850006, -0.05982685089111328, -0.08484537899494171, -0.08463805168867111, -0.04263322427868843, 0.11217007786035538, -0.17225229740142822, -0.11567119508981705, 0.17956873774528503, -0.004632641561329365, 0.042749278247356415, -0.015895918011665344, 0.04075197875499725, -0.17386670410633087, -0.05067210644483566, -0.03147958591580391, -0.06619491428136826, -0.1809258908033371, 0.044331956654787064, 0.06498933583498001, 0.03429732844233513, -0.05037061870098114, 0.06775150448083878, -0.01995770074427128, -0.0913449302315712}}, -// {Id: "5", Embedding: []float32{-0.16042101383209229, -0.005233884789049625, 0.24357753992080688, 0.040812645107507706, -0.06298056244850159, -0.07097796350717545, 0.06337567418813705, 0.011232693679630756, 0.011302603408694267, 0.09978130459785461, 0.033062200993299484, -0.15565651655197144, -0.010328262113034725, -0.04471661522984505, 0.09161527454853058, 0.08096695691347122, -0.007991013117134571, -0.05886241793632507, 0.07680786401033401, -0.0514046885073185, 0.06688099354505539, -0.09121483564376831, -0.06651730835437775, 0.11291711032390594, 0.008980179205536842, -0.023628132417798042, 0.0035034094471484423, -0.05262045934796333, 0.0734158530831337, -0.04664924368262291, -0.13142922520637512, 0.06616105139255524, 0.08684184402227402, -0.031942326575517654, 0.004450879525393248, -0.06188590079545975, -0.07829581946134567, 0.0070262388326227665, 0.017191657796502113, 0.015088655985891819, -0.000056192118790932, -0.07053299248218536, -0.055713992565870285, 0.06138134002685547, -0.07968553900718689, 0.03809307515621185, 0.0651206374168396, -0.08329790830612183, -0.06742016971111298, 0.06040041148662567, -0.1867527812719345, 0.05807913467288017, 0.03365928679704666, -0.032647714018821716, -0.011843480169773102, -0.01642605848610401, 0.004641900770366192, -0.12394584715366364, -0.08457022905349731, 0.1559869796037674, -0.11442684382200241, -0.10122374445199966, 0.11411477625370026, -0.10065338760614395, -0.0356115996837616, -0.13335444033145905, -0.0261673741042614, 0.0756693109869957, 0.08628286421298981, -0.08219288289546967, -0.01646547205746174, -0.17476244270801544, 0.036117203533649445, -0.017218975350260735, 0.0658968985080719, 0.03999672085046768, 0.061865001916885376, 0.04729278013110161, 0.03358926996588707, -0.1880149096250534, 0.22793036699295044, 0.03482583165168762, 0.012042579241096973, 0.1847819834947586, 0.13918107748031616, 0.04905824735760689, 0.1171952560544014, -0.07494692504405975, 0.2372165322303772, 0.000022831834940006956, 0.08518408983945847, 0.08484339714050293, -0.016785187646746635, 0.007275485433638096, 0.09114666283130646, 0.15295326709747314, 0.09629631787538528, 0.03109036013484001, 0.010699527338147163, 0.0587799996137619, -0.03909625858068466, -0.004016770515590906, -0.057834841310977936, 0.06676746904850006, -0.05982685089111328, -0.08484537899494171, -0.08463805168867111, -0.04263322427868843, 0.11217007786035538, -0.17225229740142822, -0.11567119508981705, 0.17956873774528503, -0.004632641561329365, 0.042749278247356415, -0.015895918011665344, 0.04075197875499725, -0.17386670410633087, -0.05067210644483566, -0.03147958591580391, -0.06619491428136826, -0.1809258908033371, 0.044331956654787064, 0.06498933583498001, 0.03429732844233513, -0.05037061870098114, 0.06775150448083878, -0.01995770074427128, -0.0913449302315712}, SearchEmbedding: []float32{-0.16042101383209229, -0.005233884789049625, 0.24357753992080688, 0.040812645107507706, -0.06298056244850159, -0.07097796350717545, 0.06337567418813705, 0.011232693679630756, 0.011302603408694267, 0.09978130459785461, 0.033062200993299484, -0.15565651655197144, -0.010328262113034725, -0.04471661522984505, 0.09161527454853058, 0.08096695691347122, -0.007991013117134571, -0.05886241793632507, 0.07680786401033401, -0.0514046885073185, 0.06688099354505539, -0.09121483564376831, -0.06651730835437775, 0.11291711032390594, 0.008980179205536842, -0.023628132417798042, 0.0035034094471484423, -0.05262045934796333, 0.0734158530831337, -0.04664924368262291, -0.13142922520637512, 0.06616105139255524, 0.08684184402227402, -0.031942326575517654, 0.004450879525393248, -0.06188590079545975, -0.07829581946134567, 0.0070262388326227665, 0.017191657796502113, 0.015088655985891819, -0.000056192118790932, -0.07053299248218536, -0.055713992565870285, 0.06138134002685547, -0.07968553900718689, 0.03809307515621185, 0.0651206374168396, -0.08329790830612183, -0.06742016971111298, 0.06040041148662567, -0.1867527812719345, 0.05807913467288017, 0.03365928679704666, -0.032647714018821716, -0.011843480169773102, -0.01642605848610401, 0.004641900770366192, -0.12394584715366364, -0.08457022905349731, 0.1559869796037674, -0.11442684382200241, -0.10122374445199966, 0.11411477625370026, -0.10065338760614395, -0.0356115996837616, -0.13335444033145905, -0.0261673741042614, 0.0756693109869957, 0.08628286421298981, -0.08219288289546967, -0.01646547205746174, -0.17476244270801544, 0.036117203533649445, -0.017218975350260735, 0.0658968985080719, 0.03999672085046768, 0.061865001916885376, 0.04729278013110161, 0.03358926996588707, -0.1880149096250534, 0.22793036699295044, 0.03482583165168762, 0.012042579241096973, 0.1847819834947586, 0.13918107748031616, 0.04905824735760689, 0.1171952560544014, -0.07494692504405975, 0.2372165322303772, 0.000022831834940006956, 0.08518408983945847, 0.08484339714050293, -0.016785187646746635, 0.007275485433638096, 0.09114666283130646, 0.15295326709747314, 0.09629631787538528, 0.03109036013484001, 0.010699527338147163, 0.0587799996137619, -0.03909625858068466, -0.004016770515590906, -0.057834841310977936, 0.06676746904850006, -0.05982685089111328, -0.08484537899494171, -0.08463805168867111, -0.04263322427868843, 0.11217007786035538, -0.17225229740142822, -0.11567119508981705, 0.17956873774528503, -0.004632641561329365, 0.042749278247356415, -0.015895918011665344, 0.04075197875499725, -0.17386670410633087, -0.05067210644483566, -0.03147958591580391, -0.06619491428136826, -0.1809258908033371, 0.044331956654787064, 0.06498933583498001, 0.03429732844233513, -0.05037061870098114, 0.06775150448083878, -0.01995770074427128, -0.0913449302315712}}}, -// }, -// }, -// } -// for _, tc := range testcases { -// t.Run(tc.name, func(t *testing.T) { -// client := GetSkyeClient(1) -// res, _ := client.GetEmbeddingsForCandidateIds(tc.request) -// if !reflect.DeepEqual(res, tc.expectedResponse) { -// t.Errorf("GetEmbeddingsForCandidateIds: %+v, expected response: %+v", res, tc.expectedResponse) -// } -// }) -// } -//} -// -//func TestClientV1_GetDotProductOfCandidatesForEmbedding(t *testing.T) { -// viper.Set("SKYE_CLIENT_V1_HOST", "localhost") -// viper.Set("SKYE_CLIENT_V1_PORT", "8083") -// viper.Set("SKYE_CLIENT_V1_DEADLINE_MS", 500) -// testcases := []struct { -// name string -// request *DotProductRequest -// expectedResponse *DotProductResponse -// }{ -// {"success", -// &DotProductRequest{ -// Entity: "catalog", -// ModelName: "test_model_v1", -// Variant: "ad", -// CandidateIds: []string{"4", "5"}, -// SourceEmbedding: []float32{-0.16042101383209229, -0.005233884789049625, 0.24357753992080688, 0.040812645107507706, -0.06298056244850159, -0.07097796350717545, 0.06337567418813705, 0.011232693679630756, 0.011302603408694267, 0.09978130459785461, 0.033062200993299484, -0.15565651655197144, -0.010328262113034725, -0.04471661522984505, 0.09161527454853058, 0.08096695691347122, -0.007991013117134571, -0.05886241793632507, 0.07680786401033401, -0.0514046885073185, 0.06688099354505539, -0.09121483564376831, -0.06651730835437775, 0.11291711032390594, 0.008980179205536842, -0.023628132417798042, 0.0035034094471484423, -0.05262045934796333, 0.0734158530831337, -0.04664924368262291, -0.13142922520637512, 0.06616105139255524, 0.08684184402227402, -0.031942326575517654, 0.004450879525393248, -0.06188590079545975, -0.07829581946134567, 0.0070262388326227665, 0.017191657796502113, 0.015088655985891819, -0.000056192118790932, -0.07053299248218536, -0.055713992565870285, 0.06138134002685547, -0.07968553900718689, 0.03809307515621185, 0.0651206374168396, -0.08329790830612183, -0.06742016971111298, 0.06040041148662567, -0.1867527812719345, 0.05807913467288017, 0.03365928679704666, -0.032647714018821716, -0.011843480169773102, -0.01642605848610401, 0.004641900770366192, -0.12394584715366364, -0.08457022905349731, 0.1559869796037674, -0.11442684382200241, -0.10122374445199966, 0.11411477625370026, -0.10065338760614395, -0.0356115996837616, -0.13335444033145905, -0.0261673741042614, 0.0756693109869957, 0.08628286421298981, -0.08219288289546967, -0.01646547205746174, -0.17476244270801544, 0.036117203533649445, -0.017218975350260735, 0.0658968985080719, 0.03999672085046768, 0.061865001916885376, 0.04729278013110161, 0.03358926996588707, -0.1880149096250534, 0.22793036699295044, 0.03482583165168762, 0.012042579241096973, 0.1847819834947586, 0.13918107748031616, 0.04905824735760689, 0.1171952560544014, -0.07494692504405975, 0.2372165322303772, 0.000022831834940006956, 0.08518408983945847, 0.08484339714050293, -0.016785187646746635, 0.007275485433638096, 0.09114666283130646, 0.15295326709747314, 0.09629631787538528, 0.03109036013484001, 0.010699527338147163, 0.0587799996137619, -0.03909625858068466, -0.004016770515590906, -0.057834841310977936, 0.06676746904850006, -0.05982685089111328, -0.08484537899494171, -0.08463805168867111, -0.04263322427868843, 0.11217007786035538, -0.17225229740142822, -0.11567119508981705, 0.17956873774528503, -0.004632641561329365, 0.042749278247356415, -0.015895918011665344, 0.04075197875499725, -0.17386670410633087, -0.05067210644483566, -0.03147958591580391, -0.06619491428136826, -0.1809258908033371, 0.044331956654787064, 0.06498933583498001, 0.03429732844233513, -0.05037061870098114, 0.06775150448083878, -0.01995770074427128, -0.0913449302315712}, -// }, -// &DotProductResponse{ -// CandidateScores: []*CandidateDotProduct{ -// {CandidateId: "4", Score: float32(0.9999998211860657)}, -// {CandidateId: "5", Score: float32(0.9999998211860657)}}, -// }, -// }, -// } -// for _, tc := range testcases { -// t.Run(tc.name, func(t *testing.T) { -// client := GetSkyeClient(1) -// res, _ := client.GetDotProductOfCandidatesForEmbedding(tc.request) -// if !reflect.DeepEqual(res, tc.expectedResponse) { -// t.Errorf("GetDotProductOfCandidatesForEmbedding: %+v, expected response: %+v", res, tc.expectedResponse) -// } -// }) -// } -//} diff --git a/helix-client/pkg/datatypeconverter/byteorder/system.go b/helix-client/pkg/datatypeconverter/byteorder/system.go deleted file mode 100644 index 74178382..00000000 --- a/helix-client/pkg/datatypeconverter/byteorder/system.go +++ /dev/null @@ -1,827 +0,0 @@ -package byteorder - -import ( - "encoding/binary" - "fmt" - - // "github.com/Meesho/orion/pkg/proto/persist" - "math" - "unsafe" - - "github.com/Meesho/BharatMLStack/helix-client/pkg/datatypeconverter/float8" - "github.com/Meesho/BharatMLStack/helix-client/pkg/datatypeconverter/types" - "github.com/x448/float16" -) - -var ByteOrder *CustomByteOrder - -type CustomByteOrder struct { - binary.ByteOrder -} - -/** - * Extensions for Uint8/16 - */ -func (c *CustomByteOrder) PutUint8FromUint32(b []byte, v uint32) { - _ = b[0] // bounds check hint to compiler; see golang.org/issue/14808 - b[0] = uint8(v) -} - -func (c *CustomByteOrder) PutUint16FromUint32(b []byte, v uint32) { - c.ByteOrder.PutUint16(b, uint16(v)) -} - -func (c *CustomByteOrder) Uint8(b []byte) uint8 { - _ = b[0] // bounds check hint to compiler; see golang.org/issue/14808 - return b[0] -} - -func (c *CustomByteOrder) Uint8AsUint32(b []byte) uint32 { - _ = b[0] // bounds check hint to compiler; see golang.org/issue/14808 - return uint32(b[0]) -} - -func (c *CustomByteOrder) Uint16AsUint32(b []byte) uint32 { - return uint32(c.ByteOrder.Uint16(b)) -} - -func (c *CustomByteOrder) Uint32Vector(b []byte) []uint32 { - if len(b)%4 != 0 { - panic("invalid byte slice length: must be a multiple of 4") - } - result := make([]uint32, len(b)/4) - for i := 0; i < len(result); i++ { - result[i] = c.Uint32(b[i*4 : i*4+4]) - } - return result -} - -func (c *CustomByteOrder) Uint8AsUint32Vector(b []byte) []uint32 { - result := make([]uint32, len(b)) - for i, byteValue := range b { - result[i] = c.Uint8AsUint32([]byte{byteValue}) - } - return result -} - -func (c *CustomByteOrder) Uint16AsUint32Vector(b []byte) []uint32 { - if len(b)%2 != 0 { - panic("invalid byte slice length: must be a multiple of 2") - } - result := make([]uint32, len(b)/2) - for i := 0; i < len(result); i++ { - result[i] = c.Uint16AsUint32(b[i*2 : i*2+2]) - } - return result -} - -func (c *CustomByteOrder) Uint64Vector(b []byte) []uint64 { - if len(b)%8 != 0 { - panic("invalid byte slice length: must be a multiple of 8") - } - result := make([]uint64, len(b)/8) - for i := 0; i < len(result); i++ { - result[i] = c.Uint64(b[i*8 : i*8+8]) - } - return result -} - -/** - * Extensions for Int8/16/32/64 - */ -func (c *CustomByteOrder) PutInt8FromInt32(b []byte, v int32) { - _ = b[0] // bounds check hint to compiler; see golang.org/issue/14808 - b[0] = uint8(v) -} - -func (c *CustomByteOrder) PutInt16FromInt32(b []byte, v int32) { - c.ByteOrder.PutUint16(b, uint16(v)) -} - -func (c *CustomByteOrder) PutInt32(b []byte, v int32) { - c.PutUint32(b, uint32(v)) -} - -func (c *CustomByteOrder) PutInt64(b []byte, v int64) { - c.PutUint64(b, uint64(v)) -} - -func (c *CustomByteOrder) Int8(b []byte) int8 { - _ = b[0] // bounds check hint to compiler; see golang.org/issue/14808 - return int8(b[0]) -} - -func (c *CustomByteOrder) Int8AsInt32(b []byte) int32 { - _ = b[0] // bounds check hint to compiler; see golang.org/issue/14808 - return int32(int8(b[0])) -} - -func (c *CustomByteOrder) Int16(b []byte) int16 { - return int16(c.ByteOrder.Uint16(b)) -} - -func (c *CustomByteOrder) Int16AsInt32(b []byte) int32 { - return int32(int16(c.ByteOrder.Uint16(b))) -} - -func (c *CustomByteOrder) Int32(b []byte) int32 { - return int32(c.Uint32(b)) -} - -func (c *CustomByteOrder) Int64(b []byte) int64 { - return int64(c.Uint64(b)) -} - -func (c *CustomByteOrder) String(b []byte) string { - var decodedString string - - // Iterate over each byte - for _, b := range b { - // Ignore padding (zeroes) - if b != 0 { - // Convert the byte to a character and append to the decoded string - decodedString += string(b) - } - } - - return decodedString - -} - -func (c *CustomByteOrder) Bool(b []byte) bool { - if len(b) < 1 { - return false - } - return b[0] != 0 -} - -/** - * Extensions for Float8/16 - */ -func (c *CustomByteOrder) PutFloat8E5M2FromFP32(b []byte, v float32) { - b[0] = uint8(float8.FP8E5M2FromFP32Value(v)) -} - -func (c *CustomByteOrder) PutFloat8E4M3FromFP32(b []byte, v float32) { - b[0] = uint8(float8.FP8E4M3FromFP32Value(v)) -} - -func (c *CustomByteOrder) PutFloat16FromFP32(b []byte, v float32) { - fp16 := float16.Fromfloat32(v) - c.ByteOrder.PutUint16(b, fp16.Bits()) -} - -func (c *CustomByteOrder) PutFloat32(b []byte, v float32) { - c.PutUint32(b, math.Float32bits(v)) -} - -func (c *CustomByteOrder) PutFloat64(b []byte, v float64) { - c.PutUint64(b, math.Float64bits(v)) -} - -func (c *CustomByteOrder) Float8E5M2AsFP32(b []byte) float32 { - return float8.FP8E5M2ToFP32Value(float8.Float8e5m2(b[0])) -} - -func (c *CustomByteOrder) Float8E4M3AsFP32(b []byte) float32 { - return float8.FP8E4M3ToFP32Value(float8.Float8e4m3(b[0])) -} - -func (c *CustomByteOrder) Float16AsFP32(b []byte) float32 { - return float16.Frombits(c.ByteOrder.Uint16(b)).Float32() -} - -func (c *CustomByteOrder) Float32(b []byte) float32 { - return math.Float32frombits(c.Uint32(b)) -} - -func (c *CustomByteOrder) Float32Vector(b []byte) []float32 { - if len(b)%4 != 0 { - panic("invalid byte slice length: must be a multiple of 4") - } - n := len(b) / 4 - result := make([]float32, n) - - for i := 0; i < n; i++ { - offset := i * 4 - result[i] = math.Float32frombits(c.Uint32(b[offset : offset+4])) - } - - return result -} - -func (c *CustomByteOrder) FP8E5M2Vector(b []byte) []float32 { - if len(b) == 0 { - return nil - } - - result := make([]float32, len(b)) // Each byte represents one FP8E5M2 - for i, byteValue := range b { - result[i] = c.Float8E5M2AsFP32([]byte{byteValue}) // Use a custom decoding function - } - - return result -} - -func (c *CustomByteOrder) FP8E4M3Vector(b []byte) []float32 { - if len(b) == 0 { - return nil - } - - result := make([]float32, len(b)) - for i, byteValue := range b { - result[i] = c.Float8E4M3AsFP32([]byte{byteValue}) // Use a custom decoding function - } - - return result -} - -func (c *CustomByteOrder) FP16Vector(b []byte) []float32 { - if len(b)%2 != 0 { - panic("invalid byte slice length: must be a multiple of 2") - } - - n := len(b) / 2 // Number of FP16 elements - result := make([]float32, n) - - for i := 0; i < n; i++ { - offset := i * 2 - result[i] = c.Float16AsFP32(b[offset : offset+2]) - } - - return result -} - -func (c *CustomByteOrder) Float64Vector(b []byte) []float64 { - if len(b)%8 != 0 { - panic("invalid byte slice length: must be a multiple of 8") - } - - n := len(b) / 8 - result := make([]float64, n) - - for i := 0; i < n; i++ { - offset := i * 8 - result[i] = math.Float64frombits(c.Uint64(b[offset : offset+8])) - } - - return result -} - -func (c *CustomByteOrder) Int8Vector(b []byte) []int8 { - result := make([]int8, len(b)) - for i, byteValue := range b { - result[i] = c.Int8([]byte{byteValue}) - } - return result -} - -func (c *CustomByteOrder) Uint8Vector(b []byte) []uint8 { - result := make([]uint8, len(b)) - for i, byteValue := range b { - result[i] = c.Uint8([]byte{byteValue}) - } - return result -} - -func (c *CustomByteOrder) Int8AsInt32Vector(b []byte) []int32 { - result := make([]int32, len(b)) - for i, byteValue := range b { - result[i] = c.Int8AsInt32([]byte{byteValue}) - } - return result -} - -func (c *CustomByteOrder) Int16Vector(b []byte) []int16 { - if len(b)%2 != 0 { - panic("invalid byte slice length: must be a multiple of 2") - } - result := make([]int16, len(b)/2) - for i := 0; i < len(result); i++ { - result[i] = c.Int16(b[i*2 : i*2+2]) - } - return result -} - -func (c *CustomByteOrder) Int16AsInt32Vector(b []byte) []int32 { - if len(b)%2 != 0 { - panic("invalid byte slice length: must be a multiple of 2") - } - result := make([]int32, len(b)/2) - for i := 0; i < len(result); i++ { - result[i] = c.Int16AsInt32(b[i*2 : i*2+2]) - } - return result -} - -func (c *CustomByteOrder) Int32Vector(b []byte) []int32 { - if len(b)%4 != 0 { - panic("invalid byte slice length: must be a multiple of 4") - } - result := make([]int32, len(b)/4) - for i := 0; i < len(result); i++ { - result[i] = c.Int32(b[i*4 : i*4+4]) - } - return result -} - -func (c *CustomByteOrder) Int64Vector(b []byte) []int64 { - if len(b)%8 != 0 { - panic("invalid byte slice length: must be a multiple of 8") - } - result := make([]int64, len(b)/8) - for i := 0; i < len(result); i++ { - result[i] = c.Int64(b[i*8 : i*8+8]) - } - return result -} - -func (c *CustomByteOrder) BoolVector(encodedValue []byte, vectorLength int) []bool { - // Allocate a bool slice for the output - result := make([]bool, vectorLength) - - // Iterate over each bit in the byte array - for i := 0; i < vectorLength; i++ { - // Determine which byte and bit within the byte to read - byteIndex := i / 8 - bitIndex := i % 8 - - // Extract the bit: shift the byte and mask it - bit := (encodedValue[byteIndex] >> bitIndex) & 1 - - // Convert the bit to a bool and store it - result[i] = bit == 1 - } - - return result -} - -func (c *CustomByteOrder) StringVector(encodedValue []byte, vectorLength int, stringLength int) []string { - // Allocate a slice for the output strings - result := make([]string, vectorLength) - - // Iterate over the vector length to decode each string - for i := 0; i < vectorLength; i++ { - // Calculate the start and end of the current string slice - start := i * stringLength - end := start + stringLength - - // Ensure we don't go out of bounds - if end > len(encodedValue) { - break - } - - // Extract the string slice and decode it using the String method - decodedString := c.String(encodedValue[start:end]) - result[i] = decodedString - } - - return result -} - -func (c *CustomByteOrder) Float64(b []byte) float64 { - return math.Float64frombits(c.Uint64(b)) -} - -const ( - ByteLow5bitsMask uint8 = 0x1F - ByteLow3bitsMask uint8 = 0x07 - ByteHigh3bitsMask uint8 = 0xE0 - MaxUint8 uint8 = 0xFF - MinUint8 uint8 = 0x00 - MaxUint16 uint16 = 0xFFFF - MinUint16 uint16 = 0x0000 - MaxUint32 uint32 = 0xFFFFFFFF - MinUint32 uint32 = 0x00000000 - MaxUint64 uint64 = 0xFFFFFFFFFFFFFFFF - MinUint64 uint64 = 0x0000000000000000 -) - -func Init() { - loadByteOrder() -} - -func loadByteOrder() { - buf := [2]byte{} - *(*uint16)(unsafe.Pointer(&buf[0])) = uint16(0xABCD) - - switch buf { - case [2]byte{0xCD, 0xAB}: - ByteOrder = &CustomByteOrder{binary.LittleEndian} - case [2]byte{0xAB, 0xCD}: - ByteOrder = &CustomByteOrder{binary.BigEndian} - default: - panic("Could not determine endianness.") - } -} - -func GetToByteFP32AndLess(dType types.DataType) (func([]byte, float32), error) { - switch dType { - case types.DataTypeFP8E5M2, types.DataTypeFP8E5M2Vector: - return ByteOrder.PutFloat8E5M2FromFP32, nil - case types.DataTypeFP8E4M3, types.DataTypeFP8E4M3Vector: - return ByteOrder.PutFloat8E4M3FromFP32, nil - case types.DataTypeFP16, types.DataTypeFP16Vector: - return ByteOrder.PutFloat16FromFP32, nil - case types.DataTypeFP32, types.DataTypeFP32Vector: - return ByteOrder.PutFloat32, nil - default: - return nil, fmt.Errorf("unsupported data type: %s", dType) - } -} - -func GetFromByteFP32AndLess(dType types.DataType) (func([]byte) float32, error) { - switch dType { - case types.DataTypeFP8E5M2, types.DataTypeFP8E5M2Vector: - return ByteOrder.Float8E5M2AsFP32, nil - case types.DataTypeFP8E4M3, types.DataTypeFP8E4M3Vector: - return ByteOrder.Float8E4M3AsFP32, nil - case types.DataTypeFP16, types.DataTypeFP16Vector: - return ByteOrder.Float16AsFP32, nil - case types.DataTypeFP32, types.DataTypeFP32Vector: - return ByteOrder.Float32, nil - default: - return nil, fmt.Errorf("unsupported data type: %s", dType) - } -} - -func GetToByteInt32AndLess(dType types.DataType) (func([]byte, int32), error) { - switch dType { - case types.DataTypeInt8, types.DataTypeInt8Vector: - return ByteOrder.PutInt8FromInt32, nil - case types.DataTypeInt16, types.DataTypeInt16Vector: - return ByteOrder.PutInt16FromInt32, nil - case types.DataTypeInt32, types.DataTypeInt32Vector: - return ByteOrder.PutInt32, nil - default: - return nil, fmt.Errorf("unsupported data type: %s", dType) - } -} - -func GetFromByteInt32AndLess(dType types.DataType) (func([]byte) int32, error) { - switch dType { - case types.DataTypeInt8, types.DataTypeInt8Vector: - return ByteOrder.Int8AsInt32, nil - case types.DataTypeInt16, types.DataTypeInt16Vector: - return ByteOrder.Int16AsInt32, nil - case types.DataTypeInt32, types.DataTypeInt32Vector: - return ByteOrder.Int32, nil - default: - return nil, fmt.Errorf("unsupported data type: %s", dType) - } -} - -func GetToByteUint32AndLess(dType types.DataType) (func([]byte, uint32), error) { - switch dType { - case types.DataTypeUint8, types.DataTypeUint8Vector: - return ByteOrder.PutUint8FromUint32, nil - case types.DataTypeUint16, types.DataTypeUint16Vector: - return ByteOrder.PutUint16FromUint32, nil - case types.DataTypeUint32, types.DataTypeUint32Vector: - return ByteOrder.PutUint32, nil - default: - return nil, fmt.Errorf("unsupported data type: %s", dType) - } -} - -func GetFromByteUint32AndLess(dType types.DataType) (func([]byte) uint32, error) { - switch dType { - case types.DataTypeUint8, types.DataTypeUint8Vector: - return ByteOrder.Uint8AsUint32, nil - case types.DataTypeUint16, types.DataTypeUint16Vector: - return ByteOrder.Uint16AsUint32, nil - case types.DataTypeUint32, types.DataTypeUint32Vector: - return ByteOrder.Uint32, nil - default: - return nil, fmt.Errorf("unsupported data type: %s", dType) - } -} - -func BinPackUint32InUint64(high, low uint32) uint64 { - return uint64(high)<<32 | uint64(low) -} - -func UnpackUint64InUint32(highLow uint64) (uint32, uint32) { - return uint32(highLow >> 32), uint32(highLow) -} - -func BinPackUint16InUint32(high, low uint16) uint32 { - return uint32(high)<<16 | uint32(low) -} - -func UnpackUint32InUint16(highLow uint32) (uint16, uint16) { - return uint16(highLow >> 16), uint16(highLow) -} - -func BinPackUint8InUint16(high, low uint8) uint16 { - return uint16(high)<<8 | uint16(low) -} - -func UnpackUint16InUint8(highLow uint16) (uint8, uint8) { - return uint8(highLow >> 8), uint8(highLow) -} - -// func ParseFeatureValue(featureLabels []string, features *persist.FeatureValues, dataType types.DataType, featureMeta map[string]config.FeatureMeta) (interface{}, error) { -// switch dataType { -// case types.DataTypeInt8, types.DataTypeInt16, types.DataTypeInt32: -// return GetInt32(featureLabels, features, featureMeta) -// case types.DataTypeUint8, types.DataTypeUint16, types.DataTypeUint32: -// return GetUInt32(featureLabels, features, featureMeta) -// case types.DataTypeInt64: -// return GetInt64(featureLabels, features, featureMeta) -// case types.DataTypeUint64: -// return GetUInt64(featureLabels, features, featureMeta) -// case types.DataTypeFP8E5M2, types.DataTypeFP8E4M3, types.DataTypeFP16, types.DataTypeFP32: -// return GetFP32(featureLabels, features, featureMeta) -// case types.DataTypeFP64: -// return GetFP64(featureLabels, features, featureMeta) -// case types.DataTypeBool: -// return GetUInt8(featureLabels, features, featureMeta) -// case types.DataTypeString: -// return GetString(featureLabels, features, featureMeta) -// case types.DataTypeInt8Vector, types.DataTypeInt16Vector, types.DataTypeInt32Vector: -// return GetInt32Vector(featureLabels, features, featureMeta) -// case types.DataTypeInt64Vector: -// return GetInt64Vector(featureLabels, features, featureMeta) -// case types.DataTypeUint8Vector, types.DataTypeUint16Vector, types.DataTypeUint32Vector: -// return GetUInt32Vector(featureLabels, features, featureMeta) -// case types.DataTypeUint64Vector: -// return GetUInt64Vector(featureLabels, features, featureMeta) -// case types.DataTypeFP8E5M2Vector, types.DataTypeFP8E4M3Vector, types.DataTypeFP16Vector, types.DataTypeFP32Vector: -// return GetFP32Vector(featureLabels, features, featureMeta) -// case types.DataTypeFP64Vector: -// return GetFP64Vector(featureLabels, features, featureMeta) -// case types.DataTypeBoolVector: -// return GetBoolVector(featureLabels, features, featureMeta) -// case types.DataTypeStringVector: -// return GetStringVector(featureLabels, features, featureMeta) -// default: -// return nil, fmt.Errorf("unknown Data type: %d", dataType) -// } -// } - -// func GetInt32(featureLabels []string, featureValues *persist.FeatureValues, featureMeta map[string]config.FeatureMeta) ([]int32, error) { -// int32Array := make([]int32, len(featureMeta)) -// labelExists := make(map[string]bool, len(featureLabels)) -// for index, label := range featureLabels { -// labelExists[label] = true -// int32Array[featureMeta[label].Sequence] = featureValues.GetValues().Int32Values[index] -// } - -// for label, meta := range featureMeta { -// if !labelExists[label] { -// int32Array[meta.Sequence] = ByteOrder.Int32(meta.DefaultValuesInBytes) -// } -// } -// return int32Array, nil -// } - -// func GetUInt32(featureLabels []string, featureValues *persist.FeatureValues, featureMeta map[string]config.FeatureMeta) ([]uint32, error) { -// uint32Array := make([]uint32, len(featureMeta)) -// labelExists := make(map[string]bool, len(featureLabels)) -// for index, label := range featureLabels { -// labelExists[label] = true -// uint32Array[featureMeta[label].Sequence] = uint32(featureValues.GetValues().Uint32Values[index]) -// } - -// for label, meta := range featureMeta { -// if !labelExists[label] { -// uint32Array[meta.Sequence] = ByteOrder.Uint32(meta.DefaultValuesInBytes) -// } -// } -// return uint32Array, nil -// } - -// func GetInt64(featureLabels []string, featureValues *persist.FeatureValues, featureMeta map[string]config.FeatureMeta) ([]int64, error) { -// int64Array := make([]int64, len(featureMeta)) -// labelExists := make(map[string]bool, len(featureLabels)) -// for index, label := range featureLabels { -// labelExists[label] = true -// int64Array[featureMeta[label].Sequence] = int64(featureValues.GetValues().Int64Values[index]) -// } - -// for label, meta := range featureMeta { -// if !labelExists[label] { -// int64Array[meta.Sequence] = ByteOrder.Int64(meta.DefaultValuesInBytes) -// } -// } - -// return int64Array, nil -// } - -// func GetUInt64(featureLabels []string, featureValues *persist.FeatureValues, featureMeta map[string]config.FeatureMeta) ([]uint64, error) { -// uint64Array := make([]uint64, len(featureMeta)) -// labelExists := make(map[string]bool, len(featureLabels)) -// for index, label := range featureLabels { -// labelExists[label] = true -// uint64Array[featureMeta[label].Sequence] = featureValues.GetValues().Uint64Values[index] -// } - -// for label, meta := range featureMeta { -// if !labelExists[label] { -// uint64Array[meta.Sequence] = ByteOrder.Uint64(meta.DefaultValuesInBytes) -// } -// } -// return uint64Array, nil -// } - -// func GetFP32(featureLabels []string, featureValues *persist.FeatureValues, featureMeta map[string]config.FeatureMeta) ([]float32, error) { -// fp32Array := make([]float32, len(featureMeta)) -// labelExists := make(map[string]bool, len(featureLabels)) -// for index, label := range featureLabels { -// labelExists[label] = true -// fp32Array[featureMeta[label].Sequence] = float32(featureValues.GetValues().Fp32Values[index]) -// } - -// for label, meta := range featureMeta { -// if !labelExists[label] { -// fp32Array[meta.Sequence] = ByteOrder.Float32(meta.DefaultValuesInBytes) -// } -// } -// return fp32Array, nil -// } - -// func GetFP64(featureLabels []string, featureValues *persist.FeatureValues, featureMeta map[string]config.FeatureMeta) ([]float64, error) { -// fp64Array := make([]float64, len(featureMeta)) -// labelExists := make(map[string]bool, len(featureLabels)) -// for index, label := range featureLabels { -// labelExists[label] = true -// fp64Array[featureMeta[label].Sequence] = featureValues.GetValues().Fp64Values[index] -// } - -// for label, meta := range featureMeta { -// if !labelExists[label] { -// fp64Array[meta.Sequence] = ByteOrder.Float64(meta.DefaultValuesInBytes) -// } -// } -// return fp64Array, nil -// } - -// func GetUInt8(featureLabels []string, featureValues *persist.FeatureValues, featureMeta map[string]config.FeatureMeta) ([]uint8, error) { -// uint8Array := make([]uint8, len(featureMeta)) -// labelExists := make(map[string]bool, len(featureLabels)) -// for index, label := range featureLabels { -// labelExists[label] = true -// uint8Array[featureMeta[label].Sequence] = func(b bool) uint8 { -// if b { -// return 1 -// } -// return 0 -// }(featureValues.GetValues().BoolValues[index]) -// } - -// for label, meta := range featureMeta { -// if !labelExists[label] { -// uint8Array[meta.Sequence] = ByteOrder.Uint8(meta.DefaultValuesInBytes) -// } -// } -// return uint8Array, nil -// } - -// func GetString(featureLabels []string, featureValues *persist.FeatureValues, featureMeta map[string]config.FeatureMeta) ([]string, error) { -// stringArray := make([]string, len(featureMeta)) -// labelExists := make(map[string]bool, len(featureLabels)) -// for index, label := range featureLabels { -// labelExists[label] = true -// stringArray[featureMeta[label].Sequence] = featureValues.GetValues().StringValues[index] -// } - -// for label, meta := range featureMeta { -// if !labelExists[label] { -// stringArray[meta.Sequence] = ByteOrder.String(meta.DefaultValuesInBytes) -// } -// } -// return stringArray, nil -// } - -// func GetInt32Vector(featureLabels []string, featureValues *persist.FeatureValues, featureMeta map[string]config.FeatureMeta) ([][]int32, error) { -// int32Vectors := make([][]int32, len(featureMeta)) -// labelExists := make(map[string]bool, len(featureLabels)) -// for index, label := range featureLabels { -// labelExists[label] = true -// int32Vectors[featureMeta[label].Sequence] = featureValues.GetValues().Vector[index].Values.Int32Values -// } - -// for label, meta := range featureMeta { -// if !labelExists[label] { -// int32Vectors[meta.Sequence] = ByteOrder.Int32Vector(meta.DefaultValuesInBytes) -// } -// } -// return int32Vectors, nil -// } - -// func GetInt64Vector(featureLabels []string, featureValues *persist.FeatureValues, featureMeta map[string]config.FeatureMeta) ([][]int64, error) { -// int64Vectors := make([][]int64, len(featureMeta)) -// labelExists := make(map[string]bool, len(featureLabels)) -// for index, label := range featureLabels { -// labelExists[label] = true -// int64Vectors[featureMeta[label].Sequence] = featureValues.GetValues().Vector[index].Values.Int64Values -// } - -// for label, meta := range featureMeta { -// if !labelExists[label] { -// int64Vectors[meta.Sequence] = ByteOrder.Int64Vector(meta.DefaultValuesInBytes) -// } -// } -// return int64Vectors, nil -// } - -// func GetUInt32Vector(featureLabels []string, featureValues *persist.FeatureValues, featureMeta map[string]config.FeatureMeta) ([][]uint32, error) { -// uint32Vectors := make([][]uint32, len(featureMeta)) -// labelExists := make(map[string]bool, len(featureLabels)) -// for index, label := range featureLabels { -// labelExists[label] = true -// uint32Vectors[featureMeta[label].Sequence] = featureValues.GetValues().Vector[index].Values.Uint32Values -// } - -// for label, meta := range featureMeta { -// if !labelExists[label] { -// uint32Vectors[meta.Sequence] = ByteOrder.Uint32Vector(meta.DefaultValuesInBytes) -// } -// } -// return uint32Vectors, nil -// } - -// func GetUInt64Vector(featureLabels []string, featureValues *persist.FeatureValues, featureMeta map[string]config.FeatureMeta) ([][]uint64, error) { -// uint64Vectors := make([][]uint64, len(featureMeta)) -// labelExists := make(map[string]bool, len(featureLabels)) -// for index, label := range featureLabels { -// labelExists[label] = true -// uint64Vectors[featureMeta[label].Sequence] = featureValues.GetValues().Vector[index].Values.Uint64Values -// } - -// for label, meta := range featureMeta { -// if !labelExists[label] { -// uint64Vectors[meta.Sequence] = ByteOrder.Uint64Vector(meta.DefaultValuesInBytes) -// } -// } -// return uint64Vectors, nil -// } - -// func GetFP32Vector(featureLabels []string, featureValues *persist.FeatureValues, featureMeta map[string]config.FeatureMeta) ([][]float32, error) { -// fp32Vectors := make([][]float32, len(featureMeta)) -// labelExists := make(map[string]bool, len(featureLabels)) -// for index, label := range featureLabels { -// labelExists[label] = true -// fp32FeatureValues := []float32{} -// for _, value := range featureValues.GetValues().Vector[index].Values.Fp32Values { -// fp32FeatureValues = append(fp32FeatureValues, float32(value)) -// } -// fp32Vectors[featureMeta[label].Sequence] = fp32FeatureValues -// } - -// for label, meta := range featureMeta { -// if !labelExists[label] { -// fp32Vectors[meta.Sequence] = ByteOrder.FP16Vector(meta.DefaultValuesInBytes) -// } -// } -// return fp32Vectors, nil -// } - -// func GetFP64Vector(featureLabels []string, featureValues *persist.FeatureValues, featureMeta map[string]config.FeatureMeta) ([][]float64, error) { -// fp64Vectors := make([][]float64, len(featureMeta)) -// labelExists := make(map[string]bool, len(featureLabels)) -// for index, label := range featureLabels { -// labelExists[label] = true -// fp64Vectors[featureMeta[label].Sequence] = featureValues.GetValues().Vector[index].Values.Fp64Values -// } - -// for label, meta := range featureMeta { -// if !labelExists[label] { -// fp64Vectors[meta.Sequence] = ByteOrder.Float64Vector(meta.DefaultValuesInBytes) -// } -// } -// return fp64Vectors, nil -// } - -// func GetBoolVector(featureLabels []string, featureValues *persist.FeatureValues, featureMeta map[string]config.FeatureMeta) ([][]bool, error) { -// boolVectors := make([][]bool, len(featureMeta)) -// labelExists := make(map[string]bool, len(featureLabels)) -// for index, label := range featureLabels { -// labelExists[label] = true -// boolVectors[featureMeta[label].Sequence] = featureValues.GetValues().Vector[index].Values.BoolValues -// } - -// for label, meta := range featureMeta { -// if !labelExists[label] { -// boolVectors[meta.Sequence] = ByteOrder.BoolVector(meta.DefaultValuesInBytes, int(meta.VectorLength)) -// } -// } -// return boolVectors, nil -// } - -// func GetStringVector(featureLabels []string, featureValues *persist.FeatureValues, featureMeta map[string]config.FeatureMeta) ([][]string, error) { -// stringVectors := make([][]string, len(featureMeta)) -// labelExists := make(map[string]bool, len(featureLabels)) -// for index, label := range featureLabels { -// labelExists[label] = true -// stringVectors[featureMeta[label].Sequence] = featureValues.GetValues().Vector[index].Values.StringValues -// } - -// for label, meta := range featureMeta { -// if !labelExists[label] { -// stringVectors[meta.Sequence] = ByteOrder.StringVector(meta.DefaultValuesInBytes, int(meta.VectorLength), int(meta.StringLength)) -// } -// } -// return stringVectors, nil -// } diff --git a/helix-client/pkg/grpc/grpcframework.go b/helix-client/pkg/grpc/grpcframework.go deleted file mode 100644 index ca43d166..00000000 --- a/helix-client/pkg/grpc/grpcframework.go +++ /dev/null @@ -1,132 +0,0 @@ -package grpc - -import ( - "net" - "net/http" - "strconv" - "sync" - - "github.com/Meesho/BharatMLStack/helix-client/pkg/httpframework" - "github.com/Meesho/BharatMLStack/helix-client/pkg/middleware" - "github.com/gin-gonic/gin" - "github.com/rs/zerolog/log" - "github.com/soheilhy/cmux" - "github.com/spf13/viper" - "google.golang.org/grpc" - "google.golang.org/grpc/reflection" -) - -type Server struct { - GRPCServer *grpc.Server - HTTPHandler *gin.Engine -} - -var ( - server *Server - once sync.Once -) - -// Init initializes the gRPC and HTTP server with the given middlewares if any -func Init(interceptors ...grpc.UnaryServerInterceptor) { - once.Do(func() { - // Create a gRPC server with the logger and recovery middleware - interceptors = append(interceptors, middleware.GRPCRecovery, middleware.GRPCLogger) - grpcServer := grpc.NewServer( - grpc.ChainUnaryInterceptor(interceptors...), - ) - - // Create a Gin router - httpframework.Init() - router := httpframework.Instance() - - // Create HTTP routes and handlers using Gin - router.GET("/health/self", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"message": "true"}) - }) - - server = &Server{ - GRPCServer: grpcServer, - HTTPHandler: router, - } - }) -} - -// Run starts the cmux multiplexer (Mux) to handle incoming connections and route them to the appropriate servers -func (server *Server) Run() error { - if !viper.IsSet("APP_PORT") { - log.Panic().Msgf("Failed to start the application - APP_PORT is not set") - } - port := viper.GetInt("APP_PORT") - - listener, err := net.Listen("tcp", ":"+strconv.Itoa(port)) - if err != nil { - log.Panic().Msgf("Failed to start the application - Failed to listen: %v", err) - } - - // Create a cmux multiplexer that will multiplex 2 protocols on same port - mux := cmux.New(listener) - - // Create a cmux listener for HTTP connections - httpListener := mux.Match(cmux.HTTP1Fast()) - // Create a cmux listener for gRPC connections - grpcListener := mux.Match(cmux.HTTP2(), cmux.HTTP2HeaderField("content-type", "application/grpc"), cmux.Any()) - - reflection.Register(server.GRPCServer) - // Start listeners for each protocol - // Start the gRPC server in a separate goroutine - go func() { - if err := server.GRPCServer.Serve(grpcListener); err != nil { - log.Panic().Msgf("Failed to serve gRPC server: %v", err) - } - }() - // Start the HTTP server in a separate goroutine - go func() { - if err := http.Serve(httpListener, server.HTTPHandler); err != nil { - log.Panic().Msgf("Failed to serve HTTP server: %v", err) - } - }() - - return mux.Serve() -} - -// InitWithServerOptions is a new initialization function that accepts grpc.ServerOption -// It initializes the gRPC and HTTP server with the given server options and middlewares. -func InitWithServerOptions(serverOptions []grpc.ServerOption, interceptors ...grpc.UnaryServerInterceptor) { - once.Do(func() { - if serverOptions == nil { - log.Info().Msg("Server options are nil") - } - // Append default middlewares - interceptors = append(interceptors, middleware.GRPCRecovery, middleware.GRPCLogger) - - // Combine provided server options with the interceptor chain - allServerOptions := append(serverOptions, grpc.ChainUnaryInterceptor(interceptors...)) - - // Create a gRPC server with the specified options - grpcServer := grpc.NewServer( - allServerOptions..., - ) - - // Create a Gin router - httpframework.Init() - router := httpframework.Instance() - - // Create HTTP routes and handlers using Gin - router.GET("/health/self", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"message": "true"}) - }) - - server = &Server{ - GRPCServer: grpcServer, - HTTPHandler: router, - } - }) -} - -// Instance returns the grpc instance -func Instance() *Server { - if server == nil { - log.Panic().Msg("Server not initialized, call Init first") - } - return server -} diff --git a/helix-client/pkg/grpc/grpcframework_test.go b/helix-client/pkg/grpc/grpcframework_test.go deleted file mode 100644 index 861287ba..00000000 --- a/helix-client/pkg/grpc/grpcframework_test.go +++ /dev/null @@ -1,399 +0,0 @@ -package grpc - -import ( - "context" - "net/http" - "os" - "sync" - "testing" - "time" - - "github.com/Meesho/BharatMLStack/helix-client/pkg/httpframework" - - "github.com/spf13/viper" - "github.com/stretchr/testify/assert" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" -) - -// resetGlobalState resets the global state for testing -func resetGlobalState() { - server = nil - once = sync.Once{} - httpframework.ResetForTesting() -} - -// Helper function to wait for server to be ready -func waitForServer(port string) bool { - maxRetries := 10 - for i := 0; i < maxRetries; i++ { - resp, err := http.Get("http://localhost:" + port + "/health/self") - if err == nil { - resp.Body.Close() - return true - } - time.Sleep(100 * time.Millisecond) - } - return false -} - -func TestRunWithoutAppPort(t *testing.T) { - os.Unsetenv("APP_PORT") - viper.Set("APP_NAME", "test-grpc-app") - viper.AutomaticEnv() - - Init() - instance := Instance() - assert.Panics(t, func() { - err := instance.Run() - if err != nil { - return - } - }) -} - -func TestInitAndRun(t *testing.T) { - resetGlobalState() - - os.Setenv("APP_PORT", "8080") - viper.Set("APP_NAME", "test-grpc-app") - viper.AutomaticEnv() - - // Call the function under test in a goroutine, as it blocks - Init() - instance := Instance() - - // Assert that the server is initialized - assert.NotNil(t, instance.HTTPHandler) - - // Assert that the server's GRPCServer is not nil - assert.NotNil(t, instance.GRPCServer) - - // Test Run method - go instance.Run() - - // Wait for the server to start - assert.True(t, waitForServer("8080"), "Server did not start within timeout") - - // Send a GET request to the /health/self endpoint - resp, err := http.Get("http://localhost:8080/health/self") - assert.NoError(t, err) - defer resp.Body.Close() - - //make grpc call to the server - conn, err := grpc.NewClient("localhost:8080", grpc.WithTransportCredentials(insecure.NewCredentials())) - assert.NoError(t, err) - defer conn.Close() - - // Check the response status code - assert.Equal(t, http.StatusOK, resp.StatusCode) -} - -func TestInitWithServerOptions(t *testing.T) { - resetGlobalState() - - os.Setenv("APP_PORT", "8081") - viper.AutomaticEnv() - - serverOpts := []grpc.ServerOption{ - grpc.MaxRecvMsgSize(1024 * 1024 * 4), // 4MB max message size - } - - // Call the function under test in a goroutine, as it blocks - InitWithServerOptions(serverOpts) - instance := Instance() - - // Assert that the server is initialized - assert.NotNil(t, instance.HTTPHandler) - - // Assert that the server's GRPCServer is not nil - assert.NotNil(t, instance.GRPCServer) - - // Test Run method - go instance.Run() - - // Wait for the server to start - assert.True(t, waitForServer("8081"), "Server did not start within timeout") - - // Send a GET request to the /health/self endpoint - resp, err := http.Get("http://localhost:8081/health/self") - assert.NoError(t, err) - defer resp.Body.Close() - - //make grpc call to the server - conn, err := grpc.NewClient("localhost:8081", grpc.WithTransportCredentials(insecure.NewCredentials())) - assert.NoError(t, err) - defer conn.Close() - - // Check the response status code - assert.Equal(t, http.StatusOK, resp.StatusCode) -} - -func TestInitWithoutServerOptions(t *testing.T) { - resetGlobalState() - - os.Setenv("APP_PORT", "8082") - viper.AutomaticEnv() - - // Call the function under test in a goroutine, as it blocks - InitWithServerOptions(nil) - instance := Instance() - - // Assert that the server is initialized - assert.NotNil(t, instance.HTTPHandler) - - // Assert that the server's GRPCServer is not nil - assert.NotNil(t, instance.GRPCServer) - - // Test Run method - go instance.Run() - - // Wait for the server to start - assert.True(t, waitForServer("8082"), "Server did not start within timeout") - - // Send a GET request to the /health/self endpoint - resp, err := http.Get("http://localhost:8082/health/self") - assert.NoError(t, err) - defer resp.Body.Close() - - //make grpc call to the server - conn, err := grpc.NewClient("localhost:8082", grpc.WithTransportCredentials(insecure.NewCredentials())) - assert.NoError(t, err) - defer conn.Close() - - // Check the response status code - assert.Equal(t, http.StatusOK, resp.StatusCode) -} - -func TestInstanceWithoutInit(t *testing.T) { - resetGlobalState() - - assert.Panics(t, func() { Instance() }) -} - -// New comprehensive tests for InitWithServerOptions - -func TestInitWithServerOptionsCustomInterceptors(t *testing.T) { - resetGlobalState() - - os.Setenv("APP_PORT", "8083") - viper.AutomaticEnv() - - // Custom interceptor for testing - customInterceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { - // This is a simple pass-through interceptor for testing - return handler(ctx, req) - } - - serverOpts := []grpc.ServerOption{ - grpc.MaxRecvMsgSize(1024 * 1024 * 2), // 2MB max message size - } - - // Call with custom interceptor - InitWithServerOptions(serverOpts, customInterceptor) - instance := Instance() - - // Assert that the server is initialized - assert.NotNil(t, instance.HTTPHandler) - assert.NotNil(t, instance.GRPCServer) - - // Test Run method - go instance.Run() - - // Wait for the server to start - assert.True(t, waitForServer("8083"), "Server did not start within timeout") - - // Send a GET request to the /health/self endpoint - resp, err := http.Get("http://localhost:8083/health/self") - assert.NoError(t, err) - defer resp.Body.Close() - - // Check the response status code - assert.Equal(t, http.StatusOK, resp.StatusCode) -} - -func TestInitWithServerOptionsEmptyServerOptions(t *testing.T) { - resetGlobalState() - - os.Setenv("APP_PORT", "8084") - viper.AutomaticEnv() - - // Test with empty server options slice - serverOpts := []grpc.ServerOption{} - - InitWithServerOptions(serverOpts) - instance := Instance() - - // Assert that the server is initialized - assert.NotNil(t, instance.HTTPHandler) - assert.NotNil(t, instance.GRPCServer) - - // Test Run method - go instance.Run() - - // Wait for the server to start - assert.True(t, waitForServer("8084"), "Server did not start within timeout") - - // Send a GET request to the /health/self endpoint - resp, err := http.Get("http://localhost:8084/health/self") - assert.NoError(t, err) - defer resp.Body.Close() - - // Check the response status code - assert.Equal(t, http.StatusOK, resp.StatusCode) -} - -func TestInitWithServerOptionsMultipleOptions(t *testing.T) { - resetGlobalState() - - os.Setenv("APP_PORT", "8085") - viper.AutomaticEnv() - - serverOpts := []grpc.ServerOption{ - grpc.MaxRecvMsgSize(1024 * 1024 * 8), // 8MB max message size - grpc.MaxSendMsgSize(1024 * 1024 * 8), // 8MB max send message size - grpc.MaxConcurrentStreams(1000), - } - - InitWithServerOptions(serverOpts) - instance := Instance() - - // Assert that the server is initialized - assert.NotNil(t, instance.HTTPHandler) - assert.NotNil(t, instance.GRPCServer) - - // Test Run method - go instance.Run() - - // Wait for the server to start - assert.True(t, waitForServer("8085"), "Server did not start within timeout") - - // Send a GET request to the /health/self endpoint - resp, err := http.Get("http://localhost:8085/health/self") - assert.NoError(t, err) - defer resp.Body.Close() - - // Check the response status code - assert.Equal(t, http.StatusOK, resp.StatusCode) -} - -func TestInitWithServerOptionsOnlyCalledOnce(t *testing.T) { - resetGlobalState() - - os.Setenv("APP_PORT", "8086") - viper.AutomaticEnv() - - serverOpts1 := []grpc.ServerOption{ - grpc.MaxRecvMsgSize(1024 * 1024 * 4), // 4MB max message size - } - - serverOpts2 := []grpc.ServerOption{ - grpc.MaxRecvMsgSize(1024 * 1024 * 8), // 8MB max message size - } - - // Call InitWithServerOptions twice - only first should take effect - InitWithServerOptions(serverOpts1) - instance1 := Instance() - - InitWithServerOptions(serverOpts2) // This should be ignored due to sync.Once - instance2 := Instance() - - // Both instances should be the same - assert.Equal(t, instance1, instance2) - assert.NotNil(t, instance1.HTTPHandler) - assert.NotNil(t, instance1.GRPCServer) -} - -func TestInitWithServerOptionsNilLogging(t *testing.T) { - resetGlobalState() - - os.Setenv("APP_PORT", "8087") - viper.AutomaticEnv() - - // Test that nil server options are handled gracefully - InitWithServerOptions(nil) - instance := Instance() - - // Assert that the server is initialized - assert.NotNil(t, instance.HTTPHandler) - assert.NotNil(t, instance.GRPCServer) - - // Test Run method - go instance.Run() - - // Wait for the server to start - assert.True(t, waitForServer("8087"), "Server did not start within timeout") - - // Send a GET request to the /health/self endpoint - resp, err := http.Get("http://localhost:8087/health/self") - assert.NoError(t, err) - defer resp.Body.Close() - - // Check the response status code - assert.Equal(t, http.StatusOK, resp.StatusCode) -} - -// Test to verify interceptor ordering in InitWithServerOptions -func TestInitWithServerOptionsInterceptorOrdering(t *testing.T) { - resetGlobalState() - - os.Setenv("APP_PORT", "8088") - viper.AutomaticEnv() - - // Track the order of interceptor calls - var callOrder []string - - // First custom interceptor - interceptor1 := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { - callOrder = append(callOrder, "interceptor1") - return handler(ctx, req) - } - - // Second custom interceptor - interceptor2 := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { - callOrder = append(callOrder, "interceptor2") - return handler(ctx, req) - } - - serverOpts := []grpc.ServerOption{ - grpc.MaxRecvMsgSize(1024 * 1024 * 4), // 4MB max message size - } - - // Call with multiple custom interceptors - InitWithServerOptions(serverOpts, interceptor1, interceptor2) - instance := Instance() - - // Assert that the server is initialized - assert.NotNil(t, instance.HTTPHandler) - assert.NotNil(t, instance.GRPCServer) - - // Note: Testing actual interceptor call order would require setting up a real gRPC service - // For now, we verify that the server was created successfully with multiple interceptors - // The ordering test would need to be done in an integration test with actual gRPC calls -} - -// Test to verify that both Init and InitWithServerOptions work similarly -func TestInitVsInitWithServerOptions(t *testing.T) { - t.Run("Init creates server properly", func(t *testing.T) { - resetGlobalState() - os.Setenv("APP_PORT", "8089") - viper.Set("APP_NAME", "test-grpc-app") - viper.AutomaticEnv() - - Init() - instance := Instance() - assert.NotNil(t, instance.HTTPHandler) - assert.NotNil(t, instance.GRPCServer) - }) - - t.Run("InitWithServerOptions creates server properly", func(t *testing.T) { - resetGlobalState() - os.Setenv("APP_PORT", "8090") - viper.AutomaticEnv() - - InitWithServerOptions(nil) - instance := Instance() - assert.NotNil(t, instance.HTTPHandler) - assert.NotNil(t, instance.GRPCServer) - }) -} diff --git a/helix-client/pkg/grpcclient/grpc.go b/helix-client/pkg/grpcclient/grpc.go deleted file mode 100644 index f0c5d7c3..00000000 --- a/helix-client/pkg/grpcclient/grpc.go +++ /dev/null @@ -1,140 +0,0 @@ -package grpcclient - -import ( - "crypto/tls" - "errors" - "time" - - "github.com/Meesho/BharatMLStack/helix-client/pkg/metric" - "github.com/rs/zerolog/log" - "github.com/spf13/viper" - "golang.org/x/net/context" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" - "google.golang.org/grpc/credentials/insecure" - "google.golang.org/grpc/resolver" - "google.golang.org/grpc/status" -) - -const ( - ResolverDefaultScheme = "dns" -) - -type Config struct { - Host string - Port string - DeadLine int - LoadBalancingPolicy string - PlainText bool -} - -type GRPCClient struct { - Conn *grpc.ClientConn - DeadLine int64 - envPrefix string -} - -func NewConnFromConfig(config *Config, envPrefix string) *GRPCClient { - conn, err := getGRPCConnections(*config) - if err != nil { - log.Panic().Msgf("error while GRPC connection initialization from config - %#v. error - %s", config, err) - } - conn.envPrefix = envPrefix - return conn -} - -func NewConn(envPrefix string) *GRPCClient { - config := newConfig(envPrefix) - conn, err := getGRPCConnections(*config) - if err != nil { - log.Panic().Msgf("error while %s GRPC connection initialization. %s", envPrefix, err) - } - conn.envPrefix = envPrefix - return conn -} - -func newConfig(envPrefix string) *Config { - config := &Config{ - DeadLine: 200_000, - PlainText: false, - } - if !viper.IsSet(envPrefix + "_HOST") { - log.Panic().Msg(envPrefix + "_HOST not set") - } - if !viper.IsSet(envPrefix + "_PORT") { - log.Panic().Msg(envPrefix + "_PORT not set") - } - if !viper.IsSet(envPrefix + "_TIMEOUT_IN_MS") { - log.Panic().Msg(envPrefix + "_TIMEOUT_IN_MS not set") - } - if viper.IsSet(envPrefix + "_GRPC_PLAIN_TEXT") { - config.PlainText = viper.GetBool(envPrefix + "_GRPC_PLAIN_TEXT") - } - if viper.IsSet(envPrefix + "_LOAD_BALANCING_POLICY") { - config.LoadBalancingPolicy = viper.GetString(envPrefix + "_LOAD_BALANCING_POLICY") - } else { - config.LoadBalancingPolicy = "round_robin" - } - config.Host = viper.GetString(envPrefix + "_HOST") - config.Port = viper.GetString(envPrefix + "_PORT") - config.DeadLine = viper.GetInt(envPrefix + "_TIMEOUT_IN_MS") - return config -} - -func getGRPCConnections(config Config) (*GRPCClient, error) { - resolver.SetDefaultScheme(ResolverDefaultScheme) - var gConn *grpc.ClientConn - var err error - if config.Host == "" { - return nil, errors.New("host is not set") - } - if config.Port == "" { - return nil, errors.New("port is not set") - } - if config.DeadLine <= 0 { - return nil, errors.New("deadline is not set or is negative") - } - - if config.LoadBalancingPolicy == "" { - log.Warn().Msgf("Load balancing policy is not set for %s. Setting it to round robin", config.Host) - config.LoadBalancingPolicy = "round_robin" - } - if config.PlainText { - gConn, err = grpc.NewClient(config.Host+":"+config.Port, - grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"`+config.LoadBalancingPolicy+`"}`), - ) - } else { - creds := credentials.NewTLS(&tls.Config{InsecureSkipVerify: true}) - gConn, err = grpc.NewClient(config.Host+":"+config.Port, - grpc.WithTransportCredentials(creds), - grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"`+config.LoadBalancingPolicy+`"}`), - ) - } - if err != nil { - return nil, err - } - return &GRPCClient{Conn: gConn, DeadLine: int64(config.DeadLine)}, nil -} - -// Invoke is a wrapper around grpc.ClientConn.Invoke and capable to generate metric for external grpc service -func (c *GRPCClient) Invoke(ctx context.Context, method string, args any, reply any, opts ...grpc.CallOption) error { - startTime := time.Now() - err := c.Conn.Invoke(ctx, method, args, reply, opts...) - var code uint32 = 0 - if err != nil { - if e, ok := status.FromError(err); ok { - code = uint32(e.Code()) - } - } - latency := time.Since(startTime) - latencyTags := metric.BuildExternalGRPCServiceLatencyTags(c.envPrefix, method, int(code)) - countTags := metric.BuildExternalGRPCServiceCountTags(c.envPrefix, method, int(code)) - metric.Timing(metric.ExternalApiRequestLatency, latency, latencyTags) - metric.Incr(metric.ExternalApiRequestCount, countTags) - return err -} - -func (c *GRPCClient) NewStream(ctx context.Context, desc *grpc.StreamDesc, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) { - return c.Conn.NewStream(ctx, desc, method, opts...) -} diff --git a/helix-client/pkg/httpframework/httpframework.go b/helix-client/pkg/httpframework/httpframework.go deleted file mode 100644 index 977da553..00000000 --- a/helix-client/pkg/httpframework/httpframework.go +++ /dev/null @@ -1,49 +0,0 @@ -package httpframework - -import ( - "os" - "sync" - - "github.com/Meesho/BharatMLStack/helix-client/pkg/middleware" - "github.com/gin-gonic/gin" - "github.com/rs/zerolog/log" - "github.com/spf13/viper" -) - -var ( - router *gin.Engine - once sync.Once -) - -// Init initializes gin engine with the given middlewares -// It sets the gin mode to release if the environment is production and use the middleware logger and recovery -func Init(middlewares ...gin.HandlerFunc) { - once.Do(func() { - env := os.Getenv("APP_ENV") - if env == "prod" || env == "production" { - gin.SetMode(gin.ReleaseMode) - } - appName := viper.GetString("APP_NAME") - if appName == "" { - log.Fatal().Msg("APP_NAME cannot be empty!!!") - } - router = gin.New() - middlewares = append(middlewares, middleware.HTTPLogger(), middleware.HTTPRecovery()) - router.Use(middlewares...) - }) -} - -// Instance returns the httpframework instance -func Instance() *gin.Engine { - if router == nil { - log.Fatal().Msg("Router not initialized") - } - return router -} - -// ResetForTesting resets the global state for testing purposes -// This function should only be used in tests -func ResetForTesting() { - router = nil - once = sync.Once{} -} diff --git a/helix-client/pkg/middleware/grpclogger.go b/helix-client/pkg/middleware/grpclogger.go deleted file mode 100644 index 5a6e0fbc..00000000 --- a/helix-client/pkg/middleware/grpclogger.go +++ /dev/null @@ -1,72 +0,0 @@ -package middleware - -import ( - "context" - "encoding/json" - "strconv" - "strings" - "time" - - "github.com/Meesho/BharatMLStack/helix-client/pkg/metric" - "github.com/rs/zerolog/log" - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/status" -) - -const ( - MetadataUserContext = "USER-CONTEXT" -) - -func GRPCLogger(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, - handler grpc.UnaryHandler) (resp interface{}, err error) { - startTime := time.Now() - - // Extract metadata from the context - md, ok := metadata.FromIncomingContext(ctx) - if !ok { - md = metadata.MD{} - } - - // Extract relevant information from the request - userContext := md.Get(MetadataUserContext) - requestHeaders, _ := json.Marshal(md) - - // Call the gRPC handler to handle the request - resp, err = handler(ctx, req) - statusCode := codes.OK - if err != nil { - statusCode = status.Code(err) - } - // Extract relevant information from the response - responseTime := time.Since(startTime) - - // Log the request and response information - logMessage := strings.Join([]string{ - info.FullMethod, - strconv.Itoa(int(statusCode)), - responseTime.String(), - string(requestHeaders), - }, " | ") - if err != nil { - log.Error().Err(err).Msg(logMessage) - } else { - log.Info().Msg(logMessage) - } - telemetryMiddleware(info, responseTime, statusCode, userContext) - return resp, err -} - -func telemetryMiddleware(info *grpc.UnaryServerInfo, responseTime time.Duration, - statusCode codes.Code, userContext []string) { - metricTags := metric.BuildTag( - metric.NewTag(metric.TagPath, info.FullMethod), - metric.NewTag(metric.TagGrpcStatusCode, strconv.Itoa(int(statusCode))), - ) - if len(userContext) != 0 { - metricTags = append(metricTags, metric.TagAsString(metric.TagUserContext, userContext[0])) - } - metric.Timing(metric.ApiRequestLatency, responseTime, metricTags) - metric.Incr(metric.ApiRequestCount, metricTags) -} diff --git a/helix-client/pkg/middleware/httplogger.go b/helix-client/pkg/middleware/httplogger.go deleted file mode 100644 index d4c7ba55..00000000 --- a/helix-client/pkg/middleware/httplogger.go +++ /dev/null @@ -1,49 +0,0 @@ -package middleware - -import ( - "strconv" - "time" - - "github.com/gin-gonic/gin" - "github.com/rs/zerolog/log" - - "github.com/Meesho/BharatMLStack/helix-client/pkg/api/http" - "github.com/Meesho/BharatMLStack/helix-client/pkg/metric" -) - -// HTTPLogger logs the request -func HTTPLogger() gin.HandlerFunc { - return func(c *gin.Context) { - startTime := time.Now() - c.Next() - endTime := time.Now() - - latency := endTime.Sub(startTime) - - // Example: - // r.GET("/users/:id", func(c *gin.Context) { - // // Request: GET /users/42 - // route := c.FullPath() // "/users/:id" - // }) - route := c.FullPath() - if route == "" { - route = "unknown" - } - - clientIP := c.ClientIP() - method := c.Request.Method - statusCode := c.Writer.Status() - userContext := c.Request.Header.Get(http.HeaderUserContext) - - metricTags := metric.BuildTag( - metric.NewTag(metric.TagPath, route), - metric.NewTag(metric.TagMethod, method), - metric.NewTag(metric.TagHttpStatusCode, strconv.Itoa(statusCode)), - metric.NewTag(metric.TagHttpStatusCode, strconv.Itoa(statusCode)), // TODO: Remove this support in future - metric.NewTag(metric.TagUserContext, userContext), - ) - metric.Incr(metric.ApiRequestCount, metricTags) - metric.Timing(metric.ApiRequestLatency, latency, metricTags) // TODO: Remove this support in future - log.Info().Msgf("[access] [%s] %s %s %d %v", clientIP, method, route, statusCode, latency) - } -} diff --git a/helix-client/pkg/middleware/httplogger_test.go b/helix-client/pkg/middleware/httplogger_test.go deleted file mode 100644 index 72aa28ec..00000000 --- a/helix-client/pkg/middleware/httplogger_test.go +++ /dev/null @@ -1,109 +0,0 @@ -package middleware - -import ( - "bytes" - "net/http" - "net/http/httptest" - "testing" - "time" - - http2 "github.com/Meesho/BharatMLStack/helix-client/pkg/api/http" - "github.com/gin-gonic/gin" - "github.com/rs/zerolog" - "github.com/rs/zerolog/log" - "github.com/stretchr/testify/assert" -) - -func TestHTTPLogger(t *testing.T) { - gin.SetMode(gin.TestMode) - - var logBuffer bytes.Buffer - log.Logger = zerolog.New(&logBuffer).With().Timestamp().Logger() - - t.Run("logs successful request with all parameters", func(t *testing.T) { - logBuffer.Reset() - router := gin.New() - router.Use(HTTPLogger()) - router.GET("/users/:id", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"message": "success"}) - }) - - req := httptest.NewRequest("GET", "/users/123", nil) - req.Header.Set(http2.HeaderUserContext, "anonymous") - w := httptest.NewRecorder() - - router.ServeHTTP(w, req) - assert.Equal(t, http.StatusOK, w.Code) - - logOutput := logBuffer.String() - assert.Contains(t, logOutput, "[access]") - assert.Contains(t, logOutput, "GET") - assert.Contains(t, logOutput, "/users/:id") - assert.Contains(t, logOutput, "200") - assert.Contains(t, logOutput, "192.0.2.1") - }) - - t.Run("handles error response", func(t *testing.T) { - logBuffer.Reset() - router := gin.New() - router.Use(HTTPLogger()) - router.GET("/error", func(c *gin.Context) { - c.JSON(http.StatusInternalServerError, gin.H{"error": "internal error"}) - }) - - req := httptest.NewRequest("GET", "/error", nil) - req.Header.Set(http2.HeaderUserContext, "anonymous") - w := httptest.NewRecorder() - - router.ServeHTTP(w, req) - assert.Equal(t, http.StatusInternalServerError, w.Code) - - logOutput := logBuffer.String() - assert.Contains(t, logOutput, "[access]") - assert.Contains(t, logOutput, "GET") - assert.Contains(t, logOutput, "/error") - assert.Contains(t, logOutput, "500") - }) - - t.Run("handles unknown route", func(t *testing.T) { - logBuffer.Reset() - router := gin.New() - router.Use(HTTPLogger()) - req := httptest.NewRequest("GET", "/*any", nil) - w := httptest.NewRecorder() - - router.ServeHTTP(w, req) - assert.Equal(t, http.StatusNotFound, w.Code) - - logOutput := logBuffer.String() - assert.Contains(t, logOutput, "[access]") - assert.Contains(t, logOutput, "GET") - assert.Contains(t, logOutput, "unknown") - assert.Contains(t, logOutput, "404") - }) - - t.Run("measures request latency", func(t *testing.T) { - logBuffer.Reset() - router := gin.New() - router.Use(HTTPLogger()) - router.GET("/slow", func(c *gin.Context) { - time.Sleep(5 * time.Millisecond) - c.JSON(http.StatusOK, gin.H{"message": "slow response"}) - }) - - req := httptest.NewRequest("GET", "/slow", nil) - w := httptest.NewRecorder() - - start := time.Now() - router.ServeHTTP(w, req) - elapsed := time.Since(start) - assert.Equal(t, http.StatusOK, w.Code) - - assert.GreaterOrEqual(t, elapsed, 5*time.Millisecond) - - logOutput := logBuffer.String() - assert.Contains(t, logOutput, "[access]") - assert.Contains(t, logOutput, "/slow") - assert.Regexp(t, `\d+(\.\d+)?(ms|µs|ns)`, logOutput) - }) -} diff --git a/helix-client/pkg/middleware/httprecovery.go b/helix-client/pkg/middleware/httprecovery.go deleted file mode 100644 index 69f3e3c4..00000000 --- a/helix-client/pkg/middleware/httprecovery.go +++ /dev/null @@ -1,32 +0,0 @@ -package middleware - -import ( - "fmt" - "runtime/debug" - - "github.com/Meesho/BharatMLStack/helix-client/pkg/api" - "github.com/gin-gonic/gin" - "github.com/rs/zerolog/log" -) - -// HTTPRecovery handles context errors/panics and sets response code accordingly -func HTTPRecovery() gin.HandlerFunc { - return func(c *gin.Context) { - defer func() { - if len(c.Errors) > 0 { - err := c.Errors.Last().Err - if apiErr, ok := err.(*api.Error); ok { - c.JSON(apiErr.StatusCode, gin.H{"error": apiErr.Message}) - c.Abort() - } - } - if err := recover(); err != nil { - log.Error().Msgf("Panic occurred: %v\n%s", err, debug.Stack()) - errorMsg := fmt.Sprintf("%v", err) - c.JSON(500, gin.H{"error": errorMsg}) - c.Abort() - } - }() - c.Next() - } -} diff --git a/helm-charts/horizon/Chart.yaml b/helm-charts/horizon/Chart.yaml new file mode 100644 index 00000000..701b977b --- /dev/null +++ b/helm-charts/horizon/Chart.yaml @@ -0,0 +1,10 @@ +apiVersion: v2 +name: horizon +description: A Helm chart for the Horizon control plane service (onboarding, GitOps, ArgoCD orchestration) +type: application +version: 1.0.0 +appVersion: "1.0.0" + +maintainers: + - name: BharatMLStack Team + email: ml-oss@meesho.com diff --git a/helm-charts/horizon/templates/NOTES.txt b/helm-charts/horizon/templates/NOTES.txt new file mode 100644 index 00000000..35a4f3c2 --- /dev/null +++ b/helm-charts/horizon/templates/NOTES.txt @@ -0,0 +1,18 @@ +{{ .Chart.Name }} has been deployed. + +Namespace: {{ .Values.namespace }} +Application: {{ .Values.applicationName }} + +{{- if .Values.service.enabled }} +Service: {{ .Values.namespace }}:{{ (index .Values.service.ports 0).port }} +{{- end }} + +{{- if .Values.ingress.enabled }} +Ingress is enabled. +{{- end }} + +{{- if .Values.autoscaling.enabled }} +Autoscaling: {{ .Values.autoscaling.minReplicas }} - {{ .Values.autoscaling.maxReplicas }} replicas +{{- else }} +Replicas: {{ .Values.replicaCount }} +{{- end }} diff --git a/helm-charts/horizon/templates/_helpers.tpl b/helm-charts/horizon/templates/_helpers.tpl new file mode 100644 index 00000000..168e0342 --- /dev/null +++ b/helm-charts/horizon/templates/_helpers.tpl @@ -0,0 +1,70 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "fullname" -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "labels.selector" -}} +app: {{ .Values.namespace }} +{{- end -}} + +{{- define "labels.primary-selector" -}} +app: {{ .Values.namespace }}-primary +{{- end -}} + +{{- define "labels.common" -}} +{{ template "labels.selector" . }} +{{- if and .Values.deployment .Values.deployment.image }} +version: {{ .Values.deployment.image.tag }} +{{- end }} +env: {{ .Values.labels.env }} +team: {{ .Values.labels.team }} +bu: {{ .Values.labels.bu }} +service: {{ .Values.applicationName }} +priority: {{ .Values.labels.priority }} +priority_v2: {{ .Values.labels.priority_v2 | default "cp3" }} +primary_owner: {{ .Values.labels.primary_owner | default .Values.labels.team }} +secondary_owner: {{ .Values.labels.secondary_owner | default .Values.labels.team }} +service_type: {{ .Values.labels.service_type | default "" | replace "," "-" | quote }} +{{- end -}} + +{{- define "labels.chart" -}} +chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" +release: {{ .Release.Name | quote }} +heritage: {{ .Release.Service | quote }} +{{- end -}} + +{{/* +Renders a value that contains template. +Usage: +{{ include "application.tplvalues.render" ( dict "value" .Values.path.to.the.Value "context" $) }} +*/}} + +{{- define "application.tplvalues.render" -}} + {{- if typeIs "string" .value }} + {{- tpl .value .context }} + {{- else }} + {{- tpl (.value | toYaml) .context }} + {{- end }} +{{- end -}} + +{{- define "canary.promURL" -}} +{{- if .Values.canary.promURL }} +{{- .Values.canary.promURL }} +{{- else if eq .Values.labels.env "prod" }} +prod-ops-metricsui.example.com/select/100/ +{{- else }} +https://sb-ops-metricsui.example.com/select/100/ +{{- end }} +{{- end -}} diff --git a/helm-charts/horizon/templates/alert-provider.yaml b/helm-charts/horizon/templates/alert-provider.yaml new file mode 100644 index 00000000..a300bb14 --- /dev/null +++ b/helm-charts/horizon/templates/alert-provider.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.canary (.Values.canary.enabled) .Values.canary.slackWebhookURL (ne .Values.canary.slackWebhookURL "") }} +apiVersion: flagger.app/v1beta1 +kind: AlertProvider +metadata: + name: flagger-status + namespace: {{ .Values.namespace }} +spec: + type: slack + {{- if .Values.canary.slackChannel }} + channel: {{ .Values.canary.slackChannel }} + {{- end }} + username: flagger + address: {{ .Values.canary.slackWebhookURL }} +{{- end }} diff --git a/helm-charts/horizon/templates/configmap.yaml b/helm-charts/horizon/templates/configmap.yaml new file mode 100644 index 00000000..fc527219 --- /dev/null +++ b/helm-charts/horizon/templates/configmap.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.configMap .Values.configMap.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Values.namespace }}-config + namespace: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +data: + {{- range $key, $value := .Values.configMap.data }} + {{ $key }}: {{ $value | quote }} + {{- end }} +{{- end }} diff --git a/helm-charts/horizon/templates/deployment.yaml b/helm-charts/horizon/templates/deployment.yaml new file mode 100644 index 00000000..34cbb1ed --- /dev/null +++ b/helm-charts/horizon/templates/deployment.yaml @@ -0,0 +1,237 @@ +{{- if and .Values.deployment .Values.deployment.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.namespace }} + namespace: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +{{- include "labels.selector" . | nindent 4 }} +spec: + {{- with .Values.deployment.minReadySeconds }} + minReadySeconds: {{ . }} + {{- end }} + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + revisionHistoryLimit: {{ .Values.deployment.revisionHistoryLimit }} + selector: + matchLabels: +{{- include "labels.selector" . | nindent 6 }} +{{- with .Values.deployment.updateStrategy }} +{{ toYaml . | indent 2 -}} +{{- end }} + template: + metadata: + annotations: + {{- with .Values.deployment.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.telegraf.enabled }} + telegraf.influxdata.com/class: "infra" + {{- end }} + labels: + {{- include "labels.common" . | nindent 8 }} + spec: + {{- if .Values.priorityClassName }} + priorityClassName: {{ .Values.priorityClassName }} + {{- end }} + topologySpreadConstraints: + - maxSkew: 1 + topologyKey: topology.kubernetes.io/zone + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: +{{- include "labels.selector" . | nindent 12 }} + {{- if .Values.deployment.image.pullSecret }} + imagePullSecrets: + - name: {{ .Values.deployment.image.pullSecret }} + {{- end }} + {{- if .Values.deployment.volumes }} + volumes: + {{- toYaml .Values.deployment.volumes | nindent 8 }} + {{- end }} + {{- if .Values.deployment.initContainers }} + initContainers: + {{- toYaml .Values.deployment.initContainers | nindent 8 }} + {{- end }} + containers: + - name: {{ .Values.applicationName }} + {{- if .Values.deployment.volumeMounts }} + volumeMounts: + {{- toYaml .Values.deployment.volumeMounts | nindent 12 }} + {{- end }} + {{- if .Values.deployment.command }} + command: {{- include "application.tplvalues.render" (dict "value" .Values.deployment.command "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.deployment.args }} + args: {{- include "application.tplvalues.render" (dict "value" .Values.deployment.args "context" $) | nindent 12 }} + {{- end }} + image: "{{ .Values.deployment.image.repository }}:{{ .Values.deployment.image.tag }}" + imagePullPolicy: {{ .Values.deployment.image.pullPolicy }} + {{- if .Values.deployment.lifecycle }} + lifecycle: {{ toYaml .Values.deployment.lifecycle | nindent 12 }} + {{- end }} + {{- with .Values.deployment.probes }} + {{- with .liveness }} + livenessProbe: + {{- with .failureThreshold }} + failureThreshold: {{ . }} + {{- end }} + httpGet: + path: {{ .path }} + port: {{ .port }} + scheme: {{ .scheme }} + {{- with .periodSeconds }} + periodSeconds: {{ . }} + {{- end }} + {{- with .successThreshold }} + successThreshold: {{ . }} + {{- end }} + {{- with .timeoutSeconds }} + timeoutSeconds: {{ . }} + {{- end }} + initialDelaySeconds: {{ .initialDelaySeconds }} + {{- end }} + {{- end }} + {{- if or .Values.externalSecret.enabled .Values.otel_enabled (and .Values.configMap .Values.configMap.enabled) }} + envFrom: + {{- if .Values.externalSecret.enabled }} + - secretRef: + name: {{ .Values.deployment.envFrom.secretRef }} + {{- if ( default false (.Values.disasterRecovery).enabled ) }} + - secretRef: + name: {{ .Values.deployment.envFrom.secretRef }}-dr + {{- end }} + {{- end }} + {{- if .Values.otel_enabled }} + - secretRef: + name: {{ .Values.deployment.envFrom.secretRef }}-otel + {{- end }} + {{- if and .Values.configMap .Values.configMap.enabled }} + - configMapRef: + name: {{ .Values.namespace }}-config + {{- end }} + {{- end }} + env: + - name: TZ + value: Asia/Kolkata + - name: NODE_IP + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + {{- if .Values.telegraf.enabled }} + - name: TELEGRAF_UDP_HOST + valueFrom: + fieldRef: + fieldPath: status.podIP + {{- end }} + {{- if .Values.otel_enabled }} + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: http://$(NODE_IP):4317 + {{- end }} + {{- with .Values.deployment.env }} + {{- range . }} + - name: {{ .name }} + value: "{{ .value }}" + {{- end }} + {{- end }} + {{- with .Values.deployment.ports }} + ports: + {{- range . }} + - containerPort: {{ .containerPort }} + name: {{ .name }} + protocol: {{ .protocol }} + {{- end }} + {{- end }} + {{- if .Values.telegraf.enabled }} + - containerPort: 9273 + name: telegraf-sc + protocol: TCP + {{- end }} + {{- with .Values.deployment.probes }} + {{- with .readiness }} + readinessProbe: + {{- with .failureThreshold }} + failureThreshold: {{ . }} + {{- end }} + httpGet: + path: {{ .path }} + port: {{ .port }} + scheme: {{ .scheme }} + {{- with .periodSeconds }} + periodSeconds: {{ . }} + {{- end }} + {{- with .successThreshold }} + successThreshold: {{ . }} + {{- end }} + {{- with .timeoutSeconds}} + timeoutSeconds: {{ . }} + {{- end }} + initialDelaySeconds: {{ .initialDelaySeconds }} + {{- end }} + {{- end }} + {{- with .Values.deployment.resources }} + resources: + {{- with .limits }} + limits: + {{- with .memory }} + memory: "{{ . }}" + {{- end }} + {{- with .cpu }} + cpu: "{{ . }}" + {{- end }} + {{- end }} + {{- with .requests }} + requests: + {{- with .memory }} + memory: "{{ . }}" + {{- end }} + {{- with .cpu }} + cpu: "{{ . }}" + {{- end }} + {{- end }} + {{- end }} + + {{- with .Values.deployment.hostAliases }} + hostAliases: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.deployment.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.deployment.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + terminationGracePeriodSeconds: {{ .Values.deployment.terminationGracePeriodSeconds | default "300" }} + {{- if .Values.deployment.serviceAccount.enabled }} + serviceAccountName: {{ .Values.namespace }} + {{- end }} + {{- if .Values.securityContext }} + podSecurityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + {{- end }} +{{- end }} diff --git a/helm-charts/horizon/templates/external-secrets.yaml b/helm-charts/horizon/templates/external-secrets.yaml new file mode 100644 index 00000000..0d602056 --- /dev/null +++ b/helm-charts/horizon/templates/external-secrets.yaml @@ -0,0 +1,30 @@ +{{- if and .Values.externalSecret .Values.externalSecret.enabled }} +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} +{{- if .Values.externalSecret.annotations }} + annotations: +{{ toYaml .Values.externalSecret.annotations | indent 4 }} +{{- end }} + {{- if and .Values.deployment .Values.deployment.enabled }} + name: {{ .Values.deployment.envFrom.secretRef }} + {{- end}} + namespace: {{ .Values.namespace }} +spec: + dataFrom: + - extract: + conversionStrategy: Default + key: {{ .Values.externalSecret.path }} + refreshInterval: 15s + secretStoreRef: + kind: {{ .Values.infrastructure.secretStore.kind | default "ClusterSecretStore" }} + name: {{ .Values.infrastructure.secretStore.name | default "vault-backend" }} + target: + creationPolicy: Owner + deletionPolicy: Retain + {{- if and .Values.deployment .Values.deployment.enabled }} + name: {{ .Values.deployment.envFrom.secretRef }} + {{- end}} +{{- end }} diff --git a/helm-charts/horizon/templates/httpproxy.yaml b/helm-charts/horizon/templates/httpproxy.yaml new file mode 100644 index 00000000..94fe8f3e --- /dev/null +++ b/helm-charts/horizon/templates/httpproxy.yaml @@ -0,0 +1,50 @@ +{{- if and .Values.ingress .Values.ingress.enabled -}} +{{- if .Values.createContourGateway -}} +{{- if or ( eq "contour-internal" .Values.ingress.ingressClassName ) ( eq "contour-external" .Values.ingress.ingressClassName ) ( eq "contour-internal-0" .Values.ingress.ingressClassName ) ( eq "contour-internal-1" .Values.ingress.ingressClassName ) ( eq "contour-external-0" .Values.ingress.ingressClassName ) ( eq "contour-external-1" .Values.ingress.ingressClassName ) }} +{{- $servicePortNumber := .Values.ingress.servicePortNumber -}} +{{- $pathType := .Values.ingress.pathType -}} +{{- $namespace := .Values.namespace -}} +{{- $ingressClassName := .Values.ingress.ingressClassName -}} +{{ $count := 0 | int }} +{{- range .Values.ingress.hosts }} +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + namespace: {{ $namespace }} + name: {{ $namespace }}-{{ $count }} + labels: +{{ include "labels.common" $ | indent 4 }} +{{ include "labels.chart" $ | indent 4 }} + annotations: + projectcontour.io/ingress.class: {{ $.Values.ingress.ingressClassName }} +spec: + ingressClassName: {{ $ingressClassName }} + virtualhost: + fqdn: "{{ .host }}" + includes: + {{- range .paths }} + - conditions: + {{- if or ( eq ( lower .pathType ) "prefix" ) ( eq ( lower .pathType ) "implementationspecific") }} + - prefix: {{ .path }} + {{- end }} + {{- if ( eq ( lower .pathType ) "exact" ) }} + - header: + name: :path + exact: {{ .path }} + - prefix: {{ .path }} + {{- end }} + {{- if .targetService }} + name: {{ .targetService | replace "/" "-" }} + namespace: {{ (split "/" .targetService)._0 }} + {{- else }} + name: {{ $namespace }} + namespace: {{ $namespace }} + {{- end }} + {{- end }} + {{ $count = add1 $count }} +--- + +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/helm-charts/horizon/templates/otel-secret.yaml b/helm-charts/horizon/templates/otel-secret.yaml new file mode 100644 index 00000000..c2514b7d --- /dev/null +++ b/helm-charts/horizon/templates/otel-secret.yaml @@ -0,0 +1,26 @@ +{{ if .Values.otel_enabled }} +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} + annotations: + flagger.app/config-tracking: disabled + name: {{ if and .Values.deployment .Values.deployment.envFrom }}{{ .Values.deployment.envFrom.secretRef }}{{ else }}{{ .Values.namespace }}{{ end }}-otel + namespace: {{ .Values.namespace }} +spec: + dataFrom: + - extract: + conversionStrategy: Default + key: {{ .Values.infrastructure.vault.otelTokenPath | default "org/prd/cntr/devop/coralogix-token" }} + refreshInterval: 15s + secretStoreRef: + kind: {{ .Values.infrastructure.secretStore.kind | default "ClusterSecretStore" }} + name: {{ .Values.infrastructure.secretStore.name | default "vault-backend" }} + target: + creationPolicy: Owner + deletionPolicy: Retain + {{- if and .Values.deployment .Values.deployment.enabled }} + name: {{ .Values.deployment.envFrom.secretRef }}-otel + {{- end}} +{{ end }} diff --git a/helm-charts/horizon/templates/pdb.yaml b/helm-charts/horizon/templates/pdb.yaml new file mode 100644 index 00000000..db87f89a --- /dev/null +++ b/helm-charts/horizon/templates/pdb.yaml @@ -0,0 +1,36 @@ +{{- if or (and .Values.podDisruptionBudget .Values.podDisruptionBudget.enabled) (and .Values.deployment .Values.deployment.enabled) }} +{{- if or ( and .Values.deployment (.Values.deployment.enabled) (.Values.autoscaling.enabled) ( gt (int .Values.autoscaling.minReplicas) 1)) ( and (eq .Values.autoscaling.enabled false) .Values.deployment ( gt ( int .Values.deployment.replicaCount) 1 )) }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +spec: + {{- if .Values.podDisruptionBudget.enabled }} + maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }} + {{- else }} + {{- if and .Values.deployment .Values.deployment.enabled }} + {{- if (eq .Values.deployment.updateStrategy.strategy.type "RollingUpdate") }} + maxUnavailable: {{ .Values.deployment.updateStrategy.strategy.rollingUpdate.maxSurge | default "10%" }} + {{- else }} + maxUnavailable: "10%" + {{- end }} + {{ else }} + maxUnavailable: "10%" + {{- end }} + {{- end }} + {{- if .Values.podDisruptionBudget.minAvailable }} + minAvailable: {{ .Values.podDisruptionBudget.minAvailable }} + {{- end }} + selector: + matchLabels: + {{- if and (.Values.canary.enabled) (eq .Values.labels.env "prod") }} + {{- include "labels.primary-selector" . | nindent 6 }} + {{- else }} + {{- include "labels.selector" . | nindent 6 }} + {{- end }} +{{- end }} +{{- end }} diff --git a/helm-charts/horizon/templates/scaledobject.yaml b/helm-charts/horizon/templates/scaledobject.yaml new file mode 100644 index 00000000..259bfea2 --- /dev/null +++ b/helm-charts/horizon/templates/scaledobject.yaml @@ -0,0 +1,56 @@ +{{- if and .Values.autoscaling .Values.autoscaling.enabled }} +apiVersion: keda.sh/v1alpha1 +kind: ScaledObject +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ .Values.namespace }} + pollingInterval: {{ .Values.autoscaling.pollingInterval }} + {{- if ( default false (.Values.disasterRecovery).enabled ) }} + minReplicaCount: 1 + {{- else }} + minReplicaCount: {{ .Values.canary.minCanaryReplicas | default $.Values.autoscaling.minReplicas }} + {{- end }} + maxReplicaCount: {{ .Values.canary.maxCanaryReplicas | default $.Values.autoscaling.maxReplicas }} + advanced: + horizontalPodAutoscalerConfig: + behavior: + scaleDown: + stabilizationWindowSeconds: {{ .Values.autoscaling.scaledown.stabilizationWindowSeconds }} + policies: + {{- range .Values.autoscaling.scaledown.policies }} + - type: {{ .type }} + value: {{ .value }} + periodSeconds: {{ .periodseconds }} + {{- end }} + selectPolicy: {{ .Values.autoscaling.scaledown.selectpolicy }} + scaleUp: + stabilizationWindowSeconds: {{ .Values.autoscaling.scaleup.stabilizationWindowSeconds }} + policies: + {{- range .Values.autoscaling.scaleup.policies }} + - type: {{ .type }} + value: {{ .value }} + periodSeconds: {{ .periodseconds }} + {{- end }} + selectPolicy: {{ .Values.autoscaling.scaleup.selectpolicy }} + triggers: + {{- if ( default false (.Values.disasterRecovery).enabled ) }} + {{- range $.Values.autoscaling.triggers }} + {{- if or (eq .type "cpu") (eq .type "memory") }} + - metadata: + {{- toYaml .metadata | nindent 8 }} + type: {{ .type }} + metricType: "Utilization" + {{- end }} + {{- end }} + {{- else }} + {{- toYaml .Values.autoscaling.triggers | nindent 2 }} + {{ end }} + +{{- end }} diff --git a/helm-charts/horizon/templates/service.yaml b/helm-charts/horizon/templates/service.yaml new file mode 100644 index 00000000..8fcc5bc8 --- /dev/null +++ b/helm-charts/horizon/templates/service.yaml @@ -0,0 +1,29 @@ +{{- if and .Values.service .Values.service.enabled }} +{{- if or (eq .Values.canary.enabled false) ( and (.Values.canary.enabled) (ne .Values.labels.env "prod")) }} +apiVersion: v1 +kind: Service +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} +{{- if .Values.service.annotations }} + annotations: +{{ toYaml .Values.service.annotations | indent 4 }} +{{- end }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + {{- range .Values.service.ports }} + - name: {{ .name }} + port: {{ .port }} + protocol: {{ .protocol }} + targetPort: {{ .targetPort }} + {{- end }} + selector: + {{- include "labels.selector" . | nindent 4 }} +{{- end }} +{{- end }} diff --git a/helm-charts/horizon/templates/serviceaccount.yaml b/helm-charts/horizon/templates/serviceaccount.yaml new file mode 100644 index 00000000..f05362f5 --- /dev/null +++ b/helm-charts/horizon/templates/serviceaccount.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.deployment (.Values.deployment.enabled) (.Values.deployment.serviceAccount.enabled) }} +apiVersion: v1 +kind: ServiceAccount +metadata: + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} + {{- with .Values.deployment.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/helm-charts/horizon/values.yaml b/helm-charts/horizon/values.yaml new file mode 100644 index 00000000..29b5ee61 --- /dev/null +++ b/helm-charts/horizon/values.yaml @@ -0,0 +1,324 @@ +# Default values for horizon helm chart + +namespace: prd-horizon +applicationName: horizon +replicaCount: 2 + +labels: + env: prd + team: bharatml + bu: ml + priority: p1 + priority_v2: cp3 + service_type: "" + +priorityClassName: "" + +telegraf: + enabled: false + +otel_enabled: false + +infrastructure: + secretStore: + name: vault-backend + kind: ClusterSecretStore + vault: + basePath: "" + otelTokenPath: "" + +deployment: + enabled: true + replicaCount: 2 + revisionHistoryLimit: 3 + image: + repository: ghcr.io/meesho/horizon + tag: latest + pullPolicy: IfNotPresent + ports: + - containerPort: 8082 + name: http + protocol: TCP + probes: + liveness: + path: /health + port: 8082 + scheme: HTTP + initialDelaySeconds: 30 + periodSeconds: 10 + failureThreshold: 3 + successThreshold: 1 + timeoutSeconds: 5 + readiness: + path: /health + port: 8082 + scheme: HTTP + initialDelaySeconds: 20 + periodSeconds: 10 + failureThreshold: 3 + successThreshold: 1 + timeoutSeconds: 5 + resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "1Gi" + cpu: "1000m" + env: + # Application + - name: APP_NAME + value: "horizon" + - name: APP_ENV + value: "PROD" + - name: APP_PORT + value: "8082" + - name: APP_LOG_LEVEL + value: "DEBUG" + - name: APP_METRIC_SAMPLING_RATE + value: "1" + - name: APP_GC_PERCENTAGE + value: "1" + # MySQL master + - name: MYSQL_MASTER_MAX_POOL_SIZE + value: "5" + - name: MYSQL_MASTER_MIN_POOL_SIZE + value: "2" + - name: MYSQL_MASTER_PASSWORD + value: "root" + - name: MYSQL_MASTER_HOST + value: "mysql" + - name: MYSQL_MASTER_PORT + value: "3306" + - name: MYSQL_DB_NAME + value: "testdb" + - name: MYSQL_MASTER_USERNAME + value: "root" + # MySQL slave + - name: MYSQL_SLAVE_MAX_POOL_SIZE + value: "5" + - name: MYSQL_SLAVE_MIN_POOL_SIZE + value: "2" + - name: MYSQL_SLAVE_PASSWORD + value: "root" + - name: MYSQL_SLAVE_HOST + value: "mysql" + - name: MYSQL_SLAVE_PORT + value: "3306" + - name: MYSQL_SLAVE_USERNAME + value: "root" + - name: MYSQL_ACTIVE_CONFIG_IDS + value: "2" + # Etcd + - name: ETCD_WATCHER_ENABLED + value: "true" + - name: ETCD_SERVER + value: "etcd:2379" + # ONFS + - name: ONLINE_FEATURE_STORE_APP_NAME + value: "onfs" + # Redis + - name: REDIS_FAILOVER_ACTIVE_CONFIG_IDS + value: "4" + # ScyllaDB (primary) + - name: SCYLLA_1_CONTACT_POINTS + value: "scylla" + - name: SCYLLA_1_KEYSPACE + value: "onfs" + - name: SCYLLA_1_NUM_CONNS + value: "1" + - name: SCYLLA_1_PORT + value: "9042" + - name: SCYLLA_1_TIMEOUT_IN_MS + value: "300000" + - name: SCYLLA_1_PASSWORD + value: "" + - name: SCYLLA_1_USERNAME + value: "" + - name: SCYLLA_ACTIVE_CONFIG_IDS + value: "1" + # Caching + - name: DISTRIBUTED_CACHE_ACTIVE_CONFIG_IDS + value: "2" + - name: IN_MEMORY_CACHE_ACTIVE_CONFIG_IDS + value: "3" + # CORS + - name: CORS_ORIGINS + value: "http://localhost:3000,http://localhost:8080" + # Service names + - name: HORIZON_APP_NAME + value: "horizon" + - name: NUMERIX_APP_NAME + value: "numerix" + - name: INFERFLOW_APP_NAME + value: "inferflow" + - name: IS_DUMMY_MODEL_ENABLED + value: "true" + # ArgoCD + - name: ARGOCD_API + value: "http://host.docker.internal:8087" + - name: ARGOCD_TOKEN + value: "" + - name: ARGOCD_NAMESPACE + value: "argocd" + - name: ARGOCD_DESTINATION_NAME + value: "in-cluster" + - name: ARGOCD_PROJECT + value: "default" + - name: ARGOCD_HELMCHART_PATH + value: "1.0.0" + - name: ARGOCD_SYNC_POLICY_OPTIONS + value: "CreateNamespace=true" + - name: ARGOCD_INSECURE + value: "true" + # Model path + - name: LOCAL_MODEL_PATH + value: "/tmp/models" + # Environments + - name: SUPPORTED_ENVIRONMENTS + value: "prd,stg,int" + - name: WORKING_ENV + value: "prd" + # Service config + - name: SERVICE_CONFIG_SOURCE + value: "local" + - name: SERVICE_CONFIG_REPO + value: "BharatMLStack-configs" + - name: SERVICE_CONFIG_PATH + value: "/app/configs" + # GitHub + - name: REPOSITORY_NAME + value: "" + - name: BRANCH_NAME + value: "main" + - name: GITHUB_APP_ID + value: "" + - name: GITHUB_INSTALLATION_ID + value: "" + - name: GITHUB_PRIVATE_KEY + value: "/app/configs/github.pem" + - name: GITHUB_OWNER + value: "" + - name: GITHUB_COMMIT_AUTHOR + value: "horizon-bot" + - name: GITHUB_COMMIT_EMAIL + value: "devops@example.com" + # GCP + - name: GCP_PROJECT_ID + value: "" + - name: GCS_ENABLED + value: "true" + - name: GCS_MODEL_BUCKET + value: "" + - name: GCS_MODEL_BASE_PATH + value: "" + - name: CLOUDSDK_CONFIG + value: "/home/nonroot/.config/gcloud" + # Skye + - name: SKYE_APP_NAME + value: "skye" + - name: SKYE_SCYLLA_ACTIVE_CONFIG_IDS + value: "2" + - name: SKYE_HOST + value: "scylla" + - name: SKYE_PORT + value: "9042" + - name: SKYE_AUTH_TOKEN + value: "" + - name: SKYE_DEADLINE_EXCEED_MS + value: "2000" + # ScyllaDB (Skye) + - name: SCYLLA_2_CONTACT_POINTS + value: "scylla" + - name: SCYLLA_2_PORT + value: "9042" + - name: SCYLLA_2_KEYSPACE + value: "skye" + - name: SCYLLA_2_USERNAME + value: "" + - name: SCYLLA_2_PASSWORD + value: "" + - name: HORIZON_TO_SKYE_SCYLLA_CONF_ID_MAP + value: "2:1" + # Skye trigger (OSS replacement for Airflow) + - name: USE_SKYE_TRIGGER_INSTEAD_OF_AIRFLOW + value: "true" + - name: SKYE_TRIGGER_URL + value: "http://skye-trigger:8080" + volumes: + - name: configs + configMap: + name: horizon-configs + volumeMounts: + - name: configs + mountPath: /app/configs + readOnly: true + serviceAccount: + enabled: false + annotations: {} + updateStrategy: + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 0 + maxSurge: 1 + terminationGracePeriodSeconds: 30 + +service: + enabled: true + type: ClusterIP + ports: + - name: http + port: 80 + targetPort: 8082 + protocol: TCP + +autoscaling: + enabled: false + minReplicas: 2 + maxReplicas: 10 + pollingInterval: 30 + scaledown: + stabilizationWindowSeconds: 300 + policies: + - type: Percent + value: 10 + periodseconds: 60 + selectpolicy: Min + scaleup: + stabilizationWindowSeconds: 0 + policies: + - type: Percent + value: 50 + periodseconds: 60 + selectpolicy: Max + triggers: + - type: cpu + metadata: + value: "70" + metricType: Utilization + +ingress: + enabled: false + ingressClassName: contour-internal +createContourGateway: false + +externalSecret: + enabled: false + path: "" + +configMap: + enabled: false + +canary: + enabled: false + promURL: "" + slackChannel: "" + slackWebhookURL: "" + +podDisruptionBudget: + enabled: false + maxUnavailable: "10%" + +disasterRecovery: + enabled: false diff --git a/helm-charts/inferflow/Chart.yaml b/helm-charts/inferflow/Chart.yaml new file mode 100644 index 00000000..58140987 --- /dev/null +++ b/helm-charts/inferflow/Chart.yaml @@ -0,0 +1,10 @@ +apiVersion: v2 +name: inferflow +description: A Helm chart for the Inferflow inference orchestration service +type: application +version: 1.0.0 +appVersion: "1.0.0" + +maintainers: + - name: BharatMLStack Team + email: ml-oss@meesho.com diff --git a/helm-charts/inferflow/templates/NOTES.txt b/helm-charts/inferflow/templates/NOTES.txt new file mode 100644 index 00000000..35a4f3c2 --- /dev/null +++ b/helm-charts/inferflow/templates/NOTES.txt @@ -0,0 +1,18 @@ +{{ .Chart.Name }} has been deployed. + +Namespace: {{ .Values.namespace }} +Application: {{ .Values.applicationName }} + +{{- if .Values.service.enabled }} +Service: {{ .Values.namespace }}:{{ (index .Values.service.ports 0).port }} +{{- end }} + +{{- if .Values.ingress.enabled }} +Ingress is enabled. +{{- end }} + +{{- if .Values.autoscaling.enabled }} +Autoscaling: {{ .Values.autoscaling.minReplicas }} - {{ .Values.autoscaling.maxReplicas }} replicas +{{- else }} +Replicas: {{ .Values.replicaCount }} +{{- end }} diff --git a/helm-charts/inferflow/templates/_helpers.tpl b/helm-charts/inferflow/templates/_helpers.tpl new file mode 100644 index 00000000..168e0342 --- /dev/null +++ b/helm-charts/inferflow/templates/_helpers.tpl @@ -0,0 +1,70 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "fullname" -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "labels.selector" -}} +app: {{ .Values.namespace }} +{{- end -}} + +{{- define "labels.primary-selector" -}} +app: {{ .Values.namespace }}-primary +{{- end -}} + +{{- define "labels.common" -}} +{{ template "labels.selector" . }} +{{- if and .Values.deployment .Values.deployment.image }} +version: {{ .Values.deployment.image.tag }} +{{- end }} +env: {{ .Values.labels.env }} +team: {{ .Values.labels.team }} +bu: {{ .Values.labels.bu }} +service: {{ .Values.applicationName }} +priority: {{ .Values.labels.priority }} +priority_v2: {{ .Values.labels.priority_v2 | default "cp3" }} +primary_owner: {{ .Values.labels.primary_owner | default .Values.labels.team }} +secondary_owner: {{ .Values.labels.secondary_owner | default .Values.labels.team }} +service_type: {{ .Values.labels.service_type | default "" | replace "," "-" | quote }} +{{- end -}} + +{{- define "labels.chart" -}} +chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" +release: {{ .Release.Name | quote }} +heritage: {{ .Release.Service | quote }} +{{- end -}} + +{{/* +Renders a value that contains template. +Usage: +{{ include "application.tplvalues.render" ( dict "value" .Values.path.to.the.Value "context" $) }} +*/}} + +{{- define "application.tplvalues.render" -}} + {{- if typeIs "string" .value }} + {{- tpl .value .context }} + {{- else }} + {{- tpl (.value | toYaml) .context }} + {{- end }} +{{- end -}} + +{{- define "canary.promURL" -}} +{{- if .Values.canary.promURL }} +{{- .Values.canary.promURL }} +{{- else if eq .Values.labels.env "prod" }} +prod-ops-metricsui.example.com/select/100/ +{{- else }} +https://sb-ops-metricsui.example.com/select/100/ +{{- end }} +{{- end -}} diff --git a/helm-charts/inferflow/templates/alert-provider.yaml b/helm-charts/inferflow/templates/alert-provider.yaml new file mode 100644 index 00000000..a300bb14 --- /dev/null +++ b/helm-charts/inferflow/templates/alert-provider.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.canary (.Values.canary.enabled) .Values.canary.slackWebhookURL (ne .Values.canary.slackWebhookURL "") }} +apiVersion: flagger.app/v1beta1 +kind: AlertProvider +metadata: + name: flagger-status + namespace: {{ .Values.namespace }} +spec: + type: slack + {{- if .Values.canary.slackChannel }} + channel: {{ .Values.canary.slackChannel }} + {{- end }} + username: flagger + address: {{ .Values.canary.slackWebhookURL }} +{{- end }} diff --git a/helm-charts/inferflow/templates/configmap.yaml b/helm-charts/inferflow/templates/configmap.yaml new file mode 100644 index 00000000..fc527219 --- /dev/null +++ b/helm-charts/inferflow/templates/configmap.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.configMap .Values.configMap.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Values.namespace }}-config + namespace: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +data: + {{- range $key, $value := .Values.configMap.data }} + {{ $key }}: {{ $value | quote }} + {{- end }} +{{- end }} diff --git a/helm-charts/inferflow/templates/deployment.yaml b/helm-charts/inferflow/templates/deployment.yaml new file mode 100644 index 00000000..34cbb1ed --- /dev/null +++ b/helm-charts/inferflow/templates/deployment.yaml @@ -0,0 +1,237 @@ +{{- if and .Values.deployment .Values.deployment.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.namespace }} + namespace: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +{{- include "labels.selector" . | nindent 4 }} +spec: + {{- with .Values.deployment.minReadySeconds }} + minReadySeconds: {{ . }} + {{- end }} + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + revisionHistoryLimit: {{ .Values.deployment.revisionHistoryLimit }} + selector: + matchLabels: +{{- include "labels.selector" . | nindent 6 }} +{{- with .Values.deployment.updateStrategy }} +{{ toYaml . | indent 2 -}} +{{- end }} + template: + metadata: + annotations: + {{- with .Values.deployment.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.telegraf.enabled }} + telegraf.influxdata.com/class: "infra" + {{- end }} + labels: + {{- include "labels.common" . | nindent 8 }} + spec: + {{- if .Values.priorityClassName }} + priorityClassName: {{ .Values.priorityClassName }} + {{- end }} + topologySpreadConstraints: + - maxSkew: 1 + topologyKey: topology.kubernetes.io/zone + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: +{{- include "labels.selector" . | nindent 12 }} + {{- if .Values.deployment.image.pullSecret }} + imagePullSecrets: + - name: {{ .Values.deployment.image.pullSecret }} + {{- end }} + {{- if .Values.deployment.volumes }} + volumes: + {{- toYaml .Values.deployment.volumes | nindent 8 }} + {{- end }} + {{- if .Values.deployment.initContainers }} + initContainers: + {{- toYaml .Values.deployment.initContainers | nindent 8 }} + {{- end }} + containers: + - name: {{ .Values.applicationName }} + {{- if .Values.deployment.volumeMounts }} + volumeMounts: + {{- toYaml .Values.deployment.volumeMounts | nindent 12 }} + {{- end }} + {{- if .Values.deployment.command }} + command: {{- include "application.tplvalues.render" (dict "value" .Values.deployment.command "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.deployment.args }} + args: {{- include "application.tplvalues.render" (dict "value" .Values.deployment.args "context" $) | nindent 12 }} + {{- end }} + image: "{{ .Values.deployment.image.repository }}:{{ .Values.deployment.image.tag }}" + imagePullPolicy: {{ .Values.deployment.image.pullPolicy }} + {{- if .Values.deployment.lifecycle }} + lifecycle: {{ toYaml .Values.deployment.lifecycle | nindent 12 }} + {{- end }} + {{- with .Values.deployment.probes }} + {{- with .liveness }} + livenessProbe: + {{- with .failureThreshold }} + failureThreshold: {{ . }} + {{- end }} + httpGet: + path: {{ .path }} + port: {{ .port }} + scheme: {{ .scheme }} + {{- with .periodSeconds }} + periodSeconds: {{ . }} + {{- end }} + {{- with .successThreshold }} + successThreshold: {{ . }} + {{- end }} + {{- with .timeoutSeconds }} + timeoutSeconds: {{ . }} + {{- end }} + initialDelaySeconds: {{ .initialDelaySeconds }} + {{- end }} + {{- end }} + {{- if or .Values.externalSecret.enabled .Values.otel_enabled (and .Values.configMap .Values.configMap.enabled) }} + envFrom: + {{- if .Values.externalSecret.enabled }} + - secretRef: + name: {{ .Values.deployment.envFrom.secretRef }} + {{- if ( default false (.Values.disasterRecovery).enabled ) }} + - secretRef: + name: {{ .Values.deployment.envFrom.secretRef }}-dr + {{- end }} + {{- end }} + {{- if .Values.otel_enabled }} + - secretRef: + name: {{ .Values.deployment.envFrom.secretRef }}-otel + {{- end }} + {{- if and .Values.configMap .Values.configMap.enabled }} + - configMapRef: + name: {{ .Values.namespace }}-config + {{- end }} + {{- end }} + env: + - name: TZ + value: Asia/Kolkata + - name: NODE_IP + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + {{- if .Values.telegraf.enabled }} + - name: TELEGRAF_UDP_HOST + valueFrom: + fieldRef: + fieldPath: status.podIP + {{- end }} + {{- if .Values.otel_enabled }} + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: http://$(NODE_IP):4317 + {{- end }} + {{- with .Values.deployment.env }} + {{- range . }} + - name: {{ .name }} + value: "{{ .value }}" + {{- end }} + {{- end }} + {{- with .Values.deployment.ports }} + ports: + {{- range . }} + - containerPort: {{ .containerPort }} + name: {{ .name }} + protocol: {{ .protocol }} + {{- end }} + {{- end }} + {{- if .Values.telegraf.enabled }} + - containerPort: 9273 + name: telegraf-sc + protocol: TCP + {{- end }} + {{- with .Values.deployment.probes }} + {{- with .readiness }} + readinessProbe: + {{- with .failureThreshold }} + failureThreshold: {{ . }} + {{- end }} + httpGet: + path: {{ .path }} + port: {{ .port }} + scheme: {{ .scheme }} + {{- with .periodSeconds }} + periodSeconds: {{ . }} + {{- end }} + {{- with .successThreshold }} + successThreshold: {{ . }} + {{- end }} + {{- with .timeoutSeconds}} + timeoutSeconds: {{ . }} + {{- end }} + initialDelaySeconds: {{ .initialDelaySeconds }} + {{- end }} + {{- end }} + {{- with .Values.deployment.resources }} + resources: + {{- with .limits }} + limits: + {{- with .memory }} + memory: "{{ . }}" + {{- end }} + {{- with .cpu }} + cpu: "{{ . }}" + {{- end }} + {{- end }} + {{- with .requests }} + requests: + {{- with .memory }} + memory: "{{ . }}" + {{- end }} + {{- with .cpu }} + cpu: "{{ . }}" + {{- end }} + {{- end }} + {{- end }} + + {{- with .Values.deployment.hostAliases }} + hostAliases: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.deployment.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.deployment.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + terminationGracePeriodSeconds: {{ .Values.deployment.terminationGracePeriodSeconds | default "300" }} + {{- if .Values.deployment.serviceAccount.enabled }} + serviceAccountName: {{ .Values.namespace }} + {{- end }} + {{- if .Values.securityContext }} + podSecurityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + {{- end }} +{{- end }} diff --git a/helm-charts/inferflow/templates/external-secrets.yaml b/helm-charts/inferflow/templates/external-secrets.yaml new file mode 100644 index 00000000..0d602056 --- /dev/null +++ b/helm-charts/inferflow/templates/external-secrets.yaml @@ -0,0 +1,30 @@ +{{- if and .Values.externalSecret .Values.externalSecret.enabled }} +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} +{{- if .Values.externalSecret.annotations }} + annotations: +{{ toYaml .Values.externalSecret.annotations | indent 4 }} +{{- end }} + {{- if and .Values.deployment .Values.deployment.enabled }} + name: {{ .Values.deployment.envFrom.secretRef }} + {{- end}} + namespace: {{ .Values.namespace }} +spec: + dataFrom: + - extract: + conversionStrategy: Default + key: {{ .Values.externalSecret.path }} + refreshInterval: 15s + secretStoreRef: + kind: {{ .Values.infrastructure.secretStore.kind | default "ClusterSecretStore" }} + name: {{ .Values.infrastructure.secretStore.name | default "vault-backend" }} + target: + creationPolicy: Owner + deletionPolicy: Retain + {{- if and .Values.deployment .Values.deployment.enabled }} + name: {{ .Values.deployment.envFrom.secretRef }} + {{- end}} +{{- end }} diff --git a/helm-charts/inferflow/templates/httpproxy.yaml b/helm-charts/inferflow/templates/httpproxy.yaml new file mode 100644 index 00000000..94fe8f3e --- /dev/null +++ b/helm-charts/inferflow/templates/httpproxy.yaml @@ -0,0 +1,50 @@ +{{- if and .Values.ingress .Values.ingress.enabled -}} +{{- if .Values.createContourGateway -}} +{{- if or ( eq "contour-internal" .Values.ingress.ingressClassName ) ( eq "contour-external" .Values.ingress.ingressClassName ) ( eq "contour-internal-0" .Values.ingress.ingressClassName ) ( eq "contour-internal-1" .Values.ingress.ingressClassName ) ( eq "contour-external-0" .Values.ingress.ingressClassName ) ( eq "contour-external-1" .Values.ingress.ingressClassName ) }} +{{- $servicePortNumber := .Values.ingress.servicePortNumber -}} +{{- $pathType := .Values.ingress.pathType -}} +{{- $namespace := .Values.namespace -}} +{{- $ingressClassName := .Values.ingress.ingressClassName -}} +{{ $count := 0 | int }} +{{- range .Values.ingress.hosts }} +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + namespace: {{ $namespace }} + name: {{ $namespace }}-{{ $count }} + labels: +{{ include "labels.common" $ | indent 4 }} +{{ include "labels.chart" $ | indent 4 }} + annotations: + projectcontour.io/ingress.class: {{ $.Values.ingress.ingressClassName }} +spec: + ingressClassName: {{ $ingressClassName }} + virtualhost: + fqdn: "{{ .host }}" + includes: + {{- range .paths }} + - conditions: + {{- if or ( eq ( lower .pathType ) "prefix" ) ( eq ( lower .pathType ) "implementationspecific") }} + - prefix: {{ .path }} + {{- end }} + {{- if ( eq ( lower .pathType ) "exact" ) }} + - header: + name: :path + exact: {{ .path }} + - prefix: {{ .path }} + {{- end }} + {{- if .targetService }} + name: {{ .targetService | replace "/" "-" }} + namespace: {{ (split "/" .targetService)._0 }} + {{- else }} + name: {{ $namespace }} + namespace: {{ $namespace }} + {{- end }} + {{- end }} + {{ $count = add1 $count }} +--- + +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/helm-charts/inferflow/templates/otel-secret.yaml b/helm-charts/inferflow/templates/otel-secret.yaml new file mode 100644 index 00000000..c2514b7d --- /dev/null +++ b/helm-charts/inferflow/templates/otel-secret.yaml @@ -0,0 +1,26 @@ +{{ if .Values.otel_enabled }} +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} + annotations: + flagger.app/config-tracking: disabled + name: {{ if and .Values.deployment .Values.deployment.envFrom }}{{ .Values.deployment.envFrom.secretRef }}{{ else }}{{ .Values.namespace }}{{ end }}-otel + namespace: {{ .Values.namespace }} +spec: + dataFrom: + - extract: + conversionStrategy: Default + key: {{ .Values.infrastructure.vault.otelTokenPath | default "org/prd/cntr/devop/coralogix-token" }} + refreshInterval: 15s + secretStoreRef: + kind: {{ .Values.infrastructure.secretStore.kind | default "ClusterSecretStore" }} + name: {{ .Values.infrastructure.secretStore.name | default "vault-backend" }} + target: + creationPolicy: Owner + deletionPolicy: Retain + {{- if and .Values.deployment .Values.deployment.enabled }} + name: {{ .Values.deployment.envFrom.secretRef }}-otel + {{- end}} +{{ end }} diff --git a/helm-charts/inferflow/templates/pdb.yaml b/helm-charts/inferflow/templates/pdb.yaml new file mode 100644 index 00000000..db87f89a --- /dev/null +++ b/helm-charts/inferflow/templates/pdb.yaml @@ -0,0 +1,36 @@ +{{- if or (and .Values.podDisruptionBudget .Values.podDisruptionBudget.enabled) (and .Values.deployment .Values.deployment.enabled) }} +{{- if or ( and .Values.deployment (.Values.deployment.enabled) (.Values.autoscaling.enabled) ( gt (int .Values.autoscaling.minReplicas) 1)) ( and (eq .Values.autoscaling.enabled false) .Values.deployment ( gt ( int .Values.deployment.replicaCount) 1 )) }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +spec: + {{- if .Values.podDisruptionBudget.enabled }} + maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }} + {{- else }} + {{- if and .Values.deployment .Values.deployment.enabled }} + {{- if (eq .Values.deployment.updateStrategy.strategy.type "RollingUpdate") }} + maxUnavailable: {{ .Values.deployment.updateStrategy.strategy.rollingUpdate.maxSurge | default "10%" }} + {{- else }} + maxUnavailable: "10%" + {{- end }} + {{ else }} + maxUnavailable: "10%" + {{- end }} + {{- end }} + {{- if .Values.podDisruptionBudget.minAvailable }} + minAvailable: {{ .Values.podDisruptionBudget.minAvailable }} + {{- end }} + selector: + matchLabels: + {{- if and (.Values.canary.enabled) (eq .Values.labels.env "prod") }} + {{- include "labels.primary-selector" . | nindent 6 }} + {{- else }} + {{- include "labels.selector" . | nindent 6 }} + {{- end }} +{{- end }} +{{- end }} diff --git a/helm-charts/inferflow/templates/scaledobject.yaml b/helm-charts/inferflow/templates/scaledobject.yaml new file mode 100644 index 00000000..259bfea2 --- /dev/null +++ b/helm-charts/inferflow/templates/scaledobject.yaml @@ -0,0 +1,56 @@ +{{- if and .Values.autoscaling .Values.autoscaling.enabled }} +apiVersion: keda.sh/v1alpha1 +kind: ScaledObject +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ .Values.namespace }} + pollingInterval: {{ .Values.autoscaling.pollingInterval }} + {{- if ( default false (.Values.disasterRecovery).enabled ) }} + minReplicaCount: 1 + {{- else }} + minReplicaCount: {{ .Values.canary.minCanaryReplicas | default $.Values.autoscaling.minReplicas }} + {{- end }} + maxReplicaCount: {{ .Values.canary.maxCanaryReplicas | default $.Values.autoscaling.maxReplicas }} + advanced: + horizontalPodAutoscalerConfig: + behavior: + scaleDown: + stabilizationWindowSeconds: {{ .Values.autoscaling.scaledown.stabilizationWindowSeconds }} + policies: + {{- range .Values.autoscaling.scaledown.policies }} + - type: {{ .type }} + value: {{ .value }} + periodSeconds: {{ .periodseconds }} + {{- end }} + selectPolicy: {{ .Values.autoscaling.scaledown.selectpolicy }} + scaleUp: + stabilizationWindowSeconds: {{ .Values.autoscaling.scaleup.stabilizationWindowSeconds }} + policies: + {{- range .Values.autoscaling.scaleup.policies }} + - type: {{ .type }} + value: {{ .value }} + periodSeconds: {{ .periodseconds }} + {{- end }} + selectPolicy: {{ .Values.autoscaling.scaleup.selectpolicy }} + triggers: + {{- if ( default false (.Values.disasterRecovery).enabled ) }} + {{- range $.Values.autoscaling.triggers }} + {{- if or (eq .type "cpu") (eq .type "memory") }} + - metadata: + {{- toYaml .metadata | nindent 8 }} + type: {{ .type }} + metricType: "Utilization" + {{- end }} + {{- end }} + {{- else }} + {{- toYaml .Values.autoscaling.triggers | nindent 2 }} + {{ end }} + +{{- end }} diff --git a/helm-charts/inferflow/templates/service.yaml b/helm-charts/inferflow/templates/service.yaml new file mode 100644 index 00000000..8fcc5bc8 --- /dev/null +++ b/helm-charts/inferflow/templates/service.yaml @@ -0,0 +1,29 @@ +{{- if and .Values.service .Values.service.enabled }} +{{- if or (eq .Values.canary.enabled false) ( and (.Values.canary.enabled) (ne .Values.labels.env "prod")) }} +apiVersion: v1 +kind: Service +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} +{{- if .Values.service.annotations }} + annotations: +{{ toYaml .Values.service.annotations | indent 4 }} +{{- end }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + {{- range .Values.service.ports }} + - name: {{ .name }} + port: {{ .port }} + protocol: {{ .protocol }} + targetPort: {{ .targetPort }} + {{- end }} + selector: + {{- include "labels.selector" . | nindent 4 }} +{{- end }} +{{- end }} diff --git a/helm-charts/inferflow/templates/serviceaccount.yaml b/helm-charts/inferflow/templates/serviceaccount.yaml new file mode 100644 index 00000000..f05362f5 --- /dev/null +++ b/helm-charts/inferflow/templates/serviceaccount.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.deployment (.Values.deployment.enabled) (.Values.deployment.serviceAccount.enabled) }} +apiVersion: v1 +kind: ServiceAccount +metadata: + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} + {{- with .Values.deployment.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/helm-charts/inferflow/values.yaml b/helm-charts/inferflow/values.yaml new file mode 100644 index 00000000..4f7699e2 --- /dev/null +++ b/helm-charts/inferflow/values.yaml @@ -0,0 +1,207 @@ +# Default values for inferflow helm chart + +namespace: prd-inferflow +applicationName: inferflow +replicaCount: 2 + +labels: + env: prd + team: bharatml + bu: ml + priority: p1 + priority_v2: cp3 + service_type: "" + +priorityClassName: "" + +telegraf: + enabled: false + +otel_enabled: false + +infrastructure: + secretStore: + name: vault-backend + kind: ClusterSecretStore + vault: + basePath: "" + otelTokenPath: "" + +deployment: + enabled: true + replicaCount: 2 + revisionHistoryLimit: 3 + image: + repository: ghcr.io/meesho/inferflow + tag: latest + pullPolicy: IfNotPresent + ports: + - containerPort: 8085 + name: http + protocol: TCP + probes: + liveness: + path: /health/self + port: 8085 + scheme: HTTP + initialDelaySeconds: 30 + periodSeconds: 10 + failureThreshold: 3 + successThreshold: 1 + timeoutSeconds: 5 + readiness: + path: /health/self + port: 8085 + scheme: HTTP + initialDelaySeconds: 20 + periodSeconds: 10 + failureThreshold: 3 + successThreshold: 1 + timeoutSeconds: 5 + resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "1Gi" + cpu: "1000m" + env: + - name: APP_ENV + value: "prod" + - name: APP_LOG_LEVEL + value: "INFO" + - name: APP_NAME + value: "inferflow" + - name: APP_PORT + value: "8085" + - name: APP_GC_PERCENTAGE + value: "1" + # Etcd + - name: ETCD_SERVER + value: "http://etcd:2379" + - name: ETCD_WATCHER_ENABLED + value: "true" + # In-memory cache + - name: IN_MEMORY_CACHE_SIZE_IN_BYTES + value: "6000000000" + # DAG topology + - name: DAG_TOPOLOGY_CACHE_SIZE + value: "500" + - name: DAG_TOPOLOGY_CACHE_TTL_SEC + value: "300" + # Numerix client + - name: NUMERIX_CLIENT_V1_AUTHTOKEN + value: "numerix" + - name: NUMERIX_CLIENT_V1_BATCHSIZE + value: "100" + - name: NUMERIX_CLIENT_V1_DEADLINE_MS + value: "5000" + - name: NUMERIX_CLIENT_V1_HOST + value: "numerix" + - name: NUMERIX_CLIENT_V1_PLAINTEXT + value: "true" + - name: NUMERIX_CLIENT_V1_PORT + value: "8083" + # ONFS client + - name: EXTERNAL_SERVICE_ONFS_FS_BATCH_SIZE + value: "50" + - name: EXTERNAL_SERVICE_ONFS_FS_CALLER_ID + value: "inferflow" + - name: EXTERNAL_SERVICE_ONFS_FS_CALLER_TOKEN + value: "inferflow" + - name: EXTERNAL_SERVICE_ONFS_FS_GRPC_PLAIN_TEXT + value: "true" + - name: EXTERNAL_SERVICE_ONFS_FS_HOST + value: "onfs-api-server" + - name: EXTERNAL_SERVICE_ONFS_FS_PORT + value: "8089" + - name: EXTERNAL_SERVICE_ONFS_FS_DEAD_LINE + value: "200" + # Predator client + - name: EXTERNAL_SERVICE_PREDATOR_PORT + value: "8090" + - name: EXTERNAL_SERVICE_PREDATOR_GRPC_PLAIN_TEXT + value: "true" + - name: EXTERNAL_SERVICE_PREDATOR_CALLER_ID + value: "inferflow" + - name: EXTERNAL_SERVICE_PREDATOR_CALLER_TOKEN + value: "inferflow" + - name: EXTERNAL_SERVICE_PREDATOR_DEADLINE + value: "200" + # Metrics + - name: METRIC_SAMPLING_RATE + value: "1" + # Kafka logging + - name: KAFKA_BOOTSTRAP_SERVERS + value: "broker:29092" + - name: KAFKA_LOGGING_TOPIC + value: "inferflow_inference_logs" + serviceAccount: + enabled: false + annotations: {} + updateStrategy: + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 0 + maxSurge: 1 + terminationGracePeriodSeconds: 30 + +service: + enabled: true + type: ClusterIP + ports: + - name: http + port: 80 + targetPort: 8085 + protocol: TCP + +autoscaling: + enabled: false + minReplicas: 2 + maxReplicas: 10 + pollingInterval: 30 + scaledown: + stabilizationWindowSeconds: 300 + policies: + - type: Percent + value: 10 + periodseconds: 60 + selectpolicy: Min + scaleup: + stabilizationWindowSeconds: 0 + policies: + - type: Percent + value: 50 + periodseconds: 60 + selectpolicy: Max + triggers: + - type: cpu + metadata: + value: "70" + metricType: Utilization + +ingress: + enabled: false + ingressClassName: contour-internal +createContourGateway: false + +externalSecret: + enabled: false + path: "" + +configMap: + enabled: false + +canary: + enabled: false + promURL: "" + slackChannel: "" + slackWebhookURL: "" + +podDisruptionBudget: + enabled: false + maxUnavailable: "10%" + +disasterRecovery: + enabled: false diff --git a/helm-charts/numerix/Chart.yaml b/helm-charts/numerix/Chart.yaml new file mode 100644 index 00000000..37e29c1a --- /dev/null +++ b/helm-charts/numerix/Chart.yaml @@ -0,0 +1,10 @@ +apiVersion: v2 +name: numerix +description: A Helm chart for the Numerix matrix operations service +type: application +version: 1.0.0 +appVersion: "1.0.0" + +maintainers: + - name: BharatMLStack Team + email: ml-oss@meesho.com diff --git a/helm-charts/numerix/templates/NOTES.txt b/helm-charts/numerix/templates/NOTES.txt new file mode 100644 index 00000000..35a4f3c2 --- /dev/null +++ b/helm-charts/numerix/templates/NOTES.txt @@ -0,0 +1,18 @@ +{{ .Chart.Name }} has been deployed. + +Namespace: {{ .Values.namespace }} +Application: {{ .Values.applicationName }} + +{{- if .Values.service.enabled }} +Service: {{ .Values.namespace }}:{{ (index .Values.service.ports 0).port }} +{{- end }} + +{{- if .Values.ingress.enabled }} +Ingress is enabled. +{{- end }} + +{{- if .Values.autoscaling.enabled }} +Autoscaling: {{ .Values.autoscaling.minReplicas }} - {{ .Values.autoscaling.maxReplicas }} replicas +{{- else }} +Replicas: {{ .Values.replicaCount }} +{{- end }} diff --git a/helm-charts/numerix/templates/_helpers.tpl b/helm-charts/numerix/templates/_helpers.tpl new file mode 100644 index 00000000..168e0342 --- /dev/null +++ b/helm-charts/numerix/templates/_helpers.tpl @@ -0,0 +1,70 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "fullname" -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "labels.selector" -}} +app: {{ .Values.namespace }} +{{- end -}} + +{{- define "labels.primary-selector" -}} +app: {{ .Values.namespace }}-primary +{{- end -}} + +{{- define "labels.common" -}} +{{ template "labels.selector" . }} +{{- if and .Values.deployment .Values.deployment.image }} +version: {{ .Values.deployment.image.tag }} +{{- end }} +env: {{ .Values.labels.env }} +team: {{ .Values.labels.team }} +bu: {{ .Values.labels.bu }} +service: {{ .Values.applicationName }} +priority: {{ .Values.labels.priority }} +priority_v2: {{ .Values.labels.priority_v2 | default "cp3" }} +primary_owner: {{ .Values.labels.primary_owner | default .Values.labels.team }} +secondary_owner: {{ .Values.labels.secondary_owner | default .Values.labels.team }} +service_type: {{ .Values.labels.service_type | default "" | replace "," "-" | quote }} +{{- end -}} + +{{- define "labels.chart" -}} +chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" +release: {{ .Release.Name | quote }} +heritage: {{ .Release.Service | quote }} +{{- end -}} + +{{/* +Renders a value that contains template. +Usage: +{{ include "application.tplvalues.render" ( dict "value" .Values.path.to.the.Value "context" $) }} +*/}} + +{{- define "application.tplvalues.render" -}} + {{- if typeIs "string" .value }} + {{- tpl .value .context }} + {{- else }} + {{- tpl (.value | toYaml) .context }} + {{- end }} +{{- end -}} + +{{- define "canary.promURL" -}} +{{- if .Values.canary.promURL }} +{{- .Values.canary.promURL }} +{{- else if eq .Values.labels.env "prod" }} +prod-ops-metricsui.example.com/select/100/ +{{- else }} +https://sb-ops-metricsui.example.com/select/100/ +{{- end }} +{{- end -}} diff --git a/helm-charts/numerix/templates/alert-provider.yaml b/helm-charts/numerix/templates/alert-provider.yaml new file mode 100644 index 00000000..a300bb14 --- /dev/null +++ b/helm-charts/numerix/templates/alert-provider.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.canary (.Values.canary.enabled) .Values.canary.slackWebhookURL (ne .Values.canary.slackWebhookURL "") }} +apiVersion: flagger.app/v1beta1 +kind: AlertProvider +metadata: + name: flagger-status + namespace: {{ .Values.namespace }} +spec: + type: slack + {{- if .Values.canary.slackChannel }} + channel: {{ .Values.canary.slackChannel }} + {{- end }} + username: flagger + address: {{ .Values.canary.slackWebhookURL }} +{{- end }} diff --git a/helm-charts/numerix/templates/configmap.yaml b/helm-charts/numerix/templates/configmap.yaml new file mode 100644 index 00000000..fc527219 --- /dev/null +++ b/helm-charts/numerix/templates/configmap.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.configMap .Values.configMap.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Values.namespace }}-config + namespace: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +data: + {{- range $key, $value := .Values.configMap.data }} + {{ $key }}: {{ $value | quote }} + {{- end }} +{{- end }} diff --git a/helm-charts/numerix/templates/deployment.yaml b/helm-charts/numerix/templates/deployment.yaml new file mode 100644 index 00000000..34cbb1ed --- /dev/null +++ b/helm-charts/numerix/templates/deployment.yaml @@ -0,0 +1,237 @@ +{{- if and .Values.deployment .Values.deployment.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.namespace }} + namespace: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +{{- include "labels.selector" . | nindent 4 }} +spec: + {{- with .Values.deployment.minReadySeconds }} + minReadySeconds: {{ . }} + {{- end }} + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + revisionHistoryLimit: {{ .Values.deployment.revisionHistoryLimit }} + selector: + matchLabels: +{{- include "labels.selector" . | nindent 6 }} +{{- with .Values.deployment.updateStrategy }} +{{ toYaml . | indent 2 -}} +{{- end }} + template: + metadata: + annotations: + {{- with .Values.deployment.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.telegraf.enabled }} + telegraf.influxdata.com/class: "infra" + {{- end }} + labels: + {{- include "labels.common" . | nindent 8 }} + spec: + {{- if .Values.priorityClassName }} + priorityClassName: {{ .Values.priorityClassName }} + {{- end }} + topologySpreadConstraints: + - maxSkew: 1 + topologyKey: topology.kubernetes.io/zone + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: +{{- include "labels.selector" . | nindent 12 }} + {{- if .Values.deployment.image.pullSecret }} + imagePullSecrets: + - name: {{ .Values.deployment.image.pullSecret }} + {{- end }} + {{- if .Values.deployment.volumes }} + volumes: + {{- toYaml .Values.deployment.volumes | nindent 8 }} + {{- end }} + {{- if .Values.deployment.initContainers }} + initContainers: + {{- toYaml .Values.deployment.initContainers | nindent 8 }} + {{- end }} + containers: + - name: {{ .Values.applicationName }} + {{- if .Values.deployment.volumeMounts }} + volumeMounts: + {{- toYaml .Values.deployment.volumeMounts | nindent 12 }} + {{- end }} + {{- if .Values.deployment.command }} + command: {{- include "application.tplvalues.render" (dict "value" .Values.deployment.command "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.deployment.args }} + args: {{- include "application.tplvalues.render" (dict "value" .Values.deployment.args "context" $) | nindent 12 }} + {{- end }} + image: "{{ .Values.deployment.image.repository }}:{{ .Values.deployment.image.tag }}" + imagePullPolicy: {{ .Values.deployment.image.pullPolicy }} + {{- if .Values.deployment.lifecycle }} + lifecycle: {{ toYaml .Values.deployment.lifecycle | nindent 12 }} + {{- end }} + {{- with .Values.deployment.probes }} + {{- with .liveness }} + livenessProbe: + {{- with .failureThreshold }} + failureThreshold: {{ . }} + {{- end }} + httpGet: + path: {{ .path }} + port: {{ .port }} + scheme: {{ .scheme }} + {{- with .periodSeconds }} + periodSeconds: {{ . }} + {{- end }} + {{- with .successThreshold }} + successThreshold: {{ . }} + {{- end }} + {{- with .timeoutSeconds }} + timeoutSeconds: {{ . }} + {{- end }} + initialDelaySeconds: {{ .initialDelaySeconds }} + {{- end }} + {{- end }} + {{- if or .Values.externalSecret.enabled .Values.otel_enabled (and .Values.configMap .Values.configMap.enabled) }} + envFrom: + {{- if .Values.externalSecret.enabled }} + - secretRef: + name: {{ .Values.deployment.envFrom.secretRef }} + {{- if ( default false (.Values.disasterRecovery).enabled ) }} + - secretRef: + name: {{ .Values.deployment.envFrom.secretRef }}-dr + {{- end }} + {{- end }} + {{- if .Values.otel_enabled }} + - secretRef: + name: {{ .Values.deployment.envFrom.secretRef }}-otel + {{- end }} + {{- if and .Values.configMap .Values.configMap.enabled }} + - configMapRef: + name: {{ .Values.namespace }}-config + {{- end }} + {{- end }} + env: + - name: TZ + value: Asia/Kolkata + - name: NODE_IP + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + {{- if .Values.telegraf.enabled }} + - name: TELEGRAF_UDP_HOST + valueFrom: + fieldRef: + fieldPath: status.podIP + {{- end }} + {{- if .Values.otel_enabled }} + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: http://$(NODE_IP):4317 + {{- end }} + {{- with .Values.deployment.env }} + {{- range . }} + - name: {{ .name }} + value: "{{ .value }}" + {{- end }} + {{- end }} + {{- with .Values.deployment.ports }} + ports: + {{- range . }} + - containerPort: {{ .containerPort }} + name: {{ .name }} + protocol: {{ .protocol }} + {{- end }} + {{- end }} + {{- if .Values.telegraf.enabled }} + - containerPort: 9273 + name: telegraf-sc + protocol: TCP + {{- end }} + {{- with .Values.deployment.probes }} + {{- with .readiness }} + readinessProbe: + {{- with .failureThreshold }} + failureThreshold: {{ . }} + {{- end }} + httpGet: + path: {{ .path }} + port: {{ .port }} + scheme: {{ .scheme }} + {{- with .periodSeconds }} + periodSeconds: {{ . }} + {{- end }} + {{- with .successThreshold }} + successThreshold: {{ . }} + {{- end }} + {{- with .timeoutSeconds}} + timeoutSeconds: {{ . }} + {{- end }} + initialDelaySeconds: {{ .initialDelaySeconds }} + {{- end }} + {{- end }} + {{- with .Values.deployment.resources }} + resources: + {{- with .limits }} + limits: + {{- with .memory }} + memory: "{{ . }}" + {{- end }} + {{- with .cpu }} + cpu: "{{ . }}" + {{- end }} + {{- end }} + {{- with .requests }} + requests: + {{- with .memory }} + memory: "{{ . }}" + {{- end }} + {{- with .cpu }} + cpu: "{{ . }}" + {{- end }} + {{- end }} + {{- end }} + + {{- with .Values.deployment.hostAliases }} + hostAliases: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.deployment.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.deployment.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + terminationGracePeriodSeconds: {{ .Values.deployment.terminationGracePeriodSeconds | default "300" }} + {{- if .Values.deployment.serviceAccount.enabled }} + serviceAccountName: {{ .Values.namespace }} + {{- end }} + {{- if .Values.securityContext }} + podSecurityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + {{- end }} +{{- end }} diff --git a/helm-charts/numerix/templates/external-secrets.yaml b/helm-charts/numerix/templates/external-secrets.yaml new file mode 100644 index 00000000..0d602056 --- /dev/null +++ b/helm-charts/numerix/templates/external-secrets.yaml @@ -0,0 +1,30 @@ +{{- if and .Values.externalSecret .Values.externalSecret.enabled }} +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} +{{- if .Values.externalSecret.annotations }} + annotations: +{{ toYaml .Values.externalSecret.annotations | indent 4 }} +{{- end }} + {{- if and .Values.deployment .Values.deployment.enabled }} + name: {{ .Values.deployment.envFrom.secretRef }} + {{- end}} + namespace: {{ .Values.namespace }} +spec: + dataFrom: + - extract: + conversionStrategy: Default + key: {{ .Values.externalSecret.path }} + refreshInterval: 15s + secretStoreRef: + kind: {{ .Values.infrastructure.secretStore.kind | default "ClusterSecretStore" }} + name: {{ .Values.infrastructure.secretStore.name | default "vault-backend" }} + target: + creationPolicy: Owner + deletionPolicy: Retain + {{- if and .Values.deployment .Values.deployment.enabled }} + name: {{ .Values.deployment.envFrom.secretRef }} + {{- end}} +{{- end }} diff --git a/helm-charts/numerix/templates/httpproxy.yaml b/helm-charts/numerix/templates/httpproxy.yaml new file mode 100644 index 00000000..94fe8f3e --- /dev/null +++ b/helm-charts/numerix/templates/httpproxy.yaml @@ -0,0 +1,50 @@ +{{- if and .Values.ingress .Values.ingress.enabled -}} +{{- if .Values.createContourGateway -}} +{{- if or ( eq "contour-internal" .Values.ingress.ingressClassName ) ( eq "contour-external" .Values.ingress.ingressClassName ) ( eq "contour-internal-0" .Values.ingress.ingressClassName ) ( eq "contour-internal-1" .Values.ingress.ingressClassName ) ( eq "contour-external-0" .Values.ingress.ingressClassName ) ( eq "contour-external-1" .Values.ingress.ingressClassName ) }} +{{- $servicePortNumber := .Values.ingress.servicePortNumber -}} +{{- $pathType := .Values.ingress.pathType -}} +{{- $namespace := .Values.namespace -}} +{{- $ingressClassName := .Values.ingress.ingressClassName -}} +{{ $count := 0 | int }} +{{- range .Values.ingress.hosts }} +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + namespace: {{ $namespace }} + name: {{ $namespace }}-{{ $count }} + labels: +{{ include "labels.common" $ | indent 4 }} +{{ include "labels.chart" $ | indent 4 }} + annotations: + projectcontour.io/ingress.class: {{ $.Values.ingress.ingressClassName }} +spec: + ingressClassName: {{ $ingressClassName }} + virtualhost: + fqdn: "{{ .host }}" + includes: + {{- range .paths }} + - conditions: + {{- if or ( eq ( lower .pathType ) "prefix" ) ( eq ( lower .pathType ) "implementationspecific") }} + - prefix: {{ .path }} + {{- end }} + {{- if ( eq ( lower .pathType ) "exact" ) }} + - header: + name: :path + exact: {{ .path }} + - prefix: {{ .path }} + {{- end }} + {{- if .targetService }} + name: {{ .targetService | replace "/" "-" }} + namespace: {{ (split "/" .targetService)._0 }} + {{- else }} + name: {{ $namespace }} + namespace: {{ $namespace }} + {{- end }} + {{- end }} + {{ $count = add1 $count }} +--- + +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/helm-charts/numerix/templates/otel-secret.yaml b/helm-charts/numerix/templates/otel-secret.yaml new file mode 100644 index 00000000..c2514b7d --- /dev/null +++ b/helm-charts/numerix/templates/otel-secret.yaml @@ -0,0 +1,26 @@ +{{ if .Values.otel_enabled }} +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} + annotations: + flagger.app/config-tracking: disabled + name: {{ if and .Values.deployment .Values.deployment.envFrom }}{{ .Values.deployment.envFrom.secretRef }}{{ else }}{{ .Values.namespace }}{{ end }}-otel + namespace: {{ .Values.namespace }} +spec: + dataFrom: + - extract: + conversionStrategy: Default + key: {{ .Values.infrastructure.vault.otelTokenPath | default "org/prd/cntr/devop/coralogix-token" }} + refreshInterval: 15s + secretStoreRef: + kind: {{ .Values.infrastructure.secretStore.kind | default "ClusterSecretStore" }} + name: {{ .Values.infrastructure.secretStore.name | default "vault-backend" }} + target: + creationPolicy: Owner + deletionPolicy: Retain + {{- if and .Values.deployment .Values.deployment.enabled }} + name: {{ .Values.deployment.envFrom.secretRef }}-otel + {{- end}} +{{ end }} diff --git a/helm-charts/numerix/templates/pdb.yaml b/helm-charts/numerix/templates/pdb.yaml new file mode 100644 index 00000000..db87f89a --- /dev/null +++ b/helm-charts/numerix/templates/pdb.yaml @@ -0,0 +1,36 @@ +{{- if or (and .Values.podDisruptionBudget .Values.podDisruptionBudget.enabled) (and .Values.deployment .Values.deployment.enabled) }} +{{- if or ( and .Values.deployment (.Values.deployment.enabled) (.Values.autoscaling.enabled) ( gt (int .Values.autoscaling.minReplicas) 1)) ( and (eq .Values.autoscaling.enabled false) .Values.deployment ( gt ( int .Values.deployment.replicaCount) 1 )) }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +spec: + {{- if .Values.podDisruptionBudget.enabled }} + maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }} + {{- else }} + {{- if and .Values.deployment .Values.deployment.enabled }} + {{- if (eq .Values.deployment.updateStrategy.strategy.type "RollingUpdate") }} + maxUnavailable: {{ .Values.deployment.updateStrategy.strategy.rollingUpdate.maxSurge | default "10%" }} + {{- else }} + maxUnavailable: "10%" + {{- end }} + {{ else }} + maxUnavailable: "10%" + {{- end }} + {{- end }} + {{- if .Values.podDisruptionBudget.minAvailable }} + minAvailable: {{ .Values.podDisruptionBudget.minAvailable }} + {{- end }} + selector: + matchLabels: + {{- if and (.Values.canary.enabled) (eq .Values.labels.env "prod") }} + {{- include "labels.primary-selector" . | nindent 6 }} + {{- else }} + {{- include "labels.selector" . | nindent 6 }} + {{- end }} +{{- end }} +{{- end }} diff --git a/helm-charts/numerix/templates/scaledobject.yaml b/helm-charts/numerix/templates/scaledobject.yaml new file mode 100644 index 00000000..259bfea2 --- /dev/null +++ b/helm-charts/numerix/templates/scaledobject.yaml @@ -0,0 +1,56 @@ +{{- if and .Values.autoscaling .Values.autoscaling.enabled }} +apiVersion: keda.sh/v1alpha1 +kind: ScaledObject +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ .Values.namespace }} + pollingInterval: {{ .Values.autoscaling.pollingInterval }} + {{- if ( default false (.Values.disasterRecovery).enabled ) }} + minReplicaCount: 1 + {{- else }} + minReplicaCount: {{ .Values.canary.minCanaryReplicas | default $.Values.autoscaling.minReplicas }} + {{- end }} + maxReplicaCount: {{ .Values.canary.maxCanaryReplicas | default $.Values.autoscaling.maxReplicas }} + advanced: + horizontalPodAutoscalerConfig: + behavior: + scaleDown: + stabilizationWindowSeconds: {{ .Values.autoscaling.scaledown.stabilizationWindowSeconds }} + policies: + {{- range .Values.autoscaling.scaledown.policies }} + - type: {{ .type }} + value: {{ .value }} + periodSeconds: {{ .periodseconds }} + {{- end }} + selectPolicy: {{ .Values.autoscaling.scaledown.selectpolicy }} + scaleUp: + stabilizationWindowSeconds: {{ .Values.autoscaling.scaleup.stabilizationWindowSeconds }} + policies: + {{- range .Values.autoscaling.scaleup.policies }} + - type: {{ .type }} + value: {{ .value }} + periodSeconds: {{ .periodseconds }} + {{- end }} + selectPolicy: {{ .Values.autoscaling.scaleup.selectpolicy }} + triggers: + {{- if ( default false (.Values.disasterRecovery).enabled ) }} + {{- range $.Values.autoscaling.triggers }} + {{- if or (eq .type "cpu") (eq .type "memory") }} + - metadata: + {{- toYaml .metadata | nindent 8 }} + type: {{ .type }} + metricType: "Utilization" + {{- end }} + {{- end }} + {{- else }} + {{- toYaml .Values.autoscaling.triggers | nindent 2 }} + {{ end }} + +{{- end }} diff --git a/helm-charts/numerix/templates/service.yaml b/helm-charts/numerix/templates/service.yaml new file mode 100644 index 00000000..8fcc5bc8 --- /dev/null +++ b/helm-charts/numerix/templates/service.yaml @@ -0,0 +1,29 @@ +{{- if and .Values.service .Values.service.enabled }} +{{- if or (eq .Values.canary.enabled false) ( and (.Values.canary.enabled) (ne .Values.labels.env "prod")) }} +apiVersion: v1 +kind: Service +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} +{{- if .Values.service.annotations }} + annotations: +{{ toYaml .Values.service.annotations | indent 4 }} +{{- end }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + {{- range .Values.service.ports }} + - name: {{ .name }} + port: {{ .port }} + protocol: {{ .protocol }} + targetPort: {{ .targetPort }} + {{- end }} + selector: + {{- include "labels.selector" . | nindent 4 }} +{{- end }} +{{- end }} diff --git a/helm-charts/numerix/templates/serviceaccount.yaml b/helm-charts/numerix/templates/serviceaccount.yaml new file mode 100644 index 00000000..f05362f5 --- /dev/null +++ b/helm-charts/numerix/templates/serviceaccount.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.deployment (.Values.deployment.enabled) (.Values.deployment.serviceAccount.enabled) }} +apiVersion: v1 +kind: ServiceAccount +metadata: + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} + {{- with .Values.deployment.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/helm-charts/numerix/values.yaml b/helm-charts/numerix/values.yaml new file mode 100644 index 00000000..c6be05c1 --- /dev/null +++ b/helm-charts/numerix/values.yaml @@ -0,0 +1,166 @@ +# Default values for numerix helm chart + +namespace: prd-numerix +applicationName: numerix +replicaCount: 2 + +labels: + env: prd + team: bharatml + bu: ml + priority: p1 + priority_v2: cp3 + service_type: "" + +# Priority class (leave empty to skip) +priorityClassName: "" + +# Telegraf sidecar metrics +telegraf: + enabled: false + +# OTEL tracing +otel_enabled: false + +# Infrastructure configuration +infrastructure: + secretStore: + name: vault-backend + kind: ClusterSecretStore + vault: + basePath: "" + otelTokenPath: "" + +# Deployment configuration +deployment: + enabled: true + replicaCount: 2 + revisionHistoryLimit: 3 + image: + repository: ghcr.io/meesho/numerix + tag: latest + pullPolicy: IfNotPresent + ports: + - containerPort: 8083 + name: grpc + protocol: TCP + probes: + liveness: + path: /health + port: 8083 + scheme: HTTP + initialDelaySeconds: 15 + periodSeconds: 10 + failureThreshold: 3 + successThreshold: 1 + timeoutSeconds: 5 + readiness: + path: /health + port: 8083 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 10 + failureThreshold: 3 + successThreshold: 1 + timeoutSeconds: 5 + resources: + requests: + memory: "256Mi" + cpu: "250m" + limits: + memory: "512Mi" + cpu: "500m" + env: + - name: APPLICATION_PORT + value: "8083" + - name: APP_ENV + value: "prd" + - name: APP_NAME + value: "numerix" + - name: APP_LOG_LEVEL + value: "ERROR" + - name: CHANNEL_BUFFER_SIZE + value: "10000" + - name: ETCD_SERVERS + value: "http://etcd:2379" + - name: METRIC_SAMPLING_RATE + value: "1" + - name: LOG_SAMPLING_RATE + value: "1" + serviceAccount: + enabled: false + annotations: {} + updateStrategy: + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 0 + maxSurge: 1 + terminationGracePeriodSeconds: 30 + +# Service configuration +service: + enabled: true + type: ClusterIP + ports: + - name: grpc + port: 80 + targetPort: 8083 + protocol: TCP + +# Autoscaling (KEDA ScaledObject) +autoscaling: + enabled: false + minReplicas: 2 + maxReplicas: 10 + pollingInterval: 30 + scaledown: + stabilizationWindowSeconds: 300 + policies: + - type: Percent + value: 10 + periodseconds: 60 + selectpolicy: Min + scaleup: + stabilizationWindowSeconds: 0 + policies: + - type: Percent + value: 50 + periodseconds: 60 + selectpolicy: Max + triggers: + - type: cpu + metadata: + value: "70" + metricType: Utilization + +# Ingress (Contour HTTPProxy) +ingress: + enabled: false + ingressClassName: contour-internal +createContourGateway: false + +# External secrets (Vault) +externalSecret: + enabled: false + path: "" + +# ConfigMap for non-secret env vars +configMap: + enabled: false + +# Canary / Flagger +canary: + enabled: false + promURL: "" + slackChannel: "" + slackWebhookURL: "" + +# Pod Disruption Budget +podDisruptionBudget: + enabled: false + maxUnavailable: "10%" + +# Disaster Recovery +disasterRecovery: + enabled: false diff --git a/helm-charts/onfs-api-server/Chart.yaml b/helm-charts/onfs-api-server/Chart.yaml new file mode 100644 index 00000000..c3a90a16 --- /dev/null +++ b/helm-charts/onfs-api-server/Chart.yaml @@ -0,0 +1,10 @@ +apiVersion: v2 +name: onfs-api-server +description: A Helm chart for the Online Feature Store API Server +type: application +version: 1.0.0 +appVersion: "1.0.0" + +maintainers: + - name: BharatMLStack Team + email: ml-oss@meesho.com diff --git a/helm-charts/onfs-api-server/templates/NOTES.txt b/helm-charts/onfs-api-server/templates/NOTES.txt new file mode 100644 index 00000000..35a4f3c2 --- /dev/null +++ b/helm-charts/onfs-api-server/templates/NOTES.txt @@ -0,0 +1,18 @@ +{{ .Chart.Name }} has been deployed. + +Namespace: {{ .Values.namespace }} +Application: {{ .Values.applicationName }} + +{{- if .Values.service.enabled }} +Service: {{ .Values.namespace }}:{{ (index .Values.service.ports 0).port }} +{{- end }} + +{{- if .Values.ingress.enabled }} +Ingress is enabled. +{{- end }} + +{{- if .Values.autoscaling.enabled }} +Autoscaling: {{ .Values.autoscaling.minReplicas }} - {{ .Values.autoscaling.maxReplicas }} replicas +{{- else }} +Replicas: {{ .Values.replicaCount }} +{{- end }} diff --git a/helm-charts/onfs-api-server/templates/_helpers.tpl b/helm-charts/onfs-api-server/templates/_helpers.tpl new file mode 100644 index 00000000..168e0342 --- /dev/null +++ b/helm-charts/onfs-api-server/templates/_helpers.tpl @@ -0,0 +1,70 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "fullname" -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "labels.selector" -}} +app: {{ .Values.namespace }} +{{- end -}} + +{{- define "labels.primary-selector" -}} +app: {{ .Values.namespace }}-primary +{{- end -}} + +{{- define "labels.common" -}} +{{ template "labels.selector" . }} +{{- if and .Values.deployment .Values.deployment.image }} +version: {{ .Values.deployment.image.tag }} +{{- end }} +env: {{ .Values.labels.env }} +team: {{ .Values.labels.team }} +bu: {{ .Values.labels.bu }} +service: {{ .Values.applicationName }} +priority: {{ .Values.labels.priority }} +priority_v2: {{ .Values.labels.priority_v2 | default "cp3" }} +primary_owner: {{ .Values.labels.primary_owner | default .Values.labels.team }} +secondary_owner: {{ .Values.labels.secondary_owner | default .Values.labels.team }} +service_type: {{ .Values.labels.service_type | default "" | replace "," "-" | quote }} +{{- end -}} + +{{- define "labels.chart" -}} +chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" +release: {{ .Release.Name | quote }} +heritage: {{ .Release.Service | quote }} +{{- end -}} + +{{/* +Renders a value that contains template. +Usage: +{{ include "application.tplvalues.render" ( dict "value" .Values.path.to.the.Value "context" $) }} +*/}} + +{{- define "application.tplvalues.render" -}} + {{- if typeIs "string" .value }} + {{- tpl .value .context }} + {{- else }} + {{- tpl (.value | toYaml) .context }} + {{- end }} +{{- end -}} + +{{- define "canary.promURL" -}} +{{- if .Values.canary.promURL }} +{{- .Values.canary.promURL }} +{{- else if eq .Values.labels.env "prod" }} +prod-ops-metricsui.example.com/select/100/ +{{- else }} +https://sb-ops-metricsui.example.com/select/100/ +{{- end }} +{{- end -}} diff --git a/helm-charts/onfs-api-server/templates/alert-provider.yaml b/helm-charts/onfs-api-server/templates/alert-provider.yaml new file mode 100644 index 00000000..a300bb14 --- /dev/null +++ b/helm-charts/onfs-api-server/templates/alert-provider.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.canary (.Values.canary.enabled) .Values.canary.slackWebhookURL (ne .Values.canary.slackWebhookURL "") }} +apiVersion: flagger.app/v1beta1 +kind: AlertProvider +metadata: + name: flagger-status + namespace: {{ .Values.namespace }} +spec: + type: slack + {{- if .Values.canary.slackChannel }} + channel: {{ .Values.canary.slackChannel }} + {{- end }} + username: flagger + address: {{ .Values.canary.slackWebhookURL }} +{{- end }} diff --git a/helm-charts/onfs-api-server/templates/configmap.yaml b/helm-charts/onfs-api-server/templates/configmap.yaml new file mode 100644 index 00000000..fc527219 --- /dev/null +++ b/helm-charts/onfs-api-server/templates/configmap.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.configMap .Values.configMap.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Values.namespace }}-config + namespace: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +data: + {{- range $key, $value := .Values.configMap.data }} + {{ $key }}: {{ $value | quote }} + {{- end }} +{{- end }} diff --git a/helm-charts/onfs-api-server/templates/deployment.yaml b/helm-charts/onfs-api-server/templates/deployment.yaml new file mode 100644 index 00000000..34cbb1ed --- /dev/null +++ b/helm-charts/onfs-api-server/templates/deployment.yaml @@ -0,0 +1,237 @@ +{{- if and .Values.deployment .Values.deployment.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.namespace }} + namespace: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +{{- include "labels.selector" . | nindent 4 }} +spec: + {{- with .Values.deployment.minReadySeconds }} + minReadySeconds: {{ . }} + {{- end }} + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + revisionHistoryLimit: {{ .Values.deployment.revisionHistoryLimit }} + selector: + matchLabels: +{{- include "labels.selector" . | nindent 6 }} +{{- with .Values.deployment.updateStrategy }} +{{ toYaml . | indent 2 -}} +{{- end }} + template: + metadata: + annotations: + {{- with .Values.deployment.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.telegraf.enabled }} + telegraf.influxdata.com/class: "infra" + {{- end }} + labels: + {{- include "labels.common" . | nindent 8 }} + spec: + {{- if .Values.priorityClassName }} + priorityClassName: {{ .Values.priorityClassName }} + {{- end }} + topologySpreadConstraints: + - maxSkew: 1 + topologyKey: topology.kubernetes.io/zone + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: +{{- include "labels.selector" . | nindent 12 }} + {{- if .Values.deployment.image.pullSecret }} + imagePullSecrets: + - name: {{ .Values.deployment.image.pullSecret }} + {{- end }} + {{- if .Values.deployment.volumes }} + volumes: + {{- toYaml .Values.deployment.volumes | nindent 8 }} + {{- end }} + {{- if .Values.deployment.initContainers }} + initContainers: + {{- toYaml .Values.deployment.initContainers | nindent 8 }} + {{- end }} + containers: + - name: {{ .Values.applicationName }} + {{- if .Values.deployment.volumeMounts }} + volumeMounts: + {{- toYaml .Values.deployment.volumeMounts | nindent 12 }} + {{- end }} + {{- if .Values.deployment.command }} + command: {{- include "application.tplvalues.render" (dict "value" .Values.deployment.command "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.deployment.args }} + args: {{- include "application.tplvalues.render" (dict "value" .Values.deployment.args "context" $) | nindent 12 }} + {{- end }} + image: "{{ .Values.deployment.image.repository }}:{{ .Values.deployment.image.tag }}" + imagePullPolicy: {{ .Values.deployment.image.pullPolicy }} + {{- if .Values.deployment.lifecycle }} + lifecycle: {{ toYaml .Values.deployment.lifecycle | nindent 12 }} + {{- end }} + {{- with .Values.deployment.probes }} + {{- with .liveness }} + livenessProbe: + {{- with .failureThreshold }} + failureThreshold: {{ . }} + {{- end }} + httpGet: + path: {{ .path }} + port: {{ .port }} + scheme: {{ .scheme }} + {{- with .periodSeconds }} + periodSeconds: {{ . }} + {{- end }} + {{- with .successThreshold }} + successThreshold: {{ . }} + {{- end }} + {{- with .timeoutSeconds }} + timeoutSeconds: {{ . }} + {{- end }} + initialDelaySeconds: {{ .initialDelaySeconds }} + {{- end }} + {{- end }} + {{- if or .Values.externalSecret.enabled .Values.otel_enabled (and .Values.configMap .Values.configMap.enabled) }} + envFrom: + {{- if .Values.externalSecret.enabled }} + - secretRef: + name: {{ .Values.deployment.envFrom.secretRef }} + {{- if ( default false (.Values.disasterRecovery).enabled ) }} + - secretRef: + name: {{ .Values.deployment.envFrom.secretRef }}-dr + {{- end }} + {{- end }} + {{- if .Values.otel_enabled }} + - secretRef: + name: {{ .Values.deployment.envFrom.secretRef }}-otel + {{- end }} + {{- if and .Values.configMap .Values.configMap.enabled }} + - configMapRef: + name: {{ .Values.namespace }}-config + {{- end }} + {{- end }} + env: + - name: TZ + value: Asia/Kolkata + - name: NODE_IP + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + {{- if .Values.telegraf.enabled }} + - name: TELEGRAF_UDP_HOST + valueFrom: + fieldRef: + fieldPath: status.podIP + {{- end }} + {{- if .Values.otel_enabled }} + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: http://$(NODE_IP):4317 + {{- end }} + {{- with .Values.deployment.env }} + {{- range . }} + - name: {{ .name }} + value: "{{ .value }}" + {{- end }} + {{- end }} + {{- with .Values.deployment.ports }} + ports: + {{- range . }} + - containerPort: {{ .containerPort }} + name: {{ .name }} + protocol: {{ .protocol }} + {{- end }} + {{- end }} + {{- if .Values.telegraf.enabled }} + - containerPort: 9273 + name: telegraf-sc + protocol: TCP + {{- end }} + {{- with .Values.deployment.probes }} + {{- with .readiness }} + readinessProbe: + {{- with .failureThreshold }} + failureThreshold: {{ . }} + {{- end }} + httpGet: + path: {{ .path }} + port: {{ .port }} + scheme: {{ .scheme }} + {{- with .periodSeconds }} + periodSeconds: {{ . }} + {{- end }} + {{- with .successThreshold }} + successThreshold: {{ . }} + {{- end }} + {{- with .timeoutSeconds}} + timeoutSeconds: {{ . }} + {{- end }} + initialDelaySeconds: {{ .initialDelaySeconds }} + {{- end }} + {{- end }} + {{- with .Values.deployment.resources }} + resources: + {{- with .limits }} + limits: + {{- with .memory }} + memory: "{{ . }}" + {{- end }} + {{- with .cpu }} + cpu: "{{ . }}" + {{- end }} + {{- end }} + {{- with .requests }} + requests: + {{- with .memory }} + memory: "{{ . }}" + {{- end }} + {{- with .cpu }} + cpu: "{{ . }}" + {{- end }} + {{- end }} + {{- end }} + + {{- with .Values.deployment.hostAliases }} + hostAliases: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.deployment.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.deployment.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + terminationGracePeriodSeconds: {{ .Values.deployment.terminationGracePeriodSeconds | default "300" }} + {{- if .Values.deployment.serviceAccount.enabled }} + serviceAccountName: {{ .Values.namespace }} + {{- end }} + {{- if .Values.securityContext }} + podSecurityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + {{- end }} +{{- end }} diff --git a/helm-charts/onfs-api-server/templates/external-secrets.yaml b/helm-charts/onfs-api-server/templates/external-secrets.yaml new file mode 100644 index 00000000..0d602056 --- /dev/null +++ b/helm-charts/onfs-api-server/templates/external-secrets.yaml @@ -0,0 +1,30 @@ +{{- if and .Values.externalSecret .Values.externalSecret.enabled }} +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} +{{- if .Values.externalSecret.annotations }} + annotations: +{{ toYaml .Values.externalSecret.annotations | indent 4 }} +{{- end }} + {{- if and .Values.deployment .Values.deployment.enabled }} + name: {{ .Values.deployment.envFrom.secretRef }} + {{- end}} + namespace: {{ .Values.namespace }} +spec: + dataFrom: + - extract: + conversionStrategy: Default + key: {{ .Values.externalSecret.path }} + refreshInterval: 15s + secretStoreRef: + kind: {{ .Values.infrastructure.secretStore.kind | default "ClusterSecretStore" }} + name: {{ .Values.infrastructure.secretStore.name | default "vault-backend" }} + target: + creationPolicy: Owner + deletionPolicy: Retain + {{- if and .Values.deployment .Values.deployment.enabled }} + name: {{ .Values.deployment.envFrom.secretRef }} + {{- end}} +{{- end }} diff --git a/helm-charts/onfs-api-server/templates/httpproxy.yaml b/helm-charts/onfs-api-server/templates/httpproxy.yaml new file mode 100644 index 00000000..94fe8f3e --- /dev/null +++ b/helm-charts/onfs-api-server/templates/httpproxy.yaml @@ -0,0 +1,50 @@ +{{- if and .Values.ingress .Values.ingress.enabled -}} +{{- if .Values.createContourGateway -}} +{{- if or ( eq "contour-internal" .Values.ingress.ingressClassName ) ( eq "contour-external" .Values.ingress.ingressClassName ) ( eq "contour-internal-0" .Values.ingress.ingressClassName ) ( eq "contour-internal-1" .Values.ingress.ingressClassName ) ( eq "contour-external-0" .Values.ingress.ingressClassName ) ( eq "contour-external-1" .Values.ingress.ingressClassName ) }} +{{- $servicePortNumber := .Values.ingress.servicePortNumber -}} +{{- $pathType := .Values.ingress.pathType -}} +{{- $namespace := .Values.namespace -}} +{{- $ingressClassName := .Values.ingress.ingressClassName -}} +{{ $count := 0 | int }} +{{- range .Values.ingress.hosts }} +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + namespace: {{ $namespace }} + name: {{ $namespace }}-{{ $count }} + labels: +{{ include "labels.common" $ | indent 4 }} +{{ include "labels.chart" $ | indent 4 }} + annotations: + projectcontour.io/ingress.class: {{ $.Values.ingress.ingressClassName }} +spec: + ingressClassName: {{ $ingressClassName }} + virtualhost: + fqdn: "{{ .host }}" + includes: + {{- range .paths }} + - conditions: + {{- if or ( eq ( lower .pathType ) "prefix" ) ( eq ( lower .pathType ) "implementationspecific") }} + - prefix: {{ .path }} + {{- end }} + {{- if ( eq ( lower .pathType ) "exact" ) }} + - header: + name: :path + exact: {{ .path }} + - prefix: {{ .path }} + {{- end }} + {{- if .targetService }} + name: {{ .targetService | replace "/" "-" }} + namespace: {{ (split "/" .targetService)._0 }} + {{- else }} + name: {{ $namespace }} + namespace: {{ $namespace }} + {{- end }} + {{- end }} + {{ $count = add1 $count }} +--- + +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/helm-charts/onfs-api-server/templates/otel-secret.yaml b/helm-charts/onfs-api-server/templates/otel-secret.yaml new file mode 100644 index 00000000..c2514b7d --- /dev/null +++ b/helm-charts/onfs-api-server/templates/otel-secret.yaml @@ -0,0 +1,26 @@ +{{ if .Values.otel_enabled }} +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} + annotations: + flagger.app/config-tracking: disabled + name: {{ if and .Values.deployment .Values.deployment.envFrom }}{{ .Values.deployment.envFrom.secretRef }}{{ else }}{{ .Values.namespace }}{{ end }}-otel + namespace: {{ .Values.namespace }} +spec: + dataFrom: + - extract: + conversionStrategy: Default + key: {{ .Values.infrastructure.vault.otelTokenPath | default "org/prd/cntr/devop/coralogix-token" }} + refreshInterval: 15s + secretStoreRef: + kind: {{ .Values.infrastructure.secretStore.kind | default "ClusterSecretStore" }} + name: {{ .Values.infrastructure.secretStore.name | default "vault-backend" }} + target: + creationPolicy: Owner + deletionPolicy: Retain + {{- if and .Values.deployment .Values.deployment.enabled }} + name: {{ .Values.deployment.envFrom.secretRef }}-otel + {{- end}} +{{ end }} diff --git a/helm-charts/onfs-api-server/templates/pdb.yaml b/helm-charts/onfs-api-server/templates/pdb.yaml new file mode 100644 index 00000000..db87f89a --- /dev/null +++ b/helm-charts/onfs-api-server/templates/pdb.yaml @@ -0,0 +1,36 @@ +{{- if or (and .Values.podDisruptionBudget .Values.podDisruptionBudget.enabled) (and .Values.deployment .Values.deployment.enabled) }} +{{- if or ( and .Values.deployment (.Values.deployment.enabled) (.Values.autoscaling.enabled) ( gt (int .Values.autoscaling.minReplicas) 1)) ( and (eq .Values.autoscaling.enabled false) .Values.deployment ( gt ( int .Values.deployment.replicaCount) 1 )) }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +spec: + {{- if .Values.podDisruptionBudget.enabled }} + maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }} + {{- else }} + {{- if and .Values.deployment .Values.deployment.enabled }} + {{- if (eq .Values.deployment.updateStrategy.strategy.type "RollingUpdate") }} + maxUnavailable: {{ .Values.deployment.updateStrategy.strategy.rollingUpdate.maxSurge | default "10%" }} + {{- else }} + maxUnavailable: "10%" + {{- end }} + {{ else }} + maxUnavailable: "10%" + {{- end }} + {{- end }} + {{- if .Values.podDisruptionBudget.minAvailable }} + minAvailable: {{ .Values.podDisruptionBudget.minAvailable }} + {{- end }} + selector: + matchLabels: + {{- if and (.Values.canary.enabled) (eq .Values.labels.env "prod") }} + {{- include "labels.primary-selector" . | nindent 6 }} + {{- else }} + {{- include "labels.selector" . | nindent 6 }} + {{- end }} +{{- end }} +{{- end }} diff --git a/helm-charts/onfs-api-server/templates/scaledobject.yaml b/helm-charts/onfs-api-server/templates/scaledobject.yaml new file mode 100644 index 00000000..259bfea2 --- /dev/null +++ b/helm-charts/onfs-api-server/templates/scaledobject.yaml @@ -0,0 +1,56 @@ +{{- if and .Values.autoscaling .Values.autoscaling.enabled }} +apiVersion: keda.sh/v1alpha1 +kind: ScaledObject +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ .Values.namespace }} + pollingInterval: {{ .Values.autoscaling.pollingInterval }} + {{- if ( default false (.Values.disasterRecovery).enabled ) }} + minReplicaCount: 1 + {{- else }} + minReplicaCount: {{ .Values.canary.minCanaryReplicas | default $.Values.autoscaling.minReplicas }} + {{- end }} + maxReplicaCount: {{ .Values.canary.maxCanaryReplicas | default $.Values.autoscaling.maxReplicas }} + advanced: + horizontalPodAutoscalerConfig: + behavior: + scaleDown: + stabilizationWindowSeconds: {{ .Values.autoscaling.scaledown.stabilizationWindowSeconds }} + policies: + {{- range .Values.autoscaling.scaledown.policies }} + - type: {{ .type }} + value: {{ .value }} + periodSeconds: {{ .periodseconds }} + {{- end }} + selectPolicy: {{ .Values.autoscaling.scaledown.selectpolicy }} + scaleUp: + stabilizationWindowSeconds: {{ .Values.autoscaling.scaleup.stabilizationWindowSeconds }} + policies: + {{- range .Values.autoscaling.scaleup.policies }} + - type: {{ .type }} + value: {{ .value }} + periodSeconds: {{ .periodseconds }} + {{- end }} + selectPolicy: {{ .Values.autoscaling.scaleup.selectpolicy }} + triggers: + {{- if ( default false (.Values.disasterRecovery).enabled ) }} + {{- range $.Values.autoscaling.triggers }} + {{- if or (eq .type "cpu") (eq .type "memory") }} + - metadata: + {{- toYaml .metadata | nindent 8 }} + type: {{ .type }} + metricType: "Utilization" + {{- end }} + {{- end }} + {{- else }} + {{- toYaml .Values.autoscaling.triggers | nindent 2 }} + {{ end }} + +{{- end }} diff --git a/helm-charts/onfs-api-server/templates/service.yaml b/helm-charts/onfs-api-server/templates/service.yaml new file mode 100644 index 00000000..8fcc5bc8 --- /dev/null +++ b/helm-charts/onfs-api-server/templates/service.yaml @@ -0,0 +1,29 @@ +{{- if and .Values.service .Values.service.enabled }} +{{- if or (eq .Values.canary.enabled false) ( and (.Values.canary.enabled) (ne .Values.labels.env "prod")) }} +apiVersion: v1 +kind: Service +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} +{{- if .Values.service.annotations }} + annotations: +{{ toYaml .Values.service.annotations | indent 4 }} +{{- end }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + {{- range .Values.service.ports }} + - name: {{ .name }} + port: {{ .port }} + protocol: {{ .protocol }} + targetPort: {{ .targetPort }} + {{- end }} + selector: + {{- include "labels.selector" . | nindent 4 }} +{{- end }} +{{- end }} diff --git a/helm-charts/onfs-api-server/templates/serviceaccount.yaml b/helm-charts/onfs-api-server/templates/serviceaccount.yaml new file mode 100644 index 00000000..f05362f5 --- /dev/null +++ b/helm-charts/onfs-api-server/templates/serviceaccount.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.deployment (.Values.deployment.enabled) (.Values.deployment.serviceAccount.enabled) }} +apiVersion: v1 +kind: ServiceAccount +metadata: + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} + {{- with .Values.deployment.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/helm-charts/onfs-api-server/values.yaml b/helm-charts/onfs-api-server/values.yaml new file mode 100644 index 00000000..68f70e45 --- /dev/null +++ b/helm-charts/onfs-api-server/values.yaml @@ -0,0 +1,238 @@ +# Default values for onfs-api-server helm chart + +namespace: prd-onfs-api-server +applicationName: onfs-api-server +replicaCount: 2 + +labels: + env: prd + team: bharatml + bu: ml + priority: p1 + priority_v2: cp3 + service_type: "" + +priorityClassName: "" + +telegraf: + enabled: false + +otel_enabled: false + +infrastructure: + secretStore: + name: vault-backend + kind: ClusterSecretStore + vault: + basePath: "" + otelTokenPath: "" + +deployment: + enabled: true + replicaCount: 2 + revisionHistoryLimit: 3 + image: + repository: ghcr.io/meesho/onfs-api-server + tag: latest + pullPolicy: IfNotPresent + ports: + - containerPort: 8089 + name: grpc + protocol: TCP + probes: + liveness: + path: /health/self + port: 8089 + scheme: HTTP + initialDelaySeconds: 30 + periodSeconds: 10 + failureThreshold: 3 + successThreshold: 1 + timeoutSeconds: 5 + readiness: + path: /health/self + port: 8089 + scheme: HTTP + initialDelaySeconds: 20 + periodSeconds: 10 + failureThreshold: 3 + successThreshold: 1 + timeoutSeconds: 5 + resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "1Gi" + cpu: "1000m" + env: + - name: APP_ENV + value: "local" + - name: APP_LOG_LEVEL + value: "DEBUG" + - name: APP_METRIC_SAMPLING_RATE + value: "1" + - name: APP_NAME + value: "onfs" + - name: APP_PORT + value: "8089" + - name: AUTH_TOKEN + value: "test" + - name: ENABLE_HTTP_API + value: "true" + # Etcd + - name: ETCD_SERVER + value: "http://etcd:2379" + - name: ETCD_WATCHER_ENABLED + value: "true" + # ScyllaDB + - name: STORAGE_SCYLLA_1_CONTACT_POINTS + value: "scylla" + - name: STORAGE_SCYLLA_1_KEYSPACE + value: "onfs" + - name: STORAGE_SCYLLA_1_PORT + value: "9042" + - name: STORAGE_SCYLLA_1_NUM_CONNS + value: "1" + - name: STORAGE_SCYLLA_1_TIMEOUT_IN_MS + value: "300000" + - name: STORAGE_SCYLLA_1_USERNAME + value: "" + - name: STORAGE_SCYLLA_1_PASSWORD + value: "" + - name: STORAGE_SCYLLA_1_MAJOR_VERSION + value: "5" + - name: STORAGE_SCYLLA_1_SCYLLA_VERSION + value: "5" + - name: STORAGE_SCYLLA_ACTIVE_CONFIG_IDS + value: "1" + # Redis + - name: STORAGE_REDIS_STANDALONE_2_ADDR + value: "redis:6379" + - name: STORAGE_REDIS_STANDALONE_2_DB + value: "0" + - name: STORAGE_REDIS_STANDALONE_2_DISABLE_IDENTITY + value: "true" + - name: STORAGE_REDIS_STANDALONE_2_MAX_IDLE_CONN + value: "32" + - name: STORAGE_REDIS_STANDALONE_2_MIN_IDLE_CONN + value: "20" + - name: STORAGE_REDIS_STANDALONE_2_MAX_ACTIVE_CONN + value: "32" + - name: STORAGE_REDIS_STANDALONE_2_MAX_RETRY + value: "-1" + - name: STORAGE_REDIS_STANDALONE_2_POOL_FIFO + value: "false" + - name: STORAGE_REDIS_STANDALONE_2_READ_TIMEOUT_IN_MS + value: "300" + - name: STORAGE_REDIS_STANDALONE_2_WRITE_TIMEOUT_IN_MS + value: "300" + - name: STORAGE_REDIS_STANDALONE_2_POOL_TIMEOUT_IN_MS + value: "300" + - name: STORAGE_REDIS_STANDALONE_2_POOL_SIZE + value: "32" + - name: STORAGE_REDIS_STANDALONE_2_CONN_MAX_IDLE_TIMEOUT_IN_MINUTES + value: "15" + - name: STORAGE_REDIS_STANDALONE_2_CONN_MAX_AGE_IN_MINUTES + value: "30" + - name: STORAGE_REDIS_STANDALONE_ACTIVE_CONFIG_IDS + value: "2" + - name: DISTRIBUTED_CACHE_CONF_IDS + value: "2" + # In-memory cache + - name: IN_MEM_CACHE_3_ENABLED + value: "true" + - name: IN_MEM_CACHE_3_NAME + value: "onfs" + - name: IN_MEM_CACHE_3_SIZE_IN_BYTES + value: "100000" + - name: IN_MEM_CACHE_ACTIVE_CONFIG_IDS + value: "3" + # P2P cache + - name: P2P_CACHE_5_ENABLED + value: "true" + - name: P2P_CACHE_5_CLUSTER_NAME + value: "onfs-cluster" + - name: P2P_CACHE_5_NAME + value: "p2p-onfs" + - name: P2P_CACHE_5_OWN_PARTITION_SIZE_IN_BYTES + value: "100000" + - name: P2P_CACHE_5_GLOBAL_SIZE_IN_BYTES + value: "1000" + - name: P2P_CACHE_5_GLOBAL_CACHE_TTL_IN_SECONDS + value: "3600" + - name: P2P_CACHE_5_NUM_CLIENTS + value: "2" + - name: P2P_CACHE_5_SERVER_PORT + value: "8088" + - name: P2P_CACHE_ACTIVE_CONFIG_IDS + value: "5" + serviceAccount: + enabled: false + annotations: {} + updateStrategy: + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 0 + maxSurge: 1 + terminationGracePeriodSeconds: 30 + +service: + enabled: true + type: ClusterIP + ports: + - name: grpc + port: 80 + targetPort: 8089 + protocol: TCP + +autoscaling: + enabled: false + minReplicas: 2 + maxReplicas: 10 + pollingInterval: 30 + scaledown: + stabilizationWindowSeconds: 300 + policies: + - type: Percent + value: 10 + periodseconds: 60 + selectpolicy: Min + scaleup: + stabilizationWindowSeconds: 0 + policies: + - type: Percent + value: 50 + periodseconds: 60 + selectpolicy: Max + triggers: + - type: cpu + metadata: + value: "70" + metricType: Utilization + +ingress: + enabled: false + ingressClassName: contour-internal +createContourGateway: false + +externalSecret: + enabled: false + path: "" + +configMap: + enabled: false + +canary: + enabled: false + promURL: "" + slackChannel: "" + slackWebhookURL: "" + +podDisruptionBudget: + enabled: false + maxUnavailable: "10%" + +disasterRecovery: + enabled: false diff --git a/helm-charts/onfs-consumer/Chart.yaml b/helm-charts/onfs-consumer/Chart.yaml new file mode 100644 index 00000000..07ef952c --- /dev/null +++ b/helm-charts/onfs-consumer/Chart.yaml @@ -0,0 +1,10 @@ +apiVersion: v2 +name: onfs-consumer +description: A Helm chart for the Online Feature Store Kafka Consumer +type: application +version: 1.0.0 +appVersion: "1.0.0" + +maintainers: + - name: BharatMLStack Team + email: ml-oss@meesho.com diff --git a/helm-charts/onfs-consumer/templates/NOTES.txt b/helm-charts/onfs-consumer/templates/NOTES.txt new file mode 100644 index 00000000..35a4f3c2 --- /dev/null +++ b/helm-charts/onfs-consumer/templates/NOTES.txt @@ -0,0 +1,18 @@ +{{ .Chart.Name }} has been deployed. + +Namespace: {{ .Values.namespace }} +Application: {{ .Values.applicationName }} + +{{- if .Values.service.enabled }} +Service: {{ .Values.namespace }}:{{ (index .Values.service.ports 0).port }} +{{- end }} + +{{- if .Values.ingress.enabled }} +Ingress is enabled. +{{- end }} + +{{- if .Values.autoscaling.enabled }} +Autoscaling: {{ .Values.autoscaling.minReplicas }} - {{ .Values.autoscaling.maxReplicas }} replicas +{{- else }} +Replicas: {{ .Values.replicaCount }} +{{- end }} diff --git a/helm-charts/onfs-consumer/templates/_helpers.tpl b/helm-charts/onfs-consumer/templates/_helpers.tpl new file mode 100644 index 00000000..168e0342 --- /dev/null +++ b/helm-charts/onfs-consumer/templates/_helpers.tpl @@ -0,0 +1,70 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "fullname" -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "labels.selector" -}} +app: {{ .Values.namespace }} +{{- end -}} + +{{- define "labels.primary-selector" -}} +app: {{ .Values.namespace }}-primary +{{- end -}} + +{{- define "labels.common" -}} +{{ template "labels.selector" . }} +{{- if and .Values.deployment .Values.deployment.image }} +version: {{ .Values.deployment.image.tag }} +{{- end }} +env: {{ .Values.labels.env }} +team: {{ .Values.labels.team }} +bu: {{ .Values.labels.bu }} +service: {{ .Values.applicationName }} +priority: {{ .Values.labels.priority }} +priority_v2: {{ .Values.labels.priority_v2 | default "cp3" }} +primary_owner: {{ .Values.labels.primary_owner | default .Values.labels.team }} +secondary_owner: {{ .Values.labels.secondary_owner | default .Values.labels.team }} +service_type: {{ .Values.labels.service_type | default "" | replace "," "-" | quote }} +{{- end -}} + +{{- define "labels.chart" -}} +chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" +release: {{ .Release.Name | quote }} +heritage: {{ .Release.Service | quote }} +{{- end -}} + +{{/* +Renders a value that contains template. +Usage: +{{ include "application.tplvalues.render" ( dict "value" .Values.path.to.the.Value "context" $) }} +*/}} + +{{- define "application.tplvalues.render" -}} + {{- if typeIs "string" .value }} + {{- tpl .value .context }} + {{- else }} + {{- tpl (.value | toYaml) .context }} + {{- end }} +{{- end -}} + +{{- define "canary.promURL" -}} +{{- if .Values.canary.promURL }} +{{- .Values.canary.promURL }} +{{- else if eq .Values.labels.env "prod" }} +prod-ops-metricsui.example.com/select/100/ +{{- else }} +https://sb-ops-metricsui.example.com/select/100/ +{{- end }} +{{- end -}} diff --git a/helm-charts/onfs-consumer/templates/alert-provider.yaml b/helm-charts/onfs-consumer/templates/alert-provider.yaml new file mode 100644 index 00000000..a300bb14 --- /dev/null +++ b/helm-charts/onfs-consumer/templates/alert-provider.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.canary (.Values.canary.enabled) .Values.canary.slackWebhookURL (ne .Values.canary.slackWebhookURL "") }} +apiVersion: flagger.app/v1beta1 +kind: AlertProvider +metadata: + name: flagger-status + namespace: {{ .Values.namespace }} +spec: + type: slack + {{- if .Values.canary.slackChannel }} + channel: {{ .Values.canary.slackChannel }} + {{- end }} + username: flagger + address: {{ .Values.canary.slackWebhookURL }} +{{- end }} diff --git a/helm-charts/onfs-consumer/templates/configmap.yaml b/helm-charts/onfs-consumer/templates/configmap.yaml new file mode 100644 index 00000000..fc527219 --- /dev/null +++ b/helm-charts/onfs-consumer/templates/configmap.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.configMap .Values.configMap.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Values.namespace }}-config + namespace: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +data: + {{- range $key, $value := .Values.configMap.data }} + {{ $key }}: {{ $value | quote }} + {{- end }} +{{- end }} diff --git a/helm-charts/onfs-consumer/templates/deployment.yaml b/helm-charts/onfs-consumer/templates/deployment.yaml new file mode 100644 index 00000000..34cbb1ed --- /dev/null +++ b/helm-charts/onfs-consumer/templates/deployment.yaml @@ -0,0 +1,237 @@ +{{- if and .Values.deployment .Values.deployment.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.namespace }} + namespace: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +{{- include "labels.selector" . | nindent 4 }} +spec: + {{- with .Values.deployment.minReadySeconds }} + minReadySeconds: {{ . }} + {{- end }} + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + revisionHistoryLimit: {{ .Values.deployment.revisionHistoryLimit }} + selector: + matchLabels: +{{- include "labels.selector" . | nindent 6 }} +{{- with .Values.deployment.updateStrategy }} +{{ toYaml . | indent 2 -}} +{{- end }} + template: + metadata: + annotations: + {{- with .Values.deployment.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.telegraf.enabled }} + telegraf.influxdata.com/class: "infra" + {{- end }} + labels: + {{- include "labels.common" . | nindent 8 }} + spec: + {{- if .Values.priorityClassName }} + priorityClassName: {{ .Values.priorityClassName }} + {{- end }} + topologySpreadConstraints: + - maxSkew: 1 + topologyKey: topology.kubernetes.io/zone + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: +{{- include "labels.selector" . | nindent 12 }} + {{- if .Values.deployment.image.pullSecret }} + imagePullSecrets: + - name: {{ .Values.deployment.image.pullSecret }} + {{- end }} + {{- if .Values.deployment.volumes }} + volumes: + {{- toYaml .Values.deployment.volumes | nindent 8 }} + {{- end }} + {{- if .Values.deployment.initContainers }} + initContainers: + {{- toYaml .Values.deployment.initContainers | nindent 8 }} + {{- end }} + containers: + - name: {{ .Values.applicationName }} + {{- if .Values.deployment.volumeMounts }} + volumeMounts: + {{- toYaml .Values.deployment.volumeMounts | nindent 12 }} + {{- end }} + {{- if .Values.deployment.command }} + command: {{- include "application.tplvalues.render" (dict "value" .Values.deployment.command "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.deployment.args }} + args: {{- include "application.tplvalues.render" (dict "value" .Values.deployment.args "context" $) | nindent 12 }} + {{- end }} + image: "{{ .Values.deployment.image.repository }}:{{ .Values.deployment.image.tag }}" + imagePullPolicy: {{ .Values.deployment.image.pullPolicy }} + {{- if .Values.deployment.lifecycle }} + lifecycle: {{ toYaml .Values.deployment.lifecycle | nindent 12 }} + {{- end }} + {{- with .Values.deployment.probes }} + {{- with .liveness }} + livenessProbe: + {{- with .failureThreshold }} + failureThreshold: {{ . }} + {{- end }} + httpGet: + path: {{ .path }} + port: {{ .port }} + scheme: {{ .scheme }} + {{- with .periodSeconds }} + periodSeconds: {{ . }} + {{- end }} + {{- with .successThreshold }} + successThreshold: {{ . }} + {{- end }} + {{- with .timeoutSeconds }} + timeoutSeconds: {{ . }} + {{- end }} + initialDelaySeconds: {{ .initialDelaySeconds }} + {{- end }} + {{- end }} + {{- if or .Values.externalSecret.enabled .Values.otel_enabled (and .Values.configMap .Values.configMap.enabled) }} + envFrom: + {{- if .Values.externalSecret.enabled }} + - secretRef: + name: {{ .Values.deployment.envFrom.secretRef }} + {{- if ( default false (.Values.disasterRecovery).enabled ) }} + - secretRef: + name: {{ .Values.deployment.envFrom.secretRef }}-dr + {{- end }} + {{- end }} + {{- if .Values.otel_enabled }} + - secretRef: + name: {{ .Values.deployment.envFrom.secretRef }}-otel + {{- end }} + {{- if and .Values.configMap .Values.configMap.enabled }} + - configMapRef: + name: {{ .Values.namespace }}-config + {{- end }} + {{- end }} + env: + - name: TZ + value: Asia/Kolkata + - name: NODE_IP + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + {{- if .Values.telegraf.enabled }} + - name: TELEGRAF_UDP_HOST + valueFrom: + fieldRef: + fieldPath: status.podIP + {{- end }} + {{- if .Values.otel_enabled }} + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: http://$(NODE_IP):4317 + {{- end }} + {{- with .Values.deployment.env }} + {{- range . }} + - name: {{ .name }} + value: "{{ .value }}" + {{- end }} + {{- end }} + {{- with .Values.deployment.ports }} + ports: + {{- range . }} + - containerPort: {{ .containerPort }} + name: {{ .name }} + protocol: {{ .protocol }} + {{- end }} + {{- end }} + {{- if .Values.telegraf.enabled }} + - containerPort: 9273 + name: telegraf-sc + protocol: TCP + {{- end }} + {{- with .Values.deployment.probes }} + {{- with .readiness }} + readinessProbe: + {{- with .failureThreshold }} + failureThreshold: {{ . }} + {{- end }} + httpGet: + path: {{ .path }} + port: {{ .port }} + scheme: {{ .scheme }} + {{- with .periodSeconds }} + periodSeconds: {{ . }} + {{- end }} + {{- with .successThreshold }} + successThreshold: {{ . }} + {{- end }} + {{- with .timeoutSeconds}} + timeoutSeconds: {{ . }} + {{- end }} + initialDelaySeconds: {{ .initialDelaySeconds }} + {{- end }} + {{- end }} + {{- with .Values.deployment.resources }} + resources: + {{- with .limits }} + limits: + {{- with .memory }} + memory: "{{ . }}" + {{- end }} + {{- with .cpu }} + cpu: "{{ . }}" + {{- end }} + {{- end }} + {{- with .requests }} + requests: + {{- with .memory }} + memory: "{{ . }}" + {{- end }} + {{- with .cpu }} + cpu: "{{ . }}" + {{- end }} + {{- end }} + {{- end }} + + {{- with .Values.deployment.hostAliases }} + hostAliases: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.deployment.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.deployment.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + terminationGracePeriodSeconds: {{ .Values.deployment.terminationGracePeriodSeconds | default "300" }} + {{- if .Values.deployment.serviceAccount.enabled }} + serviceAccountName: {{ .Values.namespace }} + {{- end }} + {{- if .Values.securityContext }} + podSecurityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + {{- end }} +{{- end }} diff --git a/helm-charts/onfs-consumer/templates/external-secrets.yaml b/helm-charts/onfs-consumer/templates/external-secrets.yaml new file mode 100644 index 00000000..0d602056 --- /dev/null +++ b/helm-charts/onfs-consumer/templates/external-secrets.yaml @@ -0,0 +1,30 @@ +{{- if and .Values.externalSecret .Values.externalSecret.enabled }} +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} +{{- if .Values.externalSecret.annotations }} + annotations: +{{ toYaml .Values.externalSecret.annotations | indent 4 }} +{{- end }} + {{- if and .Values.deployment .Values.deployment.enabled }} + name: {{ .Values.deployment.envFrom.secretRef }} + {{- end}} + namespace: {{ .Values.namespace }} +spec: + dataFrom: + - extract: + conversionStrategy: Default + key: {{ .Values.externalSecret.path }} + refreshInterval: 15s + secretStoreRef: + kind: {{ .Values.infrastructure.secretStore.kind | default "ClusterSecretStore" }} + name: {{ .Values.infrastructure.secretStore.name | default "vault-backend" }} + target: + creationPolicy: Owner + deletionPolicy: Retain + {{- if and .Values.deployment .Values.deployment.enabled }} + name: {{ .Values.deployment.envFrom.secretRef }} + {{- end}} +{{- end }} diff --git a/helm-charts/onfs-consumer/templates/httpproxy.yaml b/helm-charts/onfs-consumer/templates/httpproxy.yaml new file mode 100644 index 00000000..94fe8f3e --- /dev/null +++ b/helm-charts/onfs-consumer/templates/httpproxy.yaml @@ -0,0 +1,50 @@ +{{- if and .Values.ingress .Values.ingress.enabled -}} +{{- if .Values.createContourGateway -}} +{{- if or ( eq "contour-internal" .Values.ingress.ingressClassName ) ( eq "contour-external" .Values.ingress.ingressClassName ) ( eq "contour-internal-0" .Values.ingress.ingressClassName ) ( eq "contour-internal-1" .Values.ingress.ingressClassName ) ( eq "contour-external-0" .Values.ingress.ingressClassName ) ( eq "contour-external-1" .Values.ingress.ingressClassName ) }} +{{- $servicePortNumber := .Values.ingress.servicePortNumber -}} +{{- $pathType := .Values.ingress.pathType -}} +{{- $namespace := .Values.namespace -}} +{{- $ingressClassName := .Values.ingress.ingressClassName -}} +{{ $count := 0 | int }} +{{- range .Values.ingress.hosts }} +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + namespace: {{ $namespace }} + name: {{ $namespace }}-{{ $count }} + labels: +{{ include "labels.common" $ | indent 4 }} +{{ include "labels.chart" $ | indent 4 }} + annotations: + projectcontour.io/ingress.class: {{ $.Values.ingress.ingressClassName }} +spec: + ingressClassName: {{ $ingressClassName }} + virtualhost: + fqdn: "{{ .host }}" + includes: + {{- range .paths }} + - conditions: + {{- if or ( eq ( lower .pathType ) "prefix" ) ( eq ( lower .pathType ) "implementationspecific") }} + - prefix: {{ .path }} + {{- end }} + {{- if ( eq ( lower .pathType ) "exact" ) }} + - header: + name: :path + exact: {{ .path }} + - prefix: {{ .path }} + {{- end }} + {{- if .targetService }} + name: {{ .targetService | replace "/" "-" }} + namespace: {{ (split "/" .targetService)._0 }} + {{- else }} + name: {{ $namespace }} + namespace: {{ $namespace }} + {{- end }} + {{- end }} + {{ $count = add1 $count }} +--- + +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/helm-charts/onfs-consumer/templates/otel-secret.yaml b/helm-charts/onfs-consumer/templates/otel-secret.yaml new file mode 100644 index 00000000..c2514b7d --- /dev/null +++ b/helm-charts/onfs-consumer/templates/otel-secret.yaml @@ -0,0 +1,26 @@ +{{ if .Values.otel_enabled }} +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} + annotations: + flagger.app/config-tracking: disabled + name: {{ if and .Values.deployment .Values.deployment.envFrom }}{{ .Values.deployment.envFrom.secretRef }}{{ else }}{{ .Values.namespace }}{{ end }}-otel + namespace: {{ .Values.namespace }} +spec: + dataFrom: + - extract: + conversionStrategy: Default + key: {{ .Values.infrastructure.vault.otelTokenPath | default "org/prd/cntr/devop/coralogix-token" }} + refreshInterval: 15s + secretStoreRef: + kind: {{ .Values.infrastructure.secretStore.kind | default "ClusterSecretStore" }} + name: {{ .Values.infrastructure.secretStore.name | default "vault-backend" }} + target: + creationPolicy: Owner + deletionPolicy: Retain + {{- if and .Values.deployment .Values.deployment.enabled }} + name: {{ .Values.deployment.envFrom.secretRef }}-otel + {{- end}} +{{ end }} diff --git a/helm-charts/onfs-consumer/templates/pdb.yaml b/helm-charts/onfs-consumer/templates/pdb.yaml new file mode 100644 index 00000000..db87f89a --- /dev/null +++ b/helm-charts/onfs-consumer/templates/pdb.yaml @@ -0,0 +1,36 @@ +{{- if or (and .Values.podDisruptionBudget .Values.podDisruptionBudget.enabled) (and .Values.deployment .Values.deployment.enabled) }} +{{- if or ( and .Values.deployment (.Values.deployment.enabled) (.Values.autoscaling.enabled) ( gt (int .Values.autoscaling.minReplicas) 1)) ( and (eq .Values.autoscaling.enabled false) .Values.deployment ( gt ( int .Values.deployment.replicaCount) 1 )) }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +spec: + {{- if .Values.podDisruptionBudget.enabled }} + maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }} + {{- else }} + {{- if and .Values.deployment .Values.deployment.enabled }} + {{- if (eq .Values.deployment.updateStrategy.strategy.type "RollingUpdate") }} + maxUnavailable: {{ .Values.deployment.updateStrategy.strategy.rollingUpdate.maxSurge | default "10%" }} + {{- else }} + maxUnavailable: "10%" + {{- end }} + {{ else }} + maxUnavailable: "10%" + {{- end }} + {{- end }} + {{- if .Values.podDisruptionBudget.minAvailable }} + minAvailable: {{ .Values.podDisruptionBudget.minAvailable }} + {{- end }} + selector: + matchLabels: + {{- if and (.Values.canary.enabled) (eq .Values.labels.env "prod") }} + {{- include "labels.primary-selector" . | nindent 6 }} + {{- else }} + {{- include "labels.selector" . | nindent 6 }} + {{- end }} +{{- end }} +{{- end }} diff --git a/helm-charts/onfs-consumer/templates/scaledobject.yaml b/helm-charts/onfs-consumer/templates/scaledobject.yaml new file mode 100644 index 00000000..259bfea2 --- /dev/null +++ b/helm-charts/onfs-consumer/templates/scaledobject.yaml @@ -0,0 +1,56 @@ +{{- if and .Values.autoscaling .Values.autoscaling.enabled }} +apiVersion: keda.sh/v1alpha1 +kind: ScaledObject +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ .Values.namespace }} + pollingInterval: {{ .Values.autoscaling.pollingInterval }} + {{- if ( default false (.Values.disasterRecovery).enabled ) }} + minReplicaCount: 1 + {{- else }} + minReplicaCount: {{ .Values.canary.minCanaryReplicas | default $.Values.autoscaling.minReplicas }} + {{- end }} + maxReplicaCount: {{ .Values.canary.maxCanaryReplicas | default $.Values.autoscaling.maxReplicas }} + advanced: + horizontalPodAutoscalerConfig: + behavior: + scaleDown: + stabilizationWindowSeconds: {{ .Values.autoscaling.scaledown.stabilizationWindowSeconds }} + policies: + {{- range .Values.autoscaling.scaledown.policies }} + - type: {{ .type }} + value: {{ .value }} + periodSeconds: {{ .periodseconds }} + {{- end }} + selectPolicy: {{ .Values.autoscaling.scaledown.selectpolicy }} + scaleUp: + stabilizationWindowSeconds: {{ .Values.autoscaling.scaleup.stabilizationWindowSeconds }} + policies: + {{- range .Values.autoscaling.scaleup.policies }} + - type: {{ .type }} + value: {{ .value }} + periodSeconds: {{ .periodseconds }} + {{- end }} + selectPolicy: {{ .Values.autoscaling.scaleup.selectpolicy }} + triggers: + {{- if ( default false (.Values.disasterRecovery).enabled ) }} + {{- range $.Values.autoscaling.triggers }} + {{- if or (eq .type "cpu") (eq .type "memory") }} + - metadata: + {{- toYaml .metadata | nindent 8 }} + type: {{ .type }} + metricType: "Utilization" + {{- end }} + {{- end }} + {{- else }} + {{- toYaml .Values.autoscaling.triggers | nindent 2 }} + {{ end }} + +{{- end }} diff --git a/helm-charts/onfs-consumer/templates/service.yaml b/helm-charts/onfs-consumer/templates/service.yaml new file mode 100644 index 00000000..8fcc5bc8 --- /dev/null +++ b/helm-charts/onfs-consumer/templates/service.yaml @@ -0,0 +1,29 @@ +{{- if and .Values.service .Values.service.enabled }} +{{- if or (eq .Values.canary.enabled false) ( and (.Values.canary.enabled) (ne .Values.labels.env "prod")) }} +apiVersion: v1 +kind: Service +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} +{{- if .Values.service.annotations }} + annotations: +{{ toYaml .Values.service.annotations | indent 4 }} +{{- end }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + {{- range .Values.service.ports }} + - name: {{ .name }} + port: {{ .port }} + protocol: {{ .protocol }} + targetPort: {{ .targetPort }} + {{- end }} + selector: + {{- include "labels.selector" . | nindent 4 }} +{{- end }} +{{- end }} diff --git a/helm-charts/onfs-consumer/templates/serviceaccount.yaml b/helm-charts/onfs-consumer/templates/serviceaccount.yaml new file mode 100644 index 00000000..f05362f5 --- /dev/null +++ b/helm-charts/onfs-consumer/templates/serviceaccount.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.deployment (.Values.deployment.enabled) (.Values.deployment.serviceAccount.enabled) }} +apiVersion: v1 +kind: ServiceAccount +metadata: + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} + {{- with .Values.deployment.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/helm-charts/onfs-consumer/values.yaml b/helm-charts/onfs-consumer/values.yaml new file mode 100644 index 00000000..3704b181 --- /dev/null +++ b/helm-charts/onfs-consumer/values.yaml @@ -0,0 +1,237 @@ +# Default values for onfs-consumer helm chart + +namespace: prd-onfs-consumer +applicationName: onfs-consumer +replicaCount: 2 + +labels: + env: prd + team: bharatml + bu: ml + priority: p1 + priority_v2: cp3 + service_type: "" + +priorityClassName: "" + +telegraf: + enabled: false + +otel_enabled: false + +infrastructure: + secretStore: + name: vault-backend + kind: ClusterSecretStore + vault: + basePath: "" + otelTokenPath: "" + +deployment: + enabled: true + replicaCount: 2 + revisionHistoryLimit: 3 + image: + repository: ghcr.io/meesho/onfs-consumer + tag: latest + pullPolicy: IfNotPresent + ports: + - containerPort: 8090 + name: http + protocol: TCP + probes: + liveness: + path: /health/self + port: 8090 + scheme: HTTP + initialDelaySeconds: 30 + periodSeconds: 10 + failureThreshold: 3 + successThreshold: 1 + timeoutSeconds: 5 + readiness: + path: /health/self + port: 8090 + scheme: HTTP + initialDelaySeconds: 20 + periodSeconds: 10 + failureThreshold: 3 + successThreshold: 1 + timeoutSeconds: 5 + resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "1Gi" + cpu: "1000m" + env: + - name: APP_ENV + value: "local" + - name: APP_LOG_LEVEL + value: "DEBUG" + - name: APP_METRIC_SAMPLING_RATE + value: "1" + - name: APP_NAME + value: "onfs" + - name: APP_PORT + value: "8090" + # Kafka consumer + - name: KAFKA_CONSUMERS_FEATURE_CONSUMER_AUTO_COMMIT_INTERVAL_MS + value: "5000" + - name: KAFKA_CONSUMERS_FEATURE_CONSUMER_AUTO_OFFSET_RESET + value: "earliest" + - name: KAFKA_CONSUMERS_FEATURE_CONSUMER_BATCH_SIZE + value: "100" + - name: KAFKA_CONSUMERS_FEATURE_CONSUMER_BOOTSTRAP_SERVERS + value: "broker:29092" + - name: KAFKA_CONSUMERS_FEATURE_CONSUMER_CLIENT_ID + value: "onfs-consumer" + - name: KAFKA_CONSUMERS_FEATURE_CONSUMER_ENABLE_AUTO_COMMIT + value: "true" + - name: KAFKA_CONSUMERS_FEATURE_CONSUMER_GROUP_ID + value: "onfs-consumer-group" + - name: KAFKA_CONSUMERS_FEATURE_CONSUMER_LISTENER_CONCURRENCY + value: "2" + - name: KAFKA_CONSUMERS_FEATURE_CONSUMER_MAX_WORKERS + value: "50" + - name: KAFKA_CONSUMERS_FEATURE_CONSUMER_POLL_TIMEOUT + value: "1000" + - name: KAFKA_CONSUMERS_FEATURE_CONSUMER_SECURITY_PROTOCOL + value: "PLAINTEXT" + - name: KAFKA_CONSUMERS_FEATURE_CONSUMER_TOPIC + value: "online-feature-store.feature_ingestion" + - name: KAFKA_CONSUMERS_FEATURE_CONSUMER_BASIC_AUTH_CREDENTIAL_SOURCE + value: "USER_INFO" + - name: KAFKA_CONSUMERS_FEATURE_CONSUMER_SASL_MECHANISM + value: "PLAIN" + - name: KAFKA_CONSUMERS_FEATURE_CONSUMER_SASL_PASSWORD + value: "" + - name: KAFKA_CONSUMERS_FEATURE_CONSUMER_SASL_USERNAME + value: "" + # Etcd + - name: ETCD_SERVER + value: "http://etcd:2379" + - name: ETCD_WATCHER_ENABLED + value: "true" + # ScyllaDB + - name: STORAGE_SCYLLA_1_CONTACT_POINTS + value: "scylla" + - name: STORAGE_SCYLLA_1_KEYSPACE + value: "onfs" + - name: STORAGE_SCYLLA_1_PORT + value: "9042" + - name: STORAGE_SCYLLA_1_NUM_CONNS + value: "1" + - name: STORAGE_SCYLLA_1_TIMEOUT_IN_MS + value: "300000" + - name: STORAGE_SCYLLA_1_USERNAME + value: "" + - name: STORAGE_SCYLLA_1_PASSWORD + value: "" + - name: STORAGE_SCYLLA_1_MAJOR_VERSION + value: "5" + - name: STORAGE_SCYLLA_1_SCYLLA_VERSION + value: "5" + - name: STORAGE_SCYLLA_ACTIVE_CONFIG_IDS + value: "1" + # Redis + - name: STORAGE_REDIS_STANDALONE_2_ADDR + value: "redis:6379" + - name: STORAGE_REDIS_STANDALONE_2_DB + value: "0" + - name: STORAGE_REDIS_STANDALONE_2_DISABLE_IDENTITY + value: "true" + - name: STORAGE_REDIS_STANDALONE_2_MAX_IDLE_CONN + value: "32" + - name: STORAGE_REDIS_STANDALONE_2_MIN_IDLE_CONN + value: "20" + - name: STORAGE_REDIS_STANDALONE_2_MAX_ACTIVE_CONN + value: "32" + - name: STORAGE_REDIS_STANDALONE_2_MAX_RETRY + value: "-1" + - name: STORAGE_REDIS_STANDALONE_2_POOL_FIFO + value: "false" + - name: STORAGE_REDIS_STANDALONE_2_READ_TIMEOUT_IN_MS + value: "3000" + - name: STORAGE_REDIS_STANDALONE_2_WRITE_TIMEOUT_IN_MS + value: "3000" + - name: STORAGE_REDIS_STANDALONE_2_POOL_TIMEOUT_IN_MS + value: "3000" + - name: STORAGE_REDIS_STANDALONE_2_POOL_SIZE + value: "32" + - name: STORAGE_REDIS_STANDALONE_2_CONN_MAX_IDLE_TIMEOUT_IN_MINUTES + value: "15" + - name: STORAGE_REDIS_STANDALONE_2_CONN_MAX_AGE_IN_MINUTES + value: "30" + - name: STORAGE_REDIS_STANDALONE_ACTIVE_CONFIG_IDS + value: "2" + serviceAccount: + enabled: false + annotations: {} + updateStrategy: + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 0 + maxSurge: 1 + terminationGracePeriodSeconds: 30 + +service: + enabled: true + type: ClusterIP + ports: + - name: http + port: 80 + targetPort: 8090 + protocol: TCP + +autoscaling: + enabled: false + minReplicas: 2 + maxReplicas: 10 + pollingInterval: 30 + scaledown: + stabilizationWindowSeconds: 300 + policies: + - type: Percent + value: 10 + periodseconds: 60 + selectpolicy: Min + scaleup: + stabilizationWindowSeconds: 0 + policies: + - type: Percent + value: 50 + periodseconds: 60 + selectpolicy: Max + triggers: + - type: cpu + metadata: + value: "70" + metricType: Utilization + +ingress: + enabled: false + ingressClassName: contour-internal +createContourGateway: false + +externalSecret: + enabled: false + path: "" + +configMap: + enabled: false + +canary: + enabled: false + promURL: "" + slackChannel: "" + slackWebhookURL: "" + +podDisruptionBudget: + enabled: false + maxUnavailable: "10%" + +disasterRecovery: + enabled: false diff --git a/helm-charts/skye-admin/Chart.yaml b/helm-charts/skye-admin/Chart.yaml new file mode 100644 index 00000000..cf4aefd2 --- /dev/null +++ b/helm-charts/skye-admin/Chart.yaml @@ -0,0 +1,10 @@ +apiVersion: v2 +name: skye-admin +description: A Helm chart for the Skye Admin service (embedding platform administration) +type: application +version: 1.0.0 +appVersion: "1.0.0" + +maintainers: + - name: BharatMLStack Team + email: ml-oss@meesho.com diff --git a/helm-charts/skye-admin/templates/NOTES.txt b/helm-charts/skye-admin/templates/NOTES.txt new file mode 100644 index 00000000..35a4f3c2 --- /dev/null +++ b/helm-charts/skye-admin/templates/NOTES.txt @@ -0,0 +1,18 @@ +{{ .Chart.Name }} has been deployed. + +Namespace: {{ .Values.namespace }} +Application: {{ .Values.applicationName }} + +{{- if .Values.service.enabled }} +Service: {{ .Values.namespace }}:{{ (index .Values.service.ports 0).port }} +{{- end }} + +{{- if .Values.ingress.enabled }} +Ingress is enabled. +{{- end }} + +{{- if .Values.autoscaling.enabled }} +Autoscaling: {{ .Values.autoscaling.minReplicas }} - {{ .Values.autoscaling.maxReplicas }} replicas +{{- else }} +Replicas: {{ .Values.replicaCount }} +{{- end }} diff --git a/helm-charts/skye-admin/templates/_helpers.tpl b/helm-charts/skye-admin/templates/_helpers.tpl new file mode 100644 index 00000000..168e0342 --- /dev/null +++ b/helm-charts/skye-admin/templates/_helpers.tpl @@ -0,0 +1,70 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "fullname" -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "labels.selector" -}} +app: {{ .Values.namespace }} +{{- end -}} + +{{- define "labels.primary-selector" -}} +app: {{ .Values.namespace }}-primary +{{- end -}} + +{{- define "labels.common" -}} +{{ template "labels.selector" . }} +{{- if and .Values.deployment .Values.deployment.image }} +version: {{ .Values.deployment.image.tag }} +{{- end }} +env: {{ .Values.labels.env }} +team: {{ .Values.labels.team }} +bu: {{ .Values.labels.bu }} +service: {{ .Values.applicationName }} +priority: {{ .Values.labels.priority }} +priority_v2: {{ .Values.labels.priority_v2 | default "cp3" }} +primary_owner: {{ .Values.labels.primary_owner | default .Values.labels.team }} +secondary_owner: {{ .Values.labels.secondary_owner | default .Values.labels.team }} +service_type: {{ .Values.labels.service_type | default "" | replace "," "-" | quote }} +{{- end -}} + +{{- define "labels.chart" -}} +chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" +release: {{ .Release.Name | quote }} +heritage: {{ .Release.Service | quote }} +{{- end -}} + +{{/* +Renders a value that contains template. +Usage: +{{ include "application.tplvalues.render" ( dict "value" .Values.path.to.the.Value "context" $) }} +*/}} + +{{- define "application.tplvalues.render" -}} + {{- if typeIs "string" .value }} + {{- tpl .value .context }} + {{- else }} + {{- tpl (.value | toYaml) .context }} + {{- end }} +{{- end -}} + +{{- define "canary.promURL" -}} +{{- if .Values.canary.promURL }} +{{- .Values.canary.promURL }} +{{- else if eq .Values.labels.env "prod" }} +prod-ops-metricsui.example.com/select/100/ +{{- else }} +https://sb-ops-metricsui.example.com/select/100/ +{{- end }} +{{- end -}} diff --git a/helm-charts/skye-admin/templates/alert-provider.yaml b/helm-charts/skye-admin/templates/alert-provider.yaml new file mode 100644 index 00000000..a300bb14 --- /dev/null +++ b/helm-charts/skye-admin/templates/alert-provider.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.canary (.Values.canary.enabled) .Values.canary.slackWebhookURL (ne .Values.canary.slackWebhookURL "") }} +apiVersion: flagger.app/v1beta1 +kind: AlertProvider +metadata: + name: flagger-status + namespace: {{ .Values.namespace }} +spec: + type: slack + {{- if .Values.canary.slackChannel }} + channel: {{ .Values.canary.slackChannel }} + {{- end }} + username: flagger + address: {{ .Values.canary.slackWebhookURL }} +{{- end }} diff --git a/helm-charts/skye-admin/templates/configmap.yaml b/helm-charts/skye-admin/templates/configmap.yaml new file mode 100644 index 00000000..fc527219 --- /dev/null +++ b/helm-charts/skye-admin/templates/configmap.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.configMap .Values.configMap.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Values.namespace }}-config + namespace: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +data: + {{- range $key, $value := .Values.configMap.data }} + {{ $key }}: {{ $value | quote }} + {{- end }} +{{- end }} diff --git a/helm-charts/skye-admin/templates/deployment.yaml b/helm-charts/skye-admin/templates/deployment.yaml new file mode 100644 index 00000000..34cbb1ed --- /dev/null +++ b/helm-charts/skye-admin/templates/deployment.yaml @@ -0,0 +1,237 @@ +{{- if and .Values.deployment .Values.deployment.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.namespace }} + namespace: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +{{- include "labels.selector" . | nindent 4 }} +spec: + {{- with .Values.deployment.minReadySeconds }} + minReadySeconds: {{ . }} + {{- end }} + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + revisionHistoryLimit: {{ .Values.deployment.revisionHistoryLimit }} + selector: + matchLabels: +{{- include "labels.selector" . | nindent 6 }} +{{- with .Values.deployment.updateStrategy }} +{{ toYaml . | indent 2 -}} +{{- end }} + template: + metadata: + annotations: + {{- with .Values.deployment.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.telegraf.enabled }} + telegraf.influxdata.com/class: "infra" + {{- end }} + labels: + {{- include "labels.common" . | nindent 8 }} + spec: + {{- if .Values.priorityClassName }} + priorityClassName: {{ .Values.priorityClassName }} + {{- end }} + topologySpreadConstraints: + - maxSkew: 1 + topologyKey: topology.kubernetes.io/zone + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: +{{- include "labels.selector" . | nindent 12 }} + {{- if .Values.deployment.image.pullSecret }} + imagePullSecrets: + - name: {{ .Values.deployment.image.pullSecret }} + {{- end }} + {{- if .Values.deployment.volumes }} + volumes: + {{- toYaml .Values.deployment.volumes | nindent 8 }} + {{- end }} + {{- if .Values.deployment.initContainers }} + initContainers: + {{- toYaml .Values.deployment.initContainers | nindent 8 }} + {{- end }} + containers: + - name: {{ .Values.applicationName }} + {{- if .Values.deployment.volumeMounts }} + volumeMounts: + {{- toYaml .Values.deployment.volumeMounts | nindent 12 }} + {{- end }} + {{- if .Values.deployment.command }} + command: {{- include "application.tplvalues.render" (dict "value" .Values.deployment.command "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.deployment.args }} + args: {{- include "application.tplvalues.render" (dict "value" .Values.deployment.args "context" $) | nindent 12 }} + {{- end }} + image: "{{ .Values.deployment.image.repository }}:{{ .Values.deployment.image.tag }}" + imagePullPolicy: {{ .Values.deployment.image.pullPolicy }} + {{- if .Values.deployment.lifecycle }} + lifecycle: {{ toYaml .Values.deployment.lifecycle | nindent 12 }} + {{- end }} + {{- with .Values.deployment.probes }} + {{- with .liveness }} + livenessProbe: + {{- with .failureThreshold }} + failureThreshold: {{ . }} + {{- end }} + httpGet: + path: {{ .path }} + port: {{ .port }} + scheme: {{ .scheme }} + {{- with .periodSeconds }} + periodSeconds: {{ . }} + {{- end }} + {{- with .successThreshold }} + successThreshold: {{ . }} + {{- end }} + {{- with .timeoutSeconds }} + timeoutSeconds: {{ . }} + {{- end }} + initialDelaySeconds: {{ .initialDelaySeconds }} + {{- end }} + {{- end }} + {{- if or .Values.externalSecret.enabled .Values.otel_enabled (and .Values.configMap .Values.configMap.enabled) }} + envFrom: + {{- if .Values.externalSecret.enabled }} + - secretRef: + name: {{ .Values.deployment.envFrom.secretRef }} + {{- if ( default false (.Values.disasterRecovery).enabled ) }} + - secretRef: + name: {{ .Values.deployment.envFrom.secretRef }}-dr + {{- end }} + {{- end }} + {{- if .Values.otel_enabled }} + - secretRef: + name: {{ .Values.deployment.envFrom.secretRef }}-otel + {{- end }} + {{- if and .Values.configMap .Values.configMap.enabled }} + - configMapRef: + name: {{ .Values.namespace }}-config + {{- end }} + {{- end }} + env: + - name: TZ + value: Asia/Kolkata + - name: NODE_IP + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + {{- if .Values.telegraf.enabled }} + - name: TELEGRAF_UDP_HOST + valueFrom: + fieldRef: + fieldPath: status.podIP + {{- end }} + {{- if .Values.otel_enabled }} + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: http://$(NODE_IP):4317 + {{- end }} + {{- with .Values.deployment.env }} + {{- range . }} + - name: {{ .name }} + value: "{{ .value }}" + {{- end }} + {{- end }} + {{- with .Values.deployment.ports }} + ports: + {{- range . }} + - containerPort: {{ .containerPort }} + name: {{ .name }} + protocol: {{ .protocol }} + {{- end }} + {{- end }} + {{- if .Values.telegraf.enabled }} + - containerPort: 9273 + name: telegraf-sc + protocol: TCP + {{- end }} + {{- with .Values.deployment.probes }} + {{- with .readiness }} + readinessProbe: + {{- with .failureThreshold }} + failureThreshold: {{ . }} + {{- end }} + httpGet: + path: {{ .path }} + port: {{ .port }} + scheme: {{ .scheme }} + {{- with .periodSeconds }} + periodSeconds: {{ . }} + {{- end }} + {{- with .successThreshold }} + successThreshold: {{ . }} + {{- end }} + {{- with .timeoutSeconds}} + timeoutSeconds: {{ . }} + {{- end }} + initialDelaySeconds: {{ .initialDelaySeconds }} + {{- end }} + {{- end }} + {{- with .Values.deployment.resources }} + resources: + {{- with .limits }} + limits: + {{- with .memory }} + memory: "{{ . }}" + {{- end }} + {{- with .cpu }} + cpu: "{{ . }}" + {{- end }} + {{- end }} + {{- with .requests }} + requests: + {{- with .memory }} + memory: "{{ . }}" + {{- end }} + {{- with .cpu }} + cpu: "{{ . }}" + {{- end }} + {{- end }} + {{- end }} + + {{- with .Values.deployment.hostAliases }} + hostAliases: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.deployment.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.deployment.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + terminationGracePeriodSeconds: {{ .Values.deployment.terminationGracePeriodSeconds | default "300" }} + {{- if .Values.deployment.serviceAccount.enabled }} + serviceAccountName: {{ .Values.namespace }} + {{- end }} + {{- if .Values.securityContext }} + podSecurityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + {{- end }} +{{- end }} diff --git a/helm-charts/skye-admin/templates/external-secrets.yaml b/helm-charts/skye-admin/templates/external-secrets.yaml new file mode 100644 index 00000000..0d602056 --- /dev/null +++ b/helm-charts/skye-admin/templates/external-secrets.yaml @@ -0,0 +1,30 @@ +{{- if and .Values.externalSecret .Values.externalSecret.enabled }} +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} +{{- if .Values.externalSecret.annotations }} + annotations: +{{ toYaml .Values.externalSecret.annotations | indent 4 }} +{{- end }} + {{- if and .Values.deployment .Values.deployment.enabled }} + name: {{ .Values.deployment.envFrom.secretRef }} + {{- end}} + namespace: {{ .Values.namespace }} +spec: + dataFrom: + - extract: + conversionStrategy: Default + key: {{ .Values.externalSecret.path }} + refreshInterval: 15s + secretStoreRef: + kind: {{ .Values.infrastructure.secretStore.kind | default "ClusterSecretStore" }} + name: {{ .Values.infrastructure.secretStore.name | default "vault-backend" }} + target: + creationPolicy: Owner + deletionPolicy: Retain + {{- if and .Values.deployment .Values.deployment.enabled }} + name: {{ .Values.deployment.envFrom.secretRef }} + {{- end}} +{{- end }} diff --git a/helm-charts/skye-admin/templates/httpproxy.yaml b/helm-charts/skye-admin/templates/httpproxy.yaml new file mode 100644 index 00000000..94fe8f3e --- /dev/null +++ b/helm-charts/skye-admin/templates/httpproxy.yaml @@ -0,0 +1,50 @@ +{{- if and .Values.ingress .Values.ingress.enabled -}} +{{- if .Values.createContourGateway -}} +{{- if or ( eq "contour-internal" .Values.ingress.ingressClassName ) ( eq "contour-external" .Values.ingress.ingressClassName ) ( eq "contour-internal-0" .Values.ingress.ingressClassName ) ( eq "contour-internal-1" .Values.ingress.ingressClassName ) ( eq "contour-external-0" .Values.ingress.ingressClassName ) ( eq "contour-external-1" .Values.ingress.ingressClassName ) }} +{{- $servicePortNumber := .Values.ingress.servicePortNumber -}} +{{- $pathType := .Values.ingress.pathType -}} +{{- $namespace := .Values.namespace -}} +{{- $ingressClassName := .Values.ingress.ingressClassName -}} +{{ $count := 0 | int }} +{{- range .Values.ingress.hosts }} +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + namespace: {{ $namespace }} + name: {{ $namespace }}-{{ $count }} + labels: +{{ include "labels.common" $ | indent 4 }} +{{ include "labels.chart" $ | indent 4 }} + annotations: + projectcontour.io/ingress.class: {{ $.Values.ingress.ingressClassName }} +spec: + ingressClassName: {{ $ingressClassName }} + virtualhost: + fqdn: "{{ .host }}" + includes: + {{- range .paths }} + - conditions: + {{- if or ( eq ( lower .pathType ) "prefix" ) ( eq ( lower .pathType ) "implementationspecific") }} + - prefix: {{ .path }} + {{- end }} + {{- if ( eq ( lower .pathType ) "exact" ) }} + - header: + name: :path + exact: {{ .path }} + - prefix: {{ .path }} + {{- end }} + {{- if .targetService }} + name: {{ .targetService | replace "/" "-" }} + namespace: {{ (split "/" .targetService)._0 }} + {{- else }} + name: {{ $namespace }} + namespace: {{ $namespace }} + {{- end }} + {{- end }} + {{ $count = add1 $count }} +--- + +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/helm-charts/skye-admin/templates/otel-secret.yaml b/helm-charts/skye-admin/templates/otel-secret.yaml new file mode 100644 index 00000000..c2514b7d --- /dev/null +++ b/helm-charts/skye-admin/templates/otel-secret.yaml @@ -0,0 +1,26 @@ +{{ if .Values.otel_enabled }} +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} + annotations: + flagger.app/config-tracking: disabled + name: {{ if and .Values.deployment .Values.deployment.envFrom }}{{ .Values.deployment.envFrom.secretRef }}{{ else }}{{ .Values.namespace }}{{ end }}-otel + namespace: {{ .Values.namespace }} +spec: + dataFrom: + - extract: + conversionStrategy: Default + key: {{ .Values.infrastructure.vault.otelTokenPath | default "org/prd/cntr/devop/coralogix-token" }} + refreshInterval: 15s + secretStoreRef: + kind: {{ .Values.infrastructure.secretStore.kind | default "ClusterSecretStore" }} + name: {{ .Values.infrastructure.secretStore.name | default "vault-backend" }} + target: + creationPolicy: Owner + deletionPolicy: Retain + {{- if and .Values.deployment .Values.deployment.enabled }} + name: {{ .Values.deployment.envFrom.secretRef }}-otel + {{- end}} +{{ end }} diff --git a/helm-charts/skye-admin/templates/pdb.yaml b/helm-charts/skye-admin/templates/pdb.yaml new file mode 100644 index 00000000..db87f89a --- /dev/null +++ b/helm-charts/skye-admin/templates/pdb.yaml @@ -0,0 +1,36 @@ +{{- if or (and .Values.podDisruptionBudget .Values.podDisruptionBudget.enabled) (and .Values.deployment .Values.deployment.enabled) }} +{{- if or ( and .Values.deployment (.Values.deployment.enabled) (.Values.autoscaling.enabled) ( gt (int .Values.autoscaling.minReplicas) 1)) ( and (eq .Values.autoscaling.enabled false) .Values.deployment ( gt ( int .Values.deployment.replicaCount) 1 )) }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +spec: + {{- if .Values.podDisruptionBudget.enabled }} + maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }} + {{- else }} + {{- if and .Values.deployment .Values.deployment.enabled }} + {{- if (eq .Values.deployment.updateStrategy.strategy.type "RollingUpdate") }} + maxUnavailable: {{ .Values.deployment.updateStrategy.strategy.rollingUpdate.maxSurge | default "10%" }} + {{- else }} + maxUnavailable: "10%" + {{- end }} + {{ else }} + maxUnavailable: "10%" + {{- end }} + {{- end }} + {{- if .Values.podDisruptionBudget.minAvailable }} + minAvailable: {{ .Values.podDisruptionBudget.minAvailable }} + {{- end }} + selector: + matchLabels: + {{- if and (.Values.canary.enabled) (eq .Values.labels.env "prod") }} + {{- include "labels.primary-selector" . | nindent 6 }} + {{- else }} + {{- include "labels.selector" . | nindent 6 }} + {{- end }} +{{- end }} +{{- end }} diff --git a/helm-charts/skye-admin/templates/scaledobject.yaml b/helm-charts/skye-admin/templates/scaledobject.yaml new file mode 100644 index 00000000..259bfea2 --- /dev/null +++ b/helm-charts/skye-admin/templates/scaledobject.yaml @@ -0,0 +1,56 @@ +{{- if and .Values.autoscaling .Values.autoscaling.enabled }} +apiVersion: keda.sh/v1alpha1 +kind: ScaledObject +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ .Values.namespace }} + pollingInterval: {{ .Values.autoscaling.pollingInterval }} + {{- if ( default false (.Values.disasterRecovery).enabled ) }} + minReplicaCount: 1 + {{- else }} + minReplicaCount: {{ .Values.canary.minCanaryReplicas | default $.Values.autoscaling.minReplicas }} + {{- end }} + maxReplicaCount: {{ .Values.canary.maxCanaryReplicas | default $.Values.autoscaling.maxReplicas }} + advanced: + horizontalPodAutoscalerConfig: + behavior: + scaleDown: + stabilizationWindowSeconds: {{ .Values.autoscaling.scaledown.stabilizationWindowSeconds }} + policies: + {{- range .Values.autoscaling.scaledown.policies }} + - type: {{ .type }} + value: {{ .value }} + periodSeconds: {{ .periodseconds }} + {{- end }} + selectPolicy: {{ .Values.autoscaling.scaledown.selectpolicy }} + scaleUp: + stabilizationWindowSeconds: {{ .Values.autoscaling.scaleup.stabilizationWindowSeconds }} + policies: + {{- range .Values.autoscaling.scaleup.policies }} + - type: {{ .type }} + value: {{ .value }} + periodSeconds: {{ .periodseconds }} + {{- end }} + selectPolicy: {{ .Values.autoscaling.scaleup.selectpolicy }} + triggers: + {{- if ( default false (.Values.disasterRecovery).enabled ) }} + {{- range $.Values.autoscaling.triggers }} + {{- if or (eq .type "cpu") (eq .type "memory") }} + - metadata: + {{- toYaml .metadata | nindent 8 }} + type: {{ .type }} + metricType: "Utilization" + {{- end }} + {{- end }} + {{- else }} + {{- toYaml .Values.autoscaling.triggers | nindent 2 }} + {{ end }} + +{{- end }} diff --git a/helm-charts/skye-admin/templates/service.yaml b/helm-charts/skye-admin/templates/service.yaml new file mode 100644 index 00000000..8fcc5bc8 --- /dev/null +++ b/helm-charts/skye-admin/templates/service.yaml @@ -0,0 +1,29 @@ +{{- if and .Values.service .Values.service.enabled }} +{{- if or (eq .Values.canary.enabled false) ( and (.Values.canary.enabled) (ne .Values.labels.env "prod")) }} +apiVersion: v1 +kind: Service +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} +{{- if .Values.service.annotations }} + annotations: +{{ toYaml .Values.service.annotations | indent 4 }} +{{- end }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + {{- range .Values.service.ports }} + - name: {{ .name }} + port: {{ .port }} + protocol: {{ .protocol }} + targetPort: {{ .targetPort }} + {{- end }} + selector: + {{- include "labels.selector" . | nindent 4 }} +{{- end }} +{{- end }} diff --git a/helm-charts/skye-admin/templates/serviceaccount.yaml b/helm-charts/skye-admin/templates/serviceaccount.yaml new file mode 100644 index 00000000..f05362f5 --- /dev/null +++ b/helm-charts/skye-admin/templates/serviceaccount.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.deployment (.Values.deployment.enabled) (.Values.deployment.serviceAccount.enabled) }} +apiVersion: v1 +kind: ServiceAccount +metadata: + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} + {{- with .Values.deployment.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/helm-charts/skye-admin/values.yaml b/helm-charts/skye-admin/values.yaml new file mode 100644 index 00000000..4c93014b --- /dev/null +++ b/helm-charts/skye-admin/values.yaml @@ -0,0 +1,186 @@ +# Default values for skye-admin helm chart + +namespace: prd-skye-admin +applicationName: skye-admin +replicaCount: 2 + +labels: + env: prd + team: bharatml + bu: ml + priority: p1 + priority_v2: cp3 + service_type: "" + +priorityClassName: "" + +telegraf: + enabled: false + +otel_enabled: false + +infrastructure: + secretStore: + name: vault-backend + kind: ClusterSecretStore + vault: + basePath: "" + otelTokenPath: "" + +deployment: + enabled: true + replicaCount: 2 + revisionHistoryLimit: 3 + image: + repository: ghcr.io/meesho/skye-admin + tag: latest + pullPolicy: IfNotPresent + ports: + - containerPort: 8092 + name: http + protocol: TCP + probes: + liveness: + path: /health + port: 8092 + scheme: HTTP + initialDelaySeconds: 30 + periodSeconds: 10 + failureThreshold: 3 + successThreshold: 1 + timeoutSeconds: 5 + readiness: + path: /health + port: 8092 + scheme: HTTP + initialDelaySeconds: 20 + periodSeconds: 10 + failureThreshold: 3 + successThreshold: 1 + timeoutSeconds: 5 + resources: + requests: + memory: "256Mi" + cpu: "250m" + limits: + memory: "512Mi" + cpu: "500m" + env: + - name: APP_NAME + value: "skye" + - name: APP_ENV + value: "local" + - name: APP_LOG_LEVEL + value: "INFO" + - name: APP_METRIC_SAMPLING_RATE + value: "100" + - name: PORT + value: "8092" + # Etcd + - name: ETCD_SERVER + value: "etcd:2379" + - name: ETCD_WATCHER_ENABLED + value: "true" + # Kafka producer (model state) + - name: MODEL_STATE_PRODUCER + value: "1" + - name: KAFKA_PRODUCER_1_TOPICS + value: "skye.model-state" + - name: KAFKA_PRODUCER_1_BOOTSTRAP_SERVERS + value: "broker:29092" + - name: KAFKA_PRODUCER_1_CLIENT_ID + value: "skye-admin-producer" + # Kafka consumer (model state) + - name: MODEL_STATE_CONSUMER + value: "1" + - name: KAFKA_1_TOPICS + value: "skye.model-state" + - name: KAFKA_1_BOOTSTRAP_SERVERS + value: "broker:29092" + - name: KAFKA_1_BASIC_AUTH_CREDENTIAL_SOURCE + value: "NONE" + - name: KAFKA_1_GROUP_ID + value: "skye-admin-model-state" + - name: KAFKA_1_AUTO_OFFSET_RESET + value: "earliest" + - name: KAFKA_1_AUTO_COMMIT_INTERVAL_MS + value: "5000" + - name: KAFKA_1_ENABLE_AUTO_COMMIT + value: "false" + - name: KAFKA_1_LISTENER_CONCURRENCY + value: "1" + - name: KAFKA_1_CLIENT_ID + value: "skye-admin-consumer" + - name: KAFKA_1_BATCH_SIZE + value: "10" + - name: KAFKA_1_POLL_TIMEOUT + value: "1000" + serviceAccount: + enabled: false + annotations: {} + updateStrategy: + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 0 + maxSurge: 1 + terminationGracePeriodSeconds: 30 + +service: + enabled: true + type: ClusterIP + ports: + - name: http + port: 80 + targetPort: 8092 + protocol: TCP + +autoscaling: + enabled: false + minReplicas: 2 + maxReplicas: 10 + pollingInterval: 30 + scaledown: + stabilizationWindowSeconds: 300 + policies: + - type: Percent + value: 10 + periodseconds: 60 + selectpolicy: Min + scaleup: + stabilizationWindowSeconds: 0 + policies: + - type: Percent + value: 50 + periodseconds: 60 + selectpolicy: Max + triggers: + - type: cpu + metadata: + value: "70" + metricType: Utilization + +ingress: + enabled: false + ingressClassName: contour-internal +createContourGateway: false + +externalSecret: + enabled: false + path: "" + +configMap: + enabled: false + +canary: + enabled: false + promURL: "" + slackChannel: "" + slackWebhookURL: "" + +podDisruptionBudget: + enabled: false + maxUnavailable: "10%" + +disasterRecovery: + enabled: false diff --git a/helm-charts/skye-consumers/Chart.yaml b/helm-charts/skye-consumers/Chart.yaml new file mode 100644 index 00000000..fe010992 --- /dev/null +++ b/helm-charts/skye-consumers/Chart.yaml @@ -0,0 +1,10 @@ +apiVersion: v2 +name: skye-consumers +description: A Helm chart for the Skye Consumers service (embedding, realtime, and delta consumers) +type: application +version: 1.0.0 +appVersion: "1.0.0" + +maintainers: + - name: BharatMLStack Team + email: ml-oss@meesho.com diff --git a/helm-charts/skye-consumers/templates/NOTES.txt b/helm-charts/skye-consumers/templates/NOTES.txt new file mode 100644 index 00000000..35a4f3c2 --- /dev/null +++ b/helm-charts/skye-consumers/templates/NOTES.txt @@ -0,0 +1,18 @@ +{{ .Chart.Name }} has been deployed. + +Namespace: {{ .Values.namespace }} +Application: {{ .Values.applicationName }} + +{{- if .Values.service.enabled }} +Service: {{ .Values.namespace }}:{{ (index .Values.service.ports 0).port }} +{{- end }} + +{{- if .Values.ingress.enabled }} +Ingress is enabled. +{{- end }} + +{{- if .Values.autoscaling.enabled }} +Autoscaling: {{ .Values.autoscaling.minReplicas }} - {{ .Values.autoscaling.maxReplicas }} replicas +{{- else }} +Replicas: {{ .Values.replicaCount }} +{{- end }} diff --git a/helm-charts/skye-consumers/templates/_helpers.tpl b/helm-charts/skye-consumers/templates/_helpers.tpl new file mode 100644 index 00000000..168e0342 --- /dev/null +++ b/helm-charts/skye-consumers/templates/_helpers.tpl @@ -0,0 +1,70 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "fullname" -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "labels.selector" -}} +app: {{ .Values.namespace }} +{{- end -}} + +{{- define "labels.primary-selector" -}} +app: {{ .Values.namespace }}-primary +{{- end -}} + +{{- define "labels.common" -}} +{{ template "labels.selector" . }} +{{- if and .Values.deployment .Values.deployment.image }} +version: {{ .Values.deployment.image.tag }} +{{- end }} +env: {{ .Values.labels.env }} +team: {{ .Values.labels.team }} +bu: {{ .Values.labels.bu }} +service: {{ .Values.applicationName }} +priority: {{ .Values.labels.priority }} +priority_v2: {{ .Values.labels.priority_v2 | default "cp3" }} +primary_owner: {{ .Values.labels.primary_owner | default .Values.labels.team }} +secondary_owner: {{ .Values.labels.secondary_owner | default .Values.labels.team }} +service_type: {{ .Values.labels.service_type | default "" | replace "," "-" | quote }} +{{- end -}} + +{{- define "labels.chart" -}} +chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" +release: {{ .Release.Name | quote }} +heritage: {{ .Release.Service | quote }} +{{- end -}} + +{{/* +Renders a value that contains template. +Usage: +{{ include "application.tplvalues.render" ( dict "value" .Values.path.to.the.Value "context" $) }} +*/}} + +{{- define "application.tplvalues.render" -}} + {{- if typeIs "string" .value }} + {{- tpl .value .context }} + {{- else }} + {{- tpl (.value | toYaml) .context }} + {{- end }} +{{- end -}} + +{{- define "canary.promURL" -}} +{{- if .Values.canary.promURL }} +{{- .Values.canary.promURL }} +{{- else if eq .Values.labels.env "prod" }} +prod-ops-metricsui.example.com/select/100/ +{{- else }} +https://sb-ops-metricsui.example.com/select/100/ +{{- end }} +{{- end -}} diff --git a/helm-charts/skye-consumers/templates/alert-provider.yaml b/helm-charts/skye-consumers/templates/alert-provider.yaml new file mode 100644 index 00000000..a300bb14 --- /dev/null +++ b/helm-charts/skye-consumers/templates/alert-provider.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.canary (.Values.canary.enabled) .Values.canary.slackWebhookURL (ne .Values.canary.slackWebhookURL "") }} +apiVersion: flagger.app/v1beta1 +kind: AlertProvider +metadata: + name: flagger-status + namespace: {{ .Values.namespace }} +spec: + type: slack + {{- if .Values.canary.slackChannel }} + channel: {{ .Values.canary.slackChannel }} + {{- end }} + username: flagger + address: {{ .Values.canary.slackWebhookURL }} +{{- end }} diff --git a/helm-charts/skye-consumers/templates/configmap.yaml b/helm-charts/skye-consumers/templates/configmap.yaml new file mode 100644 index 00000000..fc527219 --- /dev/null +++ b/helm-charts/skye-consumers/templates/configmap.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.configMap .Values.configMap.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Values.namespace }}-config + namespace: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +data: + {{- range $key, $value := .Values.configMap.data }} + {{ $key }}: {{ $value | quote }} + {{- end }} +{{- end }} diff --git a/helm-charts/skye-consumers/templates/deployment.yaml b/helm-charts/skye-consumers/templates/deployment.yaml new file mode 100644 index 00000000..34cbb1ed --- /dev/null +++ b/helm-charts/skye-consumers/templates/deployment.yaml @@ -0,0 +1,237 @@ +{{- if and .Values.deployment .Values.deployment.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.namespace }} + namespace: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +{{- include "labels.selector" . | nindent 4 }} +spec: + {{- with .Values.deployment.minReadySeconds }} + minReadySeconds: {{ . }} + {{- end }} + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + revisionHistoryLimit: {{ .Values.deployment.revisionHistoryLimit }} + selector: + matchLabels: +{{- include "labels.selector" . | nindent 6 }} +{{- with .Values.deployment.updateStrategy }} +{{ toYaml . | indent 2 -}} +{{- end }} + template: + metadata: + annotations: + {{- with .Values.deployment.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.telegraf.enabled }} + telegraf.influxdata.com/class: "infra" + {{- end }} + labels: + {{- include "labels.common" . | nindent 8 }} + spec: + {{- if .Values.priorityClassName }} + priorityClassName: {{ .Values.priorityClassName }} + {{- end }} + topologySpreadConstraints: + - maxSkew: 1 + topologyKey: topology.kubernetes.io/zone + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: +{{- include "labels.selector" . | nindent 12 }} + {{- if .Values.deployment.image.pullSecret }} + imagePullSecrets: + - name: {{ .Values.deployment.image.pullSecret }} + {{- end }} + {{- if .Values.deployment.volumes }} + volumes: + {{- toYaml .Values.deployment.volumes | nindent 8 }} + {{- end }} + {{- if .Values.deployment.initContainers }} + initContainers: + {{- toYaml .Values.deployment.initContainers | nindent 8 }} + {{- end }} + containers: + - name: {{ .Values.applicationName }} + {{- if .Values.deployment.volumeMounts }} + volumeMounts: + {{- toYaml .Values.deployment.volumeMounts | nindent 12 }} + {{- end }} + {{- if .Values.deployment.command }} + command: {{- include "application.tplvalues.render" (dict "value" .Values.deployment.command "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.deployment.args }} + args: {{- include "application.tplvalues.render" (dict "value" .Values.deployment.args "context" $) | nindent 12 }} + {{- end }} + image: "{{ .Values.deployment.image.repository }}:{{ .Values.deployment.image.tag }}" + imagePullPolicy: {{ .Values.deployment.image.pullPolicy }} + {{- if .Values.deployment.lifecycle }} + lifecycle: {{ toYaml .Values.deployment.lifecycle | nindent 12 }} + {{- end }} + {{- with .Values.deployment.probes }} + {{- with .liveness }} + livenessProbe: + {{- with .failureThreshold }} + failureThreshold: {{ . }} + {{- end }} + httpGet: + path: {{ .path }} + port: {{ .port }} + scheme: {{ .scheme }} + {{- with .periodSeconds }} + periodSeconds: {{ . }} + {{- end }} + {{- with .successThreshold }} + successThreshold: {{ . }} + {{- end }} + {{- with .timeoutSeconds }} + timeoutSeconds: {{ . }} + {{- end }} + initialDelaySeconds: {{ .initialDelaySeconds }} + {{- end }} + {{- end }} + {{- if or .Values.externalSecret.enabled .Values.otel_enabled (and .Values.configMap .Values.configMap.enabled) }} + envFrom: + {{- if .Values.externalSecret.enabled }} + - secretRef: + name: {{ .Values.deployment.envFrom.secretRef }} + {{- if ( default false (.Values.disasterRecovery).enabled ) }} + - secretRef: + name: {{ .Values.deployment.envFrom.secretRef }}-dr + {{- end }} + {{- end }} + {{- if .Values.otel_enabled }} + - secretRef: + name: {{ .Values.deployment.envFrom.secretRef }}-otel + {{- end }} + {{- if and .Values.configMap .Values.configMap.enabled }} + - configMapRef: + name: {{ .Values.namespace }}-config + {{- end }} + {{- end }} + env: + - name: TZ + value: Asia/Kolkata + - name: NODE_IP + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + {{- if .Values.telegraf.enabled }} + - name: TELEGRAF_UDP_HOST + valueFrom: + fieldRef: + fieldPath: status.podIP + {{- end }} + {{- if .Values.otel_enabled }} + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: http://$(NODE_IP):4317 + {{- end }} + {{- with .Values.deployment.env }} + {{- range . }} + - name: {{ .name }} + value: "{{ .value }}" + {{- end }} + {{- end }} + {{- with .Values.deployment.ports }} + ports: + {{- range . }} + - containerPort: {{ .containerPort }} + name: {{ .name }} + protocol: {{ .protocol }} + {{- end }} + {{- end }} + {{- if .Values.telegraf.enabled }} + - containerPort: 9273 + name: telegraf-sc + protocol: TCP + {{- end }} + {{- with .Values.deployment.probes }} + {{- with .readiness }} + readinessProbe: + {{- with .failureThreshold }} + failureThreshold: {{ . }} + {{- end }} + httpGet: + path: {{ .path }} + port: {{ .port }} + scheme: {{ .scheme }} + {{- with .periodSeconds }} + periodSeconds: {{ . }} + {{- end }} + {{- with .successThreshold }} + successThreshold: {{ . }} + {{- end }} + {{- with .timeoutSeconds}} + timeoutSeconds: {{ . }} + {{- end }} + initialDelaySeconds: {{ .initialDelaySeconds }} + {{- end }} + {{- end }} + {{- with .Values.deployment.resources }} + resources: + {{- with .limits }} + limits: + {{- with .memory }} + memory: "{{ . }}" + {{- end }} + {{- with .cpu }} + cpu: "{{ . }}" + {{- end }} + {{- end }} + {{- with .requests }} + requests: + {{- with .memory }} + memory: "{{ . }}" + {{- end }} + {{- with .cpu }} + cpu: "{{ . }}" + {{- end }} + {{- end }} + {{- end }} + + {{- with .Values.deployment.hostAliases }} + hostAliases: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.deployment.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.deployment.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + terminationGracePeriodSeconds: {{ .Values.deployment.terminationGracePeriodSeconds | default "300" }} + {{- if .Values.deployment.serviceAccount.enabled }} + serviceAccountName: {{ .Values.namespace }} + {{- end }} + {{- if .Values.securityContext }} + podSecurityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + {{- end }} +{{- end }} diff --git a/helm-charts/skye-consumers/templates/external-secrets.yaml b/helm-charts/skye-consumers/templates/external-secrets.yaml new file mode 100644 index 00000000..0d602056 --- /dev/null +++ b/helm-charts/skye-consumers/templates/external-secrets.yaml @@ -0,0 +1,30 @@ +{{- if and .Values.externalSecret .Values.externalSecret.enabled }} +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} +{{- if .Values.externalSecret.annotations }} + annotations: +{{ toYaml .Values.externalSecret.annotations | indent 4 }} +{{- end }} + {{- if and .Values.deployment .Values.deployment.enabled }} + name: {{ .Values.deployment.envFrom.secretRef }} + {{- end}} + namespace: {{ .Values.namespace }} +spec: + dataFrom: + - extract: + conversionStrategy: Default + key: {{ .Values.externalSecret.path }} + refreshInterval: 15s + secretStoreRef: + kind: {{ .Values.infrastructure.secretStore.kind | default "ClusterSecretStore" }} + name: {{ .Values.infrastructure.secretStore.name | default "vault-backend" }} + target: + creationPolicy: Owner + deletionPolicy: Retain + {{- if and .Values.deployment .Values.deployment.enabled }} + name: {{ .Values.deployment.envFrom.secretRef }} + {{- end}} +{{- end }} diff --git a/helm-charts/skye-consumers/templates/httpproxy.yaml b/helm-charts/skye-consumers/templates/httpproxy.yaml new file mode 100644 index 00000000..94fe8f3e --- /dev/null +++ b/helm-charts/skye-consumers/templates/httpproxy.yaml @@ -0,0 +1,50 @@ +{{- if and .Values.ingress .Values.ingress.enabled -}} +{{- if .Values.createContourGateway -}} +{{- if or ( eq "contour-internal" .Values.ingress.ingressClassName ) ( eq "contour-external" .Values.ingress.ingressClassName ) ( eq "contour-internal-0" .Values.ingress.ingressClassName ) ( eq "contour-internal-1" .Values.ingress.ingressClassName ) ( eq "contour-external-0" .Values.ingress.ingressClassName ) ( eq "contour-external-1" .Values.ingress.ingressClassName ) }} +{{- $servicePortNumber := .Values.ingress.servicePortNumber -}} +{{- $pathType := .Values.ingress.pathType -}} +{{- $namespace := .Values.namespace -}} +{{- $ingressClassName := .Values.ingress.ingressClassName -}} +{{ $count := 0 | int }} +{{- range .Values.ingress.hosts }} +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + namespace: {{ $namespace }} + name: {{ $namespace }}-{{ $count }} + labels: +{{ include "labels.common" $ | indent 4 }} +{{ include "labels.chart" $ | indent 4 }} + annotations: + projectcontour.io/ingress.class: {{ $.Values.ingress.ingressClassName }} +spec: + ingressClassName: {{ $ingressClassName }} + virtualhost: + fqdn: "{{ .host }}" + includes: + {{- range .paths }} + - conditions: + {{- if or ( eq ( lower .pathType ) "prefix" ) ( eq ( lower .pathType ) "implementationspecific") }} + - prefix: {{ .path }} + {{- end }} + {{- if ( eq ( lower .pathType ) "exact" ) }} + - header: + name: :path + exact: {{ .path }} + - prefix: {{ .path }} + {{- end }} + {{- if .targetService }} + name: {{ .targetService | replace "/" "-" }} + namespace: {{ (split "/" .targetService)._0 }} + {{- else }} + name: {{ $namespace }} + namespace: {{ $namespace }} + {{- end }} + {{- end }} + {{ $count = add1 $count }} +--- + +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/helm-charts/skye-consumers/templates/otel-secret.yaml b/helm-charts/skye-consumers/templates/otel-secret.yaml new file mode 100644 index 00000000..c2514b7d --- /dev/null +++ b/helm-charts/skye-consumers/templates/otel-secret.yaml @@ -0,0 +1,26 @@ +{{ if .Values.otel_enabled }} +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} + annotations: + flagger.app/config-tracking: disabled + name: {{ if and .Values.deployment .Values.deployment.envFrom }}{{ .Values.deployment.envFrom.secretRef }}{{ else }}{{ .Values.namespace }}{{ end }}-otel + namespace: {{ .Values.namespace }} +spec: + dataFrom: + - extract: + conversionStrategy: Default + key: {{ .Values.infrastructure.vault.otelTokenPath | default "org/prd/cntr/devop/coralogix-token" }} + refreshInterval: 15s + secretStoreRef: + kind: {{ .Values.infrastructure.secretStore.kind | default "ClusterSecretStore" }} + name: {{ .Values.infrastructure.secretStore.name | default "vault-backend" }} + target: + creationPolicy: Owner + deletionPolicy: Retain + {{- if and .Values.deployment .Values.deployment.enabled }} + name: {{ .Values.deployment.envFrom.secretRef }}-otel + {{- end}} +{{ end }} diff --git a/helm-charts/skye-consumers/templates/pdb.yaml b/helm-charts/skye-consumers/templates/pdb.yaml new file mode 100644 index 00000000..db87f89a --- /dev/null +++ b/helm-charts/skye-consumers/templates/pdb.yaml @@ -0,0 +1,36 @@ +{{- if or (and .Values.podDisruptionBudget .Values.podDisruptionBudget.enabled) (and .Values.deployment .Values.deployment.enabled) }} +{{- if or ( and .Values.deployment (.Values.deployment.enabled) (.Values.autoscaling.enabled) ( gt (int .Values.autoscaling.minReplicas) 1)) ( and (eq .Values.autoscaling.enabled false) .Values.deployment ( gt ( int .Values.deployment.replicaCount) 1 )) }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +spec: + {{- if .Values.podDisruptionBudget.enabled }} + maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }} + {{- else }} + {{- if and .Values.deployment .Values.deployment.enabled }} + {{- if (eq .Values.deployment.updateStrategy.strategy.type "RollingUpdate") }} + maxUnavailable: {{ .Values.deployment.updateStrategy.strategy.rollingUpdate.maxSurge | default "10%" }} + {{- else }} + maxUnavailable: "10%" + {{- end }} + {{ else }} + maxUnavailable: "10%" + {{- end }} + {{- end }} + {{- if .Values.podDisruptionBudget.minAvailable }} + minAvailable: {{ .Values.podDisruptionBudget.minAvailable }} + {{- end }} + selector: + matchLabels: + {{- if and (.Values.canary.enabled) (eq .Values.labels.env "prod") }} + {{- include "labels.primary-selector" . | nindent 6 }} + {{- else }} + {{- include "labels.selector" . | nindent 6 }} + {{- end }} +{{- end }} +{{- end }} diff --git a/helm-charts/skye-consumers/templates/scaledobject.yaml b/helm-charts/skye-consumers/templates/scaledobject.yaml new file mode 100644 index 00000000..259bfea2 --- /dev/null +++ b/helm-charts/skye-consumers/templates/scaledobject.yaml @@ -0,0 +1,56 @@ +{{- if and .Values.autoscaling .Values.autoscaling.enabled }} +apiVersion: keda.sh/v1alpha1 +kind: ScaledObject +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ .Values.namespace }} + pollingInterval: {{ .Values.autoscaling.pollingInterval }} + {{- if ( default false (.Values.disasterRecovery).enabled ) }} + minReplicaCount: 1 + {{- else }} + minReplicaCount: {{ .Values.canary.minCanaryReplicas | default $.Values.autoscaling.minReplicas }} + {{- end }} + maxReplicaCount: {{ .Values.canary.maxCanaryReplicas | default $.Values.autoscaling.maxReplicas }} + advanced: + horizontalPodAutoscalerConfig: + behavior: + scaleDown: + stabilizationWindowSeconds: {{ .Values.autoscaling.scaledown.stabilizationWindowSeconds }} + policies: + {{- range .Values.autoscaling.scaledown.policies }} + - type: {{ .type }} + value: {{ .value }} + periodSeconds: {{ .periodseconds }} + {{- end }} + selectPolicy: {{ .Values.autoscaling.scaledown.selectpolicy }} + scaleUp: + stabilizationWindowSeconds: {{ .Values.autoscaling.scaleup.stabilizationWindowSeconds }} + policies: + {{- range .Values.autoscaling.scaleup.policies }} + - type: {{ .type }} + value: {{ .value }} + periodSeconds: {{ .periodseconds }} + {{- end }} + selectPolicy: {{ .Values.autoscaling.scaleup.selectpolicy }} + triggers: + {{- if ( default false (.Values.disasterRecovery).enabled ) }} + {{- range $.Values.autoscaling.triggers }} + {{- if or (eq .type "cpu") (eq .type "memory") }} + - metadata: + {{- toYaml .metadata | nindent 8 }} + type: {{ .type }} + metricType: "Utilization" + {{- end }} + {{- end }} + {{- else }} + {{- toYaml .Values.autoscaling.triggers | nindent 2 }} + {{ end }} + +{{- end }} diff --git a/helm-charts/skye-consumers/templates/service.yaml b/helm-charts/skye-consumers/templates/service.yaml new file mode 100644 index 00000000..8fcc5bc8 --- /dev/null +++ b/helm-charts/skye-consumers/templates/service.yaml @@ -0,0 +1,29 @@ +{{- if and .Values.service .Values.service.enabled }} +{{- if or (eq .Values.canary.enabled false) ( and (.Values.canary.enabled) (ne .Values.labels.env "prod")) }} +apiVersion: v1 +kind: Service +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} +{{- if .Values.service.annotations }} + annotations: +{{ toYaml .Values.service.annotations | indent 4 }} +{{- end }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + {{- range .Values.service.ports }} + - name: {{ .name }} + port: {{ .port }} + protocol: {{ .protocol }} + targetPort: {{ .targetPort }} + {{- end }} + selector: + {{- include "labels.selector" . | nindent 4 }} +{{- end }} +{{- end }} diff --git a/helm-charts/skye-consumers/templates/serviceaccount.yaml b/helm-charts/skye-consumers/templates/serviceaccount.yaml new file mode 100644 index 00000000..f05362f5 --- /dev/null +++ b/helm-charts/skye-consumers/templates/serviceaccount.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.deployment (.Values.deployment.enabled) (.Values.deployment.serviceAccount.enabled) }} +apiVersion: v1 +kind: ServiceAccount +metadata: + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} + {{- with .Values.deployment.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/helm-charts/skye-consumers/values.yaml b/helm-charts/skye-consumers/values.yaml new file mode 100644 index 00000000..f18d0bd9 --- /dev/null +++ b/helm-charts/skye-consumers/values.yaml @@ -0,0 +1,270 @@ +# Default values for skye-consumers helm chart + +namespace: prd-skye-consumers +applicationName: skye-consumers +replicaCount: 2 + +labels: + env: prd + team: bharatml + bu: ml + priority: p1 + priority_v2: cp3 + service_type: "" + +priorityClassName: "" + +telegraf: + enabled: false + +otel_enabled: false + +infrastructure: + secretStore: + name: vault-backend + kind: ClusterSecretStore + vault: + basePath: "" + otelTokenPath: "" + +deployment: + enabled: true + replicaCount: 2 + revisionHistoryLimit: 3 + image: + repository: ghcr.io/meesho/skye-consumers + tag: latest + pullPolicy: IfNotPresent + ports: + - containerPort: 8093 + name: http + protocol: TCP + probes: + liveness: + path: /health + port: 8093 + scheme: HTTP + initialDelaySeconds: 30 + periodSeconds: 10 + failureThreshold: 3 + successThreshold: 1 + timeoutSeconds: 5 + readiness: + path: /health + port: 8093 + scheme: HTTP + initialDelaySeconds: 20 + periodSeconds: 10 + failureThreshold: 3 + successThreshold: 1 + timeoutSeconds: 5 + resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "1Gi" + cpu: "1000m" + env: + - name: APP_NAME + value: "skye" + - name: APP_ENV + value: "local" + - name: APP_LOG_LEVEL + value: "INFO" + - name: APP_METRIC_SAMPLING_RATE + value: "100" + - name: PORT + value: "8093" + # Etcd + - name: ETCD_SERVER + value: "etcd:2379" + - name: ETCD_WATCHER_ENABLED + value: "true" + # Embedding consumer (ID=2) + - name: EMBEDDING_CONSUMER_KAFKA_IDS + value: "2" + - name: KAFKA_2_TOPICS + value: "skye.embedding" + - name: KAFKA_2_BOOTSTRAP_SERVERS + value: "broker:29092" + - name: KAFKA_2_BASIC_AUTH_CREDENTIAL_SOURCE + value: "NONE" + - name: KAFKA_2_GROUP_ID + value: "skye-embedding-consumer" + - name: KAFKA_2_AUTO_OFFSET_RESET + value: "earliest" + - name: KAFKA_2_AUTO_COMMIT_INTERVAL_MS + value: "5000" + - name: KAFKA_2_ENABLE_AUTO_COMMIT + value: "false" + - name: KAFKA_2_LISTENER_CONCURRENCY + value: "1" + - name: KAFKA_2_CLIENT_ID + value: "skye-embedding-consumer" + - name: KAFKA_2_BATCH_SIZE + value: "10" + - name: KAFKA_2_POLL_TIMEOUT + value: "1000" + # Embedding sequence consumer (ID=3) + - name: EMBEDDING_CONSUMER_SEQUENCE_KAFKA_IDS + value: "3" + - name: KAFKA_3_TOPICS + value: "skye.embedding-sequence" + - name: KAFKA_3_BOOTSTRAP_SERVERS + value: "broker:29092" + - name: KAFKA_3_BASIC_AUTH_CREDENTIAL_SOURCE + value: "NONE" + - name: KAFKA_3_GROUP_ID + value: "skye-embedding-seq-consumer" + - name: KAFKA_3_AUTO_OFFSET_RESET + value: "earliest" + - name: KAFKA_3_AUTO_COMMIT_INTERVAL_MS + value: "5000" + - name: KAFKA_3_ENABLE_AUTO_COMMIT + value: "false" + - name: KAFKA_3_LISTENER_CONCURRENCY + value: "1" + - name: KAFKA_3_CLIENT_ID + value: "skye-embedding-seq-consumer" + - name: KAFKA_3_BATCH_SIZE + value: "10" + - name: KAFKA_3_POLL_TIMEOUT + value: "1000" + # Realtime consumer (ID=4) + - name: REALTIME_CONSUMER_KAFKA_IDS + value: "4" + - name: KAFKA_4_TOPICS + value: "skye.realtime" + - name: KAFKA_4_BOOTSTRAP_SERVERS + value: "broker:29092" + - name: KAFKA_4_BASIC_AUTH_CREDENTIAL_SOURCE + value: "NONE" + - name: KAFKA_4_GROUP_ID + value: "skye-realtime-consumer" + - name: KAFKA_4_AUTO_OFFSET_RESET + value: "earliest" + - name: KAFKA_4_AUTO_COMMIT_INTERVAL_MS + value: "5000" + - name: KAFKA_4_ENABLE_AUTO_COMMIT + value: "false" + - name: KAFKA_4_LISTENER_CONCURRENCY + value: "1" + - name: KAFKA_4_CLIENT_ID + value: "skye-realtime-consumer" + - name: KAFKA_4_BATCH_SIZE + value: "10" + - name: KAFKA_4_POLL_TIMEOUT + value: "1000" + # Realtime producer (ID=5) + - name: REALTIME_PRODUCER_KAFKA_ID + value: "5" + - name: KAFKA_PRODUCER_5_TOPICS + value: "skye.realtime" + - name: KAFKA_PRODUCER_5_BOOTSTRAP_SERVERS + value: "broker:29092" + - name: KAFKA_PRODUCER_5_CLIENT_ID + value: "skye-realtime-producer" + # Realtime delta producer (ID=6) + - name: REALTIME_DELTA_PRODUCER_KAFKA_ID + value: "6" + - name: KAFKA_PRODUCER_6_TOPICS + value: "skye.realtime-delta" + - name: KAFKA_PRODUCER_6_BOOTSTRAP_SERVERS + value: "broker:29092" + - name: KAFKA_PRODUCER_6_CLIENT_ID + value: "skye-realtime-delta-producer" + # Realtime delta consumer (ID=7) + - name: REALTIME_DELTA_CONSUMER_KAFKA_ID + value: "7" + - name: KAFKA_7_TOPICS + value: "skye.realtime-delta" + - name: KAFKA_7_BOOTSTRAP_SERVERS + value: "broker:29092" + - name: KAFKA_7_BASIC_AUTH_CREDENTIAL_SOURCE + value: "NONE" + - name: KAFKA_7_GROUP_ID + value: "skye-realtime-delta-consumer" + - name: KAFKA_7_AUTO_OFFSET_RESET + value: "earliest" + - name: KAFKA_7_AUTO_COMMIT_INTERVAL_MS + value: "5000" + - name: KAFKA_7_ENABLE_AUTO_COMMIT + value: "false" + - name: KAFKA_7_LISTENER_CONCURRENCY + value: "1" + - name: KAFKA_7_CLIENT_ID + value: "skye-realtime-delta-consumer" + - name: KAFKA_7_BATCH_SIZE + value: "10" + - name: KAFKA_7_POLL_TIMEOUT + value: "1000" + serviceAccount: + enabled: false + annotations: {} + updateStrategy: + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 0 + maxSurge: 1 + terminationGracePeriodSeconds: 30 + +service: + enabled: true + type: ClusterIP + ports: + - name: http + port: 80 + targetPort: 8093 + protocol: TCP + +autoscaling: + enabled: false + minReplicas: 2 + maxReplicas: 10 + pollingInterval: 30 + scaledown: + stabilizationWindowSeconds: 300 + policies: + - type: Percent + value: 10 + periodseconds: 60 + selectpolicy: Min + scaleup: + stabilizationWindowSeconds: 0 + policies: + - type: Percent + value: 50 + periodseconds: 60 + selectpolicy: Max + triggers: + - type: cpu + metadata: + value: "70" + metricType: Utilization + +ingress: + enabled: false + ingressClassName: contour-internal +createContourGateway: false + +externalSecret: + enabled: false + path: "" + +configMap: + enabled: false + +canary: + enabled: false + promURL: "" + slackChannel: "" + slackWebhookURL: "" + +podDisruptionBudget: + enabled: false + maxUnavailable: "10%" + +disasterRecovery: + enabled: false diff --git a/helm-charts/skye-serving/Chart.yaml b/helm-charts/skye-serving/Chart.yaml new file mode 100644 index 00000000..064903ff --- /dev/null +++ b/helm-charts/skye-serving/Chart.yaml @@ -0,0 +1,10 @@ +apiVersion: v2 +name: skye-serving +description: A Helm chart for the Skye Serving service (embedding search and retrieval) +type: application +version: 1.0.0 +appVersion: "1.0.0" + +maintainers: + - name: BharatMLStack Team + email: ml-oss@meesho.com diff --git a/helm-charts/skye-serving/templates/NOTES.txt b/helm-charts/skye-serving/templates/NOTES.txt new file mode 100644 index 00000000..35a4f3c2 --- /dev/null +++ b/helm-charts/skye-serving/templates/NOTES.txt @@ -0,0 +1,18 @@ +{{ .Chart.Name }} has been deployed. + +Namespace: {{ .Values.namespace }} +Application: {{ .Values.applicationName }} + +{{- if .Values.service.enabled }} +Service: {{ .Values.namespace }}:{{ (index .Values.service.ports 0).port }} +{{- end }} + +{{- if .Values.ingress.enabled }} +Ingress is enabled. +{{- end }} + +{{- if .Values.autoscaling.enabled }} +Autoscaling: {{ .Values.autoscaling.minReplicas }} - {{ .Values.autoscaling.maxReplicas }} replicas +{{- else }} +Replicas: {{ .Values.replicaCount }} +{{- end }} diff --git a/helm-charts/skye-serving/templates/_helpers.tpl b/helm-charts/skye-serving/templates/_helpers.tpl new file mode 100644 index 00000000..168e0342 --- /dev/null +++ b/helm-charts/skye-serving/templates/_helpers.tpl @@ -0,0 +1,70 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "fullname" -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "labels.selector" -}} +app: {{ .Values.namespace }} +{{- end -}} + +{{- define "labels.primary-selector" -}} +app: {{ .Values.namespace }}-primary +{{- end -}} + +{{- define "labels.common" -}} +{{ template "labels.selector" . }} +{{- if and .Values.deployment .Values.deployment.image }} +version: {{ .Values.deployment.image.tag }} +{{- end }} +env: {{ .Values.labels.env }} +team: {{ .Values.labels.team }} +bu: {{ .Values.labels.bu }} +service: {{ .Values.applicationName }} +priority: {{ .Values.labels.priority }} +priority_v2: {{ .Values.labels.priority_v2 | default "cp3" }} +primary_owner: {{ .Values.labels.primary_owner | default .Values.labels.team }} +secondary_owner: {{ .Values.labels.secondary_owner | default .Values.labels.team }} +service_type: {{ .Values.labels.service_type | default "" | replace "," "-" | quote }} +{{- end -}} + +{{- define "labels.chart" -}} +chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" +release: {{ .Release.Name | quote }} +heritage: {{ .Release.Service | quote }} +{{- end -}} + +{{/* +Renders a value that contains template. +Usage: +{{ include "application.tplvalues.render" ( dict "value" .Values.path.to.the.Value "context" $) }} +*/}} + +{{- define "application.tplvalues.render" -}} + {{- if typeIs "string" .value }} + {{- tpl .value .context }} + {{- else }} + {{- tpl (.value | toYaml) .context }} + {{- end }} +{{- end -}} + +{{- define "canary.promURL" -}} +{{- if .Values.canary.promURL }} +{{- .Values.canary.promURL }} +{{- else if eq .Values.labels.env "prod" }} +prod-ops-metricsui.example.com/select/100/ +{{- else }} +https://sb-ops-metricsui.example.com/select/100/ +{{- end }} +{{- end -}} diff --git a/helm-charts/skye-serving/templates/alert-provider.yaml b/helm-charts/skye-serving/templates/alert-provider.yaml new file mode 100644 index 00000000..a300bb14 --- /dev/null +++ b/helm-charts/skye-serving/templates/alert-provider.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.canary (.Values.canary.enabled) .Values.canary.slackWebhookURL (ne .Values.canary.slackWebhookURL "") }} +apiVersion: flagger.app/v1beta1 +kind: AlertProvider +metadata: + name: flagger-status + namespace: {{ .Values.namespace }} +spec: + type: slack + {{- if .Values.canary.slackChannel }} + channel: {{ .Values.canary.slackChannel }} + {{- end }} + username: flagger + address: {{ .Values.canary.slackWebhookURL }} +{{- end }} diff --git a/helm-charts/skye-serving/templates/configmap.yaml b/helm-charts/skye-serving/templates/configmap.yaml new file mode 100644 index 00000000..fc527219 --- /dev/null +++ b/helm-charts/skye-serving/templates/configmap.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.configMap .Values.configMap.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Values.namespace }}-config + namespace: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +data: + {{- range $key, $value := .Values.configMap.data }} + {{ $key }}: {{ $value | quote }} + {{- end }} +{{- end }} diff --git a/helm-charts/skye-serving/templates/deployment.yaml b/helm-charts/skye-serving/templates/deployment.yaml new file mode 100644 index 00000000..34cbb1ed --- /dev/null +++ b/helm-charts/skye-serving/templates/deployment.yaml @@ -0,0 +1,237 @@ +{{- if and .Values.deployment .Values.deployment.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.namespace }} + namespace: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +{{- include "labels.selector" . | nindent 4 }} +spec: + {{- with .Values.deployment.minReadySeconds }} + minReadySeconds: {{ . }} + {{- end }} + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + revisionHistoryLimit: {{ .Values.deployment.revisionHistoryLimit }} + selector: + matchLabels: +{{- include "labels.selector" . | nindent 6 }} +{{- with .Values.deployment.updateStrategy }} +{{ toYaml . | indent 2 -}} +{{- end }} + template: + metadata: + annotations: + {{- with .Values.deployment.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.telegraf.enabled }} + telegraf.influxdata.com/class: "infra" + {{- end }} + labels: + {{- include "labels.common" . | nindent 8 }} + spec: + {{- if .Values.priorityClassName }} + priorityClassName: {{ .Values.priorityClassName }} + {{- end }} + topologySpreadConstraints: + - maxSkew: 1 + topologyKey: topology.kubernetes.io/zone + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: +{{- include "labels.selector" . | nindent 12 }} + {{- if .Values.deployment.image.pullSecret }} + imagePullSecrets: + - name: {{ .Values.deployment.image.pullSecret }} + {{- end }} + {{- if .Values.deployment.volumes }} + volumes: + {{- toYaml .Values.deployment.volumes | nindent 8 }} + {{- end }} + {{- if .Values.deployment.initContainers }} + initContainers: + {{- toYaml .Values.deployment.initContainers | nindent 8 }} + {{- end }} + containers: + - name: {{ .Values.applicationName }} + {{- if .Values.deployment.volumeMounts }} + volumeMounts: + {{- toYaml .Values.deployment.volumeMounts | nindent 12 }} + {{- end }} + {{- if .Values.deployment.command }} + command: {{- include "application.tplvalues.render" (dict "value" .Values.deployment.command "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.deployment.args }} + args: {{- include "application.tplvalues.render" (dict "value" .Values.deployment.args "context" $) | nindent 12 }} + {{- end }} + image: "{{ .Values.deployment.image.repository }}:{{ .Values.deployment.image.tag }}" + imagePullPolicy: {{ .Values.deployment.image.pullPolicy }} + {{- if .Values.deployment.lifecycle }} + lifecycle: {{ toYaml .Values.deployment.lifecycle | nindent 12 }} + {{- end }} + {{- with .Values.deployment.probes }} + {{- with .liveness }} + livenessProbe: + {{- with .failureThreshold }} + failureThreshold: {{ . }} + {{- end }} + httpGet: + path: {{ .path }} + port: {{ .port }} + scheme: {{ .scheme }} + {{- with .periodSeconds }} + periodSeconds: {{ . }} + {{- end }} + {{- with .successThreshold }} + successThreshold: {{ . }} + {{- end }} + {{- with .timeoutSeconds }} + timeoutSeconds: {{ . }} + {{- end }} + initialDelaySeconds: {{ .initialDelaySeconds }} + {{- end }} + {{- end }} + {{- if or .Values.externalSecret.enabled .Values.otel_enabled (and .Values.configMap .Values.configMap.enabled) }} + envFrom: + {{- if .Values.externalSecret.enabled }} + - secretRef: + name: {{ .Values.deployment.envFrom.secretRef }} + {{- if ( default false (.Values.disasterRecovery).enabled ) }} + - secretRef: + name: {{ .Values.deployment.envFrom.secretRef }}-dr + {{- end }} + {{- end }} + {{- if .Values.otel_enabled }} + - secretRef: + name: {{ .Values.deployment.envFrom.secretRef }}-otel + {{- end }} + {{- if and .Values.configMap .Values.configMap.enabled }} + - configMapRef: + name: {{ .Values.namespace }}-config + {{- end }} + {{- end }} + env: + - name: TZ + value: Asia/Kolkata + - name: NODE_IP + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + {{- if .Values.telegraf.enabled }} + - name: TELEGRAF_UDP_HOST + valueFrom: + fieldRef: + fieldPath: status.podIP + {{- end }} + {{- if .Values.otel_enabled }} + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: http://$(NODE_IP):4317 + {{- end }} + {{- with .Values.deployment.env }} + {{- range . }} + - name: {{ .name }} + value: "{{ .value }}" + {{- end }} + {{- end }} + {{- with .Values.deployment.ports }} + ports: + {{- range . }} + - containerPort: {{ .containerPort }} + name: {{ .name }} + protocol: {{ .protocol }} + {{- end }} + {{- end }} + {{- if .Values.telegraf.enabled }} + - containerPort: 9273 + name: telegraf-sc + protocol: TCP + {{- end }} + {{- with .Values.deployment.probes }} + {{- with .readiness }} + readinessProbe: + {{- with .failureThreshold }} + failureThreshold: {{ . }} + {{- end }} + httpGet: + path: {{ .path }} + port: {{ .port }} + scheme: {{ .scheme }} + {{- with .periodSeconds }} + periodSeconds: {{ . }} + {{- end }} + {{- with .successThreshold }} + successThreshold: {{ . }} + {{- end }} + {{- with .timeoutSeconds}} + timeoutSeconds: {{ . }} + {{- end }} + initialDelaySeconds: {{ .initialDelaySeconds }} + {{- end }} + {{- end }} + {{- with .Values.deployment.resources }} + resources: + {{- with .limits }} + limits: + {{- with .memory }} + memory: "{{ . }}" + {{- end }} + {{- with .cpu }} + cpu: "{{ . }}" + {{- end }} + {{- end }} + {{- with .requests }} + requests: + {{- with .memory }} + memory: "{{ . }}" + {{- end }} + {{- with .cpu }} + cpu: "{{ . }}" + {{- end }} + {{- end }} + {{- end }} + + {{- with .Values.deployment.hostAliases }} + hostAliases: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.deployment.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.deployment.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + terminationGracePeriodSeconds: {{ .Values.deployment.terminationGracePeriodSeconds | default "300" }} + {{- if .Values.deployment.serviceAccount.enabled }} + serviceAccountName: {{ .Values.namespace }} + {{- end }} + {{- if .Values.securityContext }} + podSecurityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + {{- end }} +{{- end }} diff --git a/helm-charts/skye-serving/templates/external-secrets.yaml b/helm-charts/skye-serving/templates/external-secrets.yaml new file mode 100644 index 00000000..0d602056 --- /dev/null +++ b/helm-charts/skye-serving/templates/external-secrets.yaml @@ -0,0 +1,30 @@ +{{- if and .Values.externalSecret .Values.externalSecret.enabled }} +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} +{{- if .Values.externalSecret.annotations }} + annotations: +{{ toYaml .Values.externalSecret.annotations | indent 4 }} +{{- end }} + {{- if and .Values.deployment .Values.deployment.enabled }} + name: {{ .Values.deployment.envFrom.secretRef }} + {{- end}} + namespace: {{ .Values.namespace }} +spec: + dataFrom: + - extract: + conversionStrategy: Default + key: {{ .Values.externalSecret.path }} + refreshInterval: 15s + secretStoreRef: + kind: {{ .Values.infrastructure.secretStore.kind | default "ClusterSecretStore" }} + name: {{ .Values.infrastructure.secretStore.name | default "vault-backend" }} + target: + creationPolicy: Owner + deletionPolicy: Retain + {{- if and .Values.deployment .Values.deployment.enabled }} + name: {{ .Values.deployment.envFrom.secretRef }} + {{- end}} +{{- end }} diff --git a/helm-charts/skye-serving/templates/httpproxy.yaml b/helm-charts/skye-serving/templates/httpproxy.yaml new file mode 100644 index 00000000..94fe8f3e --- /dev/null +++ b/helm-charts/skye-serving/templates/httpproxy.yaml @@ -0,0 +1,50 @@ +{{- if and .Values.ingress .Values.ingress.enabled -}} +{{- if .Values.createContourGateway -}} +{{- if or ( eq "contour-internal" .Values.ingress.ingressClassName ) ( eq "contour-external" .Values.ingress.ingressClassName ) ( eq "contour-internal-0" .Values.ingress.ingressClassName ) ( eq "contour-internal-1" .Values.ingress.ingressClassName ) ( eq "contour-external-0" .Values.ingress.ingressClassName ) ( eq "contour-external-1" .Values.ingress.ingressClassName ) }} +{{- $servicePortNumber := .Values.ingress.servicePortNumber -}} +{{- $pathType := .Values.ingress.pathType -}} +{{- $namespace := .Values.namespace -}} +{{- $ingressClassName := .Values.ingress.ingressClassName -}} +{{ $count := 0 | int }} +{{- range .Values.ingress.hosts }} +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + namespace: {{ $namespace }} + name: {{ $namespace }}-{{ $count }} + labels: +{{ include "labels.common" $ | indent 4 }} +{{ include "labels.chart" $ | indent 4 }} + annotations: + projectcontour.io/ingress.class: {{ $.Values.ingress.ingressClassName }} +spec: + ingressClassName: {{ $ingressClassName }} + virtualhost: + fqdn: "{{ .host }}" + includes: + {{- range .paths }} + - conditions: + {{- if or ( eq ( lower .pathType ) "prefix" ) ( eq ( lower .pathType ) "implementationspecific") }} + - prefix: {{ .path }} + {{- end }} + {{- if ( eq ( lower .pathType ) "exact" ) }} + - header: + name: :path + exact: {{ .path }} + - prefix: {{ .path }} + {{- end }} + {{- if .targetService }} + name: {{ .targetService | replace "/" "-" }} + namespace: {{ (split "/" .targetService)._0 }} + {{- else }} + name: {{ $namespace }} + namespace: {{ $namespace }} + {{- end }} + {{- end }} + {{ $count = add1 $count }} +--- + +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/helm-charts/skye-serving/templates/otel-secret.yaml b/helm-charts/skye-serving/templates/otel-secret.yaml new file mode 100644 index 00000000..c2514b7d --- /dev/null +++ b/helm-charts/skye-serving/templates/otel-secret.yaml @@ -0,0 +1,26 @@ +{{ if .Values.otel_enabled }} +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} + annotations: + flagger.app/config-tracking: disabled + name: {{ if and .Values.deployment .Values.deployment.envFrom }}{{ .Values.deployment.envFrom.secretRef }}{{ else }}{{ .Values.namespace }}{{ end }}-otel + namespace: {{ .Values.namespace }} +spec: + dataFrom: + - extract: + conversionStrategy: Default + key: {{ .Values.infrastructure.vault.otelTokenPath | default "org/prd/cntr/devop/coralogix-token" }} + refreshInterval: 15s + secretStoreRef: + kind: {{ .Values.infrastructure.secretStore.kind | default "ClusterSecretStore" }} + name: {{ .Values.infrastructure.secretStore.name | default "vault-backend" }} + target: + creationPolicy: Owner + deletionPolicy: Retain + {{- if and .Values.deployment .Values.deployment.enabled }} + name: {{ .Values.deployment.envFrom.secretRef }}-otel + {{- end}} +{{ end }} diff --git a/helm-charts/skye-serving/templates/pdb.yaml b/helm-charts/skye-serving/templates/pdb.yaml new file mode 100644 index 00000000..db87f89a --- /dev/null +++ b/helm-charts/skye-serving/templates/pdb.yaml @@ -0,0 +1,36 @@ +{{- if or (and .Values.podDisruptionBudget .Values.podDisruptionBudget.enabled) (and .Values.deployment .Values.deployment.enabled) }} +{{- if or ( and .Values.deployment (.Values.deployment.enabled) (.Values.autoscaling.enabled) ( gt (int .Values.autoscaling.minReplicas) 1)) ( and (eq .Values.autoscaling.enabled false) .Values.deployment ( gt ( int .Values.deployment.replicaCount) 1 )) }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +spec: + {{- if .Values.podDisruptionBudget.enabled }} + maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }} + {{- else }} + {{- if and .Values.deployment .Values.deployment.enabled }} + {{- if (eq .Values.deployment.updateStrategy.strategy.type "RollingUpdate") }} + maxUnavailable: {{ .Values.deployment.updateStrategy.strategy.rollingUpdate.maxSurge | default "10%" }} + {{- else }} + maxUnavailable: "10%" + {{- end }} + {{ else }} + maxUnavailable: "10%" + {{- end }} + {{- end }} + {{- if .Values.podDisruptionBudget.minAvailable }} + minAvailable: {{ .Values.podDisruptionBudget.minAvailable }} + {{- end }} + selector: + matchLabels: + {{- if and (.Values.canary.enabled) (eq .Values.labels.env "prod") }} + {{- include "labels.primary-selector" . | nindent 6 }} + {{- else }} + {{- include "labels.selector" . | nindent 6 }} + {{- end }} +{{- end }} +{{- end }} diff --git a/helm-charts/skye-serving/templates/scaledobject.yaml b/helm-charts/skye-serving/templates/scaledobject.yaml new file mode 100644 index 00000000..259bfea2 --- /dev/null +++ b/helm-charts/skye-serving/templates/scaledobject.yaml @@ -0,0 +1,56 @@ +{{- if and .Values.autoscaling .Values.autoscaling.enabled }} +apiVersion: keda.sh/v1alpha1 +kind: ScaledObject +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ .Values.namespace }} + pollingInterval: {{ .Values.autoscaling.pollingInterval }} + {{- if ( default false (.Values.disasterRecovery).enabled ) }} + minReplicaCount: 1 + {{- else }} + minReplicaCount: {{ .Values.canary.minCanaryReplicas | default $.Values.autoscaling.minReplicas }} + {{- end }} + maxReplicaCount: {{ .Values.canary.maxCanaryReplicas | default $.Values.autoscaling.maxReplicas }} + advanced: + horizontalPodAutoscalerConfig: + behavior: + scaleDown: + stabilizationWindowSeconds: {{ .Values.autoscaling.scaledown.stabilizationWindowSeconds }} + policies: + {{- range .Values.autoscaling.scaledown.policies }} + - type: {{ .type }} + value: {{ .value }} + periodSeconds: {{ .periodseconds }} + {{- end }} + selectPolicy: {{ .Values.autoscaling.scaledown.selectpolicy }} + scaleUp: + stabilizationWindowSeconds: {{ .Values.autoscaling.scaleup.stabilizationWindowSeconds }} + policies: + {{- range .Values.autoscaling.scaleup.policies }} + - type: {{ .type }} + value: {{ .value }} + periodSeconds: {{ .periodseconds }} + {{- end }} + selectPolicy: {{ .Values.autoscaling.scaleup.selectpolicy }} + triggers: + {{- if ( default false (.Values.disasterRecovery).enabled ) }} + {{- range $.Values.autoscaling.triggers }} + {{- if or (eq .type "cpu") (eq .type "memory") }} + - metadata: + {{- toYaml .metadata | nindent 8 }} + type: {{ .type }} + metricType: "Utilization" + {{- end }} + {{- end }} + {{- else }} + {{- toYaml .Values.autoscaling.triggers | nindent 2 }} + {{ end }} + +{{- end }} diff --git a/helm-charts/skye-serving/templates/service.yaml b/helm-charts/skye-serving/templates/service.yaml new file mode 100644 index 00000000..8fcc5bc8 --- /dev/null +++ b/helm-charts/skye-serving/templates/service.yaml @@ -0,0 +1,29 @@ +{{- if and .Values.service .Values.service.enabled }} +{{- if or (eq .Values.canary.enabled false) ( and (.Values.canary.enabled) (ne .Values.labels.env "prod")) }} +apiVersion: v1 +kind: Service +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} +{{- if .Values.service.annotations }} + annotations: +{{ toYaml .Values.service.annotations | indent 4 }} +{{- end }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + {{- range .Values.service.ports }} + - name: {{ .name }} + port: {{ .port }} + protocol: {{ .protocol }} + targetPort: {{ .targetPort }} + {{- end }} + selector: + {{- include "labels.selector" . | nindent 4 }} +{{- end }} +{{- end }} diff --git a/helm-charts/skye-serving/templates/serviceaccount.yaml b/helm-charts/skye-serving/templates/serviceaccount.yaml new file mode 100644 index 00000000..f05362f5 --- /dev/null +++ b/helm-charts/skye-serving/templates/serviceaccount.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.deployment (.Values.deployment.enabled) (.Values.deployment.serviceAccount.enabled) }} +apiVersion: v1 +kind: ServiceAccount +metadata: + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} + {{- with .Values.deployment.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/helm-charts/skye-serving/values.yaml b/helm-charts/skye-serving/values.yaml new file mode 100644 index 00000000..283a32b9 --- /dev/null +++ b/helm-charts/skye-serving/values.yaml @@ -0,0 +1,171 @@ +# Default values for skye-serving helm chart + +namespace: prd-skye-serving +applicationName: skye-serving +replicaCount: 2 + +labels: + env: prd + team: bharatml + bu: ml + priority: p1 + priority_v2: cp3 + service_type: "" + +priorityClassName: "" + +telegraf: + enabled: false + +otel_enabled: false + +infrastructure: + secretStore: + name: vault-backend + kind: ClusterSecretStore + vault: + basePath: "" + otelTokenPath: "" + +deployment: + enabled: true + replicaCount: 2 + revisionHistoryLimit: 3 + image: + repository: ghcr.io/meesho/skye-serving + tag: latest + pullPolicy: IfNotPresent + ports: + - containerPort: 8094 + name: http + protocol: TCP + probes: + liveness: + path: /health/self + port: 8094 + scheme: HTTP + initialDelaySeconds: 30 + periodSeconds: 10 + failureThreshold: 3 + successThreshold: 1 + timeoutSeconds: 5 + readiness: + path: /health/self + port: 8094 + scheme: HTTP + initialDelaySeconds: 20 + periodSeconds: 10 + failureThreshold: 3 + successThreshold: 1 + timeoutSeconds: 5 + resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "1Gi" + cpu: "1000m" + env: + - name: APP_NAME + value: "skye" + - name: APP_ENV + value: "local" + - name: APP_LOG_LEVEL + value: "INFO" + - name: APP_PORT + value: "8094" + - name: APP_METRIC_SAMPLING_RATE + value: "100" + # In-memory cache (10 MB) + - name: IN_MEMORY_CACHE_SIZE_IN_BYTES + value: "10485760" + # Etcd + - name: ETCD_SERVER + value: "etcd:2379" + - name: ETCD_WATCHER_ENABLED + value: "true" + # Redis + - name: REDIS_ADDR + value: "redis:6379" + - name: REDIS_DB + value: "0" + # Profiling + - name: PROFILING_ENABLED + value: "false" + # Storage + - name: STORAGE_AGGREGATOR_DB_COUNT + value: "0" + - name: STORAGE_EMBEDDING_STORE_COUNT + value: "0" + # Auth + - name: AUTH_TOKENS + value: "test" + serviceAccount: + enabled: false + annotations: {} + updateStrategy: + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 0 + maxSurge: 1 + terminationGracePeriodSeconds: 30 + +service: + enabled: true + type: ClusterIP + ports: + - name: http + port: 80 + targetPort: 8094 + protocol: TCP + +autoscaling: + enabled: false + minReplicas: 2 + maxReplicas: 10 + pollingInterval: 30 + scaledown: + stabilizationWindowSeconds: 300 + policies: + - type: Percent + value: 10 + periodseconds: 60 + selectpolicy: Min + scaleup: + stabilizationWindowSeconds: 0 + policies: + - type: Percent + value: 50 + periodseconds: 60 + selectpolicy: Max + triggers: + - type: cpu + metadata: + value: "70" + metricType: Utilization + +ingress: + enabled: false + ingressClassName: contour-internal +createContourGateway: false + +externalSecret: + enabled: false + path: "" + +configMap: + enabled: false + +canary: + enabled: false + promURL: "" + slackChannel: "" + slackWebhookURL: "" + +podDisruptionBudget: + enabled: false + maxUnavailable: "10%" + +disasterRecovery: + enabled: false diff --git a/helm-charts/trufflebox-ui/Chart.yaml b/helm-charts/trufflebox-ui/Chart.yaml new file mode 100644 index 00000000..e5f83fbe --- /dev/null +++ b/helm-charts/trufflebox-ui/Chart.yaml @@ -0,0 +1,10 @@ +apiVersion: v2 +name: trufflebox-ui +description: A Helm chart for the TruffleBox UI dashboard +type: application +version: 1.0.0 +appVersion: "1.0.0" + +maintainers: + - name: BharatMLStack Team + email: ml-oss@meesho.com diff --git a/helm-charts/trufflebox-ui/templates/NOTES.txt b/helm-charts/trufflebox-ui/templates/NOTES.txt new file mode 100644 index 00000000..35a4f3c2 --- /dev/null +++ b/helm-charts/trufflebox-ui/templates/NOTES.txt @@ -0,0 +1,18 @@ +{{ .Chart.Name }} has been deployed. + +Namespace: {{ .Values.namespace }} +Application: {{ .Values.applicationName }} + +{{- if .Values.service.enabled }} +Service: {{ .Values.namespace }}:{{ (index .Values.service.ports 0).port }} +{{- end }} + +{{- if .Values.ingress.enabled }} +Ingress is enabled. +{{- end }} + +{{- if .Values.autoscaling.enabled }} +Autoscaling: {{ .Values.autoscaling.minReplicas }} - {{ .Values.autoscaling.maxReplicas }} replicas +{{- else }} +Replicas: {{ .Values.replicaCount }} +{{- end }} diff --git a/helm-charts/trufflebox-ui/templates/_helpers.tpl b/helm-charts/trufflebox-ui/templates/_helpers.tpl new file mode 100644 index 00000000..168e0342 --- /dev/null +++ b/helm-charts/trufflebox-ui/templates/_helpers.tpl @@ -0,0 +1,70 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "fullname" -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "labels.selector" -}} +app: {{ .Values.namespace }} +{{- end -}} + +{{- define "labels.primary-selector" -}} +app: {{ .Values.namespace }}-primary +{{- end -}} + +{{- define "labels.common" -}} +{{ template "labels.selector" . }} +{{- if and .Values.deployment .Values.deployment.image }} +version: {{ .Values.deployment.image.tag }} +{{- end }} +env: {{ .Values.labels.env }} +team: {{ .Values.labels.team }} +bu: {{ .Values.labels.bu }} +service: {{ .Values.applicationName }} +priority: {{ .Values.labels.priority }} +priority_v2: {{ .Values.labels.priority_v2 | default "cp3" }} +primary_owner: {{ .Values.labels.primary_owner | default .Values.labels.team }} +secondary_owner: {{ .Values.labels.secondary_owner | default .Values.labels.team }} +service_type: {{ .Values.labels.service_type | default "" | replace "," "-" | quote }} +{{- end -}} + +{{- define "labels.chart" -}} +chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" +release: {{ .Release.Name | quote }} +heritage: {{ .Release.Service | quote }} +{{- end -}} + +{{/* +Renders a value that contains template. +Usage: +{{ include "application.tplvalues.render" ( dict "value" .Values.path.to.the.Value "context" $) }} +*/}} + +{{- define "application.tplvalues.render" -}} + {{- if typeIs "string" .value }} + {{- tpl .value .context }} + {{- else }} + {{- tpl (.value | toYaml) .context }} + {{- end }} +{{- end -}} + +{{- define "canary.promURL" -}} +{{- if .Values.canary.promURL }} +{{- .Values.canary.promURL }} +{{- else if eq .Values.labels.env "prod" }} +prod-ops-metricsui.example.com/select/100/ +{{- else }} +https://sb-ops-metricsui.example.com/select/100/ +{{- end }} +{{- end -}} diff --git a/helm-charts/trufflebox-ui/templates/alert-provider.yaml b/helm-charts/trufflebox-ui/templates/alert-provider.yaml new file mode 100644 index 00000000..a300bb14 --- /dev/null +++ b/helm-charts/trufflebox-ui/templates/alert-provider.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.canary (.Values.canary.enabled) .Values.canary.slackWebhookURL (ne .Values.canary.slackWebhookURL "") }} +apiVersion: flagger.app/v1beta1 +kind: AlertProvider +metadata: + name: flagger-status + namespace: {{ .Values.namespace }} +spec: + type: slack + {{- if .Values.canary.slackChannel }} + channel: {{ .Values.canary.slackChannel }} + {{- end }} + username: flagger + address: {{ .Values.canary.slackWebhookURL }} +{{- end }} diff --git a/helm-charts/trufflebox-ui/templates/configmap.yaml b/helm-charts/trufflebox-ui/templates/configmap.yaml new file mode 100644 index 00000000..fc527219 --- /dev/null +++ b/helm-charts/trufflebox-ui/templates/configmap.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.configMap .Values.configMap.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Values.namespace }}-config + namespace: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +data: + {{- range $key, $value := .Values.configMap.data }} + {{ $key }}: {{ $value | quote }} + {{- end }} +{{- end }} diff --git a/helm-charts/trufflebox-ui/templates/deployment.yaml b/helm-charts/trufflebox-ui/templates/deployment.yaml new file mode 100644 index 00000000..34cbb1ed --- /dev/null +++ b/helm-charts/trufflebox-ui/templates/deployment.yaml @@ -0,0 +1,237 @@ +{{- if and .Values.deployment .Values.deployment.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.namespace }} + namespace: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +{{- include "labels.selector" . | nindent 4 }} +spec: + {{- with .Values.deployment.minReadySeconds }} + minReadySeconds: {{ . }} + {{- end }} + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + revisionHistoryLimit: {{ .Values.deployment.revisionHistoryLimit }} + selector: + matchLabels: +{{- include "labels.selector" . | nindent 6 }} +{{- with .Values.deployment.updateStrategy }} +{{ toYaml . | indent 2 -}} +{{- end }} + template: + metadata: + annotations: + {{- with .Values.deployment.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.telegraf.enabled }} + telegraf.influxdata.com/class: "infra" + {{- end }} + labels: + {{- include "labels.common" . | nindent 8 }} + spec: + {{- if .Values.priorityClassName }} + priorityClassName: {{ .Values.priorityClassName }} + {{- end }} + topologySpreadConstraints: + - maxSkew: 1 + topologyKey: topology.kubernetes.io/zone + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: +{{- include "labels.selector" . | nindent 12 }} + {{- if .Values.deployment.image.pullSecret }} + imagePullSecrets: + - name: {{ .Values.deployment.image.pullSecret }} + {{- end }} + {{- if .Values.deployment.volumes }} + volumes: + {{- toYaml .Values.deployment.volumes | nindent 8 }} + {{- end }} + {{- if .Values.deployment.initContainers }} + initContainers: + {{- toYaml .Values.deployment.initContainers | nindent 8 }} + {{- end }} + containers: + - name: {{ .Values.applicationName }} + {{- if .Values.deployment.volumeMounts }} + volumeMounts: + {{- toYaml .Values.deployment.volumeMounts | nindent 12 }} + {{- end }} + {{- if .Values.deployment.command }} + command: {{- include "application.tplvalues.render" (dict "value" .Values.deployment.command "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.deployment.args }} + args: {{- include "application.tplvalues.render" (dict "value" .Values.deployment.args "context" $) | nindent 12 }} + {{- end }} + image: "{{ .Values.deployment.image.repository }}:{{ .Values.deployment.image.tag }}" + imagePullPolicy: {{ .Values.deployment.image.pullPolicy }} + {{- if .Values.deployment.lifecycle }} + lifecycle: {{ toYaml .Values.deployment.lifecycle | nindent 12 }} + {{- end }} + {{- with .Values.deployment.probes }} + {{- with .liveness }} + livenessProbe: + {{- with .failureThreshold }} + failureThreshold: {{ . }} + {{- end }} + httpGet: + path: {{ .path }} + port: {{ .port }} + scheme: {{ .scheme }} + {{- with .periodSeconds }} + periodSeconds: {{ . }} + {{- end }} + {{- with .successThreshold }} + successThreshold: {{ . }} + {{- end }} + {{- with .timeoutSeconds }} + timeoutSeconds: {{ . }} + {{- end }} + initialDelaySeconds: {{ .initialDelaySeconds }} + {{- end }} + {{- end }} + {{- if or .Values.externalSecret.enabled .Values.otel_enabled (and .Values.configMap .Values.configMap.enabled) }} + envFrom: + {{- if .Values.externalSecret.enabled }} + - secretRef: + name: {{ .Values.deployment.envFrom.secretRef }} + {{- if ( default false (.Values.disasterRecovery).enabled ) }} + - secretRef: + name: {{ .Values.deployment.envFrom.secretRef }}-dr + {{- end }} + {{- end }} + {{- if .Values.otel_enabled }} + - secretRef: + name: {{ .Values.deployment.envFrom.secretRef }}-otel + {{- end }} + {{- if and .Values.configMap .Values.configMap.enabled }} + - configMapRef: + name: {{ .Values.namespace }}-config + {{- end }} + {{- end }} + env: + - name: TZ + value: Asia/Kolkata + - name: NODE_IP + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + {{- if .Values.telegraf.enabled }} + - name: TELEGRAF_UDP_HOST + valueFrom: + fieldRef: + fieldPath: status.podIP + {{- end }} + {{- if .Values.otel_enabled }} + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: http://$(NODE_IP):4317 + {{- end }} + {{- with .Values.deployment.env }} + {{- range . }} + - name: {{ .name }} + value: "{{ .value }}" + {{- end }} + {{- end }} + {{- with .Values.deployment.ports }} + ports: + {{- range . }} + - containerPort: {{ .containerPort }} + name: {{ .name }} + protocol: {{ .protocol }} + {{- end }} + {{- end }} + {{- if .Values.telegraf.enabled }} + - containerPort: 9273 + name: telegraf-sc + protocol: TCP + {{- end }} + {{- with .Values.deployment.probes }} + {{- with .readiness }} + readinessProbe: + {{- with .failureThreshold }} + failureThreshold: {{ . }} + {{- end }} + httpGet: + path: {{ .path }} + port: {{ .port }} + scheme: {{ .scheme }} + {{- with .periodSeconds }} + periodSeconds: {{ . }} + {{- end }} + {{- with .successThreshold }} + successThreshold: {{ . }} + {{- end }} + {{- with .timeoutSeconds}} + timeoutSeconds: {{ . }} + {{- end }} + initialDelaySeconds: {{ .initialDelaySeconds }} + {{- end }} + {{- end }} + {{- with .Values.deployment.resources }} + resources: + {{- with .limits }} + limits: + {{- with .memory }} + memory: "{{ . }}" + {{- end }} + {{- with .cpu }} + cpu: "{{ . }}" + {{- end }} + {{- end }} + {{- with .requests }} + requests: + {{- with .memory }} + memory: "{{ . }}" + {{- end }} + {{- with .cpu }} + cpu: "{{ . }}" + {{- end }} + {{- end }} + {{- end }} + + {{- with .Values.deployment.hostAliases }} + hostAliases: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.deployment.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.deployment.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + terminationGracePeriodSeconds: {{ .Values.deployment.terminationGracePeriodSeconds | default "300" }} + {{- if .Values.deployment.serviceAccount.enabled }} + serviceAccountName: {{ .Values.namespace }} + {{- end }} + {{- if .Values.securityContext }} + podSecurityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + {{- end }} +{{- end }} diff --git a/helm-charts/trufflebox-ui/templates/external-secrets.yaml b/helm-charts/trufflebox-ui/templates/external-secrets.yaml new file mode 100644 index 00000000..0d602056 --- /dev/null +++ b/helm-charts/trufflebox-ui/templates/external-secrets.yaml @@ -0,0 +1,30 @@ +{{- if and .Values.externalSecret .Values.externalSecret.enabled }} +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} +{{- if .Values.externalSecret.annotations }} + annotations: +{{ toYaml .Values.externalSecret.annotations | indent 4 }} +{{- end }} + {{- if and .Values.deployment .Values.deployment.enabled }} + name: {{ .Values.deployment.envFrom.secretRef }} + {{- end}} + namespace: {{ .Values.namespace }} +spec: + dataFrom: + - extract: + conversionStrategy: Default + key: {{ .Values.externalSecret.path }} + refreshInterval: 15s + secretStoreRef: + kind: {{ .Values.infrastructure.secretStore.kind | default "ClusterSecretStore" }} + name: {{ .Values.infrastructure.secretStore.name | default "vault-backend" }} + target: + creationPolicy: Owner + deletionPolicy: Retain + {{- if and .Values.deployment .Values.deployment.enabled }} + name: {{ .Values.deployment.envFrom.secretRef }} + {{- end}} +{{- end }} diff --git a/helm-charts/trufflebox-ui/templates/httpproxy.yaml b/helm-charts/trufflebox-ui/templates/httpproxy.yaml new file mode 100644 index 00000000..94fe8f3e --- /dev/null +++ b/helm-charts/trufflebox-ui/templates/httpproxy.yaml @@ -0,0 +1,50 @@ +{{- if and .Values.ingress .Values.ingress.enabled -}} +{{- if .Values.createContourGateway -}} +{{- if or ( eq "contour-internal" .Values.ingress.ingressClassName ) ( eq "contour-external" .Values.ingress.ingressClassName ) ( eq "contour-internal-0" .Values.ingress.ingressClassName ) ( eq "contour-internal-1" .Values.ingress.ingressClassName ) ( eq "contour-external-0" .Values.ingress.ingressClassName ) ( eq "contour-external-1" .Values.ingress.ingressClassName ) }} +{{- $servicePortNumber := .Values.ingress.servicePortNumber -}} +{{- $pathType := .Values.ingress.pathType -}} +{{- $namespace := .Values.namespace -}} +{{- $ingressClassName := .Values.ingress.ingressClassName -}} +{{ $count := 0 | int }} +{{- range .Values.ingress.hosts }} +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + namespace: {{ $namespace }} + name: {{ $namespace }}-{{ $count }} + labels: +{{ include "labels.common" $ | indent 4 }} +{{ include "labels.chart" $ | indent 4 }} + annotations: + projectcontour.io/ingress.class: {{ $.Values.ingress.ingressClassName }} +spec: + ingressClassName: {{ $ingressClassName }} + virtualhost: + fqdn: "{{ .host }}" + includes: + {{- range .paths }} + - conditions: + {{- if or ( eq ( lower .pathType ) "prefix" ) ( eq ( lower .pathType ) "implementationspecific") }} + - prefix: {{ .path }} + {{- end }} + {{- if ( eq ( lower .pathType ) "exact" ) }} + - header: + name: :path + exact: {{ .path }} + - prefix: {{ .path }} + {{- end }} + {{- if .targetService }} + name: {{ .targetService | replace "/" "-" }} + namespace: {{ (split "/" .targetService)._0 }} + {{- else }} + name: {{ $namespace }} + namespace: {{ $namespace }} + {{- end }} + {{- end }} + {{ $count = add1 $count }} +--- + +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/helm-charts/trufflebox-ui/templates/otel-secret.yaml b/helm-charts/trufflebox-ui/templates/otel-secret.yaml new file mode 100644 index 00000000..c2514b7d --- /dev/null +++ b/helm-charts/trufflebox-ui/templates/otel-secret.yaml @@ -0,0 +1,26 @@ +{{ if .Values.otel_enabled }} +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} + annotations: + flagger.app/config-tracking: disabled + name: {{ if and .Values.deployment .Values.deployment.envFrom }}{{ .Values.deployment.envFrom.secretRef }}{{ else }}{{ .Values.namespace }}{{ end }}-otel + namespace: {{ .Values.namespace }} +spec: + dataFrom: + - extract: + conversionStrategy: Default + key: {{ .Values.infrastructure.vault.otelTokenPath | default "org/prd/cntr/devop/coralogix-token" }} + refreshInterval: 15s + secretStoreRef: + kind: {{ .Values.infrastructure.secretStore.kind | default "ClusterSecretStore" }} + name: {{ .Values.infrastructure.secretStore.name | default "vault-backend" }} + target: + creationPolicy: Owner + deletionPolicy: Retain + {{- if and .Values.deployment .Values.deployment.enabled }} + name: {{ .Values.deployment.envFrom.secretRef }}-otel + {{- end}} +{{ end }} diff --git a/helm-charts/trufflebox-ui/templates/pdb.yaml b/helm-charts/trufflebox-ui/templates/pdb.yaml new file mode 100644 index 00000000..db87f89a --- /dev/null +++ b/helm-charts/trufflebox-ui/templates/pdb.yaml @@ -0,0 +1,36 @@ +{{- if or (and .Values.podDisruptionBudget .Values.podDisruptionBudget.enabled) (and .Values.deployment .Values.deployment.enabled) }} +{{- if or ( and .Values.deployment (.Values.deployment.enabled) (.Values.autoscaling.enabled) ( gt (int .Values.autoscaling.minReplicas) 1)) ( and (eq .Values.autoscaling.enabled false) .Values.deployment ( gt ( int .Values.deployment.replicaCount) 1 )) }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +spec: + {{- if .Values.podDisruptionBudget.enabled }} + maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }} + {{- else }} + {{- if and .Values.deployment .Values.deployment.enabled }} + {{- if (eq .Values.deployment.updateStrategy.strategy.type "RollingUpdate") }} + maxUnavailable: {{ .Values.deployment.updateStrategy.strategy.rollingUpdate.maxSurge | default "10%" }} + {{- else }} + maxUnavailable: "10%" + {{- end }} + {{ else }} + maxUnavailable: "10%" + {{- end }} + {{- end }} + {{- if .Values.podDisruptionBudget.minAvailable }} + minAvailable: {{ .Values.podDisruptionBudget.minAvailable }} + {{- end }} + selector: + matchLabels: + {{- if and (.Values.canary.enabled) (eq .Values.labels.env "prod") }} + {{- include "labels.primary-selector" . | nindent 6 }} + {{- else }} + {{- include "labels.selector" . | nindent 6 }} + {{- end }} +{{- end }} +{{- end }} diff --git a/helm-charts/trufflebox-ui/templates/scaledobject.yaml b/helm-charts/trufflebox-ui/templates/scaledobject.yaml new file mode 100644 index 00000000..259bfea2 --- /dev/null +++ b/helm-charts/trufflebox-ui/templates/scaledobject.yaml @@ -0,0 +1,56 @@ +{{- if and .Values.autoscaling .Values.autoscaling.enabled }} +apiVersion: keda.sh/v1alpha1 +kind: ScaledObject +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ .Values.namespace }} + pollingInterval: {{ .Values.autoscaling.pollingInterval }} + {{- if ( default false (.Values.disasterRecovery).enabled ) }} + minReplicaCount: 1 + {{- else }} + minReplicaCount: {{ .Values.canary.minCanaryReplicas | default $.Values.autoscaling.minReplicas }} + {{- end }} + maxReplicaCount: {{ .Values.canary.maxCanaryReplicas | default $.Values.autoscaling.maxReplicas }} + advanced: + horizontalPodAutoscalerConfig: + behavior: + scaleDown: + stabilizationWindowSeconds: {{ .Values.autoscaling.scaledown.stabilizationWindowSeconds }} + policies: + {{- range .Values.autoscaling.scaledown.policies }} + - type: {{ .type }} + value: {{ .value }} + periodSeconds: {{ .periodseconds }} + {{- end }} + selectPolicy: {{ .Values.autoscaling.scaledown.selectpolicy }} + scaleUp: + stabilizationWindowSeconds: {{ .Values.autoscaling.scaleup.stabilizationWindowSeconds }} + policies: + {{- range .Values.autoscaling.scaleup.policies }} + - type: {{ .type }} + value: {{ .value }} + periodSeconds: {{ .periodseconds }} + {{- end }} + selectPolicy: {{ .Values.autoscaling.scaleup.selectpolicy }} + triggers: + {{- if ( default false (.Values.disasterRecovery).enabled ) }} + {{- range $.Values.autoscaling.triggers }} + {{- if or (eq .type "cpu") (eq .type "memory") }} + - metadata: + {{- toYaml .metadata | nindent 8 }} + type: {{ .type }} + metricType: "Utilization" + {{- end }} + {{- end }} + {{- else }} + {{- toYaml .Values.autoscaling.triggers | nindent 2 }} + {{ end }} + +{{- end }} diff --git a/helm-charts/trufflebox-ui/templates/service.yaml b/helm-charts/trufflebox-ui/templates/service.yaml new file mode 100644 index 00000000..8fcc5bc8 --- /dev/null +++ b/helm-charts/trufflebox-ui/templates/service.yaml @@ -0,0 +1,29 @@ +{{- if and .Values.service .Values.service.enabled }} +{{- if or (eq .Values.canary.enabled false) ( and (.Values.canary.enabled) (ne .Values.labels.env "prod")) }} +apiVersion: v1 +kind: Service +metadata: + labels: + {{- include "labels.common" . | nindent 4 }} + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} +{{- if .Values.service.annotations }} + annotations: +{{ toYaml .Values.service.annotations | indent 4 }} +{{- end }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + {{- range .Values.service.ports }} + - name: {{ .name }} + port: {{ .port }} + protocol: {{ .protocol }} + targetPort: {{ .targetPort }} + {{- end }} + selector: + {{- include "labels.selector" . | nindent 4 }} +{{- end }} +{{- end }} diff --git a/helm-charts/trufflebox-ui/templates/serviceaccount.yaml b/helm-charts/trufflebox-ui/templates/serviceaccount.yaml new file mode 100644 index 00000000..f05362f5 --- /dev/null +++ b/helm-charts/trufflebox-ui/templates/serviceaccount.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.deployment (.Values.deployment.enabled) (.Values.deployment.serviceAccount.enabled) }} +apiVersion: v1 +kind: ServiceAccount +metadata: + namespace: {{ .Values.namespace }} + name: {{ .Values.namespace }} + labels: +{{ include "labels.common" . | indent 4 }} +{{ include "labels.chart" . | indent 4 }} + {{- with .Values.deployment.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/helm-charts/trufflebox-ui/values.yaml b/helm-charts/trufflebox-ui/values.yaml new file mode 100644 index 00000000..06cffa41 --- /dev/null +++ b/helm-charts/trufflebox-ui/values.yaml @@ -0,0 +1,151 @@ +# Default values for trufflebox-ui helm chart + +namespace: prd-trufflebox-ui +applicationName: trufflebox-ui +replicaCount: 2 + +labels: + env: prd + team: bharatml + bu: ml + priority: p2 + priority_v2: cp3 + service_type: "" + +priorityClassName: "" + +telegraf: + enabled: false + +otel_enabled: false + +infrastructure: + secretStore: + name: vault-backend + kind: ClusterSecretStore + vault: + basePath: "" + otelTokenPath: "" + +deployment: + enabled: true + replicaCount: 2 + revisionHistoryLimit: 3 + image: + repository: ghcr.io/meesho/trufflebox-ui + tag: latest + pullPolicy: IfNotPresent + ports: + - containerPort: 80 + name: http + protocol: TCP + probes: + liveness: + path: / + port: 80 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 10 + failureThreshold: 3 + successThreshold: 1 + timeoutSeconds: 5 + readiness: + path: / + port: 80 + scheme: HTTP + initialDelaySeconds: 5 + periodSeconds: 10 + failureThreshold: 3 + successThreshold: 1 + timeoutSeconds: 5 + resources: + requests: + memory: "128Mi" + cpu: "100m" + limits: + memory: "256Mi" + cpu: "250m" + env: + - name: REACT_APP_ENVIRONMENT + value: "production" + - name: REACT_APP_HORIZON_BASE_URL + value: "http://horizon:8082" + - name: REACT_APP_ONLINE_FEATURE_STORE_ENABLED + value: "true" + - name: REACT_APP_INFERFLOW_ENABLED + value: "true" + - name: REACT_APP_NUMERIX_ENABLED + value: "true" + - name: REACT_APP_PREDATOR_ENABLED + value: "true" + - name: REACT_APP_EMBEDDING_PLATFORM_ENABLED + value: "false" + serviceAccount: + enabled: false + annotations: {} + updateStrategy: + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 0 + maxSurge: 1 + terminationGracePeriodSeconds: 30 + +service: + enabled: true + type: ClusterIP + ports: + - name: http + port: 80 + targetPort: 80 + protocol: TCP + +autoscaling: + enabled: false + minReplicas: 2 + maxReplicas: 10 + pollingInterval: 30 + scaledown: + stabilizationWindowSeconds: 300 + policies: + - type: Percent + value: 10 + periodseconds: 60 + selectpolicy: Min + scaleup: + stabilizationWindowSeconds: 0 + policies: + - type: Percent + value: 50 + periodseconds: 60 + selectpolicy: Max + triggers: + - type: cpu + metadata: + value: "70" + metricType: Utilization + +ingress: + enabled: false + ingressClassName: contour-internal +createContourGateway: false + +externalSecret: + enabled: false + path: "" + +configMap: + enabled: false + +canary: + enabled: false + promURL: "" + slackChannel: "" + slackWebhookURL: "" + +podDisruptionBudget: + enabled: false + maxUnavailable: "10%" + +disasterRecovery: + enabled: false diff --git a/horizon/VERSION b/horizon/VERSION index 79127d85..0d0c52f8 100644 --- a/horizon/VERSION +++ b/horizon/VERSION @@ -1 +1 @@ -v1.2.0 +v1.4.0 diff --git a/horizon/cmd/horizon/Dockerfile b/horizon/cmd/horizon/Dockerfile index 5aa6fbbb..1ef822cf 100644 --- a/horizon/cmd/horizon/Dockerfile +++ b/horizon/cmd/horizon/Dockerfile @@ -1,5 +1,5 @@ # Stage 1: Build the Go binary -FROM golang:1.22-bullseye AS builder +FROM golang:1.24-bullseye AS builder ARG TARGETOS ARG TARGETARCH diff --git a/horizon/cmd/horizon/main.go b/horizon/cmd/horizon/main.go index b605a09b..8de135e6 100644 --- a/horizon/cmd/horizon/main.go +++ b/horizon/cmd/horizon/main.go @@ -3,31 +3,83 @@ package main import ( "strconv" + horizonConfig "github.com/Meesho/BharatMLStack/horizon/internal" + applicationRouter "github.com/Meesho/BharatMLStack/horizon/internal/application/route" authRouter "github.com/Meesho/BharatMLStack/horizon/internal/auth/router" - middlewares "github.com/Meesho/BharatMLStack/horizon/internal/middlewares" - numerixConfig "github.com/Meesho/BharatMLStack/horizon/internal/numerix/config" - numerixRouter "github.com/Meesho/BharatMLStack/horizon/internal/numerix/router" + "github.com/Meesho/BharatMLStack/horizon/internal/configs" + connectionConfigRouter "github.com/Meesho/BharatMLStack/horizon/internal/connectionconfig/route" + deployableRouter "github.com/Meesho/BharatMLStack/horizon/internal/deployable/router" + dnsRouter "github.com/Meesho/BharatMLStack/horizon/internal/dns" + "github.com/Meesho/BharatMLStack/horizon/internal/externalcall" + inferflowConfig "github.com/Meesho/BharatMLStack/horizon/internal/inferflow/etcd" + inferflowRouter "github.com/Meesho/BharatMLStack/horizon/internal/inferflow/route" + infrastructureRouter "github.com/Meesho/BharatMLStack/horizon/internal/infrastructure/router" + "github.com/Meesho/BharatMLStack/horizon/internal/middleware" + numerixConfig "github.com/Meesho/BharatMLStack/horizon/internal/numerix/etcd" + numerixRouter "github.com/Meesho/BharatMLStack/horizon/internal/numerix/route" ofsConfig "github.com/Meesho/BharatMLStack/horizon/internal/online-feature-store/config" ofsRouter "github.com/Meesho/BharatMLStack/horizon/internal/online-feature-store/router" - "github.com/Meesho/BharatMLStack/horizon/pkg/config" + predatorRouter "github.com/Meesho/BharatMLStack/horizon/internal/predator/route" + skyeConfig "github.com/Meesho/BharatMLStack/horizon/internal/skye/etcd" + skyeRouter "github.com/Meesho/BharatMLStack/horizon/internal/skye/route" + workflowPkg "github.com/Meesho/BharatMLStack/horizon/internal/workflow" + workflowEtcd "github.com/Meesho/BharatMLStack/horizon/internal/workflow/etcd" + workflowHandler "github.com/Meesho/BharatMLStack/horizon/internal/workflow/handler" + workflowRouter "github.com/Meesho/BharatMLStack/horizon/internal/workflow/router" "github.com/Meesho/BharatMLStack/horizon/pkg/etcd" "github.com/Meesho/BharatMLStack/horizon/pkg/httpframework" "github.com/Meesho/BharatMLStack/horizon/pkg/infra" "github.com/Meesho/BharatMLStack/horizon/pkg/logger" "github.com/Meesho/BharatMLStack/horizon/pkg/metric" - "github.com/spf13/viper" + "github.com/Meesho/BharatMLStack/horizon/pkg/scheduler" +) + +type AppConfig struct { + Configs configs.Configs + DynamicConfigs configs.DynamicConfigs +} + +func (cfg *AppConfig) GetStaticConfig() interface{} { + return &cfg.Configs +} + +func (cfg *AppConfig) GetDynamicConfig() interface{} { + return &cfg.DynamicConfigs +} + +var ( + appConfig AppConfig ) func main() { - config.InitEnv() - infra.InitDBConnectors() - logger.Init() - metric.Init() - httpframework.Init(middlewares.NewMiddleware().GetMiddleWares()...) - etcd.InitFromAppName(&ofsConfig.FeatureRegistry{}, viper.GetString("ONLINE_FEATURE_STORE_APP_NAME")) - etcd.InitFromAppName(&numerixConfig.NumerixConfig{}, viper.GetString("NUMERIX_APP_NAME")) + configs.InitConfig(&appConfig) + infra.InitDBConnectors(appConfig.Configs) + etcd.InitFromAppName(&ofsConfig.FeatureRegistry{}, appConfig.Configs.OnlineFeatureStoreAppName, appConfig.Configs) + etcd.InitFromAppName(&numerixConfig.NumerixConfigRegistery{}, appConfig.Configs.NumerixAppName, appConfig.Configs) + etcd.InitFromAppName(&inferflowConfig.ModelConfigRegistery{}, appConfig.Configs.InferflowAppName, appConfig.Configs) + etcd.InitFromAppName(&inferflowConfig.HorizonRegistry{}, appConfig.Configs.HorizonAppName, appConfig.Configs) + etcd.InitFromAppName(&workflowEtcd.WorkflowRegistry{}, workflowPkg.WorkflowAppName, appConfig.Configs) + etcd.InitFromAppName(&skyeConfig.Skye{}, appConfig.Configs.SkyeAppName, appConfig.Configs) + horizonConfig.InitAll(appConfig.Configs) + logger.Init(appConfig.Configs) + metric.Init(appConfig.Configs) + httpframework.Init(middleware.NewMiddleware().GetMiddleWares()...) + workflowHandler.InitV1WorkflowHandler() + deployableRouter.Init(appConfig.Configs) + externalcall.Init(appConfig.Configs) + inferflowRouter.Init() + numerixRouter.Init() + applicationRouter.Init() + connectionConfigRouter.Init() + predatorRouter.Init() authRouter.Init() ofsRouter.Init() - numerixRouter.Init() - httpframework.Instance().Run(":" + strconv.Itoa(viper.GetInt("APP_PORT"))) + infrastructureRouter.Init() + dnsRouter.Init() + workflowRouter.Init() + skyeRouter.Init(appConfig.Configs) + scheduler.Init(appConfig.Configs) + scheduler.InitVariantScaleUpScheduler(appConfig.Configs) + scheduler.InitVariantOnboardingScheduler(appConfig.Configs) + httpframework.Instance().Run(":" + strconv.Itoa(appConfig.Configs.AppPort)) } diff --git a/horizon/configs/models/dummy_model/1/model.txt b/horizon/configs/models/dummy_model/1/model.txt new file mode 100644 index 00000000..bd4c7949 --- /dev/null +++ b/horizon/configs/models/dummy_model/1/model.txt @@ -0,0 +1,3950 @@ +tree +version=v3 +num_class=1 +num_tree_per_iteration=1 +label_index=0 +max_feature_idx=21 +objective=lambdarank +feature_names=catalog_anonymous_total_clicks catalog_anonymous_unique_clicks catalog_anonymous_views catalog_total_clicks catalog_unique_clicks catalog_num_of_orders catalog_ordered_quantity catalog_avg_rating catalog_signedup_views supplier_num_of_customers supplier_num_of_orders supplier_ordered_quantity supplier_num_catalog_orders supplier_num_product_orders user_num_add_to_carts user_clicks_total user_clicks_unique user_num_of_orders user_num_of_cancelled_orders user_num_of_shares supplier_num_catalog_offerings supplier_num_sscat_offerings +feature_infos=[0:141989] [0:144958] [0:146541] [0:149638] [0:109471] [0:137669] [0:148352] [0:151502] [0:117963] [0:120227] [0:146267] [0:149303] [0:146541] [0:149638] [0:146541] [0:149638] [0:142599] [0:145609] [0:65115] [0:135957] [0:146267] [0:149303] +tree_sizes=3571 3578 3577 3573 3618 3635 3626 3647 3650 3580 3638 3628 3612 3542 3583 3620 3619 3599 3528 3575 3571 3583 3555 3546 3565 3584 3572 3570 3584 3552 3561 3557 3541 3532 3584 3526 3540 3568 3599 3559 3579 3586 3544 3561 3555 3580 3568 3587 3551 3560 3564 3559 3566 3584 3572 3557 3559 3558 3559 3578 3579 3522 3572 3566 3576 3581 3600 3581 3563 3576 3553 3558 3580 3551 3556 3556 3561 3561 3612 3556 3548 3574 3543 3553 3554 3579 3580 3576 3559 3589 3556 3541 3542 3543 3566 3547 3577 3571 3580 3535 3547 3561 3572 3597 3577 3555 3604 3547 3535 3568 3579 3549 3548 3564 3566 3543 3571 3560 3553 3559 3554 3606 3583 3579 3570 3541 3568 3579 3546 3571 3565 3540 3544 3530 3544 3512 3522 3534 3560 3540 3570 3528 3541 3569 3549 3551 3557 3583 3554 3532 3619 3569 3575 3592 3526 3548 3591 3568 3551 3566 3557 3562 3556 3558 3548 3565 3560 3547 3567 3561 3554 3568 3564 3584 3547 3556 3567 3559 3575 3559 3572 3591 3573 3563 3584 3561 3565 3602 3576 3573 3564 3585 3593 3570 3550 3582 3609 3566 3535 3568 + +Tree=0 +num_leaves=32 +num_cat=0 +split_feature=16 1 7 16 17 2 0 11 7 20 8 20 20 2 8 12 11 5 4 5 20 9 19 9 19 11 9 16 8 0 9 +split_gain=7.98912 7.52946 8.0022 9.53811 8.42791 11.7457 7.29292 9.76608 8.55263 7.00749 6.62005 5.87594 8.24409 5.7804 5.35196 6.48088 7.90264 5.7626 5.14068 9.86531 5.07708 10.2795 8.50342 6.58547 5.66366 5.8252 5.6049 5.7105 5.78074 5.56871 5.36026 +threshold=41351.500000000007 55527.000000000007 3.5000000000000004 60.500000000000007 37.500000000000007 1082.5000000000002 7520.5000000000009 3.5000000000000004 304.50000000000006 1565.5000000000002 11.500000000000002 51823.000000000007 38526.500000000007 2.5000000000000004 248.50000000000003 1.0000000180025095e-35 1.0000000180025095e-35 2.5000000000000004 8.5000000000000018 4.5000000000000009 1.0000000180025095e-35 135.50000000000003 585.00000000000011 81.500000000000014 4203.0000000000009 1.5000000000000002 22.500000000000004 45189.500000000007 94.500000000000014 11.500000000000002 65.500000000000014 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 11 -3 4 5 9 -6 8 -8 -4 -10 12 -1 -14 15 -13 18 -16 -17 -20 21 22 -2 29 26 -26 -22 -28 30 -24 -29 +right_child=20 2 3 -5 6 -7 7 -9 10 -11 -12 14 13 -15 17 16 -18 -19 19 -21 24 -23 23 -25 25 -27 27 28 -30 -31 -32 +leaf_value=1.0251304083221884e-05 -0.19999999999999951 -0.030849428395915826 -0.20000000000000023 0.11101582885110634 0.10474790172983979 -0.13543516270642783 -0.20000000000000001 0.17910081430630076 0.15099299218612316 0.11326370961124831 -0.15821332014216899 0.12059300794035972 -0.023104481028885918 -0.10967743589736495 0.14365748835993927 -0.20000000000000007 0.064856038189650453 0.013161077286385032 0.14122176766150382 -0.13060816262813899 -0.15293015853846462 -0.20000000000000001 -0.20000000000000009 0.06722411366890603 -0.16336253478082921 0.18601528717583413 -0.078328454033074479 0.045625785178999338 0.10978753860787907 -0.019155711009130001 -0.039124826216372713 +leaf_weight=63621.143667903707 2.7352618358563694 14.816842383472247 0.84827676229178717 18.070234687998891 13.053927593515256 6.6174400127201807 2.9798705010325639 1.9222720476100219 1.9150374883320185 4.5134903724538162 1.0845476776594294 3.9525332596967955 30.734991687568254 10.29612255701795 8.137418467376845 2.691278160113141 5.9052860758965826 5.792890615790383 2.0860974694369361 3.7086569836828858 2.4030812254641196 4.9136409430066115 2.2623620853992161 6.5917131909227455 3.9893493728595777 0.5420652963221072 7.0408548205159596 14.560422046517489 5.9951368703623293 6.8833840266743209 15.309438788332043 +leaf_count=24210048 1042 5634 323 6879 4967 2522 1133 732 728 1719 411 1507 11696 3917 3095 1024 2246 2205 792 1412 915 1869 861 2506 1518 206 2678 5544 2280 2619 5825 +internal_value=0 3.79114e-05 0.0338422 0.052635 0.0206034 -0.0463031 0.0588502 -0.0169743 -0.0800084 0.0637027 0.0391945 2.97797e-06 -1.86479e-05 -0.0448286 0.042661 0.00717446 -0.0239756 0.0893908 -0.0857921 -0.0327501 -0.0330115 -0.0714513 -0.0372581 -0.00897263 -0.0149746 -0.121569 -0.00431394 0.00400979 0.0201741 -0.0638907 0.00218788 +internal_weight=0 63760.3 65.8219 51.0051 32.9349 11.9792 20.9557 7.90173 5.97946 5.36177 2.99959 63694.4 63662.2 41.0311 32.2742 18.3439 14.3913 13.9303 8.48603 5.79475 73.2267 23.3864 18.4727 15.7375 49.8403 4.53141 45.3089 42.9059 35.865 9.14575 29.8699 +internal_count=24290853 24262990 25048 19414 12535 4564 7971 3004 2272 2042 1139 24237942 24225661 15613 12281 6981 5474 5300 3228 2204 27863 8897 7028 5986 18966 1724 17242 16327 13649 3480 11369 +is_linear=0 +shrinkage=0.1 + + +Tree=1 +num_leaves=32 +num_cat=0 +split_feature=6 13 0 6 0 0 20 0 11 9 2 2 2 13 19 6 11 11 0 4 10 20 8 11 20 8 4 11 16 3 9 +split_gain=9.98474 14.9164 19.0562 9.97815 9.9291 9.44504 10.2557 9.62655 9.35223 9.0407 9.13051 8.99382 8.57651 9.60868 9.28441 8.66867 8.32451 10.2079 12.84 11.0992 8.16634 8.08782 8.04586 7.81381 11.7965 8.39001 8.81039 8.8142 7.16591 10.9177 17.1752 +threshold=9385.5000000000018 485.00000000000006 61.500000000000007 8234.0000000000018 38.500000000000007 9.5000000000000018 49.500000000000007 1.0000000180025095e-35 703.50000000000011 801.50000000000011 54.500000000000007 151.50000000000003 104.50000000000001 398.50000000000006 6.5000000000000009 8510.5000000000018 109.50000000000001 399.50000000000006 51.500000000000007 8500.0000000000018 133.50000000000003 21.500000000000004 44345.000000000007 90.500000000000014 5.5000000000000009 1206.5000000000002 9541.0000000000018 17.500000000000004 14162.500000000002 6.5000000000000009 54.500000000000007 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=3 4 8 28 5 7 9 -2 -3 11 -11 -7 -5 15 -15 20 23 18 -18 -20 -14 -21 -9 25 -25 26 -6 -28 -1 -30 -31 +right_child=1 2 -4 12 16 6 -8 22 -10 10 -12 -13 13 14 -16 -17 17 -19 19 21 -22 -23 -24 24 -26 -27 27 -29 29 30 -32 +leaf_value=3.5996095599638827e-05 -0.12183503354441261 0.11549987618592257 -0.16095895378197289 -0.014848283397801391 0.065721061670346609 -0.10538837133203725 -0.16792815747430864 0.052403718964748096 -0.064109143794526743 0.034626765068957142 -0.069484478461980137 0.078665750700843765 0.10160471124484896 -0.20000000000000001 0.062071429767076275 -0.20005949757444702 -0.15697099935453496 0.035252410193358438 0.031755355413553239 -0.016754634889375435 -0.1999999999999999 -0.14323516064080372 -0.20000000000000001 -0.072325286459293578 0.14610570520893679 -0.066371782835119339 0.1480523745731159 -0.11399662594431854 -0.01862863692474314 -0.059712635178786166 0.068495918820141041 +leaf_weight=64345.72129158496 3.9923881534632519 30.800740991078783 3.0853627460019188 43.686611610464752 39.73653544622357 18.18878493686498 5.5605959445238105 32.078639544080943 3.200294670125003 27.172443429517443 12.208284940090378 3.1086924769915631 2.248975665686888 1.7932047933572892 5.4918213046330493 8.936571889658806 6.3228903774579512 63.679663814706146 31.659983041448868 14.716742042568514 1.494189736258704 7.7014654418162527 1.3146951537346456 2.986269757209814 14.369427312907645 7.5489328087423919 1.6646736743859936 5.6065804922545794 382.81882087237318 14.78706661070464 35.615281876700465 +leaf_count=23980200 1487 11478 1151 16282 14806 6778 2075 11956 1193 10126 4549 1157 839 669 2044 3330 2352 23728 11802 5489 558 2870 490 1116 5352 2816 620 2089 142666 5510 13275 +internal_value=0 0.0171759 0.0770012 -8.91895e-05 0.00977078 -0.015265 -0.0379462 0.024921 0.0985945 -0.0260346 0.00235159 -0.0785229 -0.039663 -0.0939622 -0.0024373 -0.146547 0.0230075 0.00731806 -0.0221326 -0.00636708 -0.0187893 -0.0602053 0.0424666 0.0500789 0.108522 0.0314868 0.0472019 -0.0540034 -5.03045e-05 -0.0128684 0.030882 +internal_weight=0 336.704 37.0864 64842.6 299.618 103.625 66.2388 37.3857 34.001 60.6782 39.3807 21.2975 63.6514 19.9648 7.28503 12.6797 195.993 124.081 60.4011 54.0782 3.74317 22.4182 33.3933 71.9124 17.3557 54.5567 47.0078 7.27125 64778.9 433.221 50.4023 +internal_count=24290853 125480 13822 24165373 111658 38618 24685 13933 12671 22610 14675 7935 23722 7440 2713 4727 73040 46241 22513 20161 1397 8359 12446 26799 6468 20331 17515 2709 24141651 161451 18785 +is_linear=0 +shrinkage=0.1 + + +Tree=2 +num_leaves=32 +num_cat=0 +split_feature=5 4 4 16 8 20 4 5 4 17 16 9 9 4 9 14 8 2 15 20 20 14 1 20 17 15 20 9 9 8 5 +split_gain=10.2224 17.6286 13.6282 14.0128 19.3069 12.8498 12.4932 24.5262 12.1938 11.1852 11.0402 24.2538 28.0397 20.799 18.5111 18.2434 26.1596 17.7216 16.8627 14.8897 25.4787 14.7618 14.4331 23.5605 14.6733 15.2158 14.1657 14.0738 27.1516 21.6236 21.6833 +threshold=3357.0000000000005 1745.0000000000002 1033.0000000000002 62.500000000000007 4711.0000000000009 32260.500000000004 8884.5000000000018 562.00000000000011 11622.000000000002 410.00000000000006 11753.500000000002 79.500000000000014 243.50000000000003 94.500000000000014 74.500000000000014 7577.0000000000009 3.5000000000000004 6.5000000000000009 18044.500000000004 5.5000000000000009 1.0000000180025095e-35 7016.5000000000009 235.50000000000003 311.50000000000006 15305.500000000002 30195.500000000004 2.5000000000000004 298.50000000000006 348.50000000000006 37.500000000000007 1.5000000000000002 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 10 3 -2 -5 9 -3 8 -8 -4 -1 13 17 15 -15 16 22 -13 -18 20 -20 -14 23 24 -12 -26 -27 -23 -29 -30 -31 +right_child=2 6 5 4 -6 -7 7 -9 -10 -11 11 12 21 14 -16 -17 18 -19 19 -21 -22 27 -24 -25 25 26 -28 28 29 30 -32 +leaf_value=-8.2080172648138353e-05 0.03970676637018751 0.029313886272709883 -0.0078443793431115429 -0.16984921786466381 0.14867378402004647 0.19748180943984328 -0.0074951192875403888 -0.11741571654456606 0.10339933901580295 -0.20324805385173011 -0.19951887671447993 -0.020849967512341 -0.10135577593593979 0.012090686377764965 -0.20064098794234161 0.1056101475689889 -0.15843329003502041 -0.16884460183132599 0.090883820219520794 0.085668476624481849 -0.16411900396206691 0.11921187795811078 0.14233282726040772 -0.19999964263182868 0.13624069350842885 -0.20013229778356889 0.080908752784181331 -0.20000679748110969 0.11964746790624321 0.18793024338988087 -0.20234630757174804 +leaf_weight=64950.323844231869 6.6782200839443275 331.72103428168339 1101.8533407887371 15.393050575628878 2.1714101959951213 3.0409228801727286 47.803253257967299 18.031152390496572 12.510688064590793 2.937216146325226 1.9184043083805651 173.01551047353132 5.8068350913235909 112.12089290397125 4.245293604937614 56.315974755969364 8.1524557688971964 8.488106823875567 6.6608670814312125 12.662103923881658 9.5158342865761352 26.360916180623462 21.091189919039607 3.7455584402778177 18.273859914974313 3.0243827550439155 4.4067076610372169 4.7622827524901394 17.638222115347165 2.0058817751705638 4.9037958321569022 +leaf_count=23548598 2418 120270 399490 5584 788 1103 17334 6535 4536 1063 695 62728 2102 40651 1539 20418 2956 3079 2416 4592 3451 9555 7648 1357 6627 1095 1596 1728 6400 725 1776 +internal_value=0 0.00016194 -0.00942192 -0.083592 -0.130472 -0.00779885 0.0208313 -0.015085 0.0155073 -0.00836388 3.245e-05 0.0147593 -0.00800053 0.0358563 0.00432977 0.061024 0.0329539 -0.027771 -0.0314464 0.0044516 -0.0591199 0.0503686 0.0783645 0.0353547 0.0672674 0.0871781 -0.0334723 0.0661944 0.0185117 0.0609042 -0.0890489 +internal_weight=0 65865.5 1132.07 24.2427 17.5645 1107.83 410.066 78.3451 60.3139 1104.79 65455.4 505.115 242.982 262.134 116.366 145.767 89.4514 181.504 36.9913 28.8388 16.1767 61.4779 52.4601 31.3689 27.6234 25.705 7.43109 55.6711 29.3102 24.5479 6.90968 +internal_count=24290853 23880407 410446 8790 6372 401656 148675 28405 21870 400553 23731732 183134 88093 95041 42190 52851 32433 65807 13415 10459 5867 22286 19018 11370 10013 9318 2691 20184 10629 8901 2501 +is_linear=0 +shrinkage=0.1 + + +Tree=3 +num_leaves=32 +num_cat=0 +split_feature=4 6 3 2 18 5 18 12 1 18 3 2 19 13 19 13 15 12 15 15 14 14 14 10 4 11 18 19 15 18 13 +split_gain=56.4038 28.8632 22.6213 22.7124 37.365 23.4093 33.5733 21.1756 20.6075 20.5312 22.9652 26.7247 19.6152 19.527 17.1762 16.7685 16.3867 19.1366 16.1465 22.619 17.8768 15.6446 24.4099 15.5175 13.3929 16.8973 23.0433 15.9767 16.2264 13.9651 12.883 +threshold=19475.500000000004 106.50000000000001 4766.0000000000009 8943.0000000000018 14.500000000000002 3357.0000000000005 6.5000000000000009 4883.5000000000009 50065.500000000007 238.50000000000003 8554.0000000000018 7122.0000000000009 60.500000000000007 60502.500000000007 39.500000000000007 1.5000000000000002 1.5000000000000002 5.5000000000000009 12.500000000000002 30.500000000000004 151.50000000000003 129.50000000000003 151.50000000000003 9.5000000000000018 16.500000000000004 1.5000000000000002 89.500000000000014 106.50000000000001 9.5000000000000018 200.50000000000003 8.5000000000000018 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=2 -2 3 13 5 6 14 9 -9 24 11 -11 23 -1 15 -5 -3 -18 -14 20 -20 -16 -23 -6 -4 27 30 28 -26 -28 -27 +right_child=1 16 7 4 12 -7 -8 8 -10 10 -12 -13 18 -15 21 -17 17 -19 19 -21 -22 22 -24 -25 25 26 29 -29 -30 -31 -32 +leaf_value=-0.00022391515181203626 -0.13310080168797897 0.1606797950040405 -0.09624011880291565 -0.090466979850638118 -0.20025276680512905 0.18799530968759715 0.053762290345856736 0.14987043971094116 -0.19950552389617521 -0.098940817114102936 -0.10890641815952831 0.1729511863161842 -0.18722745128934395 -0.057592248564072669 -0.17755195872632631 0.17381857124862876 0.057185583804260469 -0.13635988277068925 -0.20037365157521184 -0.1696303964289774 0.1198482832847724 0.10780679281510777 -0.12461538614210797 0.077082416464078818 0.0099596824613496244 0.045127308305137025 -0.1046281654506543 -0.18085104375116379 0.10558377301018562 0.1182422678150902 0.11702553542998904 +leaf_weight=67030.566906737644 8.0702815413824265 14.899291438865474 10.230535379261708 4.627941918675786 2.6191861617844543 6.1702372930012634 39.527684696600772 18.25982419715729 1.8602550327777851 6.743177531170657 27.167151378642302 7.7930356524884701 24.887963306449819 59.384846800123341 15.995308578247203 4.9885958726517856 177.59134428601828 5.2598545094951978 2.2866775322472694 10.265255133854224 7.3374021857744083 6.8746255787555155 13.18539344210876 8.7822286533773859 585.76214858739695 101.59036695110262 13.883942499029219 4.2881070042494676 18.299870426533744 3.525413228897377 33.022842694132123 +leaf_count=23847847 2868 5300 3638 1647 932 2195 14062 6496 662 2401 9660 2773 8856 21128 5690 1776 63188 1870 813 3654 2610 2445 4690 3123 208404 36144 4939 1527 6512 1254 11749 +internal_value=0 0.0522702 -0.000158049 -0.000360886 -0.0395519 -9.25986e-05 -0.0137141 0.0162257 0.117568 0.0137156 -0.0546248 0.0468237 -0.103729 -0.000274695 -0.072113 0.0466316 0.0598352 0.0516181 -0.133546 -0.066373 0.0437637 -0.103784 -0.0449636 0.0133717 0.017414 0.0189432 0.0487639 0.0114912 0.0128566 -0.0594967 0.0627651 +internal_weight=0 205.821 68069.9 67237.5 147.549 91.3698 85.1996 832.427 20.1201 812.307 41.7034 14.5362 56.1787 67090 45.6719 9.61654 197.75 182.851 44.7773 19.8893 9.62408 36.0553 20.06 11.4014 770.603 760.373 152.023 608.35 604.062 17.4094 134.613 +internal_count=24290853 73226 24217627 23921468 52493 32505 30310 296159 7158 289001 14834 5174 19988 23868975 16248 3423 70358 65058 15933 7077 3423 12825 7135 4055 274167 270529 54086 216443 214916 6193 47893 +is_linear=0 +shrinkage=0.1 + + +Tree=4 +num_leaves=32 +num_cat=0 +split_feature=3 0 21 0 4 1 19 16 15 18 4 13 16 21 20 11 12 18 0 16 16 18 0 4 13 7 7 12 18 16 7 +split_gain=76.7282 41.9108 59.9814 29.7452 29.422 38.0817 28.5345 27.6924 55.6971 26.0356 29.9346 25.5544 24.2353 26.5379 25.1718 24.3113 23.7115 46.1021 40.8358 27.7165 25.5476 25.0591 27.4486 25.0405 22.8128 22.1913 21.4431 21.0508 65.5588 32.9969 26.3247 +threshold=7539.0000000000009 8134.0000000000009 11544.000000000002 7826.0000000000009 660.00000000000011 5468.5000000000009 4.5000000000000009 190.00000000000003 11.500000000000002 257.00000000000006 25288.500000000004 63827.000000000007 58.500000000000007 37798.000000000007 9142.0000000000018 6.5000000000000009 136.50000000000003 21.500000000000004 8881.5000000000018 143.50000000000003 41.500000000000007 145.50000000000003 7398.5000000000009 557.00000000000011 4.5000000000000009 2176.5000000000005 137.50000000000003 70.500000000000014 49.500000000000007 62.500000000000007 346.50000000000006 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 3 4 11 6 -6 26 9 25 16 -11 -1 15 14 -14 -4 19 18 -18 27 -19 23 -23 -22 -21 -9 -3 -2 29 -29 -30 +right_child=7 2 12 -5 5 -7 -8 8 -10 10 -12 -13 13 -15 -16 -17 17 20 -20 24 21 22 -24 -25 -26 -27 -28 28 30 -31 -32 +leaf_value=0.00029586079198419118 -0.021193449054344123 0.097940850221607506 -0.084319194627515706 -0.13911913074952195 -0.19309607691493705 0.14565914174367176 -0.17751642362075781 -0.16189469549395052 0.055315047113251949 0.10915093287732669 -0.16677363517985128 0.066767677604410339 0.021225674855824554 0.049590711981987973 -0.07657862900701512 -0.0013183710023821984 0.078798308162513875 -0.0021980159094711431 -0.20003005401890631 -0.16367062383505535 -0.011645718601429364 -0.20110035431412607 0.065891786236637703 -0.12824613939528856 0.10451757827877627 0.17974274786048841 0.044921376893625325 0.010255725837166904 -0.13629358455856952 -0.20014652335369687 -0.051715090878517479 +leaf_weight=66933.360883885427 1062.202534692944 115.70235079157283 60.128421117580729 15.29462673992384 10.210696912778074 4.9163598120212546 5.0059532716404638 42.029320355068194 19.85456184335635 28.528712930841721 4.5603125154739237 57.885005188902142 64.099259492359124 117.73363159270957 44.641398000298068 85.425812686109566 88.878348077996634 407.3483223994117 5.5824206655379376 3.6238635233603445 28.974644755711779 4.5823699166066971 24.110697072785115 50.551717978960369 25.42289237305522 1.991391273215412 223.88576260578702 129.06735953579482 80.918533540418139 7.9105534182017427 67.494050470442744 +leaf_count=23285934 369539 40250 20919 5321 3552 1711 1739 14616 6911 9924 1588 20137 22301 40959 15531 29721 30918 141716 1944 1261 10078 1596 8386 17588 8844 693 77891 44902 28150 2752 23481 +internal_value=0 0.000581399 0.0243839 0.000321462 0.0535 -0.0829991 0.059492 -0.0189011 -0.0837277 -0.016851 0.0711232 0.000353297 -0.00376889 0.0166927 -0.018926 -0.035606 -0.0183162 -0.0019046 0.0623202 -0.0255887 -0.0136717 -0.0568596 0.0232523 -0.0857639 0.0710585 -0.14644 0.0629858 -0.0276719 -0.0517842 -0.00189513 -0.0978296 +internal_weight=0 67738.3 731.75 67006.5 359.721 15.1271 344.594 2083.63 63.8753 2019.76 33.089 66991.2 372.029 226.474 108.741 145.554 1986.67 610.029 94.4608 1376.64 515.568 108.219 28.6931 79.5264 29.0468 44.0207 339.588 1347.59 285.39 136.978 148.413 +internal_count=24290853 23565966 254574 23311392 125143 5263 119880 724887 22220 702667 11512 23306071 129431 78791 37832 50640 691155 212226 32862 478929 179364 37648 9982 27666 10105 15309 118141 468824 99285 47654 51631 +is_linear=0 +shrinkage=0.1 + + +Tree=5 +num_leaves=32 +num_cat=0 +split_feature=13 0 1 0 15 18 12 16 3 9 16 16 18 19 18 13 13 3 15 19 9 5 14 5 15 9 7 8 18 0 0 +split_gain=49.7915 44.5413 54.3877 47.7763 38.6752 36.5135 44.7256 46.6242 30.8911 39.0384 27.0952 38.2821 25.7425 32.7139 28.5993 25.6965 24.2066 23.2638 36.3399 29.3136 29.1552 25.1659 22.6595 26.0204 22.3302 22.3035 21.5379 21.3752 30.1647 31.1052 30.6028 +threshold=62774.500000000007 7942.5000000000009 5310.5000000000009 134.50000000000003 54.500000000000007 224.50000000000003 66.500000000000014 58.500000000000007 4983.0000000000009 27.500000000000004 20.500000000000004 36.500000000000007 49.500000000000007 53.500000000000007 37.500000000000007 2.5000000000000004 10.500000000000002 26794.000000000004 21.500000000000004 66.500000000000014 647.50000000000011 7147.0000000000009 924.00000000000011 10.500000000000002 1.5000000000000002 1035.0000000000002 3.5000000000000004 14.500000000000002 38.500000000000007 9773.0000000000018 11951.500000000002 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 3 27 8 5 6 -4 24 -1 -10 -7 -12 13 -11 17 -6 -17 19 20 25 -19 -21 23 -2 -8 -15 -18 -3 -29 -30 -31 +right_child=22 2 4 -5 15 10 7 -9 9 12 11 -13 -14 14 -16 16 26 18 -20 21 -22 -23 -24 -25 -26 -27 -28 28 29 30 -32 +leaf_value=0.00020035501316545149 0.17688401663296921 -0.024256483052708253 0.043354365993331617 0.012691268453942698 -0.014284544330360123 -0.16359165351287874 -0.035708138369848701 0.069591346592543998 -0.17707378569373694 0.0045376249823570244 0.10054255201368617 -0.11912300563279266 0.024529379594178731 -0.19253383475685593 -0.20008260718608364 -0.12801322600593329 -0.20412460879933289 -0.15817847725107842 -0.0062011450905297893 -0.20226153170404124 0.0022735068553727355 0.0012840810366207354 -0.039942884732481886 -0.17655860747438581 0.10547618984408756 0.14190292968339388 0.10050347166314307 -0.0045275021577001829 -0.14636379997561985 -0.037907707492651647 -0.20183055032023095 +leaf_weight=64943.697082588755 2.2938501993194249 436.57556563612889 361.21009286140907 3101.119518797932 661.35442062400398 18.815628274300252 227.24896228133002 60.393082428781781 15.876504926593041 187.17836887706653 15.463693336118014 16.292412832175614 114.14838903980854 14.361320031399375 11.991318929765837 38.157254201709293 3.0217218793695784 53.259906521387165 42.65888186742086 6.3244930997025213 14.382949504302813 153.48652489623055 128.91827751600067 22.653728752746247 11.783509625471195 2.3156206160783759 10.007609168766065 67.627877610269934 53.902790557738626 108.7019458009745 12.721813683630897 +leaf_count=22244551 786 149536 123722 1062201 226524 6448 77836 20688 5435 64111 5295 5577 39096 4917 4108 13069 1036 18244 14612 2167 4926 52575 44159 7759 4036 793 3429 23156 18468 37236 4357 +internal_value=0 0.000123555 -0.0142109 0.000562661 -0.00310153 0.0133955 0.0196646 -0.00891347 -1.10495e-05 -0.0222996 -0.0684992 -0.0121565 -0.0182049 -0.0282428 -0.0487789 -0.0195677 -0.0878285 -0.0424526 -0.0784793 -0.0199365 -0.124061 -0.0067712 -0.0568244 -0.144061 -0.0287482 -0.146097 0.0298551 -0.0374872 -0.0612621 -0.083146 -0.0550822 +internal_weight=0 70764.1 2103.28 68660.8 1423.75 711.207 660.636 299.426 65559.7 615.984 50.5717 31.7561 600.108 485.959 298.781 712.541 51.1866 286.79 110.302 176.488 67.6429 159.811 153.866 24.9476 239.032 16.6769 13.0293 679.53 242.954 175.327 121.424 +internal_count=24290853 24238149 720413 23517736 487660 243602 226282 102560 22455535 210984 17320 10872 205549 166453 102342 244058 17534 98234 37782 60452 23170 54742 52704 8545 81872 5710 4465 232753 83217 60061 41593 +is_linear=0 +shrinkage=0.1 + + +Tree=6 +num_leaves=32 +num_cat=0 +split_feature=0 13 11 5 21 20 5 20 20 15 5 11 21 14 0 11 10 15 0 14 15 2 10 13 12 21 2 5 15 15 15 +split_gain=91.3694 99.1517 80.985 57.245 36.0403 32.749 30.6939 26.637 40.853 32.4048 30.1703 28.0467 27.1029 26.6806 33.6194 30.112 25.7473 24.494 42.9192 33.8947 31.8301 29.9763 26.4708 26.4151 27.2606 25.6642 34.1826 24.2728 23.5653 23.2768 22.289 +threshold=134.50000000000003 2.5000000000000004 22.500000000000004 1.0000000180025095e-35 34.500000000000007 7.5000000000000009 1.0000000180025095e-35 3780.0000000000005 49.500000000000007 251.50000000000003 67698.000000000015 28.500000000000004 47272.500000000007 4.5000000000000009 2084.5000000000005 793.50000000000011 6871.5000000000009 86.500000000000014 33795.500000000007 112.50000000000001 57.500000000000007 844.50000000000011 217.50000000000003 177.50000000000003 17.500000000000004 12641.000000000002 3.5000000000000004 12.500000000000002 69930.500000000015 23.500000000000004 15.500000000000002 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 16 6 28 5 -4 -2 8 10 11 -6 30 -13 -10 15 -15 -1 18 19 -8 22 -22 29 -21 25 -25 27 -27 -3 -20 -9 +right_child=2 3 4 -5 7 -7 17 9 13 -11 -12 12 -14 14 -16 -17 -18 -19 20 23 21 -23 -24 24 -26 26 -28 -29 -30 -31 -32 +leaf_value=3.0879714989155833e-05 -0.15175090883928208 0.0034174201289268293 -0.025734687734613487 0.027831663103855736 -0.055743135781036707 0.036617127805278114 -0.0029586554963300748 0.14912243361380764 -0.176759622234545 -0.13605067211583502 0.12347074479065075 -0.0038433256592411767 0.14505783022595128 0.074493971984797139 -0.20026018769917431 -0.20145818735883828 0.11053621444477718 0.014480207528108338 0.013162594651799524 -0.11609888440688021 0.056115671174823571 -0.19666654842753939 -0.20234839794362569 0.062558287213662786 -0.069376283004288636 -0.170795286954317 0.072668937085744387 0.10881719552924961 0.097393997706058028 -0.097523162831583798 -0.14566239584785146 +leaf_weight=61473.433139489331 14.184250993363092 2152.414052429318 949.39716602742556 2060.15715581517 266.59859781998966 92.438119794358499 3576.8936281542046 2.936893584206703 48.900822037889156 21.785549300228013 9.736764869769102 129.2481674515293 13.501126045826821 17.083314000890827 11.456416574539615 5.1453226468292987 21.091858199215494 595.58849285868928 26.458812930504791 41.55884512985358 32.224537515634438 5.4905119435279621 16.877872522425605 46.389161587576382 77.427736785197339 19.026085010264072 14.180518124368971 3.7099933668505392 27.017825626273407 67.391247553168796 20.253601822099881 +leaf_count=20779891 4795 727578 320927 696392 90118 31245 1209088 990 16530 7362 3291 43690 4563 5774 3872 1739 7130 201331 8943 14049 10893 1857 5704 15680 26174 6434 4792 1255 9133 22781 6852 +internal_value=0 0.00108854 -0.0116807 0.01588 -0.0311133 -0.0202024 -0.00487759 -0.0519078 -0.0678721 -0.0213848 -0.0494285 -0.00633079 0.0102396 -0.129585 -0.0611021 0.0106186 6.87817e-05 -0.00441702 -0.00728261 -0.00521084 -0.0600274 0.0193159 -0.0870526 -0.0450335 -0.026659 0.0130442 -0.0491749 -0.125169 0.00458242 -0.0663179 -0.10833 +internal_weight=0 65734.1 6125.88 4239.59 1588.48 1041.84 4537.4 546.647 358.921 187.725 276.335 165.94 142.749 82.5859 33.6851 22.2286 61494.5 4523.22 3927.63 3779.19 148.443 37.715 110.728 202.292 160.733 83.3058 36.9166 22.7361 2179.43 93.8501 23.1905 +internal_count=24290853 22220124 2070729 1433103 536953 352172 1533776 184781 121324 63457 93409 56095 48253 27915 11385 7513 20787021 1528981 1327650 1277472 50178 12750 37428 68384 54335 28161 12481 7689 736711 31724 7842 +is_linear=0 +shrinkage=0.1 + + +Tree=7 +num_leaves=32 +num_cat=0 +split_feature=0 4 12 12 11 10 14 12 4 11 18 6 10 19 0 16 4 18 4 19 12 12 6 18 12 0 2 4 20 6 9 +split_gain=30.0651 114.161 186.13 169.745 112.844 59.8835 54.8035 42.1116 31.7634 31.3269 29.2616 38.2084 28.8618 27.4453 25.9943 41.19 25.0477 60.2321 41.4985 33.8657 24.8381 34.4261 32.1687 32.12 31.3624 25.3353 24.6734 24.5699 25.7588 24.2554 23.8939 +threshold=134.50000000000003 1.0000000180025095e-35 1672.5000000000002 1574.0000000000002 102.50000000000001 7010.0000000000009 72276.500000000015 5252.0000000000009 74.500000000000014 1874.0000000000002 48.500000000000007 71.500000000000014 5476.5000000000009 2616.0000000000005 7743.5000000000009 77.500000000000014 6053.0000000000009 53.500000000000007 21932.500000000004 62.500000000000007 5191.5000000000009 3419.5000000000005 99.500000000000014 1.0000000180025095e-35 4506.0000000000009 4.5000000000000009 129.50000000000003 6187.5000000000009 111.50000000000001 4.5000000000000009 56.500000000000007 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 2 -1 16 14 8 9 29 10 -4 13 -12 -11 20 15 -2 -3 18 27 -19 21 22 -5 24 -23 -24 -6 28 -18 -8 -16 +right_child=4 3 6 5 26 -7 7 -9 -10 12 11 -13 -14 -15 30 -17 17 19 -20 -21 -22 23 25 -25 -26 -27 -28 -29 -30 -31 -32 +leaf_value=0.00010538618567913752 0.012568003165290192 -0.0014887273825188885 0.047536009441581054 -0.080776157013072336 0.048512969757494383 -0.0068825250400602628 0.1155541379968112 -0.12532739004496116 -0.19143245979997472 -0.178481440855324 0.16118217733726262 -0.20069843082984695 0.0052370084903844774 -0.20203922273401603 -0.0064662523329809263 -0.021310725464389484 -0.19947113853957496 -0.16282818389849038 0.027170150555461622 -0.050032400653493729 -0.044275562366080906 -0.201607748564262 0.08731456060980583 -0.19048377217447737 -0.0081602612896358312 -0.18388458528145768 -0.030942530557191733 -0.016518682342104112 0.16007982580020211 -0.099249867250628565 -0.20240428323924098 +leaf_weight=59725.243597500717 2485.7666637267466 5185.0592117528795 1127.7844805767818 67.483615053293761 680.07511027509463 386.6125827562646 16.376776431221518 34.802704154979438 16.151728020427981 10.44209359429078 13.099502687458882 3.7536622211337081 47.216482667514356 12.086888177786021 2025.3910820554956 419.42212825277238 12.981381658115422 31.969353534048423 256.11642913534888 159.01652943639783 973.98379167829989 11.997701155487446 23.474727788474414 43.96196433424484 27.79965268203523 4.0370907786418675 41.465286857681349 813.87225254270015 2.353817354887723 7.7419418839272103 6.242899563163518 +leaf_count=19428210 808600 1686661 366857 21955 221224 125760 5328 11319 5253 3400 4261 1221 15358 3931 658846 136435 4225 10395 83313 51732 316826 3902 7633 14302 9045 1316 13488 264743 764 2520 2030 +internal_value=0 -0.00057454 0.000902916 -0.0117704 0.00700769 -0.0411021 0.0391816 -0.0549497 -0.0521469 0.0438603 -0.050243 0.0805814 -0.0280348 -0.0521359 0.00160886 0.00767693 -0.00457776 -0.0171271 -0.00801419 -0.0689134 -0.0505641 -0.0848283 -0.0436204 -0.131564 -0.0664788 0.0475187 0.0439468 -0.0188815 -0.144283 0.0466035 -0.00706834 +internal_weight=0 69015.4 60969.6 8045.81 5658.36 1584.44 1244.36 58.9214 1197.83 1185.44 1181.68 16.8532 57.6586 1164.83 4936.82 2905.19 6461.37 1276.31 1085.32 190.986 1152.74 178.755 94.9954 83.7593 39.7974 27.5118 721.54 829.207 15.3352 24.1187 2031.63 +internal_count=24290853 22450230 19832992 2617238 1840623 515405 404782 19167 389645 385615 384392 5482 18758 378910 1605911 945035 2101833 415172 353045 62127 374979 58153 30904 27249 12947 8949 234712 269732 4989 7848 660876 +is_linear=0 +shrinkage=0.1 + + +Tree=8 +num_leaves=32 +num_cat=0 +split_feature=6 11 5 12 13 0 5 14 11 14 0 3 21 11 15 1 0 5 12 12 6 17 12 18 21 17 18 6 17 14 8 +split_gain=14.4958 124.499 119.949 71.4047 50.11 47.6412 37.4714 127.626 82.8923 55.8726 38.0925 37.1625 36.9049 41.3584 34.0702 31.6016 30.4124 28.9084 29.6948 27.9306 43.2945 51.0309 60.9326 35.7485 38.5388 31.5633 30.234 46.8349 41.1372 26.4432 44.5923 +threshold=53.500000000000007 1.0000000180025095e-35 1.0000000180025095e-35 4634.5000000000009 513.50000000000011 864.00000000000011 1.0000000180025095e-35 2894.0000000000005 18374.000000000004 5.5000000000000009 95.500000000000014 189.50000000000003 29.500000000000004 3180.5000000000005 224.50000000000003 153.50000000000003 7501.5000000000009 66.500000000000014 911.50000000000011 2612.5000000000005 9.5000000000000018 3234.5000000000005 8110.5000000000009 1.5000000000000002 3.5000000000000004 384.50000000000006 1.0000000180025095e-35 27.500000000000004 1333.5000000000002 7531.0000000000009 133.50000000000003 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=4 2 -2 16 -1 6 8 9 11 12 17 19 13 -8 -15 -3 -4 -11 -19 -6 29 22 23 25 -25 -22 27 28 -24 -21 -31 +right_child=1 15 3 -5 5 -7 7 -9 -10 10 -12 -13 -14 14 -16 -17 -18 18 -20 20 21 -23 26 24 -26 -27 -28 -29 -30 30 -32 +leaf_value=0.00012789810975925042 -0.0036543167139924944 -0.0026512895068072132 0.038227477143961387 0.13933990241078237 0.00017544738324136403 -0.11278366374527148 0.065119094828468921 0.12513709608860044 0.028035380598009908 0.033385752708634926 -0.025518033268166304 -0.18533051587536922 0.047811059769977023 -0.068932248106241586 -0.2024860657057056 0.018272564399353076 -0.20016172792433348 0.11674769652545321 -0.19452209710763246 -0.083931619125067039 -0.10929119473633807 0.068621271977871962 -0.049091843701466187 -0.20084837180892079 -0.065891163287810903 -0.00044506577231080767 0.036920450981865353 -0.20166106231013031 0.12175695404020798 -0.028830330502712401 -0.13339356274625763 +leaf_weight=67057.44782942954 181.4880908264895 4486.68485757913 61.434104153100634 168.25290581613808 280.18622724100715 47.97230611598934 12.968749028630553 80.865365985126118 297.29520399385365 315.50384645492886 238.85490095804562 15.211126468144355 20.713784260791726 43.314933963585645 34.169602333568037 860.20271009282442 5.8621890295180483 4.2512008226476601 10.982385482901007 189.47645498765633 35.869953587185591 65.41554271383211 53.752368438988924 30.345505693461746 69.899650867562741 103.55027679761406 152.70214605802903 14.400190602755172 19.101393774151802 403.93630399074755 45.365694592532236 +leaf_count=21601073 58464 1445287 19796 54197 90261 15449 4178 26047 95770 101638 76940 4899 6672 13954 11004 277094 1886 1370 3537 61033 11556 21072 17315 9774 22519 33352 49190 4639 6153 130119 14615 +internal_value=0 0.00481943 0.0574438 0.104519 -0.000398872 -0.014058 -0.012192 0.00636492 -0.0201477 -0.00774367 0.00491268 -0.0298317 -0.0725917 -0.100164 -0.127828 0.000714922 0.0174614 0.0268894 -0.107657 -0.028216 -0.0349357 -0.0142326 -0.025533 -0.0611977 -0.106744 -0.0284489 0.0100885 -0.0368698 -0.00429731 -0.0526007 -0.039388 +internal_weight=0 5763.92 417.037 235.549 69643.6 2586.11 2538.13 761.625 1776.51 680.759 569.592 1479.21 111.167 90.4533 77.4845 5346.89 67.2963 330.737 15.2336 1464 1183.82 545.037 479.621 239.665 100.245 139.42 239.956 87.254 72.8538 638.778 449.302 +internal_count=24290853 1856724 134343 75879 22434129 833056 817607 245340 572267 219293 183485 476497 35808 29136 24958 1722381 21682 106545 4907 471598 381337 175570 154498 77201 32293 44908 77297 28107 23468 205767 144734 +is_linear=0 +shrinkage=0.1 + + +Tree=9 +num_leaves=32 +num_cat=0 +split_feature=11 2 4 6 14 4 17 9 19 5 0 6 1 5 4 1 17 19 2 4 4 1 20 11 2 9 2 4 20 3 3 +split_gain=11.6707 30.1868 26.3193 43.0993 23.0059 19.5079 31.1183 31.602 19.2949 18.4274 36.5396 22.0856 26.237 20.8874 18.9826 18.0715 18.8713 33.8154 16.6396 33.8784 25.1098 31.0185 29.9103 22.4492 21.2407 28.3864 31.1087 21.1788 20.9221 19.9847 18.9211 +threshold=13412.000000000002 4.5000000000000009 1.0000000180025095e-35 29.500000000000004 144.50000000000003 159.50000000000003 67.500000000000014 20901.500000000004 22.500000000000004 104.50000000000001 18.500000000000004 83.500000000000014 5.5000000000000009 110.50000000000001 152.50000000000003 20.500000000000004 378.50000000000006 22.500000000000004 49.500000000000007 26.500000000000004 190.50000000000003 6.5000000000000009 2019.0000000000002 15680.500000000002 78.500000000000014 40578.000000000007 154.50000000000003 192.50000000000003 18.500000000000004 293.50000000000006 309.50000000000006 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 3 -2 -5 9 7 -7 18 -4 -11 12 13 -12 -13 16 -6 -18 20 -20 21 28 23 -22 -21 30 27 -27 -3 -30 -26 +right_child=1 8 5 4 15 6 -8 -9 -10 10 11 14 -14 -15 -16 -17 17 -19 19 24 22 -23 -24 -25 25 26 -28 -29 29 -31 -32 +leaf_value=0.00017641634471389725 -0.17856966413044298 -0.20020718813341035 0.019345882497087828 -0.20291438378452975 0.042517873650188075 0.11104707336870499 -0.20062189193508415 -0.14474536186502779 0.096101348186996324 0.1357174745988213 0.12216448143450963 -0.014733449861457408 -0.20069929588624366 -0.20007744334928934 0.17933127952807593 -0.20011048158157507 -0.1581411813518305 0.13832268223541164 -0.20042122822944031 0.058356192836701286 0.0081807665774752038 0.077951831818375561 0.10893834979451017 -0.19948136666955751 -0.12832748668067673 -0.18483138564380108 0.12544333770559601 0.10237465919607935 -0.042848853314062046 0.19465815381872253 0.04111452394717649 +leaf_weight=73994.257452489444 20.10018049919745 8.3686806196346861 583.56133063735615 6.0437595937401047 52.646994044771418 11.649461500695912 11.680644904437939 8.2506748822052014 11.852510398952289 16.094228620524518 6.2013783650472778 262.03339728957508 13.745680863736196 2.9771840723697096 5.1392305775079867 4.2668362224358125 13.544451634690633 5.3739477030467233 8.0809368570335192 52.864661893385346 6.5289130463497704 22.383699687197804 4.8516175240511066 25.687435806554276 26.996847446309403 7.396120987483302 18.428008656599559 3.9327684110612608 256.48961021078867 3.5923975277692071 8.7186160880373773 +leaf_count=23811535 6470 2693 187792 1945 16943 3750 3755 2657 3814 5178 1994 84328 4425 958 1654 1373 4357 1729 2596 17015 2099 7205 1560 8271 8687 2382 5927 1265 82535 1156 2805 +internal_value=0 -0.00876403 0.000844585 -0.0473605 -0.0151493 0.00618008 -0.0710559 0.00499452 -0.0298562 0.00892149 -0.0109461 -0.0190828 -0.113279 0.0176412 -0.0110005 -0.00018458 0.0117353 -0.0739279 -0.0331422 -0.00232036 -0.0450251 -0.0351458 -0.122538 -0.157397 0.0112074 -0.0268621 0.0452753 -0.0851292 -0.044576 -0.0395683 -0.0869645 +internal_weight=0 1489.48 1023.31 101.976 81.876 921.333 31.5808 19.9001 466.173 889.752 306.191 290.097 22.9242 9.17856 267.173 75.8322 71.5654 18.9184 454.32 126.418 327.902 290.834 37.068 32.2163 118.337 65.4724 29.7569 11.3289 268.451 260.082 35.7155 +internal_count=24290853 479318 329308 32817 26347 296491 10162 6407 150010 286329 98537 93359 7377 2952 85982 24402 23029 6086 146196 40677 105519 93589 11930 10370 38081 21066 9574 3647 86384 83691 11492 +is_linear=0 +shrinkage=0.1 + + +Tree=10 +num_leaves=32 +num_cat=0 +split_feature=16 13 8 16 11 16 4 13 11 3 15 15 2 3 1 16 16 1 8 1 1 2 4 3 14 16 16 1 2 9 9 +split_gain=11.4292 43.8677 39.4862 24.0855 22.0156 20.4816 69.212 196.202 33.5228 30.2116 28.6737 24.3975 27.7934 35.7307 26.8059 21.64 20.9669 33.6352 26.8876 20.8734 20.5314 19.5007 29.0404 32.3006 25.7852 20.8547 24.7821 21.9303 19.5376 19.181 18.6888 +threshold=7513.0000000000009 2.5000000000000004 8.5000000000000018 9558.5000000000018 346.50000000000006 86.500000000000014 1.0000000180025095e-35 485.00000000000006 57.500000000000007 12.500000000000002 251.50000000000003 2078.5000000000005 13.500000000000002 3.5000000000000004 22.500000000000004 56.500000000000007 106.50000000000001 56594.500000000007 29.500000000000004 765.00000000000011 544.00000000000011 7.5000000000000009 1.0000000180025095e-35 1.5000000000000002 9393.5000000000018 10467.000000000002 9465.0000000000018 467.00000000000006 20.500000000000004 1.5000000000000002 3.5000000000000004 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=5 21 -3 4 -4 15 11 8 -8 19 -11 12 14 -14 -7 -1 18 30 -15 20 -10 24 23 -23 -2 26 27 -24 29 -28 -18 +right_child=1 2 3 -5 -6 6 7 -9 9 10 -12 -13 13 16 -16 -17 17 -19 -20 -21 -22 22 25 -25 -26 -27 28 -29 -30 -31 -32 +leaf_value=-6.1782704836366598e-05 0.01166318338460957 -0.20109572775669726 0.13351331044876744 -0.2014507046391513 -0.20077590268453527 0.12810899585400248 0.02265861862678218 -0.050815992854347319 -0.00096138792350383088 -0.13293963391543362 0.13144670986080817 0.03933087834539814 -0.089533292004130113 0.075979815886669633 0.028217379842647251 -0.017456963716233578 0.0047316507448082121 -0.19561485177214485 -0.057453852696157603 0.081868282067749598 -0.1693293832125165 0.048955399967191116 -0.063183662276961414 -0.074586607236799329 -0.032774844387319577 -0.052367436659023228 0.091077408628203751 0.022647335749419864 -0.20013348976458795 -0.12103697493868459 -0.16730541223882331 +leaf_weight=69203.900919450098 659.68849514746398 22.260961833992042 10.367552332580091 4.3901077367190728 2.4322779839858404 31.17087921185157 1279.022373277352 637.11658289498882 738.91091349777707 29.140554156037979 4.7741529752966008 649.50990332022775 41.089998075389303 76.420906736719189 194.43265071316273 722.62038998847129 361.75705507886596 8.8441600876976718 18.820689850559575 30.440365313435905 7.3143683363450682 24.88936938799452 49.903914601309225 141.36315221502446 162.7987896517734 62.352471737394808 58.01075582249905 73.778249594266526 2.6819793267641208 4.6012927324045441 6.4266664065653449 +leaf_count=22318029 212750 7177 3342 1418 785 10052 412488 205470 238295 9397 1540 209463 13259 24643 62708 233043 116653 2852 6069 9818 2358 8027 16093 45587 52499 20111 18708 23797 865 1484 2073 +internal_value=0 -0.00937052 -0.113181 0.000667433 0.0699903 0.000161932 0.00701791 -0.0022364 0.0125754 -0.0033349 -0.0957221 0.0251918 0.0127642 -9.22445e-05 0.0420191 -0.000241544 0.00768961 -0.00290047 0.049612 0.000699378 -0.0026117 -0.00606793 -0.0236673 -0.0560914 0.00286735 -0.00221893 0.0143275 -0.0119842 0.064168 0.0754893 0.00172873 +internal_weight=0 1279.52 39.4509 17.1899 12.7998 74041.7 4115.19 2726.72 2089.6 810.58 33.9147 1388.47 738.963 513.359 225.604 69926.5 472.269 377.028 95.2416 776.666 746.225 1240.07 417.581 166.253 822.487 251.329 188.976 123.682 65.294 62.612 368.184 +internal_count=24290853 412643 12722 5545 4127 23878210 1327138 879366 673896 261408 10937 447772 238309 165549 72760 22551072 152290 121578 30712 250471 240653 399921 134672 53614 265249 81058 60947 39890 21057 20192 118726 +is_linear=0 +shrinkage=0.1 + + +Tree=11 +num_leaves=32 +num_cat=0 +split_feature=5 10 15 10 11 19 19 15 4 10 3 12 13 21 9 18 3 19 11 5 9 7 5 21 12 18 17 15 3 3 21 +split_gain=20.8234 42.8988 60.0855 39.3944 39.2596 33.7944 48.5446 34.8432 33.5969 32.6991 32.533 29.1107 25.1588 26.1881 24.779 30.1808 27.894 24.638 23.6228 22.1965 20.6708 24.3799 20.381 20.2357 23.8024 22.2409 22.0108 19.6706 19.6106 19.4525 17.9698 +threshold=115.50000000000001 6355.0000000000009 70.500000000000014 3509.5000000000005 94.500000000000014 52.500000000000007 6.5000000000000009 117.50000000000001 9.5000000000000018 20.500000000000004 12.500000000000002 58.500000000000007 1.0000000180025095e-35 59175.500000000007 2458.0000000000005 1.0000000180025095e-35 18121.500000000004 106.50000000000001 148.50000000000003 61561.000000000007 801.50000000000011 320.50000000000006 231.50000000000003 103.50000000000001 2539.5000000000005 2.5000000000000004 1267.5000000000002 40.500000000000007 10.500000000000002 1.0000000180025095e-35 1.5000000000000002 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=3 5 -3 -1 8 6 7 12 -5 27 18 30 13 -2 15 16 -8 -11 -4 20 -17 -22 23 24 -9 29 -27 -7 -20 -26 -10 +right_child=1 2 10 4 -6 9 14 22 11 17 -12 -13 -14 -15 -16 19 -18 -19 28 -21 21 -23 -24 -25 25 26 -28 -29 -30 -31 -32 +leaf_value=-0.00010488969721875814 0.029572157826268458 0.086092224296632575 -0.1993912605962386 -0.11107781270108796 -0.010374497995644849 0.024750851665046179 -0.04112172073513104 -0.19239122611441539 0.0074865916275619268 -0.18274992472638857 -0.16146707774292959 -0.19981532531939819 0.0031567504660005435 -0.11611881070241754 0.11739250310258736 -0.015740404898932494 -0.20162209744989981 -0.015624086926652736 0.017021203066937815 -0.20223873790528107 -0.025128522863408165 -0.18959231893801562 -0.0085740267157809486 -0.027884206117687233 -0.20133106220467872 -0.16288160262998372 0.17331360508865087 0.08319707937075338 0.18554823046212493 0.16470962638720288 -0.19996793956234091 +leaf_weight=67653.550095807266 541.04883433680516 145.92714795842767 4.9876930350437751 74.804365109768696 1626.3274139734713 512.18991524247394 23.5732977965381 18.967988270800561 79.835251058801077 11.13045791472541 10.390229952579828 8.2847326593473536 3188.8580272125982 12.625741635216398 10.790418744902125 333.09077718763729 20.028001719736494 42.513435834232951 517.60686970571987 7.0898033385165027 21.344771432282869 15.601716999663038 472.04317063954659 111.60418678799761 1.8484351957449705 13.367537837446433 2.2794603642541906 64.878478132246528 6.9981537704588836 6.7665706118568778 4.4058160572312763 +leaf_count=21747733 173926 46914 1600 24048 522797 164647 7578 6097 25662 3580 3342 2663 1025078 4058 3469 107074 6439 13668 166384 2279 6862 5014 151746 35880 595 4293 732 20855 2250 2174 1416 +internal_value=0 0.00559313 0.029158 -0.000492696 -0.01512 0.00261734 -0.000241628 0.00291844 -0.0612433 0.0243796 0.0137719 -0.0209536 0.00657318 0.0262499 -0.0322394 -0.036077 -0.114847 -0.0503007 0.01721 -0.0269701 -0.023612 -0.0945781 -0.0189008 -0.050384 -0.10847 -0.042861 -0.113905 0.0313218 0.0192693 0.086172 -0.0033633 +internal_weight=0 6117.55 685.91 69447.2 1793.66 5431.64 4800.93 4369.41 167.33 630.712 539.983 92.5258 3742.53 553.675 431.519 420.728 43.6013 53.6439 529.593 377.127 370.037 36.9465 626.877 154.834 43.23 24.262 15.647 577.068 524.605 8.61501 84.2411 +internal_count=24290853 1966534 220490 22324319 576586 1746044 1543294 1404579 53789 202750 173576 29741 1203062 177984 138715 135246 14017 17248 170234 121229 118950 11876 201517 49771 13891 7794 5025 185502 168634 2769 27078 +is_linear=0 +shrinkage=0.1 + + +Tree=12 +num_leaves=32 +num_cat=0 +split_feature=10 6 11 17 17 13 4 17 11 7 17 17 11 17 1 13 6 18 18 1 18 4 11 5 4 18 5 14 2 10 4 +split_gain=10.4893 42.6117 38.7754 27.0888 30.158 28.0496 25.0703 25.7907 27.9807 20.603 23.3091 20.0553 19.9804 32.6462 29.4143 32.0175 24.1838 22.2475 19.3357 20.9393 18.458 22.4566 25.3849 18.2414 31.6426 28.1924 18.2402 25.2811 23.3511 20.0461 28.9213 +threshold=7327.5000000000009 148.50000000000003 408.50000000000006 45.500000000000007 502.50000000000006 1404.5000000000002 1.0000000180025095e-35 182.50000000000003 1225.5000000000002 1.0000000180025095e-35 30.500000000000004 743.00000000000011 295.00000000000006 13150.500000000002 159.50000000000003 1074.0000000000002 31.500000000000004 2736.5000000000005 5.5000000000000009 4.5000000000000009 1.5000000000000002 5915.5000000000009 346.50000000000006 252.50000000000003 16.500000000000004 10.500000000000002 202.50000000000003 10.500000000000002 400.50000000000006 8266.0000000000018 17.500000000000004 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=12 6 9 -4 18 11 7 -2 -9 -3 -11 -6 -1 14 20 17 -15 -16 19 -5 21 22 -14 24 25 -20 28 -28 29 30 -8 +right_child=1 2 3 4 5 -7 26 8 -10 10 -12 -13 13 16 15 -17 -18 -19 23 -21 -22 -23 -24 -25 -26 -27 27 -29 -30 -31 -32 +leaf_value=-0.00011633117284425594 -0.13742117880060495 -0.064972146390824437 -0.15580122115403025 0.053185387698363576 -0.20460744747594983 -0.12986885542429721 -0.066886016730891881 -0.10175024450960182 0.039944208314729737 0.1440077386430115 -0.20412768060491571 0.091729385560604684 -0.18289059473829433 -0.20147225538916677 -0.011948856329036799 -0.17780093300295785 0.091765067975938253 -0.1998071336633982 0.099031651854342428 -0.13031355304284373 0.017955592473725576 0.01971990225137138 -0.012195620138752519 0.044103360045578084 -0.16175668507970048 -0.20113508262541493 -0.12200293172741877 0.10295012150121907 -0.20285827754383942 0.0080401794141374688 0.003946224859048433 +leaf_weight=71259.641858934832 25.287808289867826 96.652128595975228 12.952426668140104 9.5981729733757692 2.815759085584431 32.584860430972185 94.790965224674437 23.151452555437572 35.013597638113424 2.1238135700114098 20.362293814076111 12.088612655177711 8.8846730346558598 2.944162016385234 201.36316237956635 13.312665312900206 62.869142655574251 6.5078022402594788 15.780138500733303 17.661330630071461 1336.1637081980734 299.18616255256347 449.0791403465264 118.1037014714675 12.950699616689233 3.9029012192040673 5.8546369294635943 34.060288362787105 5.576374390278942 1078.9306528495508 147.09636497346219 +leaf_count=22942605 8141 31125 4169 3090 907 10491 30521 7452 11271 683 6554 3894 2857 944 64833 4286 20243 2094 5079 5684 430195 96325 144585 38018 4173 1256 1886 10964 1796 347373 47359 +internal_value=0 -0.00752644 -0.0384442 -0.015167 -0.00708868 -0.0778914 9.9285e-05 -0.0531097 -0.0164545 -0.0850303 -0.171246 0.0357449 0.00018472 0.00919731 0.00722249 -0.0274585 0.0786471 -0.0178301 0.0118013 -0.0657029 0.010887 -0.00158727 -0.0155072 0.0258173 -0.0403613 0.0395123 0.00334924 0.0699545 0.0013449 0.00220703 -0.0238116 +internal_weight=0 1807.34 357.577 238.439 225.486 47.4892 1449.76 83.4529 58.1651 119.138 22.4861 14.9044 73640 2380.31 2314.5 221.184 65.8133 207.871 177.997 27.2595 2093.31 757.15 457.964 150.737 32.6337 19.683 1366.31 39.9149 1326.39 1320.82 241.887 +internal_count=24290853 581886 115123 76761 72592 15292 466763 26864 18723 38362 7237 4801 23708967 766362 745175 71213 21187 66927 57300 8774 673962 243767 147442 48526 10508 6335 439899 12850 427049 425253 77880 +is_linear=0 +shrinkage=0.1 + + +Tree=13 +num_leaves=32 +num_cat=0 +split_feature=12 14 20 18 6 12 6 6 6 21 5 14 13 17 17 20 16 21 6 12 13 5 8 16 16 21 6 8 6 17 4 +split_gain=6.90307 13.2717 18.6072 17.2762 26.962 12.7458 20.2744 19.841 20.9474 17.574 17.4964 14.412 19.1151 14.0801 12.1989 24.0999 14.4372 14.2017 14.4338 35.0163 24.2016 16.542 19.9128 15.4311 19.175 25.846 15.0428 21.9912 14.9476 13.8638 13.6824 +threshold=7343.0000000000009 53612.000000000007 2.5000000000000004 7.5000000000000009 43.500000000000007 6427.0000000000009 259.00000000000006 102.50000000000001 83.500000000000014 29.500000000000004 162.50000000000003 58412.500000000007 46610.500000000007 87.500000000000014 77.500000000000014 17.500000000000004 67.500000000000014 16.500000000000004 12.500000000000002 6542.0000000000009 2188.0000000000005 47.500000000000007 59.500000000000007 64.500000000000014 1132.5000000000002 9.5000000000000018 1.0000000180025095e-35 32.500000000000007 81.500000000000014 324.00000000000006 4.5000000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=5 -2 -3 -4 -5 -1 7 8 9 11 13 12 14 -9 15 30 -17 18 19 29 28 22 -22 -24 25 -25 27 -21 -20 -16 -7 +right_child=1 2 3 4 -6 6 -8 10 -10 -11 -12 -13 -14 -15 17 16 -18 -19 20 26 21 -23 23 24 -26 -27 -28 -29 -30 -31 -32 +leaf_value=8.6289653461753187e-05 -0.0043105050441429689 0.12711476021424659 -0.093685695020972959 -0.19946278609565776 0.18615357544368435 0.12635030817881834 -0.20293115853202404 -0.20498062290827332 -0.16479729906175603 -0.19632657950082921 -0.20434138770283333 0.11321906607062514 -0.19870949173525876 0.11315296609337 -0.19972167001407271 -0.19998446241390408 0.20423778811210147 0.13788056958987957 -0.15303511094365096 0.093981930065533237 0.11133147982316566 -0.19570778571240846 0.20038268962622119 -0.17849023579572468 0.11539541672157244 0.13297613504158798 -0.14540101693668905 -0.19892971074643373 0.14216855543995008 0.15010281291645525 -0.19955350000127581 +leaf_weight=73047.866679341416 2019.3769581278757 5.2733096715528509 45.92349826276768 2.665911994641645 5.6685911267995834 26.962117272836625 3.8931514688301823 1.4414673130959261 6.3901981377275652 3.6993600735440841 1.9517098467331369 16.396956222713925 4.0859839469194403 39.88753276958596 1.2847664663568128 4.3732912518316871 1.1072840690612791 7.6461284626275292 10.160334545769727 7.6185164868365955 33.875675046932884 2.5937193933641529 3.051865458488467 10.874619986163454 5.6351744355633846 3.5287545258179298 22.342456043581478 3.8627207854297012 2.063637623563408 9.5820629871450347 1.3528371420688916 +leaf_count=23544820 650887 1700 14800 857 1830 8691 1258 465 2060 1191 629 5284 1317 12857 413 1410 357 2464 3272 2457 10916 837 981 3508 1816 1137 7202 1244 665 3089 439 +internal_value=0 -0.00568237 -0.0522176 -0.0696468 0.0628086 0.000161196 0.0233799 0.0271814 0.013161 0.019406 0.0882403 0.0238795 0.014837 0.102057 0.0203624 0.0736271 -0.118316 0.0058595 -0.00280722 -0.0474222 0.0249688 0.0512738 0.0625191 -0.00909291 -0.040996 -0.102183 -0.097595 -0.00456458 -0.103199 0.108744 0.110779 +internal_weight=0 2078.91 59.5313 54.258 8.3345 73283.5 235.662 231.769 188.488 182.098 43.2807 178.399 162.002 41.329 157.916 33.7955 5.48058 124.12 116.474 44.6905 71.7838 59.5598 56.9661 23.0904 20.0385 14.4034 33.8237 11.4812 12.224 10.8668 28.315 +internal_count=24290853 670074 19187 17487 2687 23620779 75959 74701 60750 58690 13951 57499 52215 13322 50898 10897 1767 40001 37537 14405 23132 19195 18358 7442 6461 4645 10903 3701 3937 3502 9130 +is_linear=0 +shrinkage=0.1 + + +Tree=14 +num_leaves=32 +num_cat=0 +split_feature=2 18 15 18 4 5 15 6 3 18 18 8 7 8 13 13 4 3 3 8 6 18 9 7 18 18 11 18 18 2 17 +split_gain=7.34504 13.8414 14.8698 10.5397 17.289 28.3806 9.53938 9.25265 8.84303 18.5566 32.4466 20.4527 21.0086 15.4418 14.9531 12.8 12.2019 11.54 10.9047 10.3657 26.224 22.6084 21.1377 21.1033 20.469 19.1627 21.1465 17.5076 29.3472 17.4522 16.5197 +threshold=17657.500000000004 1.5000000000000002 25.500000000000004 278.50000000000006 363.50000000000006 1331.5000000000002 17.500000000000004 4.5000000000000009 14983.000000000002 5.5000000000000009 53.500000000000007 12.500000000000002 122.50000000000001 17.500000000000004 1.5000000000000002 12.500000000000002 21932.500000000004 60355.500000000007 85002.000000000015 2.5000000000000004 476.00000000000006 29.500000000000004 206.00000000000003 771.50000000000011 48.500000000000007 8.5000000000000018 13.500000000000002 10.500000000000002 14.500000000000002 7497.5000000000009 1.0000000180025095e-35 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=8 -2 3 6 -5 -6 -3 -4 16 17 15 12 14 -13 -12 18 -1 -10 19 20 25 -22 -23 -21 -24 26 -11 -27 -29 30 -30 +right_child=1 2 7 4 5 -7 -8 -9 9 10 11 13 -14 -15 -16 -17 -18 -19 -20 23 21 22 24 -25 -26 27 -28 28 29 -31 -32 +leaf_value=-3.1078923105637498e-05 -0.20311587442448231 0.0097765522561456646 -0.2106843906886085 -0.19935307248832354 0.10299486509515812 -0.18932919170642837 -0.21058385885707187 0.16726051846434467 0.020122359479543037 -0.1993584650648037 0.094195968502386915 -0.19959029124732261 0.049289765297599326 -0.0079902420990240267 -0.14215454686298937 -0.14970425646607 0.13523870343330868 -0.19514658831931073 0.064076193518788224 -0.052717619396433227 -0.16429355612318258 -0.20016076183047607 0.12714553138737772 0.030381121515341766 -0.20612029812595015 0.085443172614000293 0.13779889157739955 -0.19894274368331011 0.074269124387086272 -0.069061758586915253 -0.095871888353972368 +leaf_weight=73687.048965257287 3.0855164140230036 999.60670561267762 0.7031265499535938 6.3980658007785705 9.2578083202242833 5.1791838804492718 1.9683737345039833 8.2249614235479367 157.89178938046098 10.904114374890922 3.6035809017484999 6.850747860036793 101.0783463884145 10.897376925335264 10.408289229497312 10.538506176555527 6.6690577161498359 2.5301602312829337 10.195414631045422 78.749031138693681 34.001075587933883 4.5437951256753868 8.6602657077601162 49.941907808010001 2.3411727973725638 15.126714770914985 2.2429010067135087 9.2690925583010522 27.375704206584487 26.489181064069271 7.209611263126134 +leaf_count=23764546 999 322373 225 2062 2987 1670 635 2657 50921 3515 1161 2209 32600 3514 3356 3398 2151 815 3289 25395 10964 1466 2794 16107 755 4876 722 2990 8833 8542 2326 +internal_value=0 0.00836844 0.00900115 0.00787909 -0.0625167 -0.00187463 0.00934348 0.137496 -0.000116533 -0.0123016 -0.0231208 0.0179735 0.0333824 -0.0819476 -0.0813698 -0.0414645 -1.88374e-05 0.0167272 -0.0374907 -0.041231 -0.0592643 -0.118618 -0.0187156 -0.020469 0.0562245 -0.0294443 -0.141839 -0.0121558 -0.0331436 -0.00798073 0.0388017 +internal_weight=0 1034.42 1031.34 1022.41 20.8351 14.437 1001.58 8.92809 74284.6 590.849 430.427 132.838 115.09 17.7481 14.0119 297.588 73693.7 160.422 287.05 276.855 148.164 49.5463 15.5452 128.691 11.0014 98.6173 13.147 85.4703 70.3436 61.0745 34.5853 +internal_count=24290853 333608 332609 329727 6719 4657 323008 2882 23957245 190548 138812 42840 37117 5723 4517 95972 23766697 51736 92574 89285 47783 15979 5015 41502 3549 31804 4237 27567 22691 19701 11159 +is_linear=0 +shrinkage=0.1 + + +Tree=15 +num_leaves=32 +num_cat=0 +split_feature=0 4 0 21 17 16 4 21 12 14 5 6 1 0 6 12 9 16 14 9 16 21 14 16 16 21 14 5 14 1 0 +split_gain=6.48595 23.3107 22.7536 22.5856 20.1002 32.2902 18.9509 18.0159 26.0472 19.1735 14.7442 17.1735 18.8305 13.7156 13.4442 13.4374 33.5526 29.4229 30.1904 30.4565 28.6436 20.8749 32.226 20.6046 24.4106 21.0158 20.4938 19.7499 19.3971 23.8631 39.9055 +threshold=91.500000000000014 8087.0000000000009 70.500000000000014 24.500000000000004 9.5000000000000018 56.500000000000007 8210.5000000000018 78.500000000000014 112.50000000000001 224.50000000000003 9.5000000000000018 15.500000000000002 6.5000000000000009 92.500000000000014 21.500000000000004 1.0000000180025095e-35 25695.000000000004 103.50000000000001 9.5000000000000018 5.5000000000000009 45.500000000000007 16763.000000000004 2.5000000000000004 95.500000000000014 25.500000000000004 55718.500000000007 17.500000000000004 25.500000000000004 21.500000000000004 275.50000000000006 145.50000000000003 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=2 15 -1 6 5 7 -3 -4 9 -9 -6 12 -12 -5 -13 16 17 18 19 23 21 22 -21 24 -2 -25 27 -19 -20 30 -30 +right_child=1 3 4 13 10 -7 -8 8 -10 -11 11 14 -14 -15 -16 -17 -18 26 28 20 -22 -23 -24 25 -26 -27 -28 -29 29 -31 -32 +leaf_value=-0.00015493844867723423 -0.042817800077476237 0.11087657942850707 0.002471521273216861 0.15898457657023385 -0.16762211699057467 -0.15067426271442405 -0.20396957777965607 -0.11738224766643968 0.10117430220943663 0.10034807146825316 0.13119709920522313 -0.19694566773549357 -0.19897236736372725 0.032941015880026854 -0.0061622264035862129 0.0039610305019925256 -0.20325365541768489 -0.20063733970234271 -0.058450376279314625 -0.20200283236588087 -0.14319249081763472 0.17100569774097146 0.072814654868447057 -0.20250337217166592 0.010518280695131775 0.11619141622127836 -0.020083533218111016 0.045873417195254652 0.0059673989558728033 -0.004279778567458945 -0.20150005939631815 +leaf_weight=68118.35871584258 118.01479161996394 2.6248314045369607 71.321502689039335 8.9392631181981397 5.4804899014998218 27.416555674280971 7.0372405245434493 46.191492564947112 7.5168149112723759 4.4326095841825 15.360884898109363 3.7626092319842419 1.9462441273499269 252.16321361454902 201.50355507462518 5264.8826828086167 8.704093342937993 3.3091116165742269 181.41575272352202 4.7195928562432519 6.2517515160725443 18.522257761389483 44.491107415291481 9.7523923332337272 314.44876411731821 2.6264143870212129 76.342116290703416 182.15646851324709 19.946181907434948 232.81219142509508 17.322980832424946 +leaf_count=21980047 38078 845 23009 2881 1770 8846 2271 14908 2425 1431 4957 1213 628 81372 65022 1698847 2809 1068 58537 1523 2016 5977 14357 3147 101466 847 24634 58774 6436 75122 5590 +internal_value=0 0.00295122 -0.000291941 0.0317005 -0.0245362 -0.0520876 -0.118437 -0.0312096 -0.0725264 -0.0983179 -0.00558349 -0.00159357 0.0940684 0.0372563 -0.00965936 0.00175469 -0.00760686 -0.00622476 -0.0142516 0.00220313 0.0616133 0.0805169 0.0464581 -0.00767777 -0.00403658 -0.134886 0.0235249 0.0414751 -0.0331603 -0.0161727 -0.090465 +internal_weight=0 6776.48 68503.3 270.765 384.933 156.879 9.66207 129.462 58.1409 50.6241 228.054 222.573 17.3071 261.102 205.266 6505.72 1240.84 1232.13 970.324 518.827 73.9847 67.733 49.2107 444.842 432.464 12.3788 261.808 185.466 451.497 270.081 37.2692 +internal_count=24290853 2186597 22104256 87369 124209 50619 3116 41773 18764 16339 73590 71820 5585 84253 66235 2099228 400381 397572 313096 167411 23873 21857 15880 143538 139544 3994 84476 59842 145685 87148 12026 +is_linear=0 +shrinkage=0.1 + + +Tree=16 +num_leaves=32 +num_cat=0 +split_feature=19 3 19 14 7 19 19 6 18 7 18 16 19 2 3 12 6 18 16 16 1 10 0 0 7 12 7 6 4 14 7 +split_gain=8.54591 18.866 29.8367 25.8399 32.7712 24.2801 24.0949 24.7561 23.2054 22.7705 22.6305 22.2595 17.5943 15.1992 32.661 25.3002 28.3821 19.1486 15.0072 19.8868 29.4074 26.5927 24.6487 39.5205 29.5558 26.1769 21.5807 19.5361 18.806 16.2109 21.3023 +threshold=90.500000000000014 4983.0000000000009 236.00000000000003 32.500000000000007 686.50000000000011 295.00000000000006 166.50000000000003 330.00000000000006 319.00000000000006 508.50000000000006 33.500000000000007 2.5000000000000004 312.50000000000006 5459.5000000000009 46702.500000000007 5.5000000000000009 86.500000000000014 244.00000000000003 2.5000000000000004 9.5000000000000018 84498.000000000015 1.5000000000000002 7357.5000000000009 7520.5000000000009 246.50000000000003 246.00000000000003 77.500000000000014 79.500000000000014 3057.0000000000005 3.5000000000000004 260.50000000000006 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=13 9 3 -3 6 -4 10 -8 -6 -2 -5 12 -7 14 -1 17 18 -15 27 20 -20 28 24 -24 -23 26 -25 -17 29 -21 -31 +right_child=1 2 5 4 8 11 7 -9 -10 -11 -12 -13 -14 15 -16 16 -18 -19 19 21 -22 22 23 25 -26 -27 -28 -29 -30 30 -32 +leaf_value=8.6275608567658977e-05 -0.0034289910344250697 -0.19870326710077668 0.070208813262986849 0.053827641466811495 -0.097212208575682316 -0.20037179407499256 -0.1980978245824955 0.069211589245236613 0.0944618868819318 0.040345974832818626 -0.074150836276510201 -0.20216614034480751 0.02098848921043886 0.035016795424702679 -0.1936691046822179 -0.019789928712489736 -0.13364924496840086 -0.19854597339427313 0.033498664697650067 0.0068905950621464193 -0.19992765687605354 -0.0098579923738728419 0.095023091914161278 0.04512949098321227 -0.15151238985908755 0.034571471144493819 -0.10478527725358461 0.16302662446919752 -0.14089136403302924 0.08673484938042901 -0.032446842537375967 +leaf_weight=67857.150554778724 5141.1635262352356 11.091621528612448 45.647477827616967 58.756935297395103 86.923989887058269 4.040025357855483 10.721799305814782 5.1186019789893171 6.8112173469271502 121.64014465850778 18.065421489183791 6.6872067587682968 32.28132710349746 364.57418929593405 8.7011616387171653 274.25897760601947 14.96443390555214 3.5443148324266067 342.66971781352186 502.7735851725156 5.4834288869751608 27.636494918202516 29.104071584763005 11.864844408584757 31.537993514561094 32.466713163070381 50.355346949829254 5.9725894071161738 8.0007273324299586 57.881125601241365 20.241833933629096 +leaf_count=21919528 1660718 3586 14745 18985 28076 1304 3463 1653 2201 39298 5831 2159 10430 117770 2812 88600 4834 1142 110695 162400 1771 8930 9401 3829 10183 10488 16270 1929 2584 18701 6537 +internal_value=0 -0.00377684 -0.0287832 -0.0504185 -0.0415948 0.0194116 0.000577175 -0.111721 -0.0832843 -0.00241721 0.0237324 -0.0345024 -0.00363342 0.0003009 6.1434e-05 0.00941393 0.00333917 0.032768 0.00480316 0.00998155 0.0298222 0.00103231 -0.0322678 -0.00689116 -0.085355 -0.0382167 -0.0761978 -0.0158936 0.0113784 0.0134756 0.0558546 +internal_weight=0 5548.95 286.146 197.49 186.398 88.656 92.6628 15.8404 93.7352 5262.8 76.8224 43.0086 36.3214 69649.2 67865.9 1783.33 1415.21 368.119 1400.25 1120.02 348.153 771.863 182.965 123.791 59.1745 94.6869 62.2202 280.232 588.897 580.897 78.123 +internal_count=24290853 1792449 92433 63795 60209 28638 29932 5116 30277 1700016 24816 13893 11734 22498404 21922340 576064 457152 118912 452318 361789 112466 249323 59101 39988 19113 30587 20099 90529 190222 187638 25238 +is_linear=0 +shrinkage=0.1 + + +Tree=17 +num_leaves=32 +num_cat=0 +split_feature=16 14 16 11 14 6 18 21 21 21 21 7 5 2 18 5 7 6 9 14 7 14 7 21 3 10 6 14 7 11 2 +split_gain=8.13809 30.834 21.1559 21.1123 14.6384 13.7605 13.5526 12.9388 14.3034 16.369 21.3876 12.6842 12.4607 18.9922 20.8342 19.306 16.3457 14.7827 28.0681 19.343 21.2349 24.0729 22.8253 16.5908 16.5451 15.9747 11.7856 22.6904 33.1167 21.5382 34.6141 +threshold=83.500000000000014 7531.0000000000009 58.500000000000007 170.50000000000003 5066.0000000000009 52.500000000000007 1.5000000000000002 34.500000000000007 89.500000000000014 188.50000000000003 212.50000000000003 4.5000000000000009 280.50000000000006 3740.5000000000005 33.500000000000007 243.50000000000003 571.50000000000011 51.500000000000007 1.5000000000000002 12.500000000000002 7.5000000000000009 2.5000000000000004 2.5000000000000004 44152.000000000007 841.50000000000011 5476.5000000000009 32.500000000000007 11.500000000000002 1381.5000000000002 4.5000000000000009 5924.0000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 2 -1 4 12 -3 7 -5 -9 -10 -11 -7 13 14 15 17 -16 19 -19 20 21 23 24 -4 25 -23 27 28 29 30 -2 +right_child=26 5 3 6 -6 11 -8 8 9 10 -12 -13 -14 -15 16 -17 -18 18 -20 -21 -22 22 -24 -25 -26 -27 -28 -29 -30 -31 -32 +leaf_value=7.6810042639863609e-05 -0.071129685354478375 0.066282772003167997 0.083764397893918818 -0.20148570635471744 -0.19863677103852695 0.20238250499895108 -0.19719467173384483 0.057353960197099367 -0.19822063231798204 0.12759597669646303 -0.19970466502322826 -0.12784698847831119 0.048457394511131013 -0.19403223812039858 0.10715963773270082 -0.20191661629666513 -0.19223969591844181 -0.20418979469326537 0.094180445571163507 0.044872751582981978 -0.19926064005587241 -0.13790549622665513 -0.19824217163271377 -0.19912108542291238 0.12224808635925516 0.16954682199764207 0.0017286450805635964 -0.0026424023625830784 0.091806443767976906 -0.011948030231053897 -0.0043646814923107005 +leaf_weight=68812.799790452977 198.33300783566665 93.187920972908614 24.822076061042029 3.7137100009713313 2.8171919269952914 1.4101841449737529 5.2075159212108693 17.397021510638297 6.3297098930925122 5.6924397528637183 3.0749654265819117 6.6393256795126945 194.47805073644849 4.3187270160997278 31.726143528823741 4.4326563426293424 1.9346803930820886 8.3426327890483645 5.0682094307849175 99.899933680979302 6.3857823929283759 8.9385409078095162 9.4844874060363491 2.2621708696242413 6.4118592531885943 2.083974376320838 2400.0581761625508 2491.9793775697908 25.011525018955581 499.77804882578494 127.61848977394402 +leaf_count=22253833 64136 30137 8024 1198 912 455 1685 5628 2046 1841 995 2149 62892 1394 10262 1433 625 2701 1640 32312 2069 2890 3065 730 2071 673 776170 805903 8089 161631 41264 +internal_value=0 0.000299492 0.000218891 0.0217152 0.0285345 0.0554472 -0.0463547 -0.0246605 -0.00445137 -0.0756722 0.0128028 -0.0699945 0.0300932 0.0135674 0.0178006 0.00416656 0.0899514 0.00942561 -0.0914301 0.0178639 -0.0268163 -0.006425 -0.0733958 0.0601368 -0.00547798 -0.079777 -0.00361767 -0.0074563 -0.0215571 -0.0249909 -0.0449894 +internal_weight=0 69368.9 69267.6 454.822 413.407 101.237 41.4154 36.2078 32.4941 15.0971 8.76741 8.04951 410.59 216.112 211.793 178.132 33.6608 173.7 13.4108 160.289 60.3889 54.0031 26.9189 27.0842 17.4344 11.0225 5742.78 3342.72 850.741 825.73 325.951 +internal_count=24290853 22433660 22400919 147086 133693 32741 13393 11708 10510 4882 2836 2604 132781 69889 68495 57608 10887 56175 4341 51834 19522 17453 8699 8754 5634 3563 1857193 1081023 275120 267031 105400 +is_linear=0 +shrinkage=0.1 + + +Tree=18 +num_leaves=32 +num_cat=0 +split_feature=20 10 14 13 4 4 5 3 14 4 8 13 2 12 5 2 14 14 13 10 5 13 18 13 18 13 8 18 7 12 12 +split_gain=6.02496 19.2181 23.4883 30.8918 18.5436 18.5814 18.2627 16.3342 15.4042 12.7786 12.136 15.1907 11.9468 11.6016 11.2031 12.5712 14.8198 17.3814 10.618 10.1176 12.4697 9.09947 8.90178 8.26501 13.0691 11.2727 7.97714 15.0724 12.6595 9.93378 16.4214 +threshold=36082.000000000007 2.5000000000000004 62.500000000000007 72.500000000000014 5.5000000000000009 15.500000000000002 5.5000000000000009 45.500000000000007 3.5000000000000004 20.500000000000004 243.50000000000003 3.5000000000000004 14.500000000000002 25.500000000000004 3.5000000000000004 3.5000000000000004 144.50000000000003 89.500000000000014 92.500000000000014 1.5000000000000002 1.5000000000000002 58.500000000000007 6156.5000000000009 30.500000000000004 3908.5000000000005 24.500000000000004 181.50000000000003 8880.5000000000018 1.0000000180025095e-35 16.500000000000004 61.500000000000007 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 3 4 7 8 -3 -2 -6 10 13 -12 14 22 15 16 17 18 -4 -10 -21 23 -7 24 25 -9 27 -16 29 -28 -31 +right_child=1 6 12 -5 5 9 -8 21 19 -11 11 -13 -14 -15 26 -17 -18 -19 -20 20 -22 -23 -24 -25 -26 -27 28 -29 -30 30 -32 +leaf_value=-4.7061894621135468e-05 -0.15070061830351519 0.12001927944639466 -0.19875875163544632 -0.19970506229109264 0.12659235310404152 0.051929222333842097 -0.19873680385937209 0.16212058531907045 -0.17500656904786999 -0.19674358316145107 0.10875563184048871 -0.19634843782618827 -0.1979914023856347 0.16706251485786286 0.1467558974623781 -0.20441379429673834 0.12241015366487609 -0.19831992046609917 0.052857264780393931 0.17435089415315586 -0.20011622356094702 -0.1967543672799007 -0.20001815238434195 0.12429343688030987 -0.19858275314893908 -0.19999999999999984 0.089579265996612534 -0.109440592530204 -0.20411856733818889 -0.19463174534166688 0.095869994139906156 +leaf_weight=74907.422535003076 3.5846721043344614 24.511521971202459 1.9156311303377211 9.6849601501598936 2.3726742092985686 6.5488158260122962 1.9396519034635265 4.1632332578301492 16.89729987282772 2.6996295689605168 2.6634504683315745 4.2132308112923056 2.0429528965614727 7.9096598762553176 20.67865304439329 2.7642767797224215 9.3778407431091164 4.692860022536478 13.471005377708932 2.0194691913202401 1.5889461333863435 1.1680508755380277 1.7844772412208838 18.62156130746007 2.2990128822857514 1.0833462104201315 23.771379687474113 2.583208817173726 1.9658217525575299 3.664947143639437 4.1484543550759554 +leaf_count=24223967 1158 7927 620 3132 769 2118 628 1348 5462 873 858 1365 662 2558 6686 894 3030 1518 4357 652 514 379 576 6021 742 351 7690 838 636 1187 1337 +internal_value=0 0.017044 0.00537114 -0.0310711 -0.0105579 -0.0490132 0.096645 0.0500079 -0.114635 0.00913364 0.0331735 -0.0781767 0.0411033 0.0803151 0.0465895 -0.000512357 0.0186218 -0.0298511 0.0215312 -0.142546 0.00945633 0.0763283 -0.00202236 0.0885182 0.000229573 0.0873477 0.0733037 0.118306 0.0421024 0.057427 -0.0403925 +internal_weight=0 206.831 180.38 89.3025 79.6175 48.6977 26.4512 30.9199 22.8784 25.8193 23.1196 6.87668 91.077 16.243 89.0341 32.2216 29.4573 20.0795 15.3866 20.5057 3.60842 27.3352 8.33329 26.1672 7.54559 5.24658 56.8125 23.2619 33.5506 31.5848 7.8134 +internal_count=24290853 66886 58331 28876 25744 15745 8555 9999 7397 8348 7475 2223 29455 5252 28793 10419 9525 6495 4977 6628 1166 8841 2694 8462 2441 1699 18374 7524 10850 10214 2524 +is_linear=0 +shrinkage=0.1 + + +Tree=19 +num_leaves=32 +num_cat=0 +split_feature=20 0 0 1 17 7 20 15 17 8 18 17 0 17 7 7 18 18 0 15 7 16 0 16 16 0 12 2 18 16 12 +split_gain=11.4357 10.8542 15.627 30.9008 28.9464 21.7616 19.6154 16.0697 12.3202 26.6766 14.355 13.9742 20.9539 19.5928 12.926 12.7694 24.1431 21.6863 16.2692 15.8391 17.1971 13.402 22.5617 15.7237 22.9621 24.2022 21.4957 19.8819 25.9945 26.2851 14.9425 +threshold=61147.000000000007 7652.5000000000009 7564.0000000000009 1118.0000000000002 70.500000000000014 148.50000000000003 1.0000000180025095e-35 4.5000000000000009 595.00000000000011 18.500000000000004 415.50000000000006 384.50000000000006 11494.500000000002 435.50000000000006 1255.5000000000002 72.500000000000014 12.500000000000002 3.5000000000000004 9568.5000000000018 209.50000000000003 22.500000000000004 175.50000000000003 9773.0000000000018 95.500000000000014 69.500000000000014 9568.5000000000018 8.5000000000000018 12256.000000000002 148.50000000000003 148.50000000000003 45.500000000000007 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 2 -1 4 -4 -6 -7 -5 10 -10 11 15 14 -14 -13 19 17 18 -17 -3 -21 23 -23 24 -18 30 -27 -25 29 -29 -26 +right_child=-2 8 3 7 5 6 -8 -9 9 -11 -12 12 13 -15 -16 16 21 -19 -20 20 -22 22 -24 27 25 26 -28 28 -30 -31 -32 +leaf_value=-0.00018965782540926072 0.041610291603545881 -0.0051632395848613356 -0.17636404446497794 -0.18146754804899895 -0.20279968862214295 0.14076177917396648 -0.20199148746966902 0.15722367631280601 -0.19592285317750896 0.12813612856027548 0.15843268398759902 -0.19434230696449151 -0.19958470411157661 0.083724527845647212 0.073827443234291676 0.16299496414371217 0.022617305742727793 -0.15906530263432839 -0.19282891065893845 0.034142048718370475 -0.20059451346560389 -0.18837383764632798 0.0053089297562488114 0.1195079765380454 -0.20318401062972535 0.14642955284976347 -0.2039419095840668 0.089134620978599616 -0.20630034166019981 -0.20668677002296643 0.048746509361772425 +leaf_weight=73167.114383678127 65.989953855401836 774.05125423049321 6.3569864612072733 17.810953630832959 2.6094550245907184 16.419785748468716 1.8586830450221885 1.5204699705354858 8.0983226126991195 3.7013206896372131 6.3415717519819728 11.370818031020464 3.124193769413977 11.163245726958847 2.1348593184957272 4.5876849329797578 692.86167015464162 14.054918802983591 1.7849232671433126 156.22297811193857 3.1846297448500982 8.0084601276321319 24.154124794004019 36.202290186134633 2.7454161917557967 2.0246471245773128 12.957178404962177 22.324948348803453 4.7030321717611505 3.470608099247328 16.526026090839878 +leaf_count=23663941 21343 250339 2057 5764 844 5308 601 490 2622 1197 2051 3677 1009 3612 691 1483 224098 4541 578 50526 1031 2590 7808 11710 892 658 4189 7221 1521 1122 5339 +internal_value=0 -3.65931e-05 -0.000226519 -0.0581316 0.010479 0.0673424 0.105908 -0.154829 0.00757934 -0.0942718 0.00824185 0.00771496 -0.0626459 0.0217741 -0.151952 0.00881366 0.0177087 -0.0896861 0.0633312 0.000748122 0.0294525 0.0203648 -0.0429179 0.0229288 0.0186661 -0.0612579 -0.156593 0.0693967 0.0099139 0.049334 0.0128564 +internal_weight=0 75039.5 73213.7 46.5763 27.2449 20.8879 18.2785 19.3314 1825.8 11.7996 1814 1807.66 27.7931 14.2874 13.5057 1779.86 846.406 20.4275 6.37261 933.459 159.408 825.978 32.1626 793.816 727.115 34.2533 14.9818 66.7009 30.4986 25.7956 19.2714 +internal_count=24290853 24269510 23679005 15064 8810 6753 5909 6254 590505 3819 586686 584635 8989 4621 4368 575646 273750 6602 2061 301896 51557 267148 10398 256750 235176 11078 4847 21574 9864 8343 6231 +is_linear=0 +shrinkage=0.1 + + +Tree=20 +num_leaves=32 +num_cat=0 +split_feature=20 20 20 14 14 11 18 14 14 2 4 2 3 0 14 14 15 18 14 18 3 6 18 0 18 6 18 2 3 18 10 +split_gain=8.34487 9.44489 14.1192 15.2812 13.8279 17.2313 18.306 16.4093 23.1915 29.241 15.014 25.1602 14.3381 13.0999 10.5648 13.2923 10.4975 10.4468 14.4347 16.2614 14.24 13.2989 12.4527 19.2048 19.0343 16.4157 13.297 12.3414 17.23 16.6395 10.3961 +threshold=61147.000000000007 28383.500000000004 26478.500000000004 50.500000000000007 294.50000000000006 7.5000000000000009 10060.000000000002 224.50000000000003 83.500000000000014 9.5000000000000018 8.5000000000000018 1.0000000180025095e-35 365.00000000000006 973.00000000000011 98.500000000000014 157.50000000000003 1.0000000180025095e-35 1061.0000000000002 6.5000000000000009 4157.5000000000009 293.50000000000006 1.0000000180025095e-35 6202.5000000000009 117.50000000000001 7074.5000000000009 1.5000000000000002 5908.5000000000009 1.0000000180025095e-35 225.50000000000003 8707.5000000000018 1.0000000180025095e-35 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 2 -1 -4 5 6 7 8 13 10 11 14 -11 17 -10 -16 -9 -3 21 20 -20 -19 25 -24 -25 26 -21 28 -26 -30 -29 +right_child=-2 4 3 -5 -6 -7 -8 16 9 12 -12 -13 -14 -15 15 -17 -18 18 19 22 -22 -23 23 24 27 -27 -28 30 29 -31 -32 +leaf_value=1.9833505861732516e-05 0.035367388204664861 -0.2039398482562752 0.17864969393420754 -0.20014734536052992 -0.19779845227203971 0.14841552732860736 0.047268066782257687 0.12033558272568512 -0.19901242641134265 -0.18495100055309927 -0.19276491652755842 -0.20138389853769778 0.12938900535489653 -0.19416986178510071 0.10326565546977168 -0.19940841873750814 -0.20036562558607632 0.14766049861834668 -0.18891716500785907 0.020384995693588408 -0.027391611417665919 -0.20704378703989534 -0.1881914680698594 -0.19412925552114393 -0.19940522297611529 -0.19738325707076085 0.14791426334556995 0.0061370772292377914 0.06477178830970505 -0.19860267628260028 0.12856861496053601 +leaf_weight=74755.6402290111 66.654258042806759 2.814772523706778 7.6612765048630518 1.2369347938220014 4.2564147717203005 6.2147793888580045 34.688801556243561 9.9546169983223063 1.924587746849286 1.8117729849764126 20.293436442589154 8.9916892640758288 7.288837194442749 4.1062838521320364 9.1358322970336285 1.7248839539242897 1.1372802713885892 8.9347509690560383 9.4909135524649191 35.52301114652073 12.844148448028134 1.198845715727656 6.9766147735062978 5.2498513807076952 5.9467757493257549 2.8543776359874746 10.620200091449076 23.15204429224832 10.504809699021278 3.1086641303263596 9.9019007205497456 +leaf_count=24182099 21561 910 2477 401 1380 2012 11220 3219 624 586 6562 2908 2359 1327 2954 558 368 2888 3070 11492 4154 388 2257 1696 1925 922 3436 7491 3399 1005 3205 +internal_value=0 -3.14222e-05 3.48264e-05 0.125993 -0.019034 -0.0160664 -0.0201523 -0.0310053 -0.0374336 -0.095722 -0.130881 -0.073212 0.0668094 -0.017968 0.0169292 0.0551954 0.0874533 -0.013116 -0.00944477 -0.0180133 -0.0960292 0.105698 -0.00270658 -0.0314574 -0.0125602 0.0353407 0.0497368 0.00555682 -0.0574017 0.0046297 0.0428136 +internal_weight=0 75025.2 74764.5 8.89821 260.651 256.394 250.18 215.491 204.399 51.171 42.0704 21.777 9.10061 153.228 12.7853 10.8607 11.0919 149.122 146.307 136.173 22.3351 10.1336 113.838 64.8407 57.864 48.9976 46.1432 52.6142 19.5602 13.6135 33.0539 +internal_count=24290853 24269292 24184977 2878 84315 82935 80923 69703 66116 16551 13606 7044 2945 49565 4136 3512 3587 48238 47328 44052 7224 3276 36828 20978 18721 15850 14928 17025 6329 4404 10696 +is_linear=0 +shrinkage=0.1 + + +Tree=21 +num_leaves=32 +num_cat=0 +split_feature=20 12 12 12 10 2 10 9 12 13 13 3 16 4 9 21 9 21 21 4 19 9 10 8 10 2 19 10 8 8 12 +split_gain=6.91277 13.7665 14.4517 13.7359 32.1853 11.7536 11.3239 10.7957 12.1826 11.8338 10.3237 8.2387 6.8403 6.47697 6.32415 6.23586 12.5719 10.9537 10.2012 20.6244 10.9608 9.46501 9.10159 8.81081 16.2404 13.4069 13.577 12.3011 22.7814 16.248 12.1447 +threshold=50132.000000000007 19.500000000000004 10.500000000000002 93.500000000000014 19.500000000000004 7.5000000000000009 3.5000000000000004 10.500000000000002 6.5000000000000009 102.50000000000001 262.00000000000006 63.500000000000007 1.0000000180025095e-35 20.500000000000004 4.5000000000000009 93.500000000000014 15227.000000000002 21.500000000000004 107.50000000000001 9424.5000000000018 1101.5000000000002 14774.500000000002 41812.500000000007 1.0000000180025095e-35 8.5000000000000018 4.5000000000000009 90.500000000000014 1.0000000180025095e-35 640.00000000000011 138.50000000000003 78.500000000000014 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=15 2 7 5 -5 6 13 8 -2 -10 -4 -9 -7 -3 -12 17 18 -1 19 20 23 -20 -18 24 -17 27 -27 29 -29 -25 -26 +right_child=1 3 10 4 -6 12 -8 11 9 -11 14 -13 -14 -15 -16 16 22 -19 21 -21 -22 -23 -24 25 30 26 -28 28 -30 -31 -32 +leaf_value=1.8199888633203456e-05 -0.19494389094941644 0.12847946464813639 -0.18478345811597918 -0.19723471159308376 0.16052771096024029 -0.11911764293018462 -0.20484193424408295 -0.17868095699946684 -0.19942807229190396 0.15169324527767411 0.19583870571054687 0.04946280172094572 0.1189765648220992 -0.17010825955937867 -0.19087726515995318 -0.20128455120240579 -0.20164890260084445 0.0085376796951384028 -0.0025606248576998986 -0.1966971957168 -0.11628374874482413 0.19709460024837572 0.089511763209206591 -0.19996220321403366 0.015289658459478765 -0.0016252534297591737 0.12924211580632558 0.023552260612708844 -0.1977757917780156 -0.026235699263477526 -0.18605824501108087 +leaf_weight=68519.286810280522 4.720450872089712 32.678576247883029 6.8382398763205838 5.7307512491242942 4.4806565116159618 3.8544914120575404 1.0958605423802499 1.6613462376408268 1.4262521143537013 2.935311219887808 1.3541710111312566 33.502945037442259 1.7565109161660073 0.74300474999472399 0.61490211309865106 6.5548303592950132 5.2170147867873302 1543.1454241496249 4744.2449560632158 6.9486013224814078 11.079358469636643 2.3756216345354906 1.3518175939097998 7.8577673301333588 16.867914399015721 18.827261122874916 13.693495350213196 51.128008917119587 5.1159144608536726 17.097229777486064 3.6425619856454423 +leaf_count=22168883 1526 10570 2213 1855 1450 1248 354 541 463 949 438 10835 570 241 199 2124 1691 499275 1534964 2249 3584 769 437 2542 5454 6088 4429 16544 1658 5531 1179 +internal_value=0 0.0258393 -0.00970448 0.0632991 -0.0402524 0.0896497 0.11147 0.013581 -0.0836148 0.0368749 -0.126686 0.0386841 -0.0445828 0.121841 0.0750751 -3.56345e-05 -0.00347997 0.000205844 -0.00329484 -0.0282256 -0.0205171 -0.0024607 -0.14173 -0.0129806 -0.0642598 -0.000776126 0.053479 -0.0225057 0.00342039 -0.0809383 -0.0204688 +internal_weight=0 103.393 53.0536 50.3399 10.2114 40.1284 34.5174 44.2463 9.08201 4.36156 8.80731 35.1643 5.611 33.4216 1.96907 74974.4 4912 70062.4 4905.43 158.813 151.864 4746.62 6.56883 140.785 27.0653 113.72 32.5208 81.1989 56.2439 24.955 20.5105 +internal_count=24290853 33452 17164 16288 3305 12983 11165 14314 2938 1412 2850 11376 1818 10811 637 24257401 1589243 22668158 1587115 51382 49133 1535733 2128 45549 8757 36792 10517 26275 18202 8073 6633 +is_linear=0 +shrinkage=0.1 + + +Tree=22 +num_leaves=32 +num_cat=0 +split_feature=20 13 4 11 12 0 12 8 12 13 8 11 13 2 0 9 18 18 2 7 7 4 20 20 18 18 8 17 12 18 11 +split_gain=7.89431 7.61644 6.70187 6.91984 9.87922 7.21553 14.2981 7.04586 13.784 14.4581 28.2025 6.42661 5.95653 5.49172 9.06878 8.58023 9.33108 14.35 7.94079 18.4598 23.9928 29.8492 18.8433 24.3579 16.9171 12.9179 20.5099 17.8734 12.489 12.4865 10.3647 +threshold=50132.000000000007 374.50000000000006 2.5000000000000004 1.5000000000000002 82.000000000000014 3265.5000000000005 6.5000000000000009 44.500000000000007 4.5000000000000009 24.500000000000004 221.50000000000003 1.0000000180025095e-35 86.500000000000014 9603.5000000000018 13221.000000000002 6.5000000000000009 123.50000000000001 50.500000000000007 8943.0000000000018 2176.5000000000005 642.50000000000011 44292.000000000007 114.50000000000001 67.500000000000014 6.5000000000000009 7.5000000000000009 3.5000000000000004 101.50000000000001 3.5000000000000004 67.500000000000014 14.500000000000002 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=13 2 -2 4 5 7 -7 12 -9 11 -11 -10 -4 18 -15 16 17 -16 -1 20 22 -22 23 24 -20 -26 28 -28 -27 30 -24 +right_child=1 -3 3 -5 -6 6 -8 8 9 10 -12 -13 -14 14 15 -17 -18 -19 19 -21 21 -23 29 -25 25 26 27 -29 -30 -31 -32 +leaf_value=-0.00011308432233655213 -0.20701729287991469 -0.18070857496415402 -0.1688018578329763 0.092093010501996253 -0.18309037155288566 -0.163130410872661 0.1149261882509693 0.052401220239129402 -0.16535813797533294 0.048126517682014092 -0.18424998806617676 0.15191228045427516 0.12902921704241274 0.0071008954071309751 -0.19724546515864852 -0.20004735375957317 -0.1986511755275685 0.075428330167768953 -0.18049036578836275 0.08623929522209664 -0.19804825756351016 0.046221730196050276 0.085031723346653224 -0.1976357109830448 0.16004395709895433 -0.20147431385096956 0.068436719478992963 -0.1933020032829009 0.055794712720212372 -0.19976740935449575 -0.19682950716294978 +leaf_weight=73461.574614406651 1.1596185488160689 1.7139939097687591 0.74335577129386265 17.298988999100402 2.2755830304231486 2.0878857504576471 16.185036608949304 23.954496597580146 0.75512223574333148 8.9380286709056254 12.564849157002753 4.1319005899131298 6.9479186048265547 1370.2095900119166 2.5134294333402094 3.7372723650187245 2.6053557166596866 8.315181518555617 5.5848401648690915 13.024829128524287 17.233306747279133 7.0486871987814075 24.794374084682211 9.115333814523181 4.7562960768118492 7.4411236474988964 20.435167252202522 2.9908212558366349 2.5279528789687902 1.833227511961012 1.3770807425025839 +leaf_count=23771715 377 557 241 5597 736 677 5236 7749 244 2894 4071 1333 2245 443389 816 1206 844 2689 1804 4216 5577 2281 8022 2949 1535 2409 6617 969 819 595 444 +internal_value=0 0.0282545 0.0319453 0.0348353 0.022231 0.0283538 0.0831551 0.0110993 -0.00251967 -0.0523722 -0.0876588 0.102889 0.100244 -3.72216e-05 0.00619582 -0.0660261 -0.028742 0.012138 -0.000154749 -0.0260573 -0.0399689 -0.12714 -0.0137903 -0.0489309 -0.0179384 0.00585698 -0.0161031 0.0350203 -0.136236 0.0525283 0.0702008 +internal_weight=0 98.7568 97.0428 95.8832 78.5842 76.3086 18.2729 58.0357 50.3444 26.3899 21.5029 4.88702 7.69127 74967.1 1387.38 17.1712 13.434 10.8286 73579.7 118.163 105.138 24.282 80.8562 52.8515 43.7362 38.1514 33.3951 23.426 9.96908 28.0047 26.1715 +internal_count=24290853 31957 31400 31023 25426 24690 5913 18777 16291 8542 6965 1577 2486 24258896 448944 5555 4349 3505 23809952 38237 34021 7858 26163 17102 14153 12349 10814 7586 3228 9061 8466 +is_linear=0 +shrinkage=0.1 + + +Tree=23 +num_leaves=32 +num_cat=0 +split_feature=20 12 2 3 4 4 18 3 0 0 0 18 0 0 8 18 2 16 19 18 17 18 17 16 10 10 16 19 8 20 1 +split_gain=13.4373 5.23868 5.81267 5.32149 5.22479 5.33231 9.90639 4.79584 8.64201 6.80273 15.2686 10.9175 8.3138 11.403 7.08793 6.40952 13.4354 10.4226 12.0174 10.1442 5.79484 6.02273 17.0485 6.56965 14.2201 9.40041 5.51034 4.49927 4.22212 28.9401 14.7828 +threshold=50132.000000000007 19.500000000000004 7.5000000000000009 63.500000000000007 20.500000000000004 4.5000000000000009 5706.5000000000009 70853.500000000015 9877.0000000000018 5053.5000000000009 3265.5000000000005 57.500000000000007 5593.5000000000009 6814.0000000000009 18.500000000000004 24.500000000000004 9892.0000000000018 11.500000000000002 106.50000000000001 12.500000000000002 228.50000000000003 157.50000000000003 128.50000000000003 39.500000000000007 1.0000000180025095e-35 1.5000000000000002 16.500000000000004 47851.500000000007 39511.000000000007 4.5000000000000009 33.500000000000007 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=7 3 4 -2 5 6 -3 28 9 10 11 14 -11 -14 15 16 -9 18 -17 -18 21 22 23 24 -15 26 -26 -5 -1 30 -30 +right_child=1 2 -4 27 -6 -7 -8 8 -10 12 -12 -13 13 20 -16 17 19 -19 -20 -21 -22 -23 -24 -25 25 -27 -28 -29 29 -31 -32 +leaf_value=-0.00010861583729174156 -0.15191854466928364 0.075342049498205638 -0.054370543512535098 -0.19867369509345179 -0.15090756849318154 0.093547221927639465 -0.19297203419579165 -0.19339276447701889 -0.18733322544414868 0.16572068748978469 -0.19103322845024839 -0.18907589879344475 -0.19287676157803779 -0.20082088315179006 -0.197929054702882 0.16088447114385068 0.10768625710066802 -0.19331414489331539 -0.19775749758996308 -0.19016064196168159 -0.18818910591118815 0.18498912019807681 -0.19540076782712656 0.13502775302727366 0.12017385348138745 -0.16071949870203567 -0.19353680994917974 0.02586473856055925 0.079860845561339711 -0.20148450686876354 0.0078401958755887902 +leaf_weight=74718.739369882882 1.8560250574955706 4.6209233892150214 3.9412229488953008 0.9119262850144868 1.0212180325761426 35.051956524490379 1.9595524070318786 2.767632141709341 1.8578482049051661 6.1254990008892483 3.8217156545724711 2.2436583586968473 1.9940597445238371 2.2715869680978491 1.1821849511470635 10.610169796971602 7.1686604046262783 1.095691560069098 1.0245236684568215 1.3604977424256501 0.96069498098222439 3.4857633225619784 2.6540860505774608 9.7719785021617991 10.435589958564375 1.5359208360314358 0.5916565346997239 41.683316839800682 35.784970411565155 5.9641222244827068 139.99130712993792 +leaf_count=24178970 600 1490 1275 294 330 11346 637 895 601 1980 1236 724 647 734 383 3433 2321 356 331 437 312 1128 860 3163 3377 498 193 13490 11583 1929 45300 +internal_value=0 0.0383939 0.0618229 0.0138351 0.0725592 0.0780407 -0.00455727 -4.66257e-05 0.0255793 0.0311426 -0.0037628 0.022307 0.0585528 0.0390741 0.0411203 0.052882 -0.0019463 0.101536 0.129303 0.0601763 0.0536614 0.0612182 0.0453919 0.0713639 0.0294271 0.0710589 0.103342 0.0210576 -7.15875e-05 0.0151518 0.0225023 +internal_weight=0 91.0461 46.5949 44.4513 42.6537 41.6324 6.58048 74973.4 72.9594 71.1016 31.2747 27.453 39.8268 33.7013 25.2094 24.0272 11.2968 12.7304 11.6347 8.52916 31.7073 30.7466 27.2608 24.6067 14.8348 12.5632 11.0272 42.5952 74900.5 181.74 175.776 +internal_count=24290853 29462 15078 14384 13803 13473 2127 24261391 23609 23008 10116 8880 12892 10912 8156 7773 3653 4120 3764 2758 10265 9953 8825 7965 4802 4068 3570 13784 24237782 58812 56883 +is_linear=0 +shrinkage=0.1 + + +Tree=24 +num_leaves=32 +num_cat=0 +split_feature=3 14 18 0 18 17 0 0 6 18 6 2 2 10 19 6 10 2 18 14 19 2 11 0 8 18 17 14 14 6 6 +split_gain=10.6994 7.27974 7.63299 7.54367 13.5278 11.1616 13.4076 7.72621 5.98443 10.1152 5.02391 17.5703 28.7869 36.3563 17.3571 15.7568 24.1421 17.2664 17.5896 18.434 16.8044 20.5434 16.0841 18.2661 17.1702 15.4051 14.9439 15.2423 13.3987 13.0041 16.5154 +threshold=70853.500000000015 1.0000000180025095e-35 8.5000000000000018 7743.5000000000009 34.500000000000007 42.500000000000007 876.50000000000011 9568.5000000000018 4.5000000000000009 31.500000000000004 6471.5000000000009 309.00000000000006 228.50000000000003 148.50000000000003 10.500000000000002 7781.5000000000009 376.00000000000006 29.500000000000004 238.50000000000003 20.500000000000004 24.500000000000004 18.500000000000004 109.50000000000001 3.5000000000000004 41482.500000000007 224.50000000000003 7.5000000000000009 21.500000000000004 2.5000000000000004 9305.0000000000018 9891.0000000000018 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=10 3 -3 5 7 6 -2 -5 -7 -10 -1 12 15 -14 -13 16 20 -18 19 -19 22 -22 23 -12 26 -16 27 -24 -20 -15 -31 +right_child=1 2 -4 4 -6 8 -8 -9 9 -11 11 14 13 29 25 -17 17 18 28 -21 21 -23 24 -25 -26 -27 -28 -29 -30 30 -32 +leaf_value=-0.00014913709576783464 0.19155035411113561 -0.16819361009243436 0.082739972266298922 -0.18281030198449527 -0.18133937944973033 0.091671684500302664 -0.13084231738743704 0.16558690182472274 0.13492614742055725 -0.17770716498029815 -0.19721348263373628 0.10130383710573794 -0.20310977933316743 -0.19661288961068818 -0.20218745766246632 0.0014534703310575734 -0.20189763214356207 -0.19820944796285567 -0.19763390095453509 0.2080617440417093 0.19579430530781128 -0.20106083015106738 0.094415878875977119 0.024069821710507186 -0.063183887599580962 0.058349741523800375 -0.19785900566374515 -0.20397679235824562 0.041792927305574351 0.11424766909895162 -0.10953149303852827 +leaf_weight=73613.003282228718 1.6020427457988278 1.2604591561830591 31.666784276603721 0.84913840063381796 4.9100345246843062 18.868501190328974 6.6223469681572169 2.5421989614260383 1.8464557230472554 2.3546594614163041 4.2538475038018069 34.267328969552182 15.0285922762705 2.6496705985046018 3.960596811375579 1094.8269537947199 5.2818590528331688 7.364945817855185 2.5318581401370457 1.3164601325988767 1.7384884497150768 5.223893370712176 106.82516677386593 30.311852328944951 8.4342415537685138 5.3151134350337088 1.8386708928737778 1.7397682443261135 30.419296900101472 10.417949648341162 4.8256373544572844 +leaf_count=23821278 516 407 10246 272 1591 6110 2144 822 599 761 1376 11089 4862 857 1281 354287 1709 2384 819 426 560 1694 34571 9803 2729 1720 596 563 9845 3374 1562 +internal_value=0 0.0383913 0.0731342 0.0094994 -0.0752477 0.0319803 -0.0680429 0.0783536 0.0676389 -0.0402999 -3.71283e-05 0.00594392 0.00390505 -0.0884442 0.0684556 0.00623996 0.0315216 -0.0315758 -0.00996735 -0.136602 0.0499805 -0.101967 0.0568768 -0.00316253 0.0743401 -0.0528959 0.0848462 0.0896341 0.0233962 0.00786373 0.0434063 +internal_weight=0 72.5226 32.9272 39.5954 8.30137 31.294 8.22439 3.39134 23.0696 4.20112 74991.6 1378.57 1335.03 32.9218 43.543 1302.11 207.28 46.9144 41.6326 8.68141 160.366 6.96238 153.404 34.5657 118.838 9.27571 110.404 108.565 32.9512 17.8933 15.2436 +internal_count=24290853 23468 10653 12815 2685 10130 2660 1094 7470 1360 24267385 446107 432017 10655 14090 421362 67075 15183 13474 2810 51892 2254 49638 11179 38459 3001 35730 35134 10664 5793 4936 +is_linear=0 +shrinkage=0.1 + + +Tree=25 +num_leaves=32 +num_cat=0 +split_feature=20 2 14 2 19 17 5 18 16 9 1 18 0 18 9 0 10 11 1 16 16 16 18 11 14 17 15 4 14 10 2 +split_gain=12.2652 6.77102 6.14069 6.02274 5.66045 5.35389 19.77 14.3848 7.73375 7.39393 7.23348 9.01986 7.13189 6.17803 5.83306 6.05089 5.05103 8.78779 4.92954 4.5109 22.1736 17.0234 17.0248 15.5697 15.1588 15.6453 23.6134 27.9246 33.0674 21.1339 20.2324 +threshold=50132.000000000007 59916.000000000007 262.50000000000006 15.500000000000002 1.5000000000000002 69.500000000000014 681.50000000000011 39.500000000000007 69.500000000000014 4.5000000000000009 134128.00000000003 49.500000000000007 5143.0000000000009 15.500000000000002 13.500000000000002 9568.5000000000018 19.500000000000004 2.5000000000000004 62144.000000000007 29.500000000000004 83.500000000000014 17.500000000000004 271.50000000000006 5.5000000000000009 11595.500000000002 30.500000000000004 32.500000000000007 32.500000000000007 5.5000000000000009 14.500000000000002 9.5000000000000018 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 19 3 16 5 7 -7 8 9 12 14 -12 -3 -11 15 -8 17 -2 -15 21 -21 -1 24 -24 25 26 -23 28 29 -28 -30 +right_child=2 4 -4 -5 -6 6 10 -9 -10 13 11 -13 -14 18 -16 -17 -18 -19 -20 20 -22 22 23 -25 -26 -27 27 -29 30 -31 -32 +leaf_value=-2.2895639683936006e-05 0.04207195776123078 0.021947221985595389 -0.1673881501261214 -0.18651993246695753 -0.18400741349718344 -0.21453449174105535 0.10057066925944405 -0.18913638909535627 -0.18301787164535493 -0.1552517566312826 0.13312488170797707 -0.17965345504721644 -0.17764031846031222 -0.18461322812817491 -0.15000650111271199 -0.14985812526813966 0.14490838738095116 -0.19032177184113727 0.10977253946489335 -0.012251535454862779 0.00065918915600437621 0.03965423054834416 0.1104053437087871 -0.071789795257431996 -0.20097907603643189 -0.0070118845937336407 0.10650236706872732 0.023987922457414518 -0.098630495493375298 -0.12315112661113713 0.018470006832622229 +leaf_weight=66482.188106386922 81.68089563556714 6.7007949848193764 1.4495688590395719 1.1574994075344864 1.2147085348842654 2.5374414389953008 34.69738168787444 3.5046798539115107 1.6544492701068509 1.0302055337815534 1.5175952427089208 2.3492240682826377 2.4431132363388315 0.59250598959624667 1.0106988325715054 0.99242694483837013 4.61582361254841 1.6602356786606822 14.227087573555762 1749.7857373824299 5548.2295137027031 317.30159664538223 29.32695993501693 5.5833132393890983 3.3853223705664268 331.77912959063542 4.4338272158056489 249.79767023492604 17.547946466365829 41.638737915782258 92.692144672153518 +leaf_count=21521011 26444 2171 469 374 394 821 11233 1134 534 335 490 762 791 192 327 321 1494 536 4602 566430 1796014 102711 9490 1809 1097 107401 1439 80868 5675 13477 30007 +internal_value=0 -4.44427e-05 0.0367787 0.0400998 0.0300935 0.0336436 0.0562541 0.00132094 0.0263693 0.0402296 0.0731916 -0.0568986 -0.0313795 0.0815416 0.0868981 0.093607 0.0430821 0.0374425 0.0980026 -7.44191e-05 -0.00243631 0.00018066 0.0125565 0.0812663 0.0102905 0.0109683 0.0192146 0.00324479 -0.0299041 -0.10105 -0.000169975 +internal_weight=0 74948.2 90.564 89.1145 74.4723 73.2576 43.1048 30.1528 26.6482 24.9937 40.5673 3.86682 9.14391 15.8498 36.7005 35.6898 87.957 83.3411 14.8196 74873.7 7298.02 67575.7 1093.49 34.9103 1058.58 1055.19 723.412 406.11 156.313 46.0726 110.24 +internal_count=24290853 24261536 29317 28848 24107 23713 13954 9759 8625 8091 13133 1252 2962 5129 11881 11554 28474 26980 4794 24237429 2362444 21874985 353974 11299 342675 341578 234177 131466 50598 14916 35682 +is_linear=0 +shrinkage=0.1 + + +Tree=26 +num_leaves=32 +num_cat=0 +split_feature=20 2 0 0 0 0 7 18 0 10 12 12 7 12 16 14 16 14 11 6 0 0 16 0 21 0 16 6 7 12 9 +split_gain=9.9753 9.49391 6.23229 14.9643 8.05422 9.43047 8.78531 5.83824 5.07093 5.05055 8.4178 6.13816 9.27178 5.8998 4.76023 19.9274 5.58748 5.287 5.10768 4.40772 14.2396 15.1884 14.3987 14.1584 22.2788 23.5595 28.5454 25.2433 30.9697 21.9597 20.94 +threshold=50132.000000000007 59916.000000000007 5053.5000000000009 3265.5000000000005 5593.5000000000009 6678.0000000000009 54.500000000000007 37.500000000000007 9877.0000000000018 19.500000000000004 93.500000000000014 78.500000000000014 1.0000000180025095e-35 66.500000000000014 16.500000000000004 1.0000000180025095e-35 1.0000000180025095e-35 3.5000000000000004 1.0000000180025095e-35 6921.0000000000009 87.500000000000014 80.500000000000014 7612.5000000000009 142.50000000000003 83.500000000000014 106.00000000000001 45.500000000000007 21301.500000000004 642.50000000000011 12.500000000000002 411.00000000000006 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 19 3 -3 7 -6 -7 -4 14 10 11 12 13 -2 16 -16 -8 18 -17 -1 21 22 -21 24 25 27 30 28 -22 -29 -27 +right_child=9 2 4 -5 5 6 8 -9 -10 -11 -12 -13 -14 -15 15 17 -18 -19 -20 20 23 -23 -24 -25 -26 26 -28 29 -30 -31 -32 +leaf_value=-0.00017871806555159309 0.033933297680110226 0.037433844265638311 -0.17431315799931602 -0.16915353446866746 -0.17422634087989153 -0.18722367532961204 -0.17146631522612121 0.15311078864872682 -0.14052281258105673 0.13510230681683935 -0.16781207130067896 0.11871357214261252 -0.17804732904437634 -0.18008698110645058 -0.17772796942251889 0.1075931236976293 0.10432416794391211 -0.17570277967541692 -0.18928109277268049 0.010264928099871661 0.060437766284349076 0.0780411193109391 -0.21043292341628078 0.0085578530361457746 0.0057993438243521571 -0.20321121087688432 -0.20127948117817748 0.067783753158686358 -0.1996592290080873 -0.20245568142763407 0.011819938428877904 +leaf_weight=73503.914254818199 73.074055564269656 14.375180839211682 0.5777517082169642 4.6374105447903267 2.0525379093596703 1.5617497685598198 0.76305169664556416 9.4847520254552347 1.2886563864303742 4.6088642382528624 2.1482064237934528 7.5442494375165543 2.2021024763816959 1.3111446113907721 3.8602469580946517 12.564426021417598 19.709223535581259 0.769894733733964 0.6075574019923814 937.00982545106672 7.1695883076172349 33.551358552649617 2.9655097745708181 181.76295377191855 87.208482628047932 6.0444702426902976 21.153370760206599 27.861420384608213 12.664178898092358 3.3707564100623122 18.05936846655095 +leaf_count=23804438 23666 4654 188 1502 665 508 249 3071 416 1494 694 2444 712 426 1250 4067 6384 248 197 303449 2324 10866 961 58866 28248 1954 6849 9022 4099 1092 5850 +internal_value=0 -4.01694e-05 0.0361914 -0.0129554 0.0537423 0.0349656 0.0454064 0.134311 0.0545894 0.0331089 0.0276607 0.0326518 0.0241743 0.0301609 0.0611587 0.0233399 0.0940448 0.0790119 0.0938998 -7.5147e-05 0.00561111 0.0119285 0.00956865 -0.011225 -0.0308171 -0.0639687 -0.116503 -0.0174105 -0.105638 0.038618 -0.042103 +internal_weight=0 74915 72.2524 19.0126 53.2398 43.1773 41.1248 10.0625 39.5631 90.8886 86.2798 84.1316 76.5873 74.3852 38.2744 17.8021 20.4723 13.9419 13.172 74842.7 1338.82 973.527 939.975 365.295 183.532 96.3232 45.2572 51.0659 19.8338 31.2322 24.1038 +internal_count=24290853 24261417 23399 6156 17243 13984 13319 3259 12811 29436 27942 27248 24804 24092 12395 5762 6633 4512 4264 24238018 433580 315276 304410 118304 59438 31190 14653 16537 6423 10114 7804 +is_linear=0 +shrinkage=0.1 + + +Tree=27 +num_leaves=32 +num_cat=0 +split_feature=3 17 0 0 19 19 8 19 9 0 8 12 6 6 6 6 12 14 10 6 6 14 3 12 16 14 6 6 5 17 19 +split_gain=11.3318 5.47953 3.92156 6.25217 4.02472 7.53925 3.77303 4.451 8.62813 3.75439 3.57369 11.3976 8.5042 6.29943 21.5405 8.7738 6.82154 6.30879 5.71568 3.78156 17.9856 19.8213 17.4657 15.7805 15.8299 15.6063 13.5834 22.1978 20.3568 32.1643 19.1706 +threshold=70853.500000000015 226.00000000000003 5053.5000000000009 3265.5000000000005 465.50000000000006 106.50000000000001 5.5000000000000009 65.500000000000014 493.50000000000006 9877.0000000000018 75743.500000000015 14.500000000000002 9305.0000000000018 8822.0000000000018 7504.0000000000009 4263.5000000000009 2.5000000000000004 12.500000000000002 3376.0000000000005 67.500000000000014 63.500000000000007 10185.000000000002 56.500000000000007 50694.000000000007 8521.5000000000018 60969.000000000007 104.50000000000001 106.50000000000001 124.50000000000001 55.500000000000007 2.5000000000000004 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=10 2 3 4 5 6 7 -2 -9 -4 19 12 13 14 15 -12 18 -15 -17 20 -1 23 -23 24 -22 26 28 -28 -21 -30 -31 +right_child=1 -3 9 -5 -6 -7 -8 8 -10 -11 11 -13 -14 17 -16 16 -18 -19 -20 25 21 22 -24 -25 -26 -27 27 -29 29 30 -32 +leaf_value=8.5590023567390226e-05 0.10514565505486904 -0.15355470805167318 0.072283560125342469 -0.14140022923603343 0.16403249845029724 -0.17707813037249698 -0.014924954271453308 -0.18398677298417032 0.15782072553022911 -0.13078088630200277 -0.19949260767680008 0.18761367617393176 -0.19972875346608712 0.14687285091925995 -0.19968619457685205 0.14547555437457396 -0.19123298058238974 -0.20666684953083175 -0.20955381939669265 -0.0058560127530571174 0.064738366947596368 -0.20060311511298765 0.18373911521113842 -0.19895223810452017 -0.19887524856740788 -0.14342039438751764 0.067808740593201508 -0.0010797012633481803 0.0024852556836240827 -0.19966782965302207 -0.066647357289581433 +leaf_weight=69378.298336376087 9.4296734102536117 1.4078015423146997 35.923719648155384 2.2808857874479136 2.1006013154983512 1.7992410467704747 8.5472789967898262 1.6905065218452358 1.3113923126365987 0.93415750499116157 1.0916400062851703 4.2085636963602147 1.8077609036117781 5.357641222421079 4.2052855464862651 8.0513189195189572 0.73896881390828717 0.55723934248089779 0.48052434693090695 575.71047655394068 96.237157119670883 5.6456179730594149 1.4955738158896563 2.4397802043240508 2.3331697753164908 7.8577626901096655 47.254355359706096 4615.3505119162146 78.190084437141195 12.478321257134665 82.230268818209879 +leaf_count=22470871 3050 456 11639 740 680 583 2766 547 426 303 352 1363 586 1735 1362 2608 240 181 156 186462 31169 1829 484 792 755 2545 15304 1494869 25330 4041 26629 +internal_value=0 0.0415993 0.045891 0.0170584 0.0315859 0.0193717 0.0362201 0.0713846 -0.0346667 0.0671369 -3.63225e-05 0.0366807 0.00818357 0.0265336 -0.00880387 0.0686599 0.100235 0.113566 0.12548 -4.93117e-05 0.000149111 0.0408973 -0.120111 0.0522802 0.0584986 -0.0025936 -0.0023891 -0.000381534 -0.0148929 -0.0449837 -0.0841735 +internal_weight=0 65.4253 64.0175 27.1596 24.8787 22.7781 20.9789 12.4316 3.0019 36.8579 74932 26.4989 22.2904 20.4826 14.5677 10.3625 9.27081 5.91488 8.53184 74905.5 69486.4 108.151 7.14119 101.01 98.5703 5419.07 5411.21 4662.6 748.609 172.899 94.7086 +internal_count=24290853 21190 20734 8792 8052 7372 6789 4023 973 11942 24269663 8583 7220 6634 4718 3356 3004 1916 2764 24261080 22505900 35029 2313 32716 31924 1755180 1752635 1510173 242462 56000 30670 +is_linear=0 +shrinkage=0.1 + + +Tree=28 +num_leaves=32 +num_cat=0 +split_feature=3 20 8 20 0 0 6 20 12 3 12 3 18 4 18 19 12 8 8 4 3 9 4 0 0 0 0 21 12 4 19 +split_gain=10.9266 9.57196 5.79781 6.50543 11.2088 19.4317 10.2887 8.16516 5.35643 10.4908 6.95276 4.76929 9.56533 10.8152 7.67527 4.56445 4.54257 4.06886 16.1318 23.0603 15.8548 22.0676 18.7178 12.0303 21.4131 16.9775 16.0698 16.2975 15.5031 14.4755 13.3378 +threshold=70853.500000000015 50132.000000000007 75743.500000000015 1.5000000000000002 145.50000000000003 1.0000000180025095e-35 4558.0000000000009 2.5000000000000004 61.500000000000007 63.500000000000007 49.500000000000007 225.50000000000003 8587.0000000000018 5.5000000000000009 10008.500000000002 47851.500000000007 1.0000000180025095e-35 10972.000000000002 11816.000000000002 48.500000000000007 570.00000000000011 1633.0000000000002 218.50000000000003 126.50000000000001 188.50000000000003 311.50000000000006 102.50000000000001 16.500000000000004 76.500000000000014 173.50000000000003 7.5000000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 2 17 4 5 -4 -7 16 9 -3 11 14 13 -13 15 -11 -5 -1 19 -19 22 -22 23 26 30 -26 27 28 -20 -30 -25 +right_child=-2 8 3 7 -6 6 -8 -9 -10 10 -12 12 -14 -15 -16 -17 -18 18 20 -21 21 -23 -24 24 25 -27 -28 -29 29 -31 -32 +leaf_value=-2.5928546871335166e-05 0.041604378503825229 -0.15157957327121324 0.10815402849623537 0.1810273088699183 0.15325585842734599 0.073986986886949865 -0.1905037399420445 -0.17646190194861214 0.080768251682050665 -0.21341311158125503 -0.18952878760202516 0.048957955458290674 0.049318924403440649 -0.14409525434698178 -0.19371970952333817 0.071022977571096046 -0.19566701201212086 -0.0073982490762644756 -0.0098890103355222924 -0.19839645775204906 -0.20093518725003148 0.16770071140486409 -0.15007361875389821 -0.067529581463410704 -0.20028177427123142 0.012857152208678418 -0.19887529392051009 -0.16619529736268265 0.10422523321556168 -0.20003509578525558 0.13046595307717665 +leaf_weight=74229.717827875065 63.07297647325322 3.3865959902759633 6.0326426641549977 6.3371020019985727 4.4876651640515766 1.9221753750462065 6.2625492330407724 0.79799154016654927 18.350056516152108 0.57356527302181248 1.4288375588948827 4.9064643234014529 18.55727527126146 7.1027082036889624 1.1725654606707383 34.484607221209444 0.33715974958613504 15.526763356931044 441.55974823309225 10.662062224699183 1.9341030230280001 10.124934205319731 9.0364511262159777 3.9819327593431835 4.5936032350873601 20.046094261808321 4.4591821787762447 6.4341507753124452 18.840919246431444 1.7051751343533386 23.373303109197877 +leaf_count=24047399 20433 1095 1955 2053 1453 620 2031 259 5944 185 463 1590 6014 2302 380 11172 109 5026 143047 3457 628 3278 2929 1291 1488 6495 1445 2085 6104 553 7570 +internal_value=0 -3.50273e-05 -7.42201e-05 0.0469795 0.0154714 -0.0280198 -0.128388 0.125853 0.0325643 0.0202125 0.0287399 0.0334088 0.00431744 -0.0652215 0.057952 0.0663695 0.161998 -9.06867e-05 -0.00849041 -0.0851579 -0.00481366 0.108577 -0.00737415 -0.00491794 0.0407394 -0.0268785 -0.00993687 -0.00813871 -0.005938 0.0789739 0.101645 +internal_weight=0 74918.1 74828.2 26.1773 18.705 14.2174 8.18472 7.47225 89.9627 71.6126 68.226 66.7972 30.5664 12.0092 36.2307 35.0582 6.67426 74802 572.278 26.1888 546.09 12.059 534.031 524.994 51.9949 24.6397 472.999 468.54 462.106 20.5461 27.3552 +internal_count=24290853 24270420 24241275 8480 6059 4606 2651 2421 29145 23201 22106 21643 9906 3892 11737 11357 2162 24232795 185396 8483 176913 3906 173007 170078 16844 7983 153234 151789 149704 6657 8861 +is_linear=0 +shrinkage=0.1 + + +Tree=29 +num_leaves=32 +num_cat=0 +split_feature=2 21 12 15 17 6 2 14 7 16 16 14 14 14 5 18 10 12 2 18 17 6 16 5 5 5 18 7 5 15 6 +split_gain=10.4707 8.06596 8.08901 9.73364 10.5493 10.7062 8.15229 12.8215 10.6089 10.5138 14.9211 8.0783 7.69327 10.7854 6.71359 5.88384 8.61318 8.3272 6.67561 5.01591 9.59346 8.63627 5.74896 9.53594 5.00821 4.96906 6.49116 6.39569 11.8144 4.81976 4.75381 +threshold=59916.000000000007 54465.500000000007 19.500000000000004 684.50000000000011 1.0000000180025095e-35 21.500000000000004 3498.0000000000005 1.5000000000000002 1.5000000000000002 175.50000000000003 267.50000000000006 7.5000000000000009 31.500000000000004 38.500000000000007 484.50000000000006 6817.0000000000009 2.5000000000000004 32.500000000000007 1.0000000180025095e-35 28.500000000000004 20.500000000000004 9.5000000000000018 26.500000000000004 2591.0000000000005 1.5000000000000002 2.5000000000000004 4585.5000000000009 3.5000000000000004 2233.0000000000005 104.50000000000001 5949.0000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 30 3 4 6 -6 7 8 -3 12 -11 -8 -9 -14 -12 -4 17 24 -18 20 22 -21 23 -2 -17 26 -15 -27 -29 -30 -1 +right_child=19 2 15 -5 5 -7 11 9 -10 10 14 -13 13 25 -16 16 18 -19 -20 21 -22 -23 -24 -25 -26 27 -28 28 29 -31 -32 +leaf_value=-0.00019728007315601744 0.1898398632388677 0.087184253676900109 0.094409568006676936 0.16839934150969393 -0.19428449738875719 0.10626805509621368 0.17773168843301412 0.050209140534373635 -0.19464060323218935 -0.20117212095827319 -0.13154334181546204 -0.19692798562214567 -0.1894641895006636 0.10455504398996628 0.15730432221795676 0.12434460283115883 -0.16783835928796281 -0.17328309404438416 0.11431608866567056 0.057505823553204674 -0.14683437137029237 -0.19071696076578437 0.12860380292970608 -0.14742354751906381 -0.073658908444892965 0.036278696971342027 -0.15863907458765783 -0.2002232231115281 0.17910604049515963 -0.19743308573405727 0.0055855245398807006 +leaf_weight=73305.98992538717 1.2178144454956039 1.7814781900960941 22.763850706163794 3.7428969750180832 4.8207797835348165 1.5715975025668738 4.7089883843436828 51.966730923973955 5.338057525223121 5.0915332222357375 1.1923351916484537 0.6556271738372742 2.9997546550584948 1.2752584888366971 2.4748997129499912 2.6009049471467751 0.93446249584667285 3.4900363362394273 8.1677575185894948 55.63315007527126 3.9772398396162316 1.4378884288016696 3.9620250384323299 2.6905085248872638 2.5104098357260227 31.76988183130743 3.533493515336886 3.2813309597549951 1.6839421975892039 0.42592516308650363 1449.669716914359 +leaf_count=23744304 394 576 7367 1213 1561 509 1524 16832 1725 1650 385 213 968 415 801 844 304 1130 2649 18024 1288 466 1281 870 813 10298 1143 1064 546 138 469558 +internal_value=0 -3.58381e-05 0.0218003 0.00950609 0.00473196 -0.120392 0.0115 0.00577261 -0.124121 0.0145222 -0.0904015 0.131943 0.0240026 -0.00628153 0.0633907 0.0607828 0.0175443 -0.0542089 0.0853492 0.0389602 -0.0202504 0.0512519 0.0437181 -0.0423339 0.0270957 0.00681128 -0.0888412 0.019189 -0.0815192 0.103093 -8.51393e-05 +internal_weight=0 74924.4 168.782 128.315 124.572 6.39238 118.179 112.815 7.11954 105.695 8.75877 5.36462 96.9363 44.9696 3.66723 40.4674 17.7036 8.60135 9.10222 68.9186 11.8476 57.071 7.87035 3.90832 5.11131 41.9698 4.80875 37.1611 5.3912 2.10987 74755.7 +internal_count=24290853 24268530 54668 41561 40348 2070 38278 36541 2301 34240 2836 1737 31404 14572 1186 13107 5740 2787 2953 22323 3833 18490 2545 1264 1657 13604 1558 12046 1748 684 24213862 +is_linear=0 +shrinkage=0.1 + + +Tree=30 +num_leaves=32 +num_cat=0 +split_feature=21 2 8 0 2 2 6 2 10 4 7 12 0 10 12 6 10 12 12 7 12 2 6 12 6 7 3 8 0 10 3 +split_gain=9.81079 8.60684 8.32398 7.88415 6.3878 10.3911 10.8256 9.09702 7.40669 6.64934 5.95152 7.28498 8.41127 6.3924 5.68099 5.67323 5.53774 6.63959 5.85449 6.92627 5.30483 4.8259 5.00625 4.78607 4.49975 4.32755 4.24763 5.54622 4.1982 5.20074 8.64737 +threshold=54465.500000000007 59916.000000000007 75743.500000000015 175.50000000000003 3498.0000000000005 491.50000000000006 5.5000000000000009 338.50000000000006 199.50000000000003 1.5000000000000002 2.5000000000000004 1.5000000000000002 64510.000000000007 217.50000000000003 1.0000000180025095e-35 15.500000000000002 19.500000000000004 93.500000000000014 1.5000000000000002 4.5000000000000009 1.5000000000000002 129.50000000000003 4263.5000000000009 1.0000000180025095e-35 2.5000000000000004 6.5000000000000009 115023.50000000001 1.0000000180025095e-35 1596.5000000000002 2.5000000000000004 71.500000000000014 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 2 26 21 5 7 10 9 20 14 11 13 -13 -7 24 -10 17 28 19 -18 -8 22 -4 -12 -2 -16 -1 -28 29 30 -11 +right_child=4 -3 3 -5 -6 6 8 -9 15 16 23 12 -14 -15 25 -17 18 -19 -20 -21 -22 -23 -24 -25 -26 -27 27 -29 -30 -31 -32 +leaf_value=-0.00011372451759255809 0.0073855231777355465 0.035543795098728653 -0.16440371459197789 -0.19010039185445193 0.12485532716070338 0.028000612953614508 0.0045953283935321193 0.13009795468088167 -0.19543459468583568 -0.15184132626497737 0.12569829731336846 -0.19501401095037899 0.17692089349406148 -0.11003940774915877 -0.20827937383003303 -0.058612488173813773 -0.17508083039994604 -0.1647098590671783 0.13979650489971057 0.090312254526136532 -0.17911045607623022 -0.15818960417153119 0.089057722026848612 -0.17937319632767179 -0.2004762549702937 0.10190444310984491 0.094433067109116031 -0.19868689681101773 0.060402245374412203 0.10051968952791274 0.013193664168112393 +leaf_weight=74722.074791225052 1.5810093507170666 67.857620349590434 0.8082877472043134 1.217011704109608 6.0700437531340858 7.3516889652237349 16.205327272531584 9.9119570264592749 5.0394192552776058 3.5030669322004533 7.4915093497838807 0.73170334100723167 3.5974818412214518 6.1703359899111092 0.54828232491854612 7.60223443293944 1.4920512004755426 1.7741696345619846 9.4237589472904784 2.8844655666034669 1.7407539398409415 0.88432691863272239 21.707871283753775 0.55215462157502759 3.051654783077538 2.5036892399657518 7.8543252028757689 0.70331719878595311 29.160712860408239 5.6688781543634823 33.892420818505343 +leaf_count=24203725 512 21980 259 395 1967 2383 5247 3211 1632 1135 2425 237 1164 1997 178 2463 483 577 3054 933 566 286 7034 179 989 812 2544 228 9445 1836 10977 +internal_value=0 -5.41908e-05 -8.65042e-05 0.0580531 0.0241422 0.0203657 -0.0142434 0.038913 -0.0545245 0.0294473 0.0333378 0.00115673 0.114058 -0.0349895 -0.0597512 -0.113155 0.0372544 0.0264089 0.0954098 -0.000166108 -0.013224 0.0709591 0.0799589 0.104757 -0.129538 0.0461804 -0.000105656 0.0703428 0.0311036 0.0112643 -0.00226619 +internal_weight=0 74823.1 74755.2 24.6175 167.949 161.879 56.4826 105.396 30.5877 95.4842 25.8949 17.8512 4.32919 13.522 7.68464 12.6417 87.7995 73.9992 13.8003 4.37652 17.9461 23.4005 22.5162 8.04366 4.63266 3.05197 74730.6 8.55764 72.2251 43.0644 37.3955 +internal_count=24290853 24236451 24214471 7974 54402 52435 18293 34142 9908 30931 8385 5781 1401 4380 2491 4095 28440 23970 4470 1416 5813 7579 7293 2604 1501 990 24206497 2772 23393 13948 12112 +is_linear=0 +shrinkage=0.1 + + +Tree=31 +num_leaves=32 +num_cat=0 +split_feature=21 3 8 14 14 14 4 5 14 3 14 4 14 6 5 5 6 11 8 4 4 14 14 14 14 6 13 8 9 13 5 +split_gain=15.2007 6.76693 5.84354 5.44085 5.80223 8.3284 19.5049 8.60609 5.29555 5.94968 5.90734 9.84481 6.26641 6.31309 5.91444 5.76472 5.27212 4.64874 5.90477 7.16185 6.3233 6.89657 6.39408 4.75664 5.03364 4.6424 4.40422 5.24724 4.36124 4.09723 4.08736 +threshold=54465.500000000007 70853.500000000015 75743.500000000015 262.50000000000006 224.50000000000003 89.500000000000014 10.500000000000002 9.5000000000000018 69.500000000000014 132.50000000000003 29.500000000000004 8.5000000000000018 46.500000000000007 4.5000000000000009 1331.5000000000002 924.50000000000011 16.500000000000004 21.500000000000004 4.5000000000000009 3.5000000000000004 1.0000000180025095e-35 10.500000000000002 9.5000000000000018 3.5000000000000004 20.500000000000004 9305.0000000000018 1.5000000000000002 1419.5000000000002 1.0000000180025095e-35 1.0000000180025095e-35 71.500000000000014 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 2 -1 4 5 8 -7 -8 10 -10 14 12 13 -12 15 16 17 18 19 20 23 22 -22 -2 -25 -4 -14 -28 -19 -17 -15 +right_child=3 -3 25 -5 -6 6 7 -9 9 -11 11 -13 26 30 -16 29 -18 28 -20 -21 21 -23 -24 24 -26 -27 27 -29 -30 -31 -32 +leaf_value=-0.00010997813941935725 -0.16784655339639196 0.032639435899516116 0.057357932838078378 -0.15619789856851163 0.11263839000598674 -0.1731151410738539 0.074855629440798821 -0.17021950726090226 -0.15967561552669279 0.090274490523932299 -0.17049992455590432 -0.11172038931304765 -0.083625858945875375 0.17677263024414852 -0.044571947929383286 -0.17741974871962382 -0.18974636627857089 0.11002278318825552 0.070165860430477886 0.071498361746418576 -0.087048466613532716 -0.19498774142891084 0.15665635690721899 0.066184176593657468 -0.17053236366939517 -0.15162835499408664 0.1050455693036233 -0.1866200991736513 -0.16790882603485927 0.17014097828521271 -0.031584566029346055 +leaf_weight=74741.717503512191 1.2080025094328508 63.209389785450185 24.025848226272505 1.5433874271693628 8.5439122531097365 6.5814656994771239 10.371494750259442 1.6625621647108335 0.99429986637551593 22.561236564419232 2.7220861892565216 7.3191583623411125 1.7176020169863466 1.2287710164673624 7.3070437968708566 0.36278903484344383 0.96983841026667406 12.806807705783282 22.99595051642973 10.186026030045467 3.8781550431158376 3.8343580081127584 1.4903003904037175 8.9144314138684386 0.99897404527291733 1.1121385572478164 10.683463032473806 0.6546171545051046 0.59062947821803469 5.2116892428603023 4.0274059216608293 +leaf_count=24209967 393 20474 7783 501 2767 2131 3359 539 323 7308 883 2370 558 398 2367 118 314 4148 7448 3299 1258 1241 483 2888 322 360 3459 212 191 1688 1303 +internal_value=0 -6.61151e-05 -9.3765e-05 0.0306589 0.0324634 0.0279353 -0.0347015 0.0409973 0.0367246 0.0797239 0.0274414 -0.0118278 0.0229317 -0.0468907 0.0412288 0.0497647 0.0417359 0.0450915 0.0319013 0.00306083 -0.0312385 -0.092556 -0.0193952 0.0195009 0.0423302 0.0481121 0.0655998 0.0882059 0.0977701 0.147522 0.0171245 +internal_weight=0 74830.1 74766.9 161.366 159.823 151.279 18.6155 12.0341 132.664 23.5555 109.108 28.3531 21.0339 7.97826 80.755 73.448 67.8735 66.9036 53.5062 30.5102 20.3242 9.20281 5.36846 11.1214 9.91341 25.138 13.0557 11.3381 13.3974 5.57448 5.25618 +internal_count=24290853 24238584 24218110 52269 51768 49001 6029 3898 42972 7631 35341 9183 6813 2584 26158 23791 21985 21671 17332 9884 6585 2982 1741 3603 3210 8143 4229 3671 4339 1806 1701 +is_linear=0 +shrinkage=0.1 + + +Tree=32 +num_leaves=32 +num_cat=0 +split_feature=21 3 20 19 7 14 9 10 16 20 16 3 6 20 16 14 0 0 0 7 16 14 14 0 7 4 20 20 6 16 7 +split_gain=16.6956 6.43735 7.63949 14.0416 10.2701 9.36791 7.23043 5.94897 5.91448 8.07659 5.49355 13.0991 8.1419 5.57204 5.39695 9.00749 6.41651 19.1161 15.0147 11.6173 10.3767 19.2835 10.2226 7.09242 5.08427 4.93887 4.75661 5.93572 5.35946 4.7423 16.4749 +threshold=54465.500000000007 54213.000000000007 48.500000000000007 199.50000000000003 588.00000000000011 241.50000000000003 342.00000000000006 13.500000000000002 213.50000000000003 11369.500000000002 128.50000000000003 711.00000000000011 6.5000000000000009 2019.0000000000002 1418.5000000000002 6.5000000000000009 2535.0000000000005 4871.5000000000009 5209.5000000000009 490.50000000000006 16.500000000000004 1.0000000180025095e-35 2.5000000000000004 7201.5000000000009 92.500000000000014 928.00000000000011 2603.0000000000005 5998.5000000000009 5.5000000000000009 7.5000000000000009 111.50000000000001 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 -1 4 7 14 -5 -7 -4 10 -10 13 12 -12 -2 15 16 -3 -18 25 20 29 -22 23 -23 -25 -19 -15 28 -28 -20 -31 +right_child=8 2 3 5 -6 6 -8 -9 9 -11 11 -13 -14 26 -16 -17 17 18 19 -21 21 22 -24 24 -26 -27 27 -29 -30 30 -32 +leaf_value=-0.00011178802459209765 -0.15821318636019596 0.090824176780190266 -0.17498648535683911 -0.18462308476784903 0.069500696451597274 0.10694087646993551 -0.19634578742086192 0.034602848080989047 0.11390127287894077 -0.18598713797886535 0.12566506362361868 -0.18116642527675314 -0.17806078647256765 0.1328020733371523 0.15289230114766439 -0.17036222159436154 -0.17034620773679188 -0.21054370160953104 0.010991169358578084 -0.1889376583215816 -0.17610932464844209 -0.1778019035482542 -0.18343223745467266 -0.18601812798284534 0.081959170294039832 0.14342659288726567 0.051397745424341071 0.034609810596368133 -0.16763665830204363 -0.18777913041191974 0.1094983699887548 +leaf_weight=74662.413808288373 1.5253739042673249 9.402299491921438 6.970774865185378 1.6088806906482194 41.386389524093829 8.0857080165296757 0.87071034940891046 1.6808073546271769 13.433512316085395 0.96240661758929569 3.9944925056770426 4.1037884163670242 1.1329187938245011 4.6992088712286195 2.6087339032092123 2.7785926386131896 5.8805407616309813 0.41054864320903917 29.712461639428511 3.1008377608377478 7.6345815436798175 1.2973982522962639 2.2480543525889507 0.74979164800606568 12.70205154805444 9.8862968590110523 1.856517765438183 120.2646406023996 2.8048660903004929 2.1044974461256087 16.328202171775047 +leaf_count=24186469 496 3047 2258 522 13409 2619 281 544 4352 312 1294 1329 367 1523 845 899 1908 133 9624 1005 2472 422 728 243 4112 3205 603 38954 909 683 5286 +internal_value=0 -6.78637e-05 0.0195173 -0.0398054 0.0272079 0.0375471 0.0774565 -0.134268 0.0328094 0.0938529 0.0265495 -0.0480142 0.0585558 0.0317977 0.0108258 0.00727031 0.0121351 0.00409798 0.016002 0.00062533 0.00870211 -0.0440889 0.01521 0.0454867 0.0670224 0.129313 0.0340337 0.0303184 -0.0804005 0.0357109 0.0755576 +internal_weight=0 74829.9 167.448 19.2169 148.231 10.5653 8.95642 8.65158 154.778 14.3959 140.382 9.2312 5.12741 131.151 106.845 104.236 101.458 92.0553 86.1747 75.8779 72.777 24.6319 16.9973 14.7492 13.4518 10.2968 129.625 124.926 4.66138 48.1452 18.4327 +internal_count=24290853 24240714 54245 6224 48021 3422 2900 2802 50139 4664 45475 2990 1661 42485 34612 33767 32868 29821 27913 24575 23570 7977 5505 4777 4355 3338 41989 40466 1512 15593 5969 +is_linear=0 +shrinkage=0.1 + + +Tree=33 +num_leaves=32 +num_cat=0 +split_feature=3 10 0 8 12 0 12 16 8 14 9 14 15 11 14 0 13 0 0 10 5 6 14 11 11 19 0 16 16 19 0 +split_gain=8.33548 5.04046 4.74949 4.72619 5.76496 4.40457 5.24458 15.0943 8.61867 7.93121 10.4403 7.71938 7.22017 6.20137 4.93254 3.83657 13.1797 4.09507 8.28388 4.01376 3.61731 6.77309 4.27042 3.58659 3.49558 7.60441 13.4171 9.00445 7.50424 7.23122 6.69574 +threshold=54213.000000000007 14.500000000000002 11494.500000000002 75743.500000000015 14.500000000000002 64510.000000000007 1.0000000180025095e-35 88.500000000000014 1.5000000000000002 2.5000000000000004 1.0000000180025095e-35 12.500000000000002 180.50000000000003 21.500000000000004 14.500000000000002 5.5000000000000009 147.50000000000003 20.500000000000004 166.50000000000003 1606.5000000000002 362.50000000000006 5.5000000000000009 54.500000000000007 11.500000000000002 3.5000000000000004 69.500000000000014 4.5000000000000009 17.500000000000004 11.500000000000002 62.500000000000007 7520.5000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=3 2 24 5 15 -1 7 8 -7 23 -11 -10 -12 20 -13 16 -5 19 -19 -17 21 22 -8 -9 25 29 27 -27 30 -2 -26 +right_child=1 -3 -4 4 -6 6 13 9 11 10 12 14 -14 -15 -16 17 -18 18 -20 -21 -22 -23 -24 -25 28 26 -28 -29 -30 -31 -32 +leaf_value=-8.3229809673363023e-05 0.020178186811435041 -0.19949427677721762 -0.17552607295603576 0.11882187365909909 0.14411860663970141 -0.15125329833941076 0.1233593089632966 -0.1608578678280487 -0.16669538771288295 -0.1817360183143627 0.12325910994694278 0.17230454891031544 -0.17453540205317608 -0.17278972129267001 -0.14664579019149135 -0.17164166161768935 -0.18295192796380949 -0.16122664396071984 0.10753797348565959 0.098622132464915413 0.11488230043687522 -0.17687431654847002 -0.16229520352367433 0.13060813938956359 0.032435202996288846 -0.17351976004042099 0.17243101562940621 0.13116126527075614 -0.15310811573757738 0.13731723687362096 0.13798470787912959 +leaf_weight=74744.908510846726 116.95759114643442 1.0146672183764156 1.1831653854169406 2.6191536872647765 4.6247773813083759 7.2858364990097488 3.51848015747965 0.44082358293235557 1.4581714720698071 2.5353683017892754 4.2073641946772113 2.5487513374537234 1.0095203147502614 0.98590075224637885 0.59878006984945376 0.5829186966875558 3.2344967407407239 2.3604498256463549 2.2304521740879864 9.5877575047779811 12.342635015142148 1.3533827876672146 0.61479056999087323 9.9865581494523195 16.499352810700664 6.3922335532261041 1.8628168762661514 1.1435096003115175 1.587166575482114 5.5186309688724569 9.4539132537320238 +leaf_count=24214481 37891 328 382 850 1498 2359 1140 143 474 822 1363 826 328 320 193 189 1049 764 722 3105 3999 439 199 3232 5346 2073 604 371 515 1787 3061 +internal_value=0 0.022686 0.0240898 -4.9004e-05 0.0432161 -6.36043e-05 0.0299429 0.00403452 -0.0835657 0.061335 -0.015269 0.0235105 0.0656328 0.0713507 0.111628 0.0205798 -0.0479264 0.0477458 -0.0306496 0.0831323 0.0848509 0.0172931 0.0808705 0.118286 0.0255713 0.0188043 -0.0678814 -0.127286 0.0579747 0.0254563 0.0708834 +internal_weight=0 161.613 160.598 74819 25.24 74793.8 48.8864 30.0712 11.8915 18.1796 7.75225 4.6057 5.21688 18.8152 3.14753 20.6152 5.85365 14.7616 4.5909 10.1707 17.8293 5.48665 4.13327 10.4274 159.415 131.875 9.39856 7.53574 27.5404 122.476 25.9533 +internal_count=24290853 52358 52030 24238495 8177 24230318 15837 9740 3852 5888 2513 1493 1691 6097 1019 6679 1899 4780 1486 3294 5777 1778 1339 3375 51648 42726 3048 2444 8922 39678 8407 +is_linear=0 +shrinkage=0.1 + + +Tree=34 +num_leaves=32 +num_cat=0 +split_feature=21 2 8 15 17 2 12 18 17 16 6 18 6 2 18 2 14 17 17 18 17 19 17 1 12 8 18 12 14 17 2 +split_gain=16.7673 7.49007 5.94604 4.51923 6.09705 4.0858 12.3587 3.88315 6.2725 4.89827 5.00574 3.88959 3.82781 3.75046 9.91089 12.6242 15.2271 11.2334 21.3598 21.8167 18.0983 15.2377 15.9159 30.3898 41.6348 23.1776 15.3573 20.1577 17.9593 15.3585 14.5954 +threshold=54465.500000000007 59916.000000000007 75743.500000000015 292.50000000000006 1.0000000180025095e-35 90.500000000000014 47.500000000000007 28.500000000000004 20.500000000000004 26.500000000000004 17.500000000000004 192.50000000000003 9.5000000000000018 8853.5000000000018 11.500000000000002 8943.0000000000018 207.50000000000003 24.500000000000004 18.500000000000004 19.500000000000004 22.500000000000004 2.5000000000000004 32.500000000000007 40695.500000000007 1234.0000000000002 17.500000000000004 26.500000000000004 18.500000000000004 5.5000000000000009 35.500000000000007 42546.500000000007 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 2 13 4 -2 -4 -7 8 9 10 -3 12 -9 -1 15 16 -15 18 -16 20 -20 22 23 25 -25 -19 27 -24 -29 -28 -22 +right_child=3 7 5 -5 -6 6 -8 11 -10 -11 -12 -13 -14 14 17 -17 -18 21 19 -21 30 -23 26 24 -26 -27 29 28 -30 -31 -32 +leaf_value=-0.00021209255185274659 0.03029399951107889 -0.13387565180846497 0.073610116118594526 0.088496990881492349 -0.14596502396296826 -0.15683138942323968 0.14273468981952162 0.05097232489397055 -0.13422894896926471 0.11296616766018 0.13893956500865587 -0.15006852325286468 -0.1442082143955366 0.14581208396530226 -0.0049607327731798715 0.017675427469409422 -0.19781823946812821 0.087691586614030786 -0.20207836050335948 0.049117925931186572 -0.13750907837532386 0.084147574112237389 0.13543329933946949 -0.19923754304897331 0.13398968389759328 -0.20151814985823624 0.095901103194358572 -0.14521664741610915 0.15401825883913847 0.00010531547629507828 0.085057207013083683 +leaf_weight=73301.362213464832 137.10291957700974 2.3567488628614215 17.854938665579542 13.439109324594027 1.9910343728843134 3.1152136044693171 2.4684117762371898 54.226982082182076 3.3974581465590745 3.5743839791975915 0.94113772112177674 1.0162921512091987 1.0237644303124387 13.444518834527114 166.17510483670048 221.67101520928554 1.4263428142294277 64.115807879366912 15.963492028182372 8.6794193752575648 6.6808991928119212 24.1226913043065 3.8029643872287151 10.266264738747852 5.9068685146048656 2.8962117722257963 17.076273214188404 19.37120617507026 2.2373470156453541 840.21574233716819 5.2711230297572911 +leaf_count=23749191 44422 763 5780 4355 644 1012 802 17574 1097 1156 304 331 332 4356 53837 71818 463 20774 5172 2810 2165 7815 1230 3326 1913 939 5533 6274 725 272232 1708 +internal_value=0 -6.75237e-05 -9.73737e-05 0.0331213 0.027771 0.050262 -0.0243991 0.0334691 -0.0230778 0.0318722 -0.0560207 0.0437899 0.0473557 -0.000113168 0.00496005 0.023659 0.112853 0.00125184 -0.0201916 -0.0893539 -0.132407 0.00564379 0.00368319 0.0454986 -0.0775339 0.0751921 -0.000257459 -0.0768699 -0.114234 0.00201346 -0.0393521 +internal_weight=0 74820.7 74754.1 152.533 139.094 23.4386 5.58363 66.5368 10.2697 6.87227 3.29789 56.267 55.2507 74730.7 1429.32 236.542 14.8709 1192.78 202.77 36.5949 27.9155 990.011 965.889 83.1852 16.1731 67.012 882.704 25.4115 21.6086 857.292 11.952 +internal_count=24290853 24241432 24219875 49421 45066 7594 1814 21557 3320 2223 1067 18237 17906 24212281 463090 76637 4819 386453 65692 11855 9045 320761 312946 26952 5239 21713 285994 8229 6999 277765 3873 +is_linear=0 +shrinkage=0.1 + + +Tree=35 +num_leaves=32 +num_cat=0 +split_feature=21 2 17 17 17 0 17 0 0 0 17 0 10 9 17 0 9 13 10 16 17 1 9 17 5 4 10 16 0 0 9 +split_gain=13.8072 7.07677 9.98407 15.493 15.3498 12.5332 11.8054 11.0527 8.94672 9.6754 11.4495 12.7215 8.49931 8.26581 7.76828 8.02363 6.73341 6.51269 6.03134 15.2733 7.09427 5.56116 5.77807 5.60131 5.33512 5.48992 5.25314 4.6441 4.45041 4.39017 11.46 +threshold=54465.500000000007 50964.500000000007 130.50000000000003 110.50000000000001 215.50000000000003 1887.5000000000002 99.500000000000014 11244.500000000002 9877.0000000000018 7481.5000000000009 55.500000000000007 5778.5000000000009 1.5000000000000002 5.5000000000000009 279.50000000000006 2371.0000000000005 1.5000000000000002 16.500000000000004 1.5000000000000002 4.5000000000000009 29.500000000000004 55527.000000000007 4.5000000000000009 33.500000000000007 732.50000000000011 2836.0000000000005 1.0000000180025095e-35 13.500000000000002 5143.0000000000009 7942.5000000000009 5.5000000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 -1 3 6 5 -4 8 -7 9 10 18 13 14 27 17 -16 -11 -6 21 20 -20 26 28 -24 -5 -26 -3 -12 -23 -18 -31 +right_child=-2 2 4 24 12 7 -8 -9 -10 16 11 -13 -14 -15 15 -17 29 -19 19 -21 -22 22 23 -25 25 -27 -28 -29 -30 30 -32 +leaf_value=-0.00010400996245509853 0.029946701995960213 0.12613859326826771 0.14386221105829577 -0.14815332672075632 0.10724288162905815 -0.18111846267844789 -0.17562391601095403 0.093551766008254683 -0.16154887326161843 -0.17062460418448888 0.08677099327507326 -0.18738297363058867 -0.17747216974703098 -0.16817671118996413 0.17266030871364799 -0.1697552247195224 0.12149468037287303 -0.18526041104337498 -0.17575913930706938 -0.17964471472317958 0.12551472456640447 0.002219868850295965 -0.13229899272261811 0.10697993318499975 0.12824200503537037 -0.18050701308180031 -0.19823085573015775 -0.16139167909962615 -0.17081838357803139 -0.17470036426468394 0.082765121857945975 +leaf_weight=74643.019744399877 153.64400838196161 10.846382785122842 1.7506147101521481 0.76928492682054894 12.514915396488508 10.395162380184045 3.0527735091745845 1.7053752150968637 2.5066609029308884 1.0678339818259726 6.1023958157747966 5.6911051925271741 1.740512317221145 2.1508360281586647 0.93439424037933294 2.5570463981421199 15.352517985214947 0.81049684435129243 0.94163139408919772 4.1359027585713193 4.5989822461269796 5.7476390834199291 1.1230382450157756 7.5919118176680058 21.112839527660984 0.59206025605089863 0.52336515812203366 0.86042544408701349 2.0047601375263184 2.0461206709151147 11.147781979991121 +leaf_count=24194902 49801 3517 562 249 4061 3374 989 553 813 348 1980 1844 565 696 304 826 4975 263 305 1340 1491 1863 364 2461 6842 193 170 277 648 664 3613 +internal_value=0 -6.15254e-05 0.0222119 0.0365879 -0.0265676 -0.106227 0.0175641 -0.142408 0.0245485 0.030242 0.00438794 -0.0700782 0.03289 0.0031738 0.054662 -0.0781165 0.0759175 0.0894518 0.033776 -0.0342327 0.074313 0.0574167 0.0202774 0.0761456 0.110647 0.11982 0.111207 0.0561045 -0.0425276 0.0851399 0.0428372 +internal_weight=0 74785.4 142.375 109.966 32.4085 13.8512 87.4921 12.1005 84.4393 81.9326 52.3184 14.8048 18.5574 9.11366 16.8169 3.49144 29.6143 13.3254 37.5136 9.67652 5.54061 27.8371 16.4673 8.71495 22.4742 21.7049 11.3697 6.96282 7.7524 28.5464 13.1939 +internal_count=24290853 24241052 46150 35642 10508 4489 28358 3927 27369 26556 16956 4797 6019 2953 5454 1130 9600 4324 12159 3136 1796 9023 5336 2825 7284 7035 3687 2257 2511 9252 4277 +is_linear=0 +shrinkage=0.1 + + +Tree=36 +num_leaves=32 +num_cat=0 +split_feature=21 3 2 16 0 8 8 15 12 12 13 1 12 12 8 13 13 11 0 15 7 7 16 16 2 11 11 10 16 8 0 +split_gain=12.8094 10.4376 12.4297 6.55466 5.58562 5.16626 4.8062 4.16214 3.94541 4.68062 5.27166 4.35121 6.13643 5.11045 12.7161 3.6015 4.01625 3.59524 5.79719 8.96992 11.3686 7.54905 5.80274 5.5969 5.20112 7.4648 4.26802 4.02213 4.34362 9.8049 5.37303 +threshold=54465.500000000007 52500.500000000007 5780.0000000000009 17.500000000000004 10503.000000000002 75743.500000000015 2.5000000000000004 684.50000000000011 19.500000000000004 10.500000000000002 262.00000000000006 9614.5000000000018 30.500000000000004 61.500000000000007 119.50000000000001 69.500000000000014 105.50000000000001 2.5000000000000004 7743.5000000000009 1.5000000000000002 28.500000000000004 252.50000000000003 92.500000000000014 11.500000000000002 9226.0000000000018 4.5000000000000009 3.5000000000000004 1.5000000000000002 33.500000000000007 7.5000000000000009 5778.5000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 5 3 -3 17 -1 -6 8 9 -2 -11 13 -13 14 15 -10 -17 18 27 20 -20 -21 -23 24 25 -19 -26 -4 -29 30 -30 +right_child=7 2 4 -5 6 -7 -8 -9 11 10 -12 12 -14 -15 -16 16 -18 23 19 21 -22 22 -24 -25 26 -27 -28 28 29 -31 -32 +leaf_value=-0.00013051693619503305 0.020989903289466025 -0.16058612061134442 0.046663342926871958 0.1156585730550122 0.051852850132182139 0.047219736937882083 -0.15896740352191704 0.12543103660308527 -0.17956665707092268 -0.14263774658700573 0.096419833122459012 0.12745836550662482 -0.16162423421349414 0.072177242155698471 -0.13493950168160662 0.12002781576308508 -0.17687487722785999 0.046075800652893961 0.13404189261491137 -0.18930670004374381 -0.11193329541382127 0.12543927251106476 -0.1732868255196239 -0.14300170511833885 -0.15497651858401479 -0.19373425605146233 0.10912879385852217 -0.025264895930536255 -0.15941316523269305 -0.14725947287678942 0.15458370090782295 +leaf_weight=74587.573522876803 107.56854178570211 5.1554732524382407 74.709873407089617 1.0306521733291445 1.7431678673019622 23.049732219136786 2.8483696896291804 4.3343969819834447 0.51417577639222312 3.5294087892980306 1.2488492155680431 10.280029961606484 0.79078180622309524 14.285039597423745 5.1364575115730986 5.6257284448947757 0.49575894401641551 14.338787782005964 2.2477549433824615 1.0623375136055981 11.452922585653139 6.5917700873687863 0.72142639628145833 1.3094097672728811 0.6315247787861179 1.4272261757869262 19.678178519010544 18.462731636653192 0.6051972908899178 1.7172589411493389 5.4756637863465585 +leaf_count=24178025 34864 1670 24220 334 564 7472 924 1406 166 1144 405 3335 255 4633 1665 1823 161 4648 732 343 3710 2137 234 425 203 463 6378 5986 197 557 1774 +internal_value=0 -5.92954e-05 0.0246033 -0.114562 0.0298201 -0.000115889 -0.0789299 0.0288289 0.0260277 0.016688 -0.0801575 0.0542889 0.106809 0.0319748 -0.0168095 0.0746312 0.0959827 0.0329325 0.024681 -0.0217394 -0.0715782 0.0597867 0.0959708 0.0600909 0.0674624 0.0243669 0.100917 0.0348303 0.00116639 0.0637448 0.123333 +internal_weight=0 74781.8 171.21 6.18613 165.024 74610.6 4.59154 153.809 149.475 112.347 4.77826 37.128 11.0708 26.0572 11.7721 6.63566 6.12149 160.432 123.047 22.0762 13.7007 8.37553 7.3132 37.3851 36.0757 15.766 20.3097 100.971 26.2609 7.79812 6.08086 +internal_count=24290853 24240996 55499 2004 53495 24185497 1488 49857 48451 36413 1549 12038 3590 8448 3815 2150 1984 52007 39890 7156 4442 2714 2371 12117 11692 5111 6581 32734 8514 2528 1971 +is_linear=0 +shrinkage=0.1 + + +Tree=37 +num_leaves=32 +num_cat=0 +split_feature=21 2 18 0 18 0 0 4 0 18 6 19 20 15 19 0 15 13 20 19 15 20 15 20 2 19 19 11 18 7 2 +split_gain=12.1034 10.5878 4.26489 3.98404 3.64368 5.42614 3.64183 3.38749 17.7694 11.64 13.0419 10.2428 13.5039 12.6519 11.1349 11.0277 10.041 12.5213 10.5901 14.1234 21.2606 12.5714 10.5251 14.1026 15.0119 18.2632 13.5066 13.2653 12.5403 19.6436 13.5065 +threshold=54465.500000000007 50964.500000000007 192.50000000000003 2371.0000000000005 9.5000000000000018 876.50000000000011 1463.0000000000002 21932.500000000004 3.5000000000000004 1.0000000180025095e-35 330.00000000000006 18.500000000000004 13.500000000000002 12.500000000000002 25.500000000000004 1.5000000000000002 1.5000000000000002 2.5000000000000004 24.500000000000004 39.500000000000007 5.5000000000000009 18.500000000000004 12.500000000000002 127.50000000000001 7363.0000000000009 87.500000000000014 52.500000000000007 3.5000000000000004 25.500000000000004 1667.5000000000002 1910.5000000000002 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 7 4 -4 5 -3 -6 -1 9 10 -9 13 -13 -11 15 -14 -16 18 21 20 -20 -18 23 24 27 -26 -24 -21 30 -30 -25 +right_child=-2 2 3 -5 6 -7 -8 8 -10 11 -12 12 14 -15 16 -17 17 -19 19 22 -22 -23 26 28 25 -27 -28 -29 29 -31 -32 +leaf_value=-0.00015220166331989925 0.028221331262593453 0.13372410306167742 0.11574669482237171 -0.14743230342736385 -0.15491840555575148 -0.15008859265424443 0.035104361414720515 -0.19367139248488374 0.12278470164492246 -0.012478820338692095 -0.020706260847513536 0.15091890618984483 -0.17827206455358086 -0.19257283517061352 0.098667318109337543 0.16692723485720701 -0.19498053991804526 -0.13003720826404472 0.040587865182698669 -0.033425684997087222 -0.19519283781989566 0.10520244019088219 0.11149026387781946 0.18657754722554548 0.039587266297673708 -0.12592540904229568 0.019627399291727545 -0.19211107668546024 0.10052991402496854 -0.18889157134472204 -0.12874155393231007 +leaf_weight=74322.820724356294 151.65993447229266 0.9304635349544691 0.75095984712243025 2.4576794933382189 1.0167101901024569 2.4405578715377487 126.07688321001478 5.6941406148835112 13.40971126698423 16.202020481985532 18.5972964077082 7.2343722528312346 5.2833860964747137 5.13781760513666 13.79174368875101 1.1219543605111537 1.5054644576739544 6.1403894536197177 7.2156770224682978 11.389136535581203 8.1370770757785049 19.034347078879364 19.737492450629361 1.6962789241224507 27.313571677543223 8.8193928549881075 84.645026886602864 9.8016729013761488 24.699247208365708 2.5911036756588137 6.8207952895318158 +leaf_count=24092676 49162 296 245 796 331 792 40873 1847 4346 5253 6030 2345 1714 1665 4474 363 487 1990 2341 3691 2639 6168 6399 551 8853 2863 27433 3176 8005 840 2209 +internal_value=0 -5.72342e-05 0.0280613 -0.0858371 0.0308625 -0.0717511 0.0335842 -0.000107586 0.0100635 0.0052282 -0.0612509 0.0108292 0.016158 -0.0558386 0.0124047 -0.117807 0.0156969 0.01092 0.0146282 0.00801158 -0.0843777 0.0832005 0.015193 -0.0092458 -0.0400011 -0.000811337 0.0369976 -0.106825 0.0399901 0.0730506 -0.0659419 +internal_weight=0 74782.5 133.673 3.20864 130.465 3.37102 127.094 74648.8 326.019 312.609 24.2914 288.318 266.978 21.3398 259.744 6.40534 253.338 239.547 233.406 212.866 15.3528 20.5398 197.514 93.1312 57.3238 36.133 104.383 21.1908 35.8074 27.2904 8.51707 +internal_count=24290853 24241691 43333 1041 42292 1088 41204 24198358 105682 101336 7877 93459 86541 6918 84196 2077 82119 77645 75655 69000 4980 6655 64020 30188 18583 11716 33832 6867 11605 8845 2760 +is_linear=0 +shrinkage=0.1 + + +Tree=38 +num_leaves=32 +num_cat=0 +split_feature=1 20 20 4 20 15 7 14 17 20 4 2 17 20 0 14 16 4 20 17 20 4 4 15 4 1 13 9 15 20 20 +split_gain=10.3657 7.94533 6.22665 5.78381 13.429 13.849 9.80049 13.5844 10.4395 9.68791 9.59621 19.3986 16.6639 25.0426 14.442 14.9887 13.7518 18.0518 13.9289 13.6449 23.9822 14.6465 14.1296 13.4974 13.7853 16.4866 13.4185 13.0056 12.7076 13.1688 26.1034 +threshold=65745.500000000015 10909.500000000002 50132.000000000007 21932.500000000004 172.50000000000003 1.5000000000000002 440.50000000000006 83.500000000000014 1.5000000000000002 160.50000000000003 8500.0000000000018 11029.000000000002 11.500000000000002 210.50000000000003 92.500000000000014 1560.0000000000002 1804.5000000000002 8782.5000000000018 1.0000000180025095e-35 8.5000000000000018 20.500000000000004 9716.0000000000018 8944.0000000000018 9.5000000000000018 10162.000000000002 1.0000000180025095e-35 3.5000000000000004 122.50000000000001 8.5000000000000018 37.500000000000007 7.5000000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=2 -2 3 10 5 -5 8 -8 -6 -7 -1 12 13 19 15 28 17 -16 -19 -12 -21 22 -22 24 25 -20 -18 -17 29 30 -14 +right_child=1 -3 -4 4 6 9 7 -9 -10 -11 11 -13 14 -15 16 27 26 18 23 20 21 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 +leaf_value=-2.9819571467103992e-05 0.037755296470586029 -0.14733151346176396 0.027748523164037926 0.089680034092742791 0.13169124395454007 -0.00026928634837921245 -0.18618667464337343 0.043559002424886417 -0.19185576994630349 -0.18282991948471522 -0.012470482857009751 -0.1884156458410837 0.060669496514733773 -0.17523766604291321 -0.20242509753731164 -0.2007040781164755 0.18007725888888593 -0.19814372228830368 -0.10170531112637252 -0.155580091322123 -0.19918815912643162 -0.19547708191448876 0.082551341938585904 0.14400092658693672 0.031829561321963531 0.052239944957340294 -0.062383881838954894 0.11181403910808858 0.13485172750886834 0.083110072454370812 -0.082843308881248343 +leaf_weight=73854.415917155697 92.13488656860136 2.3792125608306369 80.537818121549208 17.46840895828791 18.439487311115958 247.21029438759433 2.7617043177597216 37.792886705894489 1.0542738477233786 2.9413903451059005 327.10891647957033 6.2351199174299827 30.89629197132308 10.556140963453798 5.6834708421956739 4.5793091162922783 6.423921848414464 4.0354814548045388 26.420806277659722 18.857752936542965 2.0558718368411091 3.0416598098818204 13.268237789161502 5.4528062151512122 28.68721032445319 9.4429682943737117 3.5405789774376899 1.8776014598552135 14.232611764338797 32.136719492060365 21.489249943639152 +leaf_count=23941163 29868 770 26106 5664 5983 80138 895 12246 340 953 106044 2022 10014 3419 1842 1483 2081 1309 8562 6111 667 987 4303 1767 9298 3061 1148 611 4614 10420 6964 +internal_value=0 0.0330961 -4.17982e-05 -7.1737e-05 0.013185 0.00359548 0.0559229 0.0279137 0.114193 -0.00241591 -0.000130098 -0.0129872 -0.0110675 -0.0233982 0.0126505 0.0377834 -0.0168327 -0.0306764 -0.0174925 -0.0189988 -0.0763673 0.00496743 0.0447534 -0.00707856 -0.0198407 -0.0611714 0.0939261 -0.109827 0.0474346 0.0327145 0.00179862 +internal_weight=0 94.5141 74838.6 74758.1 327.668 267.62 60.0484 40.5546 19.4938 250.152 74430.4 576.023 569.788 374.889 194.899 105.212 89.6872 79.7227 74.0393 364.332 37.2235 18.3658 15.3241 70.0038 64.551 35.8638 9.9645 6.45691 98.7549 84.5223 52.3855 +internal_count=24290853 30638 24260215 24234109 106219 86755 19464 13141 6323 81091 24127890 186727 184705 121531 63174 34106 29068 25839 23997 118112 12068 5957 4970 22688 20921 11623 3229 2094 32012 27398 16978 +is_linear=0 +shrinkage=0.1 + + +Tree=39 +num_leaves=32 +num_cat=0 +split_feature=1 21 1 17 16 1 15 16 18 2 16 21 2 15 16 17 16 11 2 14 17 9 4 14 0 0 18 9 16 4 17 +split_gain=7.9989 4.71062 4.30771 12.7 23.043 12.1015 11.1761 19.31 31.4683 35.4422 12.7248 13.9413 9.67813 9.9692 9.6321 9.59594 14.4076 8.64254 8.32198 20.778 17.1772 18.5928 17.7801 25.5951 22.0196 12.9182 12.2747 11.9228 12.0395 9.58868 8.72627 +threshold=65745.500000000015 54465.500000000007 25401.500000000004 324.00000000000006 23.500000000000004 44894.500000000007 408.50000000000006 164.50000000000003 114.50000000000001 523.50000000000011 257.50000000000006 44955.000000000007 172.50000000000003 229.50000000000003 462.50000000000006 525.50000000000011 22.500000000000004 21.500000000000004 23136.000000000004 1.5000000000000002 179.50000000000003 4.5000000000000009 97.500000000000014 1.0000000180025095e-35 7826.0000000000009 1785.0000000000002 55.500000000000007 7.5000000000000009 5.5000000000000009 621.50000000000011 71.500000000000014 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 2 18 6 5 15 7 -4 9 10 11 12 -9 -14 17 16 -5 -8 -1 20 22 -22 24 25 -20 -24 -25 -27 29 -29 -26 +right_child=-2 -3 3 4 -6 -7 14 8 -10 -11 -12 -13 13 -15 -16 -17 -18 -19 19 -21 21 -23 23 26 30 27 -28 28 -30 -31 -32 +leaf_value=-0.00012079240666738707 0.028921316495776079 0.020710348893298931 0.010508923418621537 -0.16019630054303813 -0.18049165463434724 -0.18756889462830428 0.091071273208467293 -0.10516500138611627 0.087045190654372925 -0.16642522971300572 -0.20102509852546785 -0.20272635570996683 -0.19813662053335146 0.082421431362397282 -0.1977747515555961 0.13001927604317773 0.13653520373322933 -0.19071850637201909 -0.19120725874347386 -0.1900066338233285 0.12023480037266393 -0.19154416541776581 0.026882583964362501 -0.20073593220452113 0.1545646118661137 -0.18192097344916958 0.15102913642470722 -0.20168805258577463 -0.19643470525432311 0.10760718570666619 -0.18986975167736209 +leaf_weight=73639.247177977304 95.508069862713455 109.27522717646207 866.10208587552188 3.9347925602924132 10.116544153337598 2.5744284330867222 27.869568410096697 3.9906722637824812 14.562653811532071 25.0369360037148 3.0298062886577091 2.6660295177716753 1.3526381445117319 19.894798352615908 1.3038463217671949 7.0649660141207278 2.8012134470045567 1.1326391296461213 2.7484880634583524 7.5878830341389394 13.52987001929432 2.227636265102773 5.733507406897842 1.1804726626723994 9.4156626975163782 19.484061512164775 6.2127861588960513 1.2766550076194088 3.3275462116580456 4.6647373766172677 0.79788719536736685 +leaf_count=23876986 30967 35432 280829 1274 3285 835 9037 1293 4721 8116 982 865 438 6453 423 2289 907 368 891 2462 4388 721 1855 381 3053 6322 2016 414 1077 1514 259 +internal_value=0 -3.69191e-05 -6.72649e-05 0.00647378 -0.0618349 0.0114699 0.00834528 0.00641142 -0.0439031 -0.0779735 -0.0063836 0.0147504 0.0377236 0.0645608 0.0681129 0.0485985 -0.0367985 0.0800664 -0.000155414 -0.0327627 -0.0158625 0.0761587 -0.0423026 -0.0739797 0.0600448 -0.110176 0.0948632 -0.137507 -0.0441445 0.0411475 0.127657 +internal_weight=0 74820.1 74710.9 993.434 26.4919 16.3754 966.942 936.636 70.5335 55.9709 30.9339 27.9041 25.2381 21.2474 30.3061 13.801 6.73601 29.0022 73717.4 78.1872 70.5993 15.7575 54.8418 41.8798 12.962 34.4865 7.39326 28.753 9.26894 5.94139 10.2135 +internal_count=24290853 24259886 24224454 322115 8590 5305 313525 303697 22868 18147 10031 9049 8184 6891 9828 4470 2181 9405 23902339 25353 22891 5109 17782 13579 4203 11182 2397 9327 3005 1928 3312 +is_linear=0 +shrinkage=0.1 + + +Tree=40 +num_leaves=32 +num_cat=0 +split_feature=1 20 12 7 20 8 20 12 18 6 18 18 4 12 0 1 19 0 16 16 16 12 15 15 7 6 16 7 17 0 4 +split_gain=7.10248 5.3825 7.85715 4.74227 4.96098 4.24959 4.18712 6.59465 4.27444 8.41981 6.86868 5.18037 8.26474 4.30822 4.14679 4.01225 11.4618 11.0064 6.33358 7.03226 18.9711 6.40068 5.8991 10.5218 9.07364 5.28045 4.86357 16.2382 10.6038 14.1052 9.93428 +threshold=65745.500000000015 9852.0000000000018 1.5000000000000002 2.5000000000000004 9142.0000000000018 75743.500000000015 50132.000000000007 19.500000000000004 13860.500000000002 1.5000000000000002 10307.000000000002 9948.0000000000018 16.500000000000004 10.500000000000002 5053.5000000000009 16619.500000000004 2.5000000000000004 16446.000000000004 5.5000000000000009 35.500000000000007 559.00000000000011 51.500000000000007 618.00000000000011 588.50000000000011 3.5000000000000004 5.5000000000000009 1.5000000000000002 29.500000000000004 17.500000000000004 779.50000000000011 173.50000000000003 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=5 3 -3 4 -2 6 15 8 9 10 11 12 13 -8 -5 17 22 -1 -18 21 -21 -20 23 26 -24 -22 27 -17 29 -29 -30 +right_child=1 2 -4 14 -6 -7 7 -9 -10 -11 -12 -13 -14 -15 -16 16 18 -19 19 20 25 -23 24 -25 -26 -27 -28 28 30 -31 -32 +leaf_value=-0.0001678032972743191 -0.12485388161407063 -0.1564286664760014 0.063734328016267119 -0.015637476092814517 0.077886022573973104 0.042650647686122242 0.026673379377303619 0.052092596648063164 0.092188821664829668 -0.14006726126161737 -0.17209487345487443 0.1069203535132623 -0.17654381310007775 -0.13437179256516679 0.048426730652915959 -0.19659774874304489 -0.19093041567940292 -0.19434994521436696 -0.19942388794424817 -0.19677835655837639 -0.21276873058632029 0.14652935188535715 -0.18555619881104812 -0.20174012927258614 0.13858378562095386 0.15128067080056826 0.0054721688616248664 0.16771362636129228 -0.081753614819496584 -0.1923389261634128 0.017544137777312924 +leaf_weight=73269.513690103733 3.6079441586043677 3.7032704690354867 2.8828329818788916 11.746973441564476 1.8136693690903483 23.315791549161077 22.035570201900565 39.290776267705951 4.1039628945291033 4.7321170486975452 2.1127679766505016 5.0534967372077508 2.5012430665083221 1.7965571459499177 72.227703950702562 4.8482567374594501 0.85421771020628767 2.9190796671900889 0.5583364069461848 2.8101069281110522 0.43245413817930956 12.686620043939909 1.0016818225849409 2.4916784493252626 6.265168622136116 5.0638527870178223 1315.0878805053944 1.3386160871013988 11.900536736182401 5.8126965491101146 65.690355480415747 +leaf_count=23758840 1168 1202 935 3814 588 7561 7145 12741 1330 1533 685 1639 811 583 23416 1575 276 945 180 910 140 4115 323 808 2035 1646 426431 437 3859 1887 21295 +internal_value=0 0.0271851 -0.0600601 0.0336127 -0.0570322 -3.48779e-05 -4.8185e-05 0.0225881 -0.00479436 -0.015205 0.00243294 0.0141811 -0.00361604 0.0145332 0.039465 -7.29171e-05 0.00516035 -0.000175539 0.076124 0.086709 0.0145769 0.131946 0.00403624 0.00357215 0.0939035 0.122637 0.00393634 -0.0186079 -0.00842479 -0.124943 0.0023143 +internal_weight=0 95.9824 6.5861 89.3963 5.42161 74814.2 74790.9 81.6265 42.3357 38.2318 33.4996 31.3869 26.3334 23.8321 83.9747 74709.3 1436.84 73272.4 22.4056 21.5514 8.30641 13.245 1414.44 1407.17 7.26685 5.49631 1404.68 89.5905 84.7422 7.15131 77.5909 +internal_count=24290853 31123 2137 28986 1756 24259730 24252169 26467 13726 12396 10863 10178 8539 7728 27230 24225702 465917 23759785 7267 6991 2696 4295 458650 456292 2358 1786 455484 29053 27478 2324 25154 +is_linear=0 +shrinkage=0.1 + + +Tree=41 +num_leaves=32 +num_cat=0 +split_feature=3 0 0 21 4 4 17 17 20 20 14 10 5 19 14 14 21 10 17 17 18 0 8 0 18 1 4 18 17 17 0 +split_gain=7.34192 7.97804 13.8454 7.12078 5.68959 10.0653 5.47755 7.4139 5.26822 10.6556 17.2014 10.6861 7.43961 7.33327 6.17924 6.13859 10.5386 5.48836 11.3848 18.4375 13.3517 11.3864 11.6928 10.4284 9.12507 9.10309 9.02576 8.84161 7.84773 7.74402 8.73793 +threshold=20112.500000000004 13221.000000000002 17035.000000000004 54465.500000000007 21932.500000000004 31143.500000000004 781.00000000000011 529.00000000000011 199.50000000000003 210.50000000000003 92.500000000000014 13.500000000000002 25286.500000000004 272.00000000000006 244.50000000000003 103.50000000000001 1.0000000180025095e-35 1.0000000180025095e-35 4.5000000000000009 12.500000000000002 14.500000000000002 11951.500000000002 10.500000000000002 11807.500000000002 16.500000000000004 25401.500000000004 2435.5000000000005 278.50000000000006 525.50000000000011 324.00000000000006 11951.500000000002 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=3 6 -3 4 -1 -6 7 8 13 14 12 15 -11 17 -10 -12 -17 18 -2 -20 25 23 -23 24 -22 -21 -25 -26 29 30 -19 +right_child=1 2 -4 -5 5 -7 -8 -9 9 10 11 -13 -14 -15 -16 16 -18 28 19 20 21 22 -24 26 27 -27 -28 -29 -30 -31 -32 +leaf_value=-0.00017644005428681469 0.13908930133481806 -0.18932290869715857 0.097042328110174561 0.021470078382745797 0.14620075806975594 -0.22071993443299173 0.13699175049383569 -0.14889662514577981 0.14594876538453436 0.046818408220017503 0.15183584216015611 -0.17977319587610663 -0.19401577682211202 -0.19190343904631904 -0.19829038816411049 0.062253043653774591 -0.19553032414734917 0.003015986667121457 -0.15782573676940614 -0.19642585129950835 -0.18229721228771945 0.14565062625773148 -0.18955919064854609 -0.19383213225134602 0.014248363768912612 0.10459117179128399 0.10135045885643626 -0.18759521192041695 0.16148433109363913 -0.12304371045259946 -0.18815862234943831 +leaf_weight=73594.942321767681 7.4754447254817924 5.0792251676903097 2.5290308357798494 152.39644403557759 5.3823732174932948 0.86822485551238049 3.3136538776452644 2.9948592700675354 10.304686069022861 1.7442486286163319 6.3762492393143537 1.8855495898751531 4.8470087074674666 1.8453702856786538 0.54924568103160787 17.071560696000237 1.7482933360151935 673.37572993934737 6.1114528488833448 1.0525036155595469 2.409242143854498 10.16295817052014 1.1593071478418995 4.3893592454260206 347.48454426700482 22.087572819553316 1.3558315567206589 2.1838484457111909 3.0770281576551488 4.9622781696525626 2.3993459587218231 +leaf_count=23866468 2422 1648 818 49423 1746 282 1074 972 3341 566 2067 611 1572 599 178 5537 569 218372 1981 341 784 3298 374 1424 112688 7165 439 708 998 1610 778 +internal_value=0 0.00792868 -0.0941334 -0.000123626 -0.000168337 0.0952345 0.00860842 0.00823494 0.00864916 0.0423654 0.0145919 0.0498524 -0.130284 0.00727378 0.128529 0.0670364 0.0383059 0.00761108 0.0168229 0.0145287 0.0172138 0.0125948 0.111328 0.00947068 0.0116514 0.0908997 -0.124171 0.0129878 0.00214349 0.00142325 0.00233722 +internal_weight=0 1149.98 7.60826 73753.6 73601.2 6.2506 1142.37 1139.05 1136.06 44.5268 33.6729 27.0817 6.59126 1091.53 10.8539 25.1961 18.8199 1089.69 405.872 398.397 392.285 369.145 11.3223 357.823 352.078 23.1401 5.74519 349.668 683.814 680.737 675.775 +internal_count=24290853 372934 2466 23917919 23868496 2028 370468 369394 368422 14441 10922 8784 2138 353981 3519 8173 6106 353382 131624 129202 127221 119715 3672 116043 114180 7506 1863 113396 221758 220760 219150 +is_linear=0 +shrinkage=0.1 + + +Tree=42 +num_leaves=32 +num_cat=0 +split_feature=3 21 18 14 3 15 16 1 3 20 7 3 14 15 16 7 1 12 14 7 9 18 13 3 14 6 12 3 8 13 6 +split_gain=6.96875 5.0162 4.73316 4.0622 3.6341 11.6473 8.68705 11.6291 7.3487 5.4799 7.8783 4.07515 5.33679 5.23734 8.36453 5.55323 4.93945 4.37999 4.04832 3.78251 3.72106 3.64315 5.70579 4.73969 4.3389 9.25532 11.2504 6.05873 5.02656 3.99668 3.88852 +threshold=54213.000000000007 54465.500000000007 1.5000000000000002 262.50000000000006 1587.5000000000002 93.500000000000014 92.500000000000014 84498.000000000015 3724.0000000000005 9733.0000000000018 4.5000000000000009 1494.5000000000002 1.5000000000000002 152.50000000000003 67.500000000000014 1.5000000000000002 7848.0000000000009 383.50000000000006 5.5000000000000009 5.5000000000000009 1.0000000180025095e-35 13860.500000000002 143.50000000000003 338.50000000000006 69.500000000000014 1.0000000180025095e-35 78.500000000000014 132.50000000000003 115.50000000000001 252.50000000000003 6.5000000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 -1 -2 4 11 -6 8 -8 -7 10 -9 12 -3 14 16 -15 17 21 -18 -17 -10 22 24 -24 25 28 -27 -26 -14 -25 -28 +right_child=2 3 -4 -5 5 6 7 9 20 -11 -12 -13 13 15 -16 19 18 -19 -20 -21 -22 -23 23 29 27 26 30 -29 -30 -31 -32 +leaf_value=-7.9978256895532705e-05 -0.16793311681465817 -0.15637992416763855 0.023463768512853159 -0.14581992907079996 -0.092569768901233085 -0.18613449721572203 -0.14518967980288497 -0.18326604323069717 0.098337124875715262 -0.097284228967632752 0.091643596255947049 0.17724305387310818 -0.19075659567638517 -0.17638085929729347 -0.17301980122516319 0.11128535961346697 -0.16840976791598472 0.11062268978826335 0.065386044090096879 -0.16375342465625756 -0.15237469785390367 0.093968383940377653 -0.17226736093031647 -0.15186846572983681 -0.16146847672310727 -0.16522335748306696 -0.18544273004813092 0.067229557454059272 0.04349904391190712 0.081111737824087579 0.1219762218652605 +leaf_weight=74602.288884672773 1.3036310597526597 1.5563420980470244 145.52418961741205 1.499287822342011 11.18370438396232 1.0274582015117655 5.5459525311598581 1.1748845215770405 16.37357764213812 2.7835656061070031 9.2474943569395673 1.8473461782559741 0.97636761574540543 0.76332593988627095 2.1675256283488116 13.10749999794643 0.76048890617676335 3.9003341430798164 28.362818227615207 0.51985661312937814 0.61420105234719713 3.8759290403686455 3.5233990065753451 1.1589206290664154 1.2604220681823779 6.8200417678453942 0.49594888300634954 14.310632319829891 14.812361304182557 2.0191818755120039 2.4151087161153555 +leaf_count=24193260 426 507 47191 486 3630 334 1798 380 5309 902 2999 599 317 247 701 4255 245 1265 9197 169 199 1256 1142 377 408 2212 161 4639 4803 655 784 +internal_value=0 -4.27482e-05 0.0217644 0.0179805 0.0195898 -0.00320811 0.0239736 -0.0236699 0.0735653 0.0273634 0.0606538 0.0300354 0.0273902 0.030215 0.0209576 0.086091 0.025922 0.00843873 0.059281 0.100793 0.0892725 0.000725078 -0.00683689 -0.0923958 0.0071169 -0.0182658 -0.0949753 0.0487173 0.0290128 -0.00384637 0.0696021 +internal_weight=0 74756.4 146.828 154.104 152.605 47.9508 36.7671 18.7519 18.0152 13.2059 10.4224 104.654 102.807 101.25 86.8595 14.3907 84.692 55.5686 29.1233 13.6274 16.9878 51.6683 47.7924 6.7015 41.0909 25.5198 9.7311 15.5711 15.7887 3.1781 2.91106 +internal_count=24290853 24243236 47617 49976 49490 15551 11921 6079 5842 4281 3379 33939 33340 32833 28162 4671 27461 18019 9442 4424 5508 16754 15498 2174 13324 8277 3157 5047 5120 1032 945 +is_linear=0 +shrinkage=0.1 + + +Tree=43 +num_leaves=32 +num_cat=0 +split_feature=20 4 9 20 11 16 17 14 0 18 0 20 7 18 15 14 18 20 18 18 20 14 20 8 14 20 14 11 16 7 6 +split_gain=5.51973 4.02166 9.73312 10.0602 11.3577 10.9231 19.6831 10.0805 8.15414 26.067 8.24968 7.24862 7.23361 11.1048 6.93288 6.51974 12.5601 19.6764 14.2596 12.717 8.23785 8.01311 9.8271 19.4029 12.6327 9.81928 7.87276 7.68649 12.7201 13.772 14.1527 +threshold=50132.000000000007 21932.500000000004 27.500000000000004 172.50000000000003 6.5000000000000009 17.500000000000004 2.5000000000000004 3671.5000000000005 1.0000000180025095e-35 23.500000000000004 1.5000000000000002 152.50000000000003 440.50000000000006 2.5000000000000004 22.500000000000004 44.500000000000007 6.5000000000000009 4.5000000000000009 4.5000000000000009 2.5000000000000004 3.5000000000000004 413.50000000000006 39.500000000000007 3.5000000000000004 184.50000000000003 36.500000000000007 806.50000000000011 5.5000000000000009 1.5000000000000002 642.50000000000011 162.50000000000003 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 -1 -3 5 12 6 8 -6 11 -10 -11 15 -5 -14 -15 16 18 20 19 -4 -18 22 23 24 -17 -25 -23 28 29 30 -24 +right_child=-2 2 3 4 7 -7 -8 -9 9 10 -12 -13 13 14 -16 21 17 -19 -20 -21 -22 26 27 25 -26 -27 -28 -29 -30 -31 -32 +leaf_value=-7.5959514180547189e-05 0.026892972233294121 -0.1735957657112743 0.03609139856332904 0.11397498674188825 -0.18989557981761873 0.11061339009384544 -0.18141791871953228 0.14961555784651734 -0.18267609825670963 -0.056031755638231706 0.13393768015708199 -0.16666820568192386 -0.18757593304710313 0.063775113448683779 -0.10890354254737017 0.0043991502939203049 -0.19547073354744671 -0.14266002237887046 0.15181713574675681 -0.19143191326360853 0.186282189306512 -0.20046773688086206 0.1631366442871505 0.073755269730999592 -0.11276628210339482 -0.19152995424365585 -0.013199333227668151 0.083214117335452642 0.11538383337520404 0.025811673940097948 -0.18299309923756046 +leaf_weight=74496.932962447405 76.242572590708733 2.8303024928900404 7.8271760502248089 18.406041487120092 3.3598788149538441 9.3434135887073335 5.8104432423133394 1.1822478398680685 8.8634778207633662 3.8743284360971284 5.575918494258076 2.3040171655593431 2.0787813583156085 30.66282169171609 2.5158412796445182 19.745394519995898 0.69368850160390438 13.868805017438715 6.3322260165587059 3.5802666013478301 3.053213101811707 2.4377071002963957 1.449534217827021 26.735310589108852 17.23430453450419 1.4720797796035174 28.383464846294373 37.383502978889737 12.560866235871798 40.827375254360959 6.3839465295895934 +leaf_count=24160149 24726 915 2540 5973 1090 3031 1886 383 2873 1258 1807 748 674 9941 815 6404 226 4490 2055 1162 995 790 469 8672 5591 477 9205 12123 4074 13241 2070 +internal_value=0 -2.74039e-05 0.0110421 0.0126552 0.0503095 0.00440775 0.000537493 -0.101526 0.00475656 -0.0594857 0.0560557 0.00982179 0.063161 0.0366337 0.0506814 0.01159 -0.0279138 -0.0877263 0.0314807 -0.0353176 0.115606 0.0187667 0.0275689 -0.00255668 -0.0502055 0.0599106 -0.0280108 0.0474846 0.0256673 0.00250873 -0.118944 +internal_weight=0 74823.7 326.776 323.946 58.2056 265.74 256.397 4.54213 250.587 18.3137 9.45025 232.273 53.6635 35.2574 33.1787 229.969 35.3554 17.6157 17.7397 11.4074 3.7469 194.613 163.792 65.1871 36.9797 28.2074 30.8212 98.6052 61.2217 48.6609 7.83348 +internal_count=24290853 24266127 105978 105063 18876 86187 83156 1473 81270 5938 3065 75332 17403 11430 10756 74584 11468 5711 5757 3702 1221 63116 53121 21144 11995 9149 9995 31977 19854 15780 2539 +is_linear=0 +shrinkage=0.1 + + +Tree=44 +num_leaves=32 +num_cat=0 +split_feature=3 17 17 0 0 17 7 16 0 17 12 7 12 17 7 6 14 16 9 12 7 1 9 18 0 5 17 17 18 12 16 +split_gain=7.62129 7.21608 16.1639 13.2957 9.71505 17.1931 23.895 23.9934 14.9734 11.3909 8.55093 8.39346 8.30256 8.25304 16.8535 14.8074 14.6536 16.935 16.0474 15.641 18.4026 9.97342 9.95341 9.81453 9.69598 8.85432 9.16387 8.72608 12.4399 8.35752 10.7908 +threshold=20112.500000000004 30.500000000000004 28.500000000000004 1463.0000000000002 7446.5000000000009 18.500000000000004 52.500000000000007 40.500000000000007 4731.0000000000009 26.500000000000004 93.500000000000014 475.50000000000006 4356.0000000000009 53.500000000000007 571.50000000000011 7.5000000000000009 2.5000000000000004 16.500000000000004 1.5000000000000002 61.500000000000007 260.50000000000006 23653.500000000004 9.5000000000000018 28.500000000000004 5053.5000000000009 687.00000000000011 49.500000000000007 47.500000000000007 34.500000000000007 45.500000000000007 11.500000000000002 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 4 12 5 -2 8 -8 -7 -6 -9 -4 -3 14 16 22 17 18 -5 20 29 -17 24 -21 -16 -25 -27 -19 -29 30 -20 +right_child=1 3 11 13 9 6 7 10 -10 -11 -12 -13 -14 -15 15 21 -18 27 19 23 -22 -23 -24 25 -26 26 -28 28 -30 -31 -32 +leaf_value=-0.00012592355501248718 0.014588487887779254 -0.18686819920704381 0.10951810131843687 -0.19316554020492194 0.083061154847008573 0.1543415934658636 -0.19104936116818028 0.16312990115109668 -0.18515657746488168 -0.19268410464628777 -0.16937496158105572 -0.1815583917501023 0.16344693836144564 0.0082740214726724735 -0.17340034959707018 0.034743101332896806 0.07943751715264058 -0.18776449656823399 -0.082412203307757989 -0.16407605948155315 -0.19677160559339862 -0.19943593551093675 -0.055361605670715942 -0.20112208488623706 0.11710696414914001 0.0065225521190829822 -0.1399710220278497 0.083807246691497833 -0.18855599060820366 0.16121080433890334 0.14515306148796489 +leaf_weight=73751.71197962435 391.01457617446431 5.4833246025373219 23.216026511276144 5.5765565446345127 28.225027454231171 5.9503454205114421 14.449245161493311 3.1422727225581175 1.6619558343663809 1.5820821381639678 1.0259438998764379 1.0348222539760161 0.77176189678721119 541.02248972572852 1.2141083597671301 3.5012355290818951 9.091288081253877 12.249465629574845 4.5329892123118096 6.4352745137875891 2.4923603241331866 3.7843487064819783 4.9269409058615556 2.789860527496784 21.387049125041813 28.21544271952007 5.0316249732859424 3.5192375245969734 3.2034174331929535 8.9256471162661892 3.8564554390031844 +leaf_count=23918122 126805 1777 7529 1808 9156 1930 4691 1015 539 513 332 336 250 175456 389 1137 2949 3976 1470 2087 809 1227 1600 907 6939 9146 1631 1143 1039 2895 1250 +internal_value=0 0.00808048 0.0175843 0.00147411 0.0132711 0.00933095 -0.0690447 -0.130076 0.0802206 0.0684253 0.0812888 0.0970974 -0.143646 0.0028254 -0.019723 0.0398749 -0.0413538 -0.0540012 -0.0306491 -0.0160973 0.0572859 -0.0868964 0.0734261 -0.0503205 0.101501 -0.0300067 -0.0156478 -0.137523 -0.0459768 0.0938553 0.0221949 +internal_weight=0 1149.31 471.302 678.011 447.051 417.244 26.2298 18.6175 7.6123 29.8071 4.16822 24.2508 6.25509 671.756 130.733 34.8137 95.9196 86.8283 67.8562 62.2797 19.8075 7.28558 27.5281 42.4722 22.6012 36.0369 33.2471 18.9721 6.72265 17.3151 8.38944 +internal_count=24290853 372731 152846 219885 144981 135312 8507 6038 2469 9669 1347 7865 2027 217858 42402 11292 31110 28161 22003 20195 6424 2364 8928 13771 7328 11684 10777 6158 2182 5615 2720 +is_linear=0 +shrinkage=0.1 + + +Tree=45 +num_leaves=32 +num_cat=0 +split_feature=3 17 10 2 8 8 2 17 3 17 20 11 10 21 20 20 20 20 0 6 1 0 0 17 17 0 17 13 13 0 4 +split_gain=8.48323 12.0766 16.586 15.3921 10.2919 9.88027 13.8734 9.24477 8.57166 8.21052 8.773 8.13086 8.36711 7.8843 7.80228 13.7674 7.98128 20.1009 7.03311 7.00192 6.78935 7.26253 8.97515 6.97986 37.0632 13.2738 20.5638 17.5054 11.4146 14.431 21.1046 +threshold=20112.500000000004 39.500000000000007 5.5000000000000009 2240.5000000000005 15.500000000000002 3.5000000000000004 2617.0000000000005 40.500000000000007 44346.000000000007 9.5000000000000018 13.500000000000002 1.5000000000000002 14.500000000000002 54465.500000000007 39.500000000000007 49.500000000000007 61.500000000000007 127.50000000000001 1463.0000000000002 889.50000000000011 55527.000000000007 7282.5000000000009 7398.5000000000009 87.500000000000014 84.500000000000014 11764.500000000002 121.50000000000001 3.5000000000000004 3.5000000000000004 6596.5000000000009 177.50000000000003 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=13 2 3 -2 5 11 -7 -3 14 10 -10 12 -4 -1 -8 -16 -17 -18 -9 -19 23 -22 -23 24 28 -25 -27 -28 29 -20 -31 +right_child=1 7 4 -5 -6 6 8 18 9 -11 -12 -13 -14 -15 15 16 17 19 20 -21 21 22 -24 25 -26 26 27 -29 -30 30 -32 +leaf_value=-0.00017894859167008717 -0.17801580804577444 -0.1823210515965604 -0.19582508516902905 0.040783662821989211 -0.19089111778580956 -0.17986022963487799 0.084975535786159892 -0.16436169055428504 0.12055530509503116 -0.19394361188429743 -0.042040372047428831 -0.0053367615035360069 0.0043236149934037281 0.02317464177063842 -0.18644228914322089 0.10819426486890182 -0.17056895146603723 0.070482714175353969 0.086128757739446019 -0.18099502039467219 0.059797549252140494 -0.18803828822673238 0.010392021451947503 -0.019047239252253 0.1131015022197942 -0.18343384966950962 -0.16393887304461743 0.085806378334013014 -0.019598863826501639 0.040490186661159999 -0.1984433482232969 +leaf_weight=73607.527184906765 3.2567430589115238 2.7923831585794678 4.6566783670568821 251.97462281303888 2.7296490743465247 3.0548090907977885 36.611550920177251 2.5916146071394897 4.5583282932348146 2.4459450091235331 12.199536243977489 158.0145833795832 3.7874746643938124 144.84695851412835 2.9737420154269776 10.967480674153192 5.1058769586379666 16.946161308442246 26.082341361325234 1.1845741944853205 37.804420846048743 2.4668582482845514 30.000270142874797 301.79694722447312 30.578573561506346 3.6717608198523513 3.0637935982085756 33.431481685838662 140.9911452322267 12.356328724301422 5.2749300019349894 +leaf_count=23869859 1054 901 1510 81708 885 993 11873 841 1484 793 3950 51241 1229 46974 966 3555 1655 5492 8460 384 12260 800 9729 97873 9917 1192 994 10839 45725 4006 1711 +internal_value=0 0.00850994 0.0197938 0.0379918 0.00228231 0.004291 0.0298311 -0.000769366 0.0367195 -0.0227933 0.00218749 -0.0104459 -0.106052 -0.000133083 0.0522078 0.0199391 0.0378821 0.00469541 3.5194e-05 0.0540524 0.000714143 0.0300052 -0.00468475 -0.0029796 0.0111258 -0.0118596 0.0421449 0.0648402 -0.00575671 0.0388889 -0.0309941 +internal_weight=0 1153.37 520.468 255.231 265.236 262.507 96.048 632.903 92.9932 19.2038 16.7579 166.459 8.44415 73752.4 73.7894 37.1778 34.2041 23.2366 630.11 18.1307 627.519 70.2715 32.4671 557.247 215.283 341.964 40.167 36.4953 184.705 43.7136 17.6313 +internal_count=24290853 374020 168772 82762 86010 85125 31145 205248 30152 6227 5434 53980 2739 23916833 23925 12052 11086 7531 204347 5876 203506 22789 10529 180717 69819 110898 13025 11833 59902 14177 5717 +is_linear=0 +shrinkage=0.1 + + +Tree=46 +num_leaves=32 +num_cat=0 +split_feature=3 18 7 16 2 0 18 10 4 16 19 7 4 4 0 18 18 4 7 7 18 17 18 0 0 17 7 7 14 4 1 +split_gain=9.74208 8.2703 16.9519 15.1125 13.4952 12.0828 11.6506 16.5769 11.7416 10.5821 14.0845 19.2599 16.9916 16.5473 12.6351 12.6209 15.6693 11.4379 16.8412 14.5261 18.9233 20.7638 20.7969 14.1409 23.096 14.6602 14.1919 13.9419 13.8699 10.728 10.3274 +threshold=20112.500000000004 68.500000000000014 1839.0000000000002 148.50000000000003 8312.5000000000018 4607.5000000000009 74.500000000000014 1.0000000180025095e-35 231.50000000000003 4.5000000000000009 179.50000000000003 189.50000000000003 332.50000000000006 121.50000000000001 11764.500000000002 180.50000000000003 84.500000000000014 539.50000000000011 979.00000000000011 712.50000000000011 84.500000000000014 187.50000000000003 136.00000000000003 7357.5000000000009 9372.5000000000018 103.50000000000001 8.5000000000000018 6.5000000000000009 1.0000000180025095e-35 360.50000000000006 40695.500000000007 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 3 6 -2 -4 -6 7 8 30 10 11 12 -8 -13 17 16 -12 19 -19 20 -11 22 23 26 29 -26 -22 -23 -29 -25 -3 +right_child=1 2 4 -5 5 -7 9 -9 -10 14 15 13 -14 -15 -16 -17 -18 18 -20 -21 21 27 -24 24 25 -27 -28 28 -30 -31 -32 +leaf_value=-0.00014169524334139587 0.016585476165719745 0.13163201729419763 -0.092686782752128827 -0.16765425422260299 0.13796039598975843 -0.19195618401732356 -0.18420851351556869 -0.16543006390656967 -0.11693411572446849 -0.19218882220804576 0.12892282119412965 0.065707484650451203 0.039580965395267328 -0.15079661384120646 0.062227361228916402 0.1371994663222813 -0.19340938116939377 0.026697214533880798 -0.12709678201691194 0.048816271457853617 0.17230402799467359 0.14971729199779832 0.14147108663168015 0.090955977148168582 0.20358627179140487 -0.12244655797492453 -0.16830638270556197 -0.055145181685735012 -0.19067845082024648 -0.18285406869415671 -0.20190088768314418 +leaf_weight=73759.101052192127 732.87785876159614 6.3435111732687819 3.6773898923420374 4.4793517459183922 17.430212250677872 1.1856030984781671 4.069032579718626 10.950899013783781 4.87182205717545 7.618448613677173 2.6385255465283972 4.1395869920961585 20.413925341243157 23.978902155911783 34.449140241515124 7.6165595231577754 3.5202932426473126 125.48210646859661 7.5485693141235961 22.141163004678674 1.4162714183330525 2.2686857951339325 9.761608185246585 21.372502476209778 1.5413247689139087 13.109544554958118 8.9769354672171158 19.805263879999984 12.202903910307212 1.5336145390756417 1.0875021624378858 +leaf_count=23921610 237695 2059 1192 1449 5651 386 1321 3552 1580 2470 855 1344 6621 7772 11174 2471 1142 40697 2448 7180 463 739 3166 6931 498 4252 2910 6417 3959 497 352 +internal_value=0 0.00917976 -0.00237543 0.0154662 0.0823681 0.116949 -0.007362 -0.0759383 0.00371941 -0.00287765 -0.0388869 -0.0624599 0.00238747 -0.118923 0.00538633 0.0511272 -0.0553177 -0.00229925 0.0179705 -0.0244474 -0.0407328 -0.0281893 0.00842535 -0.0186598 0.00990769 -0.0881467 -0.121892 -0.0898371 -0.106816 0.0726238 0.0828207 +internal_weight=0 1138.51 401.152 737.357 22.2932 18.6158 378.859 23.2537 12.3028 355.605 66.3768 52.6014 24.483 28.1185 289.228 13.7754 6.15882 254.779 133.031 121.748 99.6071 91.9887 57.7118 47.9502 37.557 14.6509 10.3932 34.2769 32.0082 22.9061 7.43101 +internal_count=24290853 369243 130099 239144 7229 6037 122870 7543 3991 115327 21526 17058 7942 9116 93801 4468 1997 82627 43145 39482 32302 29832 18717 15551 12178 4750 3373 11115 10376 7428 2411 +is_linear=0 +shrinkage=0.1 + + +Tree=47 +num_leaves=32 +num_cat=0 +split_feature=3 7 18 18 21 7 13 14 4 18 18 7 2 19 18 19 14 10 7 13 9 18 4 14 19 18 4 18 19 19 19 +split_gain=12.4806 7.24471 10.6949 10.2784 6.81967 5.95406 11.8553 29.5969 10.9455 8.66944 9.6804 12.1731 9.6983 7.48 7.16136 6.65547 8.38792 5.95351 7.78162 7.04753 6.33919 9.86836 7.65085 7.38139 6.94534 7.74993 11.6973 10.1173 7.01949 6.17757 6.86663 +threshold=20112.500000000004 33.500000000000007 17.500000000000004 200.50000000000003 54465.500000000007 77.500000000000014 16.500000000000004 1.0000000180025095e-35 660.00000000000011 19.500000000000004 65.500000000000014 73.500000000000014 33271.000000000007 1.0000000180025095e-35 103.50000000000001 1.0000000180025095e-35 2.5000000000000004 1.0000000180025095e-35 90.500000000000014 2.5000000000000004 11.500000000000002 213.00000000000003 891.50000000000011 1.0000000180025095e-35 3.5000000000000004 10.500000000000002 653.50000000000011 11.500000000000002 1.5000000000000002 219.50000000000003 211.00000000000003 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=4 2 -2 -4 -1 6 8 -8 9 -3 11 -11 -12 -14 -15 16 -13 19 -19 -7 21 24 -23 -24 25 26 -21 -27 -29 30 -20 +right_child=1 5 3 -5 -6 17 7 -9 -10 10 12 15 13 14 -16 -17 -18 18 29 20 -22 22 23 -25 -26 27 -28 28 -30 -31 -32 +leaf_value=-0.00020236416899202991 -0.18209215011441648 0.11679394273905919 0.053640572649963227 -0.17895473051085889 0.021475560264543749 -0.15475047673759937 0.048609829326698883 -0.18588070292710573 -0.15061601527558333 -0.18166495791568041 -0.12263261999842529 -0.19392544485863719 0.065184355785935191 0.089406917540472017 -0.19245658403616706 0.18254775273294765 0.14252107226362093 -0.13134017158693009 0.0038770233948531708 -0.19298176298036687 0.11173586685997626 -0.17261849085055506 -0.18001246929591963 0.10981078078726279 0.14193905413736857 0.12890832819613351 0.020585291808982092 0.024252486211302413 -0.13211637075902227 0.035987064895646327 -0.17692307932746462 +leaf_weight=73618.75328133702 2.0921018125955007 3.7634217157028607 69.56908472336363 1.9532081862562325 145.40689477490378 2.2203755795489988 43.370503473095596 6.1453416909789658 8.8341517725493741 8.5822733712848258 5.299151420826095 1.9639600776135915 14.110668493784035 1.4078276772052043 2.5058240853250027 1.6168256364762781 1.1899954378604887 4.1275960090570143 596.41534401971148 4.1100147850811464 8.0224859195295704 4.5623502047528728 1.2958959436509756 2.7300334663596004 4.9792213018517932 9.255247295368461 6.8204421834088862 242.21829649421852 2.9052475763019165 63.742744827119168 2.1080394152086219 +leaf_count=23877854 680 1225 22561 633 47163 722 14068 1993 2867 2782 1717 637 4575 456 812 525 386 1340 193447 1334 2603 1480 420 884 1614 3003 2212 78558 943 20675 684 +internal_value=0 0.0104396 0.0407697 0.0472886 -0.000159632 0.00832189 -0.0150496 0.0195075 -0.0497762 -0.0277477 -0.0425793 -0.110478 -0.00370643 0.0312579 -0.091064 0.0175832 -0.0669833 0.0107383 0.00553899 0.0227221 0.0240956 0.0215744 -0.0839559 0.0165203 0.0249276 0.0227315 -0.0597191 0.0262744 0.0223992 0.0063921 0.00324023 +internal_weight=0 1127.92 73.6144 71.5223 73764.2 1054.3 98.7899 49.5158 49.2741 40.4399 36.6765 13.3531 23.3235 18.0243 3.91365 4.77078 3.15396 955.513 666.394 289.12 286.899 278.877 8.58828 4.02593 270.288 265.309 10.9305 254.379 245.124 662.266 598.523 +internal_count=24290853 365836 23874 23194 23925017 341962 32043 16061 15982 13115 11890 4330 7560 5843 1268 1548 1023 309919 216146 93773 93051 90448 2784 1304 87664 86050 3546 82504 79501 214806 194131 +is_linear=0 +shrinkage=0.1 + + +Tree=48 +num_leaves=32 +num_cat=0 +split_feature=3 18 8 18 15 9 10 16 17 10 3 8 13 16 13 18 18 5 9 16 5 16 16 15 18 8 17 8 16 16 16 +split_gain=13.0834 4.7731 12.4649 8.57563 13.8599 10.8118 16.7536 9.99709 10.8107 8.68276 8.46292 8.01283 20.9055 8.26609 7.85229 10.1777 7.43176 17.3242 27.722 12.9887 12.2539 11.8427 21.8094 11.4664 11.2766 9.92036 13.2791 10.9778 7.62141 7.39877 14.5167 +threshold=20112.500000000004 130.50000000000003 20.500000000000004 142.50000000000003 2.5000000000000004 348.50000000000006 2.5000000000000004 6.5000000000000009 781.00000000000011 16.500000000000004 23443.500000000004 6.5000000000000009 11.500000000000002 7.5000000000000009 3.5000000000000004 148.50000000000003 95.500000000000014 482.50000000000006 6.5000000000000009 148.50000000000003 633.50000000000011 143.50000000000003 112.50000000000001 9.5000000000000018 123.50000000000001 4.5000000000000009 164.50000000000003 7.5000000000000009 26.500000000000004 97.500000000000014 69.500000000000014 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 16 3 4 11 6 7 8 10 -7 -5 -3 -13 -14 15 -9 19 23 20 21 -19 22 29 -18 -20 26 -22 -27 -26 30 -2 +right_child=1 2 -4 5 -6 9 -8 14 -10 -11 -12 12 13 -15 -16 -17 17 18 24 -21 25 -23 -24 -25 28 27 -28 -29 -30 -31 -32 +leaf_value=-0.00016306017228185285 0.012243310126646532 0.12312313996655631 -0.18291807469276053 0.065193115553733291 -0.17314147533668112 0.11226437439084982 -0.083119853955914563 0.12980950329881985 0.15799420929061975 -0.17857730642033376 -0.1430127452197468 -0.18799136338905564 -0.18322478618440705 0.12255224268735043 0.043918210522332482 -0.17373015581212714 -0.14194459823071867 0.14632579123600645 -0.17421078776717078 -0.15185217418475147 0.10168564897732796 0.12405439965847771 -0.16763652235721216 0.15231277648240385 -0.17858805845934345 -0.18568820197711733 -0.18206270680245398 0.0015559922250519579 0.098630508236984382 0.094089458435819659 -0.10740670105549417 +leaf_weight=73762.598078943571 825.76658282637072 22.226528570754454 2.8060098046262274 2.5197213806677601 2.195254008867777 15.390865625231529 17.71047968059429 1.5795917748473574 1.9687256759498257 1.099815523950382 8.6684068440081301 3.4320025865454218 0.97099792468361457 9.8761721564223972 76.650202965087374 3.6737485425546765 14.346500346204264 8.4144269339740259 10.612924346467478 4.8878897298709481 16.002447878359821 9.267801901092751 6.8254673366900525 1.4589216616004694 1.4370761300669972 3.6239511923049603 1.8388360138051201 23.0238477945677 3.2001191668678075 10.796459717908872 10.266154598211868 +leaf_count=23926734 267857 7209 911 816 710 4994 5744 513 639 357 2816 1114 315 3203 24862 1190 4649 2725 3446 1582 5191 3006 2214 474 466 1175 597 7472 1039 3502 3331 +internal_value=0 0.0107147 0.0261091 0.0296011 0.0708964 0.0172373 0.0061778 0.0228146 -0.0580972 0.0928672 -0.0961219 0.0855715 0.0271199 0.0951802 0.0358122 -0.0824608 0.00795262 -0.0204566 0.00141859 0.0107012 0.0356608 0.0116219 0.0104013 -0.114783 -0.11737 0.0147303 0.0724407 -0.0239082 0.0127199 0.0118363 0.0107741 +internal_weight=0 1122.54 170.769 167.963 38.701 129.262 112.771 95.0604 13.1569 16.4907 11.1881 36.5057 14.2792 10.8472 81.9035 5.25334 951.769 83.9591 68.1536 867.81 52.9035 862.922 853.655 15.8054 15.2501 44.4891 17.8413 26.6478 4.6372 846.829 836.033 +internal_count=24290853 364119 55393 54482 12551 41931 36580 30836 4271 5351 3632 11841 4632 3518 26565 1703 308726 27234 22111 281492 17160 279910 276904 5123 4951 14435 5788 8647 1505 274690 271188 +is_linear=0 +shrinkage=0.1 + + +Tree=49 +num_leaves=32 +num_cat=0 +split_feature=3 21 16 5 2 8 4 18 21 2 15 3 2 2 5 8 8 17 5 2 8 14 17 18 8 17 18 1 1 17 2 +split_gain=11.8778 5.90357 5.12122 10.6591 9.22143 7.45414 17.178 12.6131 9.97081 9.2988 8.34551 8.11039 11.3575 7.2953 7.02757 7.01996 6.51266 12.7834 7.31325 6.71837 10.1097 8.40077 7.68805 8.30326 8.37745 6.85581 7.3922 6.69137 9.11725 6.38297 9.03052 +threshold=20112.500000000004 54465.500000000007 2.5000000000000004 233.50000000000003 3174.0000000000005 3.5000000000000004 899.00000000000011 45.500000000000007 1.0000000180025095e-35 2617.0000000000005 106.50000000000001 25426.500000000004 11670.000000000002 5459.5000000000009 183.50000000000003 14.500000000000002 6.5000000000000009 120.50000000000001 697.00000000000011 3174.0000000000005 10.500000000000002 207.50000000000003 39.500000000000007 35.500000000000007 9.5000000000000018 110.50000000000001 67.500000000000014 46669.000000000007 50065.500000000007 1.0000000180025095e-35 9226.0000000000018 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 -1 3 14 -4 6 -5 8 -8 -7 -10 12 -9 -6 15 -2 18 19 -11 -18 21 22 23 24 -21 26 -22 -24 -29 30 -13 +right_child=2 -3 4 5 13 9 7 11 10 16 -12 29 -14 -15 -16 -17 17 -19 -20 20 25 -23 27 -25 -26 -27 -28 28 -30 -31 -32 +leaf_value=-0.00019527237599670558 0.15293118596375938 0.019942967265196069 0.12174269473727575 -0.17873226486074531 -0.17284617747179307 -0.15789549840444361 -0.01168504697713056 -0.18161993486091016 -0.1737427899046447 -0.17044147238987384 0.1602842630263753 0.11239046375187507 0.12417272448406802 0.014878885904612171 -0.16418056761714572 -0.18048932511348981 0.14412540384486905 -0.11738310122727671 0.046351466700522086 0.0040726319331584838 0.027135287783877762 -0.18407722905436824 -0.18190717266591891 0.09883205721430055 -0.18921685589923321 0.1489120221249447 -0.17457229002654936 0.12652017509369159 -0.1702111684964942 0.01397477780713715 -0.17738372745554198 +leaf_weight=73611.939624483755 1.6951009575277551 145.85836581504554 8.0839167303638515 5.8064696940709828 2.0764689782808992 2.9827921333489931 160.11226687769522 3.3924655823502681 6.1828645770438007 1.5872129732742895 0.85092031955718983 19.922710595885295 1.8919492354616521 678.74310371214233 6.2756290043471381 1.006359933177009 3.8366940575069739 7.9059513300308017 79.224381632229779 12.615555141310329 72.881758107512724 3.4208171577774911 4.5926383456680906 7.3749290902633211 2.7270109269302329 4.5216085431165984 1.8633440535049874 2.2998477406799793 1.8834706606576217 16.707176767347846 1.1368247417267401 +leaf_count=23879055 550 47314 2623 1883 677 973 51940 1099 2005 513 276 6461 613 220173 2035 327 1243 2569 25697 4089 23640 1111 1491 2391 884 1467 604 746 612 5423 369 +internal_value=0 -0.000155448 0.0102042 0.00170525 0.015567 0.00397915 -0.00905922 -0.00437219 -0.0168042 0.0174083 -0.133333 0.0438952 -0.0721387 0.0143063 -0.10613 0.0287239 0.0199376 0.00571905 0.0420935 0.0139656 0.009592 -0.0352428 -0.0190764 0.0116325 -0.0302829 0.0293402 0.0221069 -0.0985698 -0.00707826 0.0601309 0.096748 +internal_weight=0 73757.8 1123.6 434.699 688.903 425.722 216.004 210.197 167.146 209.718 7.03378 43.0511 5.28441 680.82 8.97709 2.70146 206.735 125.924 80.8116 118.018 114.181 34.9143 31.4935 22.7175 15.3426 79.2667 74.7451 8.77596 4.18332 37.7667 21.0595 +internal_count=24290853 23926369 364484 141011 223473 138099 70069 68186 54221 68030 2281 13965 1712 220850 2912 877 67057 40847 26210 38278 37035 11324 10213 7364 4973 25711 24244 2849 1358 12253 6830 +is_linear=0 +shrinkage=0.1 + + +Tree=50 +num_leaves=32 +num_cat=0 +split_feature=3 21 7 16 16 0 16 16 0 12 0 18 16 3 0 9 16 0 16 16 13 7 0 6 0 13 10 16 18 3 16 +split_gain=11.3538 4.87065 4.24111 10.376 7.35592 12.5146 8.84208 15.5911 13.0034 10.6297 10.1855 8.48261 8.39195 7.68471 7.34893 7.29609 7.23651 6.25491 8.76287 17.3661 15.1168 11.4327 10.3981 9.07669 6.63953 5.77281 5.76839 8.00459 16.3417 11.3261 10.2786 +threshold=20112.500000000004 54465.500000000007 33.500000000000007 1.0000000180025095e-35 148.50000000000003 4486.5000000000009 95.500000000000014 69.500000000000014 5892.5000000000009 322.00000000000006 8946.5000000000018 39.500000000000007 86.500000000000014 22301.500000000004 3356.5000000000005 10.500000000000002 1047.5000000000002 4986.5000000000009 7.5000000000000009 12.500000000000002 4.5000000000000009 12.500000000000002 6189.0000000000009 1.5000000000000002 11807.500000000002 28.500000000000004 1.0000000180025095e-35 36.500000000000007 27.500000000000004 22301.500000000004 62.500000000000007 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 -1 3 -2 6 -6 7 26 -8 11 -9 -10 -12 -13 -5 -15 -7 -16 20 -20 -19 -22 -21 24 -24 -23 -4 -28 -29 -30 -31 +right_child=2 -3 4 14 5 16 8 10 9 -11 12 13 -14 15 17 -17 -18 18 19 22 21 25 23 -25 -26 -27 27 28 29 30 -32 +leaf_value=-0.00018820910045798346 -0.170643003112284 0.018055841998194863 0.019928998813179263 -0.17325115994337181 0.070699720383111606 -0.14243600289671526 -0.16002956002904015 -0.16908952809475844 -0.16831634235277149 -0.17184007819005043 -0.16267909614353646 -0.19701809073266555 0.11210354613874665 0.099506507006483294 0.10362476232608049 -0.1801949860713018 0.091067257342014124 -0.17603238729935397 -0.18689525566852638 -0.17384145459534184 -0.18606426281935246 0.12186383909542352 0.06680298750390018 -0.16659075401216372 -0.19025403674653366 -0.19433358534479986 0.0060197365336765105 0.063893218593153292 0.069331303995359292 -0.16919708036838504 -0.0087064891123898088 +leaf_weight=73605.088732990902 2.4417943313019341 146.62505774665624 316.68086323424359 1.6010014152852807 5.3704253531177555 10.49911257525673 2.6987827869306775 8.202865921193732 1.3624641085043538 1.7934321586508293 1.7300105419708405 0.93950840039178629 3.1084285169490613 37.14761470007943 13.975883455481378 0.9566275132674481 1.5192767867119972 2.2233956770505783 4.4867062050034283 2.4492852964904186 1.3500488753197739 21.925145567976873 25.129010461154401 1.9598670020059206 1.0466478206217278 0.59300806513056059 619.0027359668893 9.7067482249112782 3.6963234962313463 13.523019649845084 5.6611592758563338 +leaf_count=23879033 796 47567 102737 522 1743 3408 878 2662 439 585 562 307 1007 12048 4535 310 494 721 1452 791 438 7105 8158 637 340 192 200815 3147 1201 4387 1836 +internal_value=0 -0.000151938 0.00998025 0.0322926 0.00828732 -0.056209 0.00938019 0.00739469 0.0527759 0.0663854 -0.101216 0.0769591 0.0138535 0.0855182 0.0387499 0.0924845 -0.112918 0.043267 0.0294752 -0.00317246 0.0733591 0.0965906 0.0237792 0.0409826 0.0565244 0.113537 0.00885753 0.00347666 -0.0448297 -0.090954 -0.121837 +internal_weight=0 73751.7 1122.78 79.1818 1043.6 17.3888 1026.21 981.312 44.8984 42.1996 13.0413 40.4062 4.83844 39.0438 76.74 38.1042 12.0184 75.139 61.1631 35.0715 26.0916 23.8682 30.5848 28.1355 26.1757 22.5182 968.271 651.59 32.5873 22.8805 19.1842 +internal_count=24290853 23926600 364253 25687 338566 5645 332921 318354 14567 13689 4231 13104 1569 12665 24891 12358 3902 24369 19834 11378 8456 7735 9926 9135 8498 7297 314123 211386 10571 7424 6223 +is_linear=0 +shrinkage=0.1 + + +Tree=51 +num_leaves=32 +num_cat=0 +split_feature=3 7 16 9 16 9 5 5 13 7 4 4 4 19 19 11 9 19 3 6 19 16 19 16 7 2 19 16 19 19 19 +split_gain=11.8825 7.11487 5.0795 4.76613 7.30297 13.8576 8.96736 6.56749 5.21797 5.19494 4.51008 9.41181 4.80373 30.0157 15.3396 8.04368 7.8922 7.63379 10.1959 6.0414 18.3298 16.0861 14.9607 8.42087 7.00543 9.94915 11.1396 9.63745 9.18663 5.83825 7.22306 +threshold=20112.500000000004 33.500000000000007 1.0000000180025095e-35 3.5000000000000004 21.500000000000004 1.5000000000000002 967.50000000000011 1127.0000000000002 12.500000000000002 12.500000000000002 31143.500000000004 21932.500000000004 13147.000000000002 20.500000000000004 33.500000000000007 6.5000000000000009 109.50000000000001 100.50000000000001 15122.500000000002 300.00000000000006 56.500000000000007 5.5000000000000009 45.500000000000007 2.5000000000000004 527.00000000000011 6326.0000000000009 312.50000000000006 1.0000000180025095e-35 106.50000000000001 77.500000000000014 41.500000000000007 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=10 2 -2 4 7 -6 -7 9 -9 -4 11 12 -1 -14 16 19 -15 -17 -19 23 22 -22 -21 24 25 26 -16 29 -26 30 -27 +right_child=1 -3 3 -5 5 6 -8 8 -10 -11 -12 -13 13 14 15 17 -18 18 -20 20 21 -23 -24 -25 28 27 -28 -29 -30 -31 -32 +leaf_value=-0.00013136225670326053 -0.14533162164398603 0.0081174351069413959 -0.16497092961368309 0.092289225607716699 -0.18374201477123664 0.062904584570917257 -0.14010012093515034 0.10154555229421801 -0.18392396869987157 0.026728170884310145 -0.21033632033483529 0.13583571042604015 -0.19802670294792846 -0.19234427927334441 -0.19426983758352614 -0.19204436267355629 0.096682038867893727 0.08434081868088121 -0.17303111521557368 -0.19147953351979533 -0.18608717607203951 0.10005642098508494 0.13154019678928253 -0.18793135486493073 0.1056392688107015 0.11820953252681304 0.1862774876669715 -0.18392855938157071 -0.18021062936993332 0.068893007719526667 -0.1866680614221678 +leaf_weight=73644.561758914788 1.4475989067868784 1038.7197755442612 1.5516455454053382 15.87870744243264 4.0933415015606469 11.629574269987641 2.6768171407165928 20.441712698200721 0.66100222762906824 15.894723473582415 1.0209130896255363 5.0891432487405828 8.8352684057317656 1.0030328833963746 4.5141387578332823 5.1563732904614863 16.262930120807141 3.5346277626231313 2.7265807550866157 1.8395810923539118 10.16997732920572 2.4350433060899368 6.5004794555716217 2.0281235484289928 12.308518411824478 1.2195958341471915 0.92722301930189122 2.0554973775288081 1.2373147550970305 16.042127832304686 2.1417363323271275 +leaf_count=23894994 468 337028 504 5153 1328 3776 869 6633 214 5155 330 1651 2865 326 1463 1674 5273 1147 885 598 3300 790 2109 659 3998 397 300 667 398 5204 697 +internal_value=0 0.0102554 0.040155 0.0438419 0.0303336 -0.0214996 0.0249211 0.055074 0.0926038 0.00967883 -0.000154767 -0.000151858 -0.000161242 -0.0219616 -0.00507202 -0.0246744 0.0798916 -0.101941 -0.0277374 -0.0107638 -0.0547159 -0.13081 0.0602912 0.0109101 0.0208807 -0.00865196 -0.129424 0.0219722 0.0795289 0.0437842 -0.076049 +internal_weight=0 1112.99 74.2751 72.8275 56.9488 18.3997 14.3064 38.5491 21.1027 17.4464 73751.6 73750.6 73745.5 100.938 92.1029 74.8369 17.266 11.4176 6.26121 63.4194 20.9451 12.605 8.34006 42.4743 40.4462 26.9003 5.44136 21.459 13.5458 19.4035 3.36133 +internal_count=24290853 361128 24100 23632 18479 5973 4645 12506 6847 5659 23929725 23929395 23927744 32750 29885 24286 5599 3706 2032 20580 6797 4090 2707 13783 13124 8728 1763 6965 4396 6298 1094 +is_linear=0 +shrinkage=0.1 + + +Tree=52 +num_leaves=32 +num_cat=0 +split_feature=2 19 17 3 17 13 10 17 2 16 11 0 16 6 18 13 16 6 16 17 16 15 5 18 18 15 16 9 2 10 5 +split_gain=9.80066 8.63609 16.1984 10.8404 9.48971 10.3978 6.79301 5.79182 5.2627 5.22491 5.18382 7.93603 5.89811 9.23882 8.32004 7.81059 7.6574 16.5668 13.3638 11.3755 11.2756 10.4552 18.1614 14.216 9.16427 8.96304 12.8265 12.3138 8.51087 8.4692 7.6287 +threshold=19913.500000000004 2.5000000000000004 3.5000000000000004 30217.500000000004 4.5000000000000009 11.500000000000002 14.500000000000002 5.5000000000000009 108419.00000000001 20.500000000000004 3.5000000000000004 2912.0000000000005 53.500000000000007 12.500000000000002 224.50000000000003 30.500000000000004 29.500000000000004 6.5000000000000009 26.500000000000004 53.500000000000007 25.500000000000004 1.0000000180025095e-35 2849.5000000000005 41.500000000000007 140.50000000000003 1.5000000000000002 21.500000000000004 3.5000000000000004 33271.000000000007 2.5000000000000004 585.50000000000011 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 8 -3 5 -4 7 -6 -2 -7 12 -12 16 14 15 -14 18 21 20 -20 25 23 -23 -18 30 26 -9 28 -28 -27 -25 +right_child=1 3 4 -5 6 9 -8 10 -10 -11 11 -13 13 -15 -16 -17 17 -19 19 -21 -22 22 -24 24 -26 29 27 -29 -30 -31 -32 +leaf_value=-0.00011825853094643506 -0.18907654503770405 -0.18615003673085451 -0.19669991445344806 0.1110557212437245 -0.18729778803798869 0.15648880618387431 -0.17608363145998368 0.0072772910623929467 0.092698759244887199 -0.19722966570100497 -0.20217728884556987 0.053687381433074811 0.04182039160781155 -0.11919908230712813 -0.14431997822841466 -0.16349831601246362 -0.1742407057576274 0.051121540555979672 -0.18149364266704007 0.09673326074146632 -0.17668039249709186 -0.18227989839171363 0.039497388091926849 0.083293125655726941 -0.13332331182174453 0.10556939819284115 -0.18105847154675761 -0.17672527950702191 0.028199726447480616 -0.17775534732894899 -0.031883307576112636 +leaf_weight=74077.462896879777 5.272641396499238 1.3443945383187372 1.0198662679176766 14.082857257919384 1.4839873397722829 8.8160383561626041 1.9654608964046918 465.63905042459373 0.75813759125594504 0.43836749996990043 1.2516778176650394 38.457951803226024 109.33902282139752 4.0639962994027874 2.5508259159396394 1.8847304480150331 5.2182468539103857 19.716534866485745 1.5664894817164192 23.737061747349799 3.3918663681251919 12.775892937032042 5.193436902249231 12.472154421964666 4.0236937839654265 13.480531866778618 2.4262230389285859 7.4307448036852284 9.7710128348844574 1.1446350293699641 10.67096470663091 +leaf_count=24034091 1712 435 332 4570 480 2861 638 151078 246 141 409 12477 35474 1319 826 611 1693 6398 509 7700 1100 4143 1686 4040 1307 4374 788 2411 3169 371 3464 +internal_value=0 0.0110694 0.00959648 0.085156 0.0108752 0.106338 0.00958408 0.0100657 -0.153654 0.139734 0.010453 0.0456223 0.00850386 0.0289539 0.0342459 0.0383412 0.00447852 -0.0265843 0.00859628 0.0795088 0.00503102 -0.0570104 -0.118183 -0.0230682 0.0059693 0.00626397 0.00393936 -0.0752471 -0.013425 0.0833951 0.0301869 +internal_weight=0 791.388 775.961 15.4273 769.93 10.2743 759.656 757.691 6.03078 9.25441 756.207 39.7096 716.497 117.839 113.775 111.224 598.659 70.0709 528.588 25.3036 503.284 50.3544 17.9693 32.3851 27.1668 499.892 485.267 19.628 12.1972 14.6252 23.1431 +internal_count=24290853 256762 251757 5005 249799 3334 246465 245827 1958 3002 245347 12886 232461 38230 36911 36085 194231 22731 171500 8209 163291 16333 5829 10504 8811 162191 157446 6368 3957 4745 7504 +is_linear=0 +shrinkage=0.1 + + +Tree=53 +num_leaves=32 +num_cat=0 +split_feature=2 20 11 8 14 4 10 14 10 2 10 0 18 10 13 13 0 4 12 12 10 18 18 18 8 0 0 10 8 8 1 +split_gain=10.8335 6.94941 5.85679 6.01927 11.918 15.3056 13.0754 11.2589 15.0674 16.175 9.05052 11.3139 15.9039 12.0068 11.2269 9.01255 7.9242 14.3441 13.5523 11.1579 11.8286 13.4295 9.32451 15.7744 8.21391 8.15539 10.0065 10.7931 8.17229 7.79188 9.56035 +threshold=19913.500000000004 2.5000000000000004 3.5000000000000004 5.5000000000000009 1.0000000180025095e-35 121.50000000000001 4.5000000000000009 1.5000000000000002 1.0000000180025095e-35 28401.000000000004 1.0000000180025095e-35 5335.5000000000009 44.500000000000007 6.5000000000000009 15.500000000000002 9.5000000000000018 7501.5000000000009 148.50000000000003 70.500000000000014 1234.0000000000002 1.5000000000000002 180.50000000000003 74.500000000000014 77.500000000000014 6.5000000000000009 7336.5000000000009 5593.5000000000009 1.5000000000000002 6.5000000000000009 14.500000000000002 62144.000000000007 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 3 -2 10 -6 7 -7 15 -10 -5 12 13 14 -12 -9 17 -13 24 22 -21 -22 -20 -24 -18 26 -19 29 -29 30 -28 +right_child=1 -3 -4 4 5 6 -8 8 9 -11 11 16 -14 -15 -16 -17 18 25 19 20 21 -23 23 -25 -26 -27 27 28 -30 -31 -32 +leaf_value=-0.00012362707158196747 0.022855069524152921 0.080119224667171196 0.046944082642636588 0.010625340396459846 -0.17007371930638032 0.068503099312881191 -0.15303239552566816 0.019105213861697813 -0.16884983269952533 0.064452661987055376 -0.033001007574296609 -0.18410664926815601 -0.17354567892369296 0.060222609946568265 -0.18933773072109469 -0.1155039362990819 -0.19956349040265386 0.11203590026020467 -0.1240862139553576 0.11263050622517407 -0.18030818270548191 0.15861909639439853 0.13150540350207202 -0.078260739695611592 0.030200321150820043 0.06143092091663889 -0.14762829962291268 0.082494309580299741 -0.1384419155955991 0.087531456838010988 0.069388260646957203 +leaf_weight=74080.817434127472 204.62870182438928 14.571273154695517 41.501602423581062 175.74344454632956 3.7684078091988331 65.287392086640466 3.6172591093927613 8.383860971429387 3.2420016793766981 35.644044288419536 20.51263966399711 3.3814173167920663 10.250562951317987 10.088931294041684 5.9188335593789807 12.229134976398198 1.6272534880554292 5.8559136968688099 23.600943988843937 6.9784490859019552 4.0229055240051821 1.6480060699395833 4.5850152717903283 16.435818964426289 35.492378010763787 34.330835457076319 4.4604731341823989 1.9998096923227411 10.282898689794818 8.5766373171936703 3.7253888526465744 +leaf_count=24036993 66397 4728 13465 57024 1222 21181 1173 2722 1051 11568 6659 1100 3323 3276 1921 3966 529 1897 7658 2264 1306 535 1489 5329 11515 11137 1448 647 3338 2784 1208 +internal_value=0 0.0117055 0.0104072 0.00831952 0.00261808 0.0285652 0.0343949 0.039828 0.00836323 0.0450018 -0.00618636 -0.0200072 -0.0634786 -0.0325849 -0.0680097 -0.0607548 -0.00783263 0.0170027 -0.0269381 -0.0574434 0.0254581 -0.0818136 -0.0809443 -0.032507 0.0201279 0.0268253 -0.00721487 -0.0312575 -0.10247 0.0209236 -0.048864 +internal_weight=0 782.392 767.821 726.319 521.691 132.172 128.404 124.786 59.499 38.886 389.519 213.775 46.771 36.5204 26.4315 20.613 167.004 72.6134 94.3908 57.2711 12.6494 5.67091 44.6218 21.0208 37.1196 69.232 34.9011 29.0452 12.2827 16.7625 8.18586 +internal_count=24290853 253860 249132 235667 169270 42883 41661 40488 19307 12619 126387 69363 15179 11856 8580 6688 54184 23559 30625 18581 4105 1841 14476 6818 12044 22459 11322 9425 3985 5440 2656 +is_linear=0 +shrinkage=0.1 + + +Tree=54 +num_leaves=32 +num_cat=0 +split_feature=2 17 7 7 7 6 17 17 8 15 12 4 17 15 15 13 7 14 7 17 7 12 13 17 4 14 8 4 13 6 6 +split_gain=12.3138 7.92999 6.8325 6.66346 7.16851 10.9297 9.12201 9.96174 7.56074 6.83622 5.92603 5.77469 11.019 5.63903 6.1992 6.22673 6.67298 9.83982 7.37404 5.59514 8.44534 6.59086 5.50397 7.83058 5.26944 5.05335 8.24716 19.5132 8.10125 7.14083 6.74451 +threshold=19913.500000000004 15.500000000000002 1255.5000000000002 33.500000000000007 73.500000000000014 4.5000000000000009 30.500000000000004 53.500000000000007 4.5000000000000009 1.5000000000000002 40.500000000000007 332.50000000000006 7.5000000000000009 9.5000000000000018 2.5000000000000004 16.500000000000004 183.50000000000003 5.5000000000000009 2176.5000000000005 11.500000000000002 457.00000000000006 2412.0000000000005 21.500000000000004 139.50000000000003 653.50000000000011 1.0000000180025095e-35 10.500000000000002 163.50000000000003 92.500000000000014 6.5000000000000009 18.500000000000004 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 11 -3 5 6 8 -8 -5 10 -9 12 -2 14 25 16 17 -16 -18 20 21 -13 23 -12 -25 28 29 -28 -6 -27 -29 +right_child=1 3 -4 4 13 -7 7 9 -10 -11 22 19 -14 -15 15 -17 18 -19 -20 -21 -22 -23 -24 24 -26 26 27 30 -30 -31 -32 +leaf_value=-0.00013096839227937947 0.076417621316402853 0.038279679329470927 -0.15400442548594551 -0.15590446032503322 0.0063409015721220229 -0.18478674245135487 -0.16110405783731707 0.067702132050112551 0.10891473236989371 -0.14426524148995298 -0.13773900230286262 0.059313604984179258 -0.18620866533695457 0.088089537390182271 0.12172316893100665 -0.16430948556020303 -0.15514696351636065 -0.18521971430197889 0.11449289359472165 0.096178983814126096 -0.16592178504461108 -0.1706396166892887 0.016578889481687138 0.031538775519851889 -0.14047059696224271 0.005849354657056713 -0.17233445375842643 0.061961539997998388 -0.15905094835267805 -0.14013449732860056 -0.17755412691975181 +leaf_weight=74090.406472409988 3.7214308476031865 72.626881782874989 1.6460566914174695 1.2361886061262328 415.1898821345967 4.0015022878069422 5.2646821793168774 8.9060837139841151 8.4312934917397779 3.8444621551898299 7.7956285254331332 23.744212214951411 2.7993330871686339 9.0905276045668852 4.8089528779964876 3.8171021201414979 4.5031260277028196 1.3341675112606024 1.3090732675045726 26.330779701820575 2.0074987237749147 1.3154699581209559 17.560594263137318 8.263663132733198 2.270274977083317 43.361930945859058 3.8539450701209708 75.774597427604022 2.9828598151216275 3.6313319152104659 1.1941895965719584 +leaf_count=24040163 1205 23567 535 402 134714 1298 1708 2892 2735 1246 2529 7703 909 2953 1562 1237 1461 432 425 8543 653 427 5698 2682 735 14068 1250 24589 968 1178 386 +internal_value=0 0.0125592 0.0469893 0.00957811 0.00631304 -0.0244855 -0.0143955 -0.0304372 0.0750521 -0.0162944 -0.00531177 0.0525109 -0.0363266 0.0099588 0.00869448 -0.0531121 -0.0176089 0.0550611 -0.0944164 0.0633594 0.0314327 0.0472426 -0.02343 -0.0617605 -0.00553269 0.0104799 0.0278813 0.0472504 0.00516115 -0.00543132 0.0582454 +internal_weight=0 772.618 61.5648 711.053 638.426 67.5744 63.5729 53.9054 9.66748 48.6407 44.7962 59.9187 6.52076 570.852 561.761 15.7724 11.9553 6.14312 5.8122 53.398 27.0672 25.0597 35.8902 18.3296 10.5339 545.989 127.816 80.8227 418.173 46.9933 76.9688 +internal_count=24290853 250690 19975 230715 207148 21925 20627 17490 3137 15782 14536 19440 2114 185223 182270 5117 3880 1994 1886 17326 8783 8130 11644 5946 3417 177153 41471 26225 135682 15246 24975 +is_linear=0 +shrinkage=0.1 + + +Tree=55 +num_leaves=32 +num_cat=0 +split_feature=1 21 20 1 20 16 4 7 20 13 21 13 21 10 7 6 16 6 1 16 13 20 16 20 13 7 13 9 11 16 1 +split_gain=9.84747 5.83058 29.3043 24.9239 18.9714 12.4501 17.4828 23.1033 17.2709 18.9704 14.6686 12.1976 17.5358 13.9208 13.7584 10.3878 21.9366 10.006 14.2163 10.7864 9.73528 9.23523 9.00338 8.8291 8.82137 11.3482 8.2205 7.84665 7.60519 7.42139 7.31885 +threshold=20447.500000000004 32715.500000000004 4450.5000000000009 56594.500000000007 5522.5000000000009 99.500000000000014 6.5000000000000009 3.5000000000000004 8498.0000000000018 1.0000000180025095e-35 47069.500000000007 1.0000000180025095e-35 35749.500000000007 235.00000000000003 3.5000000000000004 15.500000000000002 15.500000000000002 13.500000000000002 43123.000000000007 12.500000000000002 1.5000000000000002 10740.500000000002 39.500000000000007 14227.500000000002 14.500000000000002 5.5000000000000009 1.5000000000000002 7.5000000000000009 17.500000000000004 157.00000000000003 55527.000000000007 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 -2 3 20 15 6 7 8 9 -6 -10 12 -7 14 -13 30 -17 19 -19 -8 -3 -21 -20 -14 25 26 -16 -22 -12 -15 -4 +right_child=1 2 4 -5 5 11 17 -9 10 -11 28 13 23 29 24 16 -18 18 22 21 27 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 +leaf_value=-0.00014999493496406808 0.012852338955649082 -0.19105872889044317 0.12341640279800946 0.0062738223144502975 0.043572445270449817 -0.197907404863505 0.1997599383670971 -0.11538267054541439 -0.19820845159473049 -0.18165609929788878 0.016295399043698706 -0.19358761820500456 0.051086256844611017 -0.069421428439807822 0.044915401333873985 0.17346163338137116 -0.19683475226373792 -0.19268227540507812 -0.17122866018096564 -0.19182564004222086 -0.19475921388197293 0.19503280879226062 0.09883817667381406 -0.19703582249654567 0.1031294484411697 -0.19516502397369062 -0.19073649567181669 0.20575778252031302 -0.19032940594503195 0.08234505393112472 -0.098576382348323666 +leaf_weight=73598.198138282401 925.71547251773882 17.023110460489992 23.673445510212339 14.89105591207044 41.067984780063853 3.0144823060836634 1.162985324859618 31.494521991116926 7.5843037196900722 4.114290183177217 9.9206172703998146 6.8676175174769041 91.062395081971772 4.3084079911932376 7.4541512706782687 2.8772511295974246 3.603364500682801 1.9886241871863628 1.3011980358278403 4.4026154379826039 0.83060466754250151 0.71767418645322312 24.053095091949217 1.4570670996326942 4.0311657572165123 4.5108718439005306 1.8471478617284445 1.1898819599300623 2.1711876117624334 12.778506090398876 1.5845371999312181 +leaf_count=23882410 300393 5523 7681 4834 13323 978 379 10223 2463 1335 3219 2227 29543 1398 2419 933 1169 645 421 1428 270 232 7805 473 1309 1464 601 386 705 4149 515 +internal_value=0 0.00877039 -0.00257767 -0.0906428 0.00741552 -0.00126337 -0.0234467 -0.0451126 -0.0109902 0.0230632 -0.089187 0.0197323 0.0394452 -0.0253238 -0.0733129 0.0805114 -0.0324313 0.0386348 0.0647842 -0.0751593 -0.166426 -0.137602 0.0849782 0.0471786 -0.027021 -0.0650062 -0.00188278 0.0411087 -0.0208059 0.0440776 0.10949 +internal_weight=0 1258.7 332.984 33.9347 299.05 267.311 129.979 96.3529 64.8584 45.1823 19.6761 137.332 95.5339 41.7979 24.711 31.7386 6.48062 33.6262 27.3429 6.28327 19.0436 5.12029 25.3543 92.5195 17.8433 13.8122 9.3013 2.02049 12.0918 17.0869 25.258 +internal_count=24290853 408443 108050 11013 97037 86739 42178 31268 21045 14658 6387 44561 30994 13567 8020 10298 2102 10910 8871 2039 6179 1660 8226 30016 5793 4484 3020 656 3924 5547 8196 +is_linear=0 +shrinkage=0.1 + + +Tree=56 +num_leaves=32 +num_cat=0 +split_feature=1 5 5 1 15 15 2 15 20 17 1 20 3 15 15 12 1 15 9 3 4 11 20 5 15 4 3 8 4 20 1 +split_gain=10.9117 6.1329 5.98694 5.80188 5.5388 8.91469 6.26976 17.4426 17.6508 16.792 14.8735 13.9605 12.5874 9.85274 9.11038 6.56609 8.46511 12.7729 9.03554 6.9107 6.42113 6.1587 6.09799 8.84885 11.5597 8.19731 5.92598 16.2117 11.0842 18.1115 11.1677 +threshold=21734.500000000004 2.5000000000000004 9.5000000000000018 21633.500000000004 712.00000000000011 570.00000000000011 179.50000000000003 61.500000000000007 9852.0000000000018 1.0000000180025095e-35 48700.000000000007 1638.5000000000002 647.50000000000011 32.500000000000007 283.50000000000006 2.5000000000000004 38269.500000000007 262.50000000000006 1.5000000000000002 632.50000000000011 1.5000000000000002 41.500000000000007 4615.5000000000009 71.500000000000014 89.500000000000014 3.5000000000000004 1240.5000000000002 6.5000000000000009 6.5000000000000009 4450.5000000000009 23306.000000000004 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=3 -2 -3 -1 5 6 7 8 11 10 15 20 -11 -10 -14 16 18 22 -9 -17 -4 26 -18 24 -24 -25 27 -8 29 -28 -31 +right_child=1 2 4 -5 -6 -7 21 9 13 12 -12 -13 14 -15 -16 19 17 -19 -20 -21 -22 -23 23 25 -26 -27 28 -29 -30 30 -32 +leaf_value=-0.00014892667313731826 0.13928065597011577 -0.20434119940753065 0.085641987642398512 -0.16472025174841853 -0.19801184361246119 0.096874158557149231 0.032444347345307108 0.084947723329799385 0.19597471983857562 -0.20170552373222331 -0.19976824790287595 -0.15841086077452229 -0.19232668654056237 -0.19404160930107583 0.18420883453344372 -0.18844282655453676 0.13860435497729889 -0.20489963815941722 -0.19254040697367844 0.16085924771324497 -0.18427888395210612 0.11521138269171252 -0.19490453753618012 -0.15389351610144286 0.11843632368912059 0.015844812278905229 -0.14212106425743531 -0.18625181692446105 0.012081764833125161 0.08887750994606039 -0.016655684009849701 +leaf_weight=73658.626978129265 3.6292179610973099 1.3128624293021847 4.0872438349761131 2.1422639510128638 1.2870626214426 11.576143459999001 161.4148097889265 33.899215161101893 3.0647401395253833 6.0363848940469307 2.7971760202199212 18.015868328744546 0.98354064859449875 0.82130825147032727 1.8535668849945068 2.3899850166635583 3.9044704469852212 2.7061919907573602 1.2155278637073923 0.74231464695185412 1.1236135624349115 5.6382702286355189 1.373615051619711 4.9293769057840091 8.2407035618089122 6.7292624244000763 10.894622649881056 3.4622885189019135 756.13488615578535 10.919349848409182 122.74661565979477 +leaf_count=23902716 1179 426 1329 695 416 3756 52379 11002 994 1958 907 5842 319 268 601 775 1265 878 394 241 366 1829 447 1600 2677 2182 3535 1123 245378 3542 39834 +internal_value=0 0.00948341 0.00908766 -0.000153713 0.00932333 0.00954801 0.00868849 -0.0146418 -0.0837127 0.00942859 0.0260975 -0.116716 -0.120053 0.113546 0.0536752 0.0356511 0.0426773 0.00154135 0.0753422 -0.105663 0.0274391 0.0109734 0.0237306 0.00264653 0.0736687 -0.0559221 0.0104219 0.0278519 0.00723124 -0.0181398 -0.00803454 +internal_weight=0 1193.93 1190.3 73660.8 1188.99 1187.7 1176.12 104.914 27.1128 77.8013 68.9278 23.2267 8.87349 3.88605 2.83711 66.1307 62.9984 27.8836 35.1147 3.1323 5.21086 1071.21 25.1774 21.273 9.61432 11.6586 1065.57 164.877 900.695 144.561 133.666 +internal_count=24290853 387442 386263 23903411 385837 385421 381665 34045 8799 25246 22368 7537 2878 1262 920 21461 20445 9049 11396 1016 1695 347620 8171 6906 3124 3782 345791 53502 292289 46911 43376 +is_linear=0 +shrinkage=0.1 + + +Tree=57 +num_leaves=32 +num_cat=0 +split_feature=1 15 18 14 20 14 11 10 20 14 15 20 2 15 14 14 10 0 13 9 10 7 11 15 1 0 11 21 15 21 7 +split_gain=11.6317 5.7945 7.22478 6.42453 13.4043 14.2142 13.914 12.3395 11.1962 10.5449 10.4259 22.8477 44.1402 12.8161 12.3189 10.0096 11.6382 11.0684 9.78786 18.4052 11.6875 9.3915 9.29113 9.22234 9.02346 15.1101 9.64442 14.2401 12.696 10.7395 11.8672 +threshold=19949.500000000004 394.50000000000006 2.5000000000000004 15.500000000000002 9231.5000000000018 129.50000000000003 3.5000000000000004 775.50000000000011 8771.0000000000018 48.500000000000007 100.50000000000001 5647.5000000000009 291.50000000000006 229.50000000000003 77.500000000000014 28.500000000000004 136.50000000000003 23996.500000000004 1.5000000000000002 2.5000000000000004 83.500000000000014 11.500000000000002 6.5000000000000009 157.50000000000003 59686.500000000007 48692.000000000007 7.5000000000000009 38521.000000000007 86.500000000000014 33938.500000000007 28.500000000000004 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 3 -3 -2 5 7 23 10 -9 15 24 -12 -13 14 21 17 -17 -8 20 -20 -7 -14 -18 -6 25 26 28 -28 29 -5 -31 +right_child=1 2 -4 4 6 18 9 8 -10 -11 11 12 13 -15 -16 16 22 -19 19 -21 -22 -23 -24 -25 -26 -27 27 -29 -30 30 -32 +leaf_value=-0.0001638286391505393 0.011463473882964793 0.052674242680581455 -0.1897269315643019 0.064571191833773509 -0.18844739362648935 -0.1813693793339074 0.11397949777993253 -0.18973956882957863 0.051648018332178081 -0.1695212628366517 0.11346608584378137 -0.18734963732855472 0.082842555444958887 -0.18077517229135731 -0.19085385934664864 -0.12605726427166686 -0.18361813885501968 -0.18326531345588523 -0.17244664960005415 0.12089191320714393 0.13983705153251078 -0.20074680688749505 0.11109556467208137 0.093077852322190582 0.06337587343144814 -0.183051863069257 -0.19356651381259277 0.0021364332005735483 -0.19525068445771857 -0.17205685991172467 0.14686925619476712 +leaf_weight=73589.671169540146 1022.4886405275029 41.790529050573241 1.2668498054845247 20.468399871548179 1.3680356754921357 1.2195992486085803 1.5129949604161099 6.5612718166667019 2.7172554560238495 10.776133304752873 21.139196784351952 10.9729852330056 30.765257293940525 2.420129947946406 1.8870559409260739 4.5204845059197387 1.2403602161211873 7.2821815769420937 3.9521966999163833 4.6621627375716344 15.917328997049479 1.2138384585268784 7.7753602372249588 7.7868565944954753 8.8117463280796056 6.9676423422060898 7.0258869898971152 7.8973599198507145 2.7609686604700974 4.4071395593928164 1.5867993030697105 +leaf_count=23878383 331784 13559 411 6640 443 394 493 2128 882 3494 6861 3561 9982 784 613 1466 403 2361 1283 1513 5162 394 2526 2528 2860 2261 2281 2563 896 1429 515 +internal_value=0 0.00948425 0.0455422 0.00822006 -0.00790878 0.00507811 -0.0581062 -0.00768282 -0.119048 -0.0882787 0.000369481 0.0270495 -0.0116049 0.0415404 0.0574274 -0.0490747 0.00489173 -0.132132 0.0732666 -0.0136894 0.116977 0.0720783 0.0705496 0.0510089 -0.0300826 -0.0461943 -0.0245941 -0.0900008 0.00880672 0.0300972 -0.0876263 +internal_weight=0 1271.16 43.0574 1228.11 205.617 163.354 42.2624 137.603 9.27853 33.1075 128.324 68.3985 47.2593 36.2863 33.8662 22.3314 13.5362 8.79518 25.7513 8.61436 17.1369 31.9791 9.01572 9.15489 59.9259 51.1142 44.1466 14.9232 29.2233 26.4623 5.99394 +internal_count=24290853 412470 13970 398500 66716 53002 13714 44650 3010 10743 41640 22195 15334 11773 10989 7249 4395 2854 8352 2796 5556 10376 2929 2971 19445 16585 14324 4844 9480 8584 1944 +is_linear=0 +shrinkage=0.1 + + +Tree=58 +num_leaves=32 +num_cat=0 +split_feature=1 9 8 18 7 16 6 5 7 5 15 16 16 7 16 7 0 10 16 16 10 10 12 16 10 10 6 5 12 1 10 +split_gain=12.2832 4.96184 4.35934 11.5791 10.716 9.82893 11.9659 10.0162 9.63558 12.7494 9.86774 8.29334 16.222 17.6922 8.2477 7.95613 14.9846 7.4862 7.12319 6.66804 6.80853 6.65208 6.32223 5.88172 12.0769 7.68677 7.84506 5.8586 5.63978 6.32516 5.5059 +threshold=21734.500000000004 34.500000000000007 1.5000000000000002 1.0000000180025095e-35 7.5000000000000009 225.50000000000003 17.500000000000004 243.50000000000003 7.5000000000000009 79.500000000000014 180.50000000000003 18.500000000000004 9.5000000000000018 219.50000000000003 132.50000000000003 35.500000000000007 3491.5000000000005 199.50000000000003 71.500000000000014 21.500000000000004 70.500000000000014 148.50000000000003 1.5000000000000002 24.500000000000004 15193.500000000002 2535.5000000000005 15.500000000000002 2591.0000000000005 1.0000000180025095e-35 25759.000000000004 4730.0000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 11 5 18 8 7 -7 9 -4 14 12 15 -14 22 17 -17 -2 -5 20 27 -6 28 24 -21 -25 -27 -13 29 -10 -31 +right_child=1 -3 3 4 21 6 -8 -9 10 -11 -12 19 13 -15 -16 16 -18 -19 -20 23 -22 -23 -24 25 -26 26 -28 -29 -30 30 -32 +leaf_value=-0.00016298778824403693 0.062411365332856898 -0.18041132419428058 -0.0042681844878553641 -0.16566950012328327 0.009657959871717213 -0.20576201172826059 -0.18461729191428208 0.11452968134464875 0.091140243186250872 -0.13945080325192488 -0.17765145201339366 -0.1753312222191814 -0.17405279959331676 0.12830685132809713 0.130311547635485 0.18537237310604682 -0.17864872063984652 -0.15789502100936484 0.12244387516303411 -0.1744827066117387 0.11542561028088823 -0.18148684952283528 -0.17168960961723889 0.02422789733205798 0.15551159539903514 -0.17323246025403755 0.041927122131589216 0.12572413046964773 0.061565826860630238 -0.040594388290691237 0.14442952861013558 +leaf_weight=73661.489875584433 20.497439785307513 1.3659940945217375 11.860556507017465 0.92508826032280822 653.76668831027928 1.0659761124989007 1.8503618761897076 11.614356437930835 4.4623036461416623 16.942779332399368 3.5357103452552101 1.6601222774479567 10.375365373503881 2.3789687138050786 4.1489665035624048 1.5238323635421682 4.3844940620474517 1.6679486776993133 11.853910517645998 3.9615868934779419 13.425969037634784 1.8257585212122638 2.4826118972850955 340.96595224225894 1.5402011200785635 3.9999875979265189 2.9403261718107387 1.0585767459124324 9.5483714706497249 43.168001784652006 1.670560561120509 +leaf_count=23903883 6653 444 3850 300 212160 348 601 3766 1446 5499 1147 539 3368 772 1346 497 1421 543 3844 1286 4358 591 803 110641 500 1298 954 343 3099 14011 542 +internal_value=0 0.0100681 0.0102865 0.00590038 0.0108935 -0.0238034 0.0529391 0.0876043 -0.0352031 -0.0837857 -0.0149277 0.0186308 -0.0241383 -0.117656 -0.00614122 0.0183483 -0.0847631 0.0458333 0.101587 0.0233559 0.086203 0.00912564 -0.015372 0.0204849 -0.0821023 0.0221072 -0.0820782 -0.0581096 -0.00877754 -0.0224013 -0.0337009 +internal_weight=0 1192.47 1191.1 780.722 668.371 112.351 14.5307 12.6803 97.8199 28.8033 69.0165 410.381 40.828 12.7543 65.4808 28.0737 5.90833 22.1654 12.779 369.553 16.1447 655.592 61.3318 353.408 5.50179 347.906 6.94031 2.7187 58.8492 49.3009 44.8386 +internal_count=24290853 386970 386526 253353 216895 36458 4715 4114 31743 9349 22394 133173 13254 4140 21247 9114 1918 7196 4144 119919 5240 212751 19901 114679 1786 112893 2252 882 19098 15999 14553 +is_linear=0 +shrinkage=0.1 + + +Tree=59 +num_leaves=32 +num_cat=0 +split_feature=1 14 14 10 2 16 1 17 14 14 3 11 10 8 20 10 11 16 16 20 15 12 15 17 15 11 17 14 2 10 14 +split_gain=12.7126 7.09317 10.5361 8.72752 8.84711 8.04755 6.36456 8.86969 5.64665 15.8321 5.81149 5.63958 4.97202 10.5456 10.0339 9.12485 4.68924 7.48891 6.99559 6.49083 15.4494 9.6847 9.14799 16.2249 8.88251 8.38913 7.53067 7.22135 9.96341 9.3278 7.71724 +threshold=22211.000000000004 103.50000000000001 77.500000000000014 47.500000000000007 175.50000000000003 304.00000000000006 53338.500000000007 9.5000000000000018 200.50000000000003 521.50000000000011 3394.0000000000005 30.500000000000004 1414.0000000000002 3.5000000000000004 9355.5000000000018 8904.5000000000018 5.5000000000000009 29.500000000000004 1.5000000000000002 10740.500000000002 192.50000000000003 1.0000000180025095e-35 93.500000000000014 3.5000000000000004 82.500000000000014 3.5000000000000004 4.5000000000000009 4.5000000000000009 1335.5000000000002 475.00000000000006 7.5000000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 12 4 -4 6 8 -8 10 -10 11 -3 16 15 -15 -14 19 18 -18 20 22 -22 23 24 27 -21 -25 29 30 -2 -29 +right_child=1 5 3 -5 -6 -7 7 -9 9 -11 -12 -13 13 14 -16 -17 17 -19 -20 25 21 -23 -24 26 -26 -27 -28 28 -30 -31 -32 +leaf_value=-0.00016549070867702436 -0.10659464676921922 0.10595354675017117 -0.17364703836872664 -0.17126909595480111 0.11873820986300386 -0.13163613816509817 -0.15362622225653863 0.11555033599875414 -0.17105875157994097 0.12086580829875793 -0.17362333829497145 -0.18124581664895772 -0.18103395320398746 -0.16911691957326219 0.090402531598124336 -0.014416902452007857 -0.17855328629087835 0.012724587703540478 0.054679838205400157 -0.087731861409865594 -0.17547823701160212 0.0038231876934695557 0.045892914817245736 0.10573646316760887 -0.18355752371641665 0.10476851831348855 0.0071104656142430781 0.091172655242068468 -0.16052375221717982 0.20195922883823034 -0.02348799795904807 +leaf_weight=73664.851546971346 16.405410705279795 28.998854711651809 1.6159024897497127 5.913351068564225 2.8781545390957035 2.3403435262152916 3.0649057151749721 2.0382340889191255 3.2544250843347973 4.3290046506735962 0.80196095118299027 0.70023223978932847 6.3586496199714038 1.8312241765670458 7.9908557338640094 6.8040374821284786 1.3110649131704111 238.15030452120118 67.291674675128888 2.7520041433162961 7.6138552124029939 4.9846243098145351 56.358023567241617 7.8342073797248295 4.5178472140687509 12.763595409924164 657.50801873476303 8.7113794203614869 3.7320147546706712 1.0419828891754139 17.996197923610453 +leaf_count=23905365 5327 9411 527 1915 936 761 994 661 1058 1404 261 226 2064 594 2594 2209 426 77283 21835 893 2472 1618 18287 2546 1466 4142 213361 2828 1211 338 5840 +internal_value=0 0.0102625 0.00871987 -0.0914372 0.0136069 0.04897 0.0587571 -0.046115 0.0728094 -0.00441347 0.0920092 0.0991821 0.00964073 -0.0363946 0.0420178 -0.0949065 0.0105949 0.0211107 0.0502225 0.00657383 0.0053106 -0.104537 0.00709836 0.00405218 -0.0495204 0.0706247 0.00827176 -0.0368748 -0.00747487 -0.0881674 0.0139116 +internal_weight=0 1187.89 1142.36 10.4074 4.49406 45.528 43.1876 5.10314 38.0845 7.58343 30.501 29.6991 1131.96 22.9848 9.82208 13.1627 1108.97 306.753 68.6027 802.219 786.704 12.5985 774.105 717.747 52.4048 15.5156 665.342 47.887 30.4396 17.4474 26.7076 +internal_count=24290853 385488 370712 3378 1463 14776 14015 1655 12360 2462 9898 9637 367334 7461 3188 4273 359873 99544 22261 260329 255294 4090 251204 232917 17010 5035 215907 15544 9879 5665 8668 +is_linear=0 +shrinkage=0.1 + + +Tree=60 +num_leaves=32 +num_cat=0 +split_feature=1 15 14 5 6 20 16 12 5 14 14 14 14 16 7 17 0 1 15 15 20 15 16 10 5 5 15 3 3 13 5 +split_gain=11.2843 5.46335 4.49953 9.20129 27.1789 9.23843 10.2593 7.30789 6.29862 5.64716 5.64201 6.38554 4.53506 4.49385 3.78637 6.90835 3.47887 3.44306 10.1807 16.1527 20.9089 19.2747 37.1945 15.5466 16.2152 15.5343 26.8705 28.1863 15.2742 14.4909 13.6428 +threshold=22211.000000000004 618.00000000000011 103.50000000000001 33.500000000000007 10.500000000000002 9142.0000000000018 52.500000000000007 24.500000000000004 13.500000000000002 92.500000000000014 36.500000000000007 38.500000000000007 174.50000000000003 518.50000000000011 1839.0000000000002 84.500000000000014 11022.500000000002 621.00000000000011 315.00000000000006 100.50000000000001 11930.000000000002 21.500000000000004 21.500000000000004 268.00000000000006 44.500000000000007 183.50000000000003 82.500000000000014 2181.5000000000005 1091.0000000000002 439.00000000000006 280.50000000000006 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=17 2 8 12 -5 6 -6 -7 10 14 -2 -12 -4 -3 -10 -16 -17 -1 19 21 23 29 -23 24 -21 26 28 -28 -24 -19 -27 +right_child=1 13 3 4 5 7 -8 -9 9 -11 11 -13 -14 -15 15 16 -18 18 -20 20 -22 22 25 -25 -26 30 27 -29 -30 -31 -32 +leaf_value=-3.4236015669025968e-05 -0.18157503694637345 0.10496633337779018 0.14431365742732852 -0.16925659223418971 -0.094063168888431012 -0.16635264013252937 0.10735248741834665 0.11775233533938821 0.008130960685892339 -0.1595160538252397 0.18027909085207711 -0.19224186927010692 -0.18678886412009635 -0.17434739783827805 -0.16088485791805754 0.11770595150576324 -0.1560504870296352 -0.0018460864804795485 -0.062934693687708279 -0.19179112904688242 -0.19136272702568455 -0.16912354753411174 0.045639593958022688 0.06431391968739894 0.01328349216635823 -0.10037466365168664 -0.19687260281295915 0.19037414537994291 -0.18824309611175258 -0.18997175584045789 -0.025222349757240265 +leaf_weight=71405.783494377421 3.2826902723754747 8.6181760976614861 8.8587808902375365 6.3993133518379173 2.8745103308174356 2.8943800149718291 21.033761082915589 1.3175180158577857 1114.0229210241814 1.999930899706668 1.1711207302287219 0.75795706707867783 0.43393722834298465 0.61727146420162249 1.1336829733336333 8.0287018653470987 0.49269113841001022 1760.5519768242666 28.939582120394334 3.9785797510994589 4.440307854441925 18.308467725291848 69.532738539739512 74.613885622820817 124.789470362477 30.372586498968303 9.9558531291550008 2.3170233115088186 2.9091230842750511 4.1040222882293156 118.01070814329432 +leaf_count=23175419 1069 2798 2874 2078 935 942 6822 426 361567 649 382 244 142 199 368 2608 158 571401 9393 1289 1440 5945 22570 24217 40504 9857 3230 752 944 1335 38296 +internal_value=0 0.00968524 0.00908292 0.0405265 0.016749 0.0590784 0.0831361 -0.0774822 0.00786475 0.00837256 -0.101815 0.0339113 0.128852 0.0862978 0.00867137 0.0710247 0.101878 -0.000155675 -0.00400482 -0.00323796 0.0233064 -0.00597423 -0.0318792 0.0279931 0.00694725 -0.0210995 0.0130661 -0.123763 0.0362473 -0.00228361 -0.0406053 +internal_weight=0 1183.94 1174.7 43.8122 34.5195 28.1202 23.9083 4.2119 1130.89 1125.68 5.21177 1.92908 9.29272 9.23545 1123.68 9.65508 8.52139 73658.6 2252.82 2223.88 207.822 2016.06 251.407 203.382 128.768 233.098 84.7147 12.2729 72.4419 1764.66 148.383 +internal_count=24290853 384261 381264 14219 11203 9125 7757 1368 367045 365350 1695 626 3016 2997 364701 3134 2766 23906592 731173 721780 67450 654330 81594 66010 41793 75649 27496 3982 23514 572736 48153 +is_linear=0 +shrinkage=0.1 + + +Tree=61 +num_leaves=32 +num_cat=0 +split_feature=1 7 18 15 4 5 15 18 15 15 2 10 2 5 15 7 2 2 11 0 21 2 15 15 11 7 15 12 2 15 15 +split_gain=9.14788 4.08533 4.73195 3.79075 7.85607 11.5221 7.24067 6.57706 8.84608 12.7082 10.1997 11.1752 7.56242 13.6807 8.51375 6.49184 6.38207 6.0331 5.44455 5.29798 4.65056 4.61567 12.9087 5.73472 5.6109 5.20677 4.96136 6.15223 5.233 4.11521 3.95265 +threshold=20447.500000000004 1839.0000000000002 65.500000000000014 209.50000000000003 2.5000000000000004 888.00000000000011 201.50000000000003 1.5000000000000002 408.50000000000006 213.50000000000003 242.50000000000003 44.500000000000007 870.00000000000011 54.500000000000007 452.00000000000006 8.5000000000000018 338.50000000000006 550.00000000000011 38.500000000000007 7282.5000000000009 36513.500000000007 344.00000000000006 283.50000000000006 213.50000000000003 19.500000000000004 7.5000000000000009 452.00000000000006 1.5000000000000002 367.50000000000006 452.00000000000006 570.00000000000011 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 3 -3 6 7 21 -2 8 9 16 11 -11 13 14 20 -15 -5 -9 -12 -19 -10 25 23 -23 26 -6 27 -24 -28 -14 -17 +right_child=1 2 -4 4 5 -7 -8 17 12 10 18 -13 29 15 -16 30 -18 19 -20 -21 -22 22 24 -25 -26 -27 28 -29 -30 -31 -32 +leaf_value=-0.00014398142290340894 0.006647218362088942 -0.14213651720901299 0.099184137411902643 0.1684386963272036 0.11049476597414576 -0.11323528919179045 -0.17542989363836295 0.12199781184451367 -0.20594937949516626 -0.16888377751281497 -0.16957144644645716 0.052691148656401912 -0.19561701244663912 -0.19813454382784915 -0.19866633593892238 -0.18284087315727357 -0.17638940872829223 0.11382702008583773 0.10767455438394276 -0.1814456107724382 0.15225652169371004 0.055681770712957913 0.1358154679248286 -0.18361212582295558 -0.092283321840393409 -0.17604740069510016 0.10099774954412802 -0.15780051002234141 -0.19031527020515596 0.10378173379863427 0.1535958493654887 +leaf_weight=73593.129357874495 1129.4549397730007 0.90198331139981647 8.1952904359786753 3.280384466983381 21.63089995470364 3.7232522582635275 2.1883093911455935 7.3151591300265846 0.39060467761010098 3.260556563909633 10.365080808522176 7.5399266958702356 0.47979920962825318 4.2529397985199466 0.94625326697132539 0.52674275380559255 0.64172870200127352 1.0441906452178957 0.76027903927024532 1.4535633942577986 5.027075712394435 1.4491900787688816 12.751630000537261 3.2418403601041064 1.7666467735543836 0.65330153470858832 1.3254181265365326 0.75593484030105251 1.153116079745814 10.633281055721453 1.0360733666457234 +leaf_count=23885749 366585 296 2656 1064 7019 1208 710 2375 128 1058 3363 2449 154 1380 307 171 208 339 246 471 1631 470 4142 1051 574 213 431 244 372 3453 336 +internal_value=0 0.00848937 0.0752575 0.00799915 0.0259531 0.0557859 0.00629513 0.00143499 -0.0134908 -0.0537674 -0.0834233 -0.0142001 0.031204 -0.0232236 0.0780919 -0.134089 0.112019 0.07618 -0.150625 -0.0580063 0.126431 0.0698556 0.0378462 -0.109688 0.0768308 0.102094 0.0955199 0.119384 -0.0345331 0.0908554 0.0402008 +internal_weight=0 1248.15 9.09727 1239.05 107.405 48.4512 1131.64 58.9536 49.1407 25.848 21.9258 10.8005 23.2928 12.1797 6.36393 5.81576 3.92211 9.81291 11.1254 2.49775 5.41768 44.728 22.4438 4.69103 17.7527 22.2842 15.9861 13.5076 2.47853 11.1131 1.56282 +internal_count=24290853 405104 2952 402152 34857 15724 367295 19133 15948 8388 7116 3507 7560 3953 2066 1887 1272 3185 3609 810 1759 14516 7284 1521 5763 7232 5189 4386 803 3607 507 +is_linear=0 +shrinkage=0.1 + + +Tree=62 +num_leaves=32 +num_cat=0 +split_feature=3 21 7 20 2 3 16 5 14 5 11 5 20 16 15 16 5 5 14 14 21 21 21 16 12 3 20 20 16 16 15 +split_gain=6.2292 4.00568 11.808 7.80576 18.4995 24.8658 17.6702 17.9838 20.2029 16.0768 15.3681 15.1919 12.2457 11.3763 11.0125 14.5354 15.7456 14.6972 15.9294 13.5639 14.5972 12.5298 11.893 10.8104 22.1968 13.0987 10.8554 11.7552 10.7496 10.0257 13.6949 +threshold=20112.500000000004 1353.0000000000002 9793.5000000000018 6944.5000000000009 52.500000000000007 82.500000000000014 241.50000000000003 697.00000000000011 7.5000000000000009 1520.0000000000002 5.5000000000000009 116.50000000000001 7221.0000000000009 423.50000000000006 242.50000000000003 148.50000000000003 178.50000000000003 327.50000000000006 413.50000000000006 1.5000000000000002 753.00000000000011 587.00000000000011 19.500000000000004 39.500000000000007 1.0000000180025095e-35 160.50000000000003 14227.500000000002 13813.500000000002 40.500000000000007 30.500000000000004 67.500000000000014 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 3 -3 -1 6 -6 7 12 10 -8 -9 -7 -5 14 15 16 21 18 19 20 -18 -13 -16 24 -14 -26 27 -25 -10 -19 -31 +right_child=-2 2 -4 4 5 11 9 8 28 -11 -12 13 23 -15 22 -17 17 29 -20 -21 -22 -23 -24 26 25 -27 -28 -29 -30 30 -32 +leaf_value=-0.00016456739889827261 0.0074762893777299347 0.0039779003099109696 0.15942483818027539 -0.19776395245829068 -0.18689210472156589 -0.11572945512477938 -0.19325521976017723 -0.1889729305506157 0.038484883162540132 0.14743042405369081 0.10017523205936066 0.097142891411583096 -0.19567525951860532 0.15090276830542423 0.13331946519507892 0.088784268577047576 -0.2037428843298674 -0.1973999565449287 0.15657580675934821 -0.19483424888291168 0.13361251641898927 -0.1883330288905235 -0.1871661526497839 0.044607966847315869 0.076793300064489742 -0.20452761372413214 0.16379891494514559 -0.19487441727186589 -0.19587564296733551 0.076578780300298063 -0.038549157251140921 +leaf_weight=71493.492751330239 1098.0956790818454 1937.1720965850982 4.8990159428503821 2.2929269254673263 10.909907385590484 17.961629233090207 7.2746235531521943 2.3583507859148112 2.4254012997262171 1.7109011572320012 8.3331918250769359 13.685950260609387 5.8514867143239835 3.9004114059498525 1.3903539932798574 12.162974256556479 2.1983305385802003 2.9921761414734638 1.944026507437228 14.11119087244151 3.0790961598977447 1.7320398567244399 6.9259718226385303 92.586521413293667 11.731022318126632 1.9269742574542768 7.5637696781195691 2.0960644511505953 10.137243198463691 13.83380594011396 40.821480615297332 +leaf_count=23205425 356420 628770 1588 747 3542 5832 2359 765 789 557 2707 4446 1898 1266 450 3948 713 969 632 4582 998 562 2249 30053 3804 626 2454 680 3286 4487 13249 +internal_value=0 -0.000111335 0.00437003 -0.000232552 -0.0162242 -0.0416068 0.00775525 0.0160599 -0.0646414 -0.128387 0.0363947 -0.030015 0.0311882 -0.0170532 -0.0227558 -0.0141063 -0.0273635 -0.0454084 -0.116321 -0.143684 -0.00691405 0.0650728 -0.133586 0.0354999 -0.0327147 0.0371024 0.0485158 0.0393064 -0.150629 -0.0191667 -0.00940911 +internal_weight=0 73739.5 1942.07 71797.4 303.938 147.649 156.288 147.303 23.2542 8.98552 10.6915 136.739 124.049 118.778 114.877 106.561 94.3981 78.9801 21.3326 19.3886 5.27743 15.418 8.31633 121.756 19.5095 13.658 102.246 94.6826 12.5626 57.6475 54.6553 +internal_count=24290853 23934433 630358 23304075 98650 47925 50725 47809 7547 2916 3472 44383 40262 38551 37285 34586 30638 25630 6925 6293 1711 5008 2699 39515 6328 4430 33187 30733 4075 18705 17736 +is_linear=0 +shrinkage=0.1 + + +Tree=63 +num_leaves=32 +num_cat=0 +split_feature=1 16 0 20 16 20 16 14 7 8 5 8 2 5 2 5 14 2 4 14 8 14 7 2 2 9 7 20 16 20 2 +split_gain=7.3493 6.91799 9.8809 10.6821 10.132 9.14803 8.78229 8.20095 10.5421 15.4412 14.8404 7.45663 6.70352 18.5908 6.63414 7.16817 10.4577 7.1196 6.1832 8.30615 8.21511 13.5816 11.4623 7.89621 11.2662 6.95411 6.21686 6.10215 6.04144 7.1007 5.53524 +threshold=20447.500000000004 64.500000000000014 38097.000000000007 8498.0000000000018 28.500000000000004 9733.0000000000018 7.5000000000000009 1.0000000180025095e-35 273.50000000000006 8.5000000000000018 278.50000000000006 10.500000000000002 1335.5000000000002 50.500000000000007 895.00000000000011 124.50000000000001 86.500000000000014 523.50000000000011 2.5000000000000004 1.5000000000000002 4.5000000000000009 1.5000000000000002 252.50000000000003 38757.000000000007 48495.000000000007 3.5000000000000004 821.50000000000011 3074.0000000000005 69.500000000000014 9231.5000000000018 175.50000000000003 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 30 6 5 -5 -4 8 9 10 12 -11 13 -3 15 16 17 -8 19 -9 -20 23 -23 26 -25 27 -22 -21 29 -29 -2 +right_child=1 7 3 4 -6 -7 14 18 -10 11 -12 -13 -14 -15 -16 -17 -18 -19 20 25 21 22 -24 24 -26 -27 -28 28 -30 -31 -32 +leaf_value=-0.00012922817596098851 -0.036351857010627729 0.089729917676241264 -0.1556637250162716 -0.16861640876924322 -0.15725145035157473 0.13069987000773114 -0.17244695689376205 -0.1840562303426786 -0.15842612714789928 0.12649781439127525 -0.15736717627399202 -0.15734980550610445 0.079607878705383481 -0.18722512318267009 -0.098374067722476422 0.022422260371873157 0.10821602016044703 -0.020114331464640544 0.049742802601509595 -0.18994167217617469 0.12284206515872892 -0.11672200809981347 0.0086104476296772186 -0.12772152781027415 0.068691667557689282 -0.1578756315948493 -0.17885205652604064 0.10067536699205884 0.010589045950556365 -0.1671289577107096 0.0079784022885887335 +leaf_weight=73568.383087781127 29.302432513970416 4.8807227885117808 4.1033796091796821 1.5993837623391289 10.782274645403957 2.8240717389271595 6.1920915529772156 2.1860374750103793 5.1162669888144583 9.2799984563607705 8.2200941187329573 1.0280149297323067 7.1933124989736816 4.8145304494537413 6.9387444671883705 51.042619395535439 3.1142861081752917 6.081313305767253 129.77108082285849 1.4478328583063547 13.424845902365632 12.158781790873038 18.248979397903895 4.5267825158662154 8.2294501778087561 2.3736927490681401 0.71963949158089313 12.675964594352989 154.68076731145266 1.0739520064089436 726.70412546058651 +leaf_count=23884791 9513 1585 1331 519 3500 918 2011 712 1663 3014 2671 334 2334 1565 2254 16568 1012 1976 42129 475 4357 3946 5923 1470 2674 771 234 4114 50211 345 235933 +internal_value=0 0.00760115 0.00248228 -0.0283353 -0.104967 0.0224764 -0.0132946 0.0184065 -0.0242467 -0.00486328 -0.0471703 0.0981898 0.00646533 -0.0478022 -0.00533215 0.00438622 -0.0554413 -0.0969681 0.0231888 0.00964516 0.0358172 0.00428363 -0.0415047 0.0560414 -0.00100915 0.0121034 0.107493 0.0144785 0.0162357 0.0797582 0.00626018 +internal_weight=0 1250.74 848.685 92.6782 15.2057 4.42346 77.4724 402.051 40.5329 35.4167 25.1087 10.308 16.8886 9.69525 73.3691 66.4303 15.3877 12.2734 361.518 174.438 187.08 57.3085 30.4078 26.9007 12.7562 172.252 14.1445 169.879 168.431 13.7499 756.007 +internal_count=24290853 406062 275535 30089 4937 1437 25152 130527 13166 11503 8155 3348 5484 3150 23821 21567 4999 3987 117361 56628 60733 18604 9869 8735 4144 55916 4591 55145 54670 4459 245446 +is_linear=0 +shrinkage=0.1 + + +Tree=64 +num_leaves=32 +num_cat=0 +split_feature=1 16 6 3 20 16 6 14 9 20 17 12 17 7 10 16 9 13 16 16 11 21 16 14 20 7 16 20 7 7 16 +split_gain=7.18718 6.68296 8.57198 15.7471 10.9265 8.14047 7.03688 7.82366 6.33817 15.6904 10.1603 7.36905 11.4144 11.2123 9.12016 8.19812 7.58639 6.58843 23.2227 14.1734 7.41432 12.333 9.64532 15.0532 23.1904 13.762 13.5025 10.3349 12.556 8.49916 7.42497 +threshold=20447.500000000004 64.500000000000014 18.500000000000004 1240.5000000000002 6376.5000000000009 62.500000000000007 17.500000000000004 6.5000000000000009 4.5000000000000009 2.5000000000000004 35.500000000000007 58.500000000000007 12.500000000000002 393.00000000000006 9.5000000000000018 9.5000000000000018 14.500000000000002 10.500000000000002 36.500000000000007 1.0000000180025095e-35 6.5000000000000009 66.500000000000014 28.500000000000004 2.5000000000000004 7932.5000000000009 4.5000000000000009 43.500000000000007 10132.500000000002 10.500000000000002 10.500000000000002 24.500000000000004 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 5 6 -4 -5 8 -3 -8 17 10 11 13 16 -10 -14 -15 -13 20 19 -19 -2 -22 29 24 26 -25 -24 28 -27 30 -23 +right_child=1 2 3 4 -6 -7 7 -9 9 -11 -12 12 14 15 -16 -17 -18 18 -20 -21 21 22 23 25 -26 27 -28 -29 -30 -31 -32 +leaf_value=-0.00012755057218241076 -0.02530196362388426 0.01997630149155967 -0.15493977833181447 -0.16921092286622946 0.029205057727474587 -0.1129109650499911 -0.16443255859779368 0.113485330914606 -0.19336844376609819 0.10427222924008446 -0.0019811982041811828 -0.16295168494947743 0.073727489970027232 0.066437960579489752 -0.16349505309112411 -0.16701058404604929 0.044889962943068733 -0.081663259778913685 -0.11942846537237845 0.025472932062480965 0.14280449297271769 0.0058184787335589568 -0.066419267323216347 -0.13400846820135062 -0.18175235052998057 -0.17156508089774047 0.1243294254923587 0.10238108827946456 0.037158848615489813 -0.16993567980479263 0.08312179623699395 +leaf_weight=73566.33861485681 187.20801323885098 349.13406983819732 7.734798452933318 3.1325556172523674 24.343754332512617 6.0668861457670564 1.0981594831682731 13.050457079894839 3.1538700815290204 18.303006997681223 224.173059785855 3.8135848911042549 58.220235173008405 8.0914910978171957 1.6670611110748712 1.8478193222545076 3.2552540424512699 13.221103848482015 13.019592377619118 187.02215645358956 6.3013841940555713 30.01584730570903 4.9384777153609383 17.368246586818714 4.1981958187534465 5.5023059310624394 14.930549232754855 5.6927629953715941 6.0521748181199655 2.047879457473754 21.201357447658665 +leaf_count=23886350 60785 113364 2511 1020 7902 1970 357 4237 1025 5946 72786 1238 18904 2630 540 594 1057 4291 4227 60724 2040 9748 1604 5639 1362 1787 4852 1849 1965 665 6884 +internal_value=0 0.00753195 0.0182119 -0.028898 0.00658379 0.00250913 0.022778 0.0919144 0.00334151 0.0143494 0.00893938 0.0395218 0.0529387 -0.0290901 0.067124 0.0230375 -0.067239 -0.0035029 0.00998493 0.0183992 -0.0129198 0.00668327 -0.000978798 -0.0289439 0.031797 -0.0711754 0.0769185 -0.00790158 -0.0622366 0.0298307 0.0378182 +internal_weight=0 1245.81 398.494 35.2111 27.4763 847.312 363.283 14.1486 841.245 322.525 304.222 80.0493 66.9561 13.0932 59.8873 9.93931 7.06884 518.72 213.263 200.243 305.457 118.249 111.948 58.6827 24.0672 34.6155 19.869 17.2472 11.5545 53.2651 51.2172 +internal_count=24290853 404503 129391 11433 8922 275112 117958 4594 273142 104720 98774 25988 21739 4249 19444 3224 2295 168422 69242 65015 99180 38395 36355 19058 7818 11240 6456 5601 3752 17297 16632 +is_linear=0 +shrinkage=0.1 + + +Tree=65 +num_leaves=32 +num_cat=0 +split_feature=3 18 2 7 7 18 2 16 7 3 12 16 16 16 16 7 6 18 4 16 16 19 13 18 16 9 4 16 16 16 9 +split_gain=5.29584 7.70238 11.4555 10.2499 9.92315 15.6102 17.0063 9.10547 8.92882 14.3363 10.0641 8.68796 7.50577 6.58497 9.83719 6.4217 5.75117 5.6455 5.61856 5.57264 8.80137 6.01934 5.52706 10.8948 5.50735 5.39112 5.29327 5.28031 5.02465 8.80175 14.8962 +threshold=17534.500000000004 123.50000000000001 24208.500000000004 12.500000000000002 45.500000000000007 107.50000000000001 48495.000000000007 106.50000000000001 72.500000000000014 22301.500000000004 1.0000000180025095e-35 213.50000000000003 186.50000000000003 143.50000000000003 116.50000000000001 49.500000000000007 13.500000000000002 462.50000000000006 21932.500000000004 75.500000000000014 49.500000000000007 1.5000000000000002 22.500000000000004 209.50000000000003 35.500000000000007 95.500000000000014 290.50000000000006 132.50000000000003 38.500000000000007 45.500000000000007 6.5000000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=18 3 8 27 7 12 11 19 9 -3 25 -7 13 14 15 -6 22 -12 -1 20 21 -5 -4 -24 -8 -10 -15 -2 -17 30 -30 +right_child=1 2 16 4 5 6 24 -9 10 -11 17 -13 -14 26 -16 28 -18 -19 -20 -21 -22 -23 23 -25 -26 -27 -28 -29 29 -31 -32 +leaf_value=-0.0001174776098449413 -0.17219181580229684 -0.17697265058131018 0.014557355880405375 0.032725912446272098 -0.12128679929655445 -0.15229254166324124 0.086117391935147919 -0.14207288829870521 0.038008030690930644 0.080193566092237253 0.075398134743229983 0.060650421519575684 -0.14000924909006793 -0.1701565372752373 -0.16264879852954012 0.0011885566274177609 -0.093901574327432624 -0.16514316406970642 0.10187176211963873 0.12867386114146104 -0.17354676735479135 0.16059443183603775 -0.17766660304015608 0.073512412909845476 -0.15921460422283232 -0.16498235596799085 0.090565546559761581 0.095704136248092084 0.10495565136581986 0.0062870237116228163 -0.16111649173984199 +leaf_weight=73543.169047529664 4.8035536654060698 4.2068115936126542 113.55552333231026 74.724513695458882 4.1390730725834137 16.326441292418174 6.4327793673146507 2.8027013305108985 2.4347453953814684 4.4723169535573106 71.754459090880118 2.17072528542485 3.6670614650065536 0.83179095375817169 3.5999920202593776 815.62957079097396 5.6662980986438916 0.98916678951354597 5.4019239598419508 6.6997946014162144 1.9972597081214178 3.8722476614639154 4.2658483026316381 2.9013269336137455 1.0667709412518886 2.8281096470309421 12.199205681332385 0.86881836404791091 21.290310583135582 58.205581006390275 2.3349140213103956 +leaf_count=23881009 1566 1366 36873 24274 1341 5300 2089 909 790 1452 23298 705 1193 268 1170 264842 1838 322 1754 2176 649 1257 1386 942 347 919 3963 282 6912 18902 759 +internal_value=0 0.00643674 0.023763 0.00289941 0.00363201 0.000617598 -0.0758023 0.0353462 0.0517607 -0.044456 0.062466 -0.127303 0.00277257 0.00334279 0.00232673 0.00298546 0.00456035 0.0721273 -0.000109987 0.0410425 0.0337577 0.0390256 0.00918181 -0.0759874 0.0512202 -0.0710733 0.0739232 -0.131159 0.00355861 0.0271815 0.0786594 +internal_weight=0 1256.74 213.075 1043.66 1037.99 947.894 25.9967 90.0965 86.6856 8.67913 78.0065 18.4972 921.897 918.23 905.199 901.599 126.389 72.7436 73548.6 87.2938 80.594 78.5968 120.723 7.16718 7.49955 5.26286 13.031 5.67237 897.46 81.8308 23.6252 +internal_count=24290853 408090 69186 338904 337056 307791 8441 29265 28147 2818 25329 6005 299350 298157 293926 292756 41039 23620 23882763 28356 26180 25531 39201 2328 2436 1709 4231 1848 291415 26573 7671 +is_linear=0 +shrinkage=0.1 + + +Tree=66 +num_leaves=32 +num_cat=0 +split_feature=8 12 20 20 13 7 8 4 4 8 9 6 8 9 12 8 20 17 20 9 20 12 17 4 20 4 8 16 12 20 4 +split_gain=3.09036 4.06464 7.54288 4.89925 4.34307 4.9959 3.45297 27.339 24.302 18.0255 30.1901 38.0924 26.6073 24.2111 21.5769 20.0021 19.6577 22.1561 19.9095 18.0988 17.7569 18.0874 16.3334 15.2469 34.4105 27.2799 29.1218 30.9498 18.5209 17.9308 17.1329 +threshold=75743.500000000015 14.500000000000002 1.5000000000000002 2.5000000000000004 138.50000000000003 94425.000000000015 5306.5000000000009 338.00000000000006 306.50000000000006 6773.0000000000009 16889.000000000004 1.0000000180025095e-35 7033.0000000000009 81.500000000000014 5.5000000000000009 6545.0000000000009 13.500000000000002 4.5000000000000009 45.500000000000007 896.00000000000011 97.500000000000014 356.50000000000006 15.500000000000002 84.500000000000014 169.50000000000003 107.50000000000001 8050.5000000000009 21.500000000000004 3277.5000000000005 1956.0000000000002 82.500000000000014 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=6 2 4 -4 -2 -6 -1 8 9 10 11 14 -11 22 -8 -12 -16 -18 -19 -15 -20 -22 -13 30 -25 29 27 -27 -28 -26 -14 +right_child=1 -3 3 -5 5 -7 7 -9 -10 12 15 13 23 19 16 -17 17 18 20 -21 21 -23 -24 24 25 26 28 -29 -30 -31 -32 +leaf_value=8.9193217065950932e-05 0.075291341393028191 0.12122920874481541 0.11856647111683552 -0.14435446685976117 -0.0092521131292754352 -0.16386492097368463 -0.19880632716277949 0.077147572546758081 -0.19823053051375705 0.069718034574553694 0.043782875114555357 0.083701691226718652 0.00042898998528299284 -0.20046936860098552 0.078229053836236129 -0.19988441217536643 -0.15707067392692503 -0.19925499159455506 0.053759799978296545 -0.021946376817309123 -0.14684561369883109 0.029536138172588641 -0.20127548668514891 -0.05715409273086472 -0.12786457673446602 -0.19701897214930095 0.055542463274815518 0.10890413541693872 -0.20314413526080277 0.053111024320094874 0.10246682792661516 +leaf_weight=73170.122103690752 2.7029843362397576 4.532821766100823 6.9999694918515152 0.7885675849393009 5.3477069103391823 3.4305479861213826 15.857723572524263 39.894955348252552 6.5955468127503982 50.954468962969258 59.311839297879487 31.151331053755712 933.72831280826358 6.4075890493695615 7.9213140064384779 3.5717072191182515 28.691234659054317 7.3090791121358043 18.304438679129817 49.933972795843147 14.617005750071259 9.6536686902400088 2.1500152535736552 151.83246726635844 20.337885709828701 10.334336180356329 105.08478537504561 4.8632246993947765 2.8425397546961895 7.4912050828570491 16.750566174043342 +leaf_count=23761712 877 1472 2274 255 1737 1115 5147 12955 2142 16543 19262 10116 303230 2081 2571 1160 9320 2373 5944 16213 4746 3137 698 49310 6604 3356 34128 1579 922 2434 5440 +internal_value=0 0.0360266 0.0159844 0.0919465 -0.0355461 -0.0696749 -1.1469e-05 -0.00459886 -0.00668181 -0.00587149 -0.0301942 -0.0498905 -0.00111814 -0.0022949 -0.0915752 0.0299429 -0.0719162 -0.0870525 -0.0467811 -0.0422494 -0.0206052 -0.0766897 0.0653029 -0.00399816 -0.0235403 0.0102691 0.0304795 -0.0991235 0.0487293 -0.0791485 0.00222723 +internal_weight=0 23.8026 19.2698 7.78854 11.4812 8.77825 74775.7 1605.59 1565.7 1559.1 254.881 191.997 1304.22 89.6429 102.354 62.8835 86.4967 78.5754 49.8842 56.3416 42.5751 24.2707 33.3013 1253.27 302.786 150.954 123.125 15.1976 107.927 27.8291 950.479 +internal_count=24290853 7730 6258 2529 3729 2852 24283123 521411 508456 506314 82768 62346 423546 29108 33238 20422 28091 25520 16200 18294 13827 7883 10814 407003 98333 49023 39985 4935 35050 9038 308670 +is_linear=0 +shrinkage=0.1 + + +Tree=67 +num_leaves=32 +num_cat=0 +split_feature=1 15 16 0 15 2 10 16 17 2 16 8 16 7 6 11 11 16 15 16 3 15 16 16 8 7 16 10 15 15 16 +split_gain=8.34603 7.23611 4.49664 14.6666 6.30314 13.5176 13.8268 9.64789 6.74879 5.85979 5.32983 11.8502 8.51752 4.88456 4.63586 3.94842 5.17799 9.03095 14.101 8.10327 10.4817 8.36764 11.1557 11.5172 4.51328 8.80392 8.98816 5.45223 11.6286 13.5013 5.54222 +threshold=20447.500000000004 408.50000000000006 241.50000000000003 4486.5000000000009 209.50000000000003 550.00000000000011 1414.0000000000002 148.50000000000003 307.50000000000006 144.50000000000003 143.50000000000003 3.5000000000000004 138.50000000000003 7.5000000000000009 2.5000000000000004 41.500000000000007 21.500000000000004 103.50000000000001 185.50000000000003 86.500000000000014 711.00000000000011 25.500000000000004 60.500000000000007 69.500000000000014 1.5000000000000002 4.5000000000000009 11.500000000000002 260.50000000000006 120.50000000000001 128.50000000000003 53.500000000000007 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 4 -4 7 9 -7 10 -9 -6 12 -12 15 14 -11 16 24 19 -19 20 21 -18 -23 -24 -2 26 -26 30 -29 -30 -27 +right_child=1 -3 3 -5 5 6 -8 8 -10 13 11 -13 -14 -15 -16 -17 17 18 -20 -21 -22 22 23 -25 25 27 -28 28 29 -31 -32 +leaf_value=-0.00013673991933998208 0.018270077410716609 0.053898857459672517 0.084224453649461434 -0.1064072354789623 -0.089446366505894478 -0.14990979888625408 0.061751294088599806 -0.12234048810411804 0.074344320286446261 -0.17206722812468211 -0.17516845414540391 0.10835353817371468 -0.1397653956231254 -0.14881128172878264 0.082503430316751503 0.083302779875082972 0.10528285636919575 -0.18473750858457216 0.10082434903860205 0.067290989746848009 -0.11265369939146441 -0.15555525522396957 0.1164189289337946 -0.16555441786986147 0.15312925198956928 0.0021784892478301523 -0.15971198504526518 -0.14466926164455943 0.1425869823191597 -0.11297351342870399 0.026453058547456422 +leaf_weight=73512.326508997736 292.04887476238946 33.652136092772707 6.0683859136188429 12.049935190065296 2.3374538053758434 6.7258751859772001 5.7034608343383297 9.2891807147097989 2.1479473981307811 0.72779846226330747 1.698467734036966 11.163635033066383 3.9851200823904938 0.96990900195669283 41.794433375471272 6.6870631946367203 6.3719691280275645 6.9999384214170259 2.2965445161098614 11.228680668165905 10.484861414239274 4.7615651061059889 5.399725096533075 1.9795924413483588 1.0939015001058567 610.60273490515829 5.7236290342407301 7.4149373250547788 4.5866097598336619 3.7634712853468955 111.18052255194925 +leaf_count=23890809 94911 10935 1966 3920 761 2187 1853 3020 698 235 550 3630 1295 316 13584 2173 2067 2274 747 3651 3407 1547 1758 644 353 198443 1864 2409 1491 1224 36131 +internal_value=0 0.00816612 0.00688071 -0.0425588 0.00764036 0.0397101 -0.0527847 0.00597355 -0.0854021 0.0647953 0.00691551 0.0709139 0.00616487 0.0730849 0.0781463 0.00669712 0.0062254 -0.025364 -0.114194 -0.00483505 -0.0327642 0.0124817 -0.0362236 0.0407761 0.00773483 0.00360137 -0.109515 0.00464697 -0.0535296 0.0274031 0.00591764 +internal_weight=0 1230.94 1197.29 18.1183 1179.17 58.2589 12.4293 1120.91 11.4371 45.8296 1109.47 12.8621 1096.61 43.4921 42.5222 1092.62 1085.94 49.5229 9.29648 40.2264 28.9977 18.5129 12.1409 7.37932 1036.41 744.366 6.81753 737.548 15.765 8.35008 721.783 +internal_count=24290853 400044 389109 5886 383223 18936 4040 364287 3718 14896 360569 4180 356389 14135 13819 355094 352921 16095 3021 13074 9423 6016 3949 2402 336826 241915 2217 239698 5124 2715 234574 +is_linear=0 +shrinkage=0.1 + + +Tree=68 +num_leaves=32 +num_cat=0 +split_feature=1 15 11 15 14 10 9 13 15 11 14 14 14 3 13 8 2 14 13 2 14 15 15 15 2 14 14 14 9 1 14 +split_gain=8.66932 5.08554 4.4668 8.80002 4.05875 5.93206 13.2457 11.6382 5.06429 7.51281 6.39619 4.80313 4.25674 4.32781 13.4251 8.65009 7.2366 10.9159 6.30007 5.99342 9.65358 8.93587 13.4427 10.3895 8.2487 7.81976 10.6763 5.9752 5.64837 5.61635 4.48184 +threshold=22211.000000000004 394.50000000000006 13.500000000000002 570.00000000000011 129.50000000000003 199.50000000000003 1.0000000180025095e-35 1.5000000000000002 301.50000000000006 17.500000000000004 10.500000000000002 12.500000000000002 120.50000000000001 1367.5000000000002 1.5000000000000002 6.5000000000000009 633.50000000000011 109.50000000000001 1.5000000000000002 144.50000000000003 66.500000000000014 128.50000000000003 185.50000000000003 61.500000000000007 122.00000000000001 5.5000000000000009 13.500000000000002 77.500000000000014 1.0000000180025095e-35 67705.000000000015 4.5000000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 4 -3 -4 8 6 -6 -7 12 -10 -11 -12 13 14 15 19 17 18 -15 20 21 23 -23 25 -22 -2 -27 -18 -25 -16 -24 +right_child=1 2 3 -5 5 7 -8 -9 9 10 11 -13 -14 16 29 -17 27 -19 -20 -21 24 22 30 28 -26 26 -28 -29 -30 -31 -32 +leaf_value=-0.00013483857392471564 -0.16915129887821459 0.06454475835726628 -0.17738638266415177 0.056173251437827677 -0.10984421297467317 0.12846106153919967 0.092020679812060732 -0.14437089201389761 -0.1624230345086804 -0.18389298894140699 0.13242059928170624 -0.15462247824207084 -0.16913467921583006 -0.18073242618188903 -0.1654065630464352 -0.15455717897024335 0.0052745639502651889 0.14649820305142203 0.12606505095511161 0.032500536002706186 0.14981898633376778 -0.19433293342843833 0.12579757503818179 0.057292114617968917 -0.16768405605849912 0.071244708272310872 -0.15825339093064048 -0.14408736494333049 -0.17925548857955043 0.07465753445288878 -0.15513770596472609 +leaf_weight=73577.238340175114 4.8599664493231094 27.583644239231944 2.4588319740141733 4.6906753365183249 6.0915434503112937 11.782491632271556 6.9695895599434152 1.8026958250557061 4.3752847650321183 1.213634754065424 3.1484583956189458 0.71540866408031423 1.3683703647111531 5.8799408255144936 5.4007262974628238 2.7277424174826583 816.33120164879074 1.5822052955627439 0.75531123252585519 193.90810792701086 5.8038820559158912 6.2672797818668169 2.3948823991231625 19.568342128768567 0.95255166105926026 5.6466295630671066 3.1622056345222518 2.6871971873624707 1.0643598067108531 1.1891095475584732 0.74436387210153032 +leaf_count=23916035 1578 8966 801 1524 1979 3833 2267 584 1422 391 1024 235 445 1912 1755 887 265344 514 246 63029 1885 2037 781 6360 310 1835 1032 871 345 386 240 +internal_value=0 0.00860354 0.0462874 -0.0241517 0.00743322 0.0459936 -0.00212649 0.0922574 0.00649208 -0.066385 0.016371 0.0792736 0.00712859 0.00735172 0.0187778 0.0225345 0.00384765 -0.0895274 -0.145809 0.0245113 -0.00618714 -0.0233832 -0.109727 0.00029481 0.105056 -0.0673217 -0.0111408 0.00478451 0.0450896 -0.122088 0.0591835 +internal_weight=0 1153.13 34.7332 7.14951 1118.39 26.6463 13.0611 13.5852 1091.75 9.45279 5.0775 3.86387 1082.29 1080.93 253.69 247.1 827.236 8.21746 6.63525 244.373 50.4645 43.708 9.40653 34.3015 6.75643 13.6688 8.80884 819.018 20.6327 6.58984 3.13925 +internal_count=24290853 374818 11291 2325 363527 8663 4246 4417 354864 3072 1650 1259 351792 351347 82460 80319 268887 2672 2158 79432 16403 14208 3058 11150 2195 4445 2867 266215 6705 2141 1021 +is_linear=0 +shrinkage=0.1 + + +Tree=69 +num_leaves=32 +num_cat=0 +split_feature=3 18 17 9 2 18 9 7 8 3 7 9 7 18 13 17 3 13 6 8 17 5 18 17 5 5 18 9 7 10 18 +split_gain=4.93755 4.62713 10.7852 18.6271 14.3085 8.03562 9.87834 9.41031 8.03047 7.42125 6.99505 6.87047 6.85821 7.08997 21.0213 10.2899 27.2965 16.0257 9.61292 6.93436 6.87726 18.484 17.0106 9.54809 11.4721 14.2803 11.1482 21.3609 15.9043 11.6506 10.058 +threshold=17534.500000000004 84.500000000000014 105.50000000000001 15.500000000000002 57685.500000000007 87.500000000000014 6.5000000000000009 1839.0000000000002 21.500000000000004 19552.000000000004 13.500000000000002 4.5000000000000009 25.500000000000004 74.500000000000014 13.500000000000002 87.500000000000014 32218.500000000004 6.5000000000000009 6.5000000000000009 14.500000000000002 84.500000000000014 697.00000000000011 55.500000000000007 63.500000000000007 228.50000000000003 293.50000000000006 55.500000000000007 10.500000000000002 296.50000000000006 1.5000000000000002 63.500000000000007 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 10 4 -4 6 9 -7 -8 -3 -2 -6 -12 15 -15 20 -17 -18 19 -19 23 -22 -23 24 -14 -26 27 28 -25 -30 -28 +right_child=1 5 3 -5 11 7 8 -9 -10 -11 12 -13 13 14 -16 16 17 18 -20 -21 21 22 -24 26 25 -27 30 -29 29 -31 -32 +leaf_value=-0.00010603497507174259 -0.15519414024958444 -0.19246850571861748 -0.12458991059255903 0.12157497578469295 0.099526338890521343 0.007597230146620948 -0.17571690839106516 0.080936775003393857 0.1559251664852927 0.1094060261671308 0.058940727833826138 -0.15573328578393741 -0.069997774781530617 0.082546779869848805 -0.17446498166749164 -0.18334196861067775 -0.17902577577765455 0.081125665736427155 -0.16668204360172534 -0.17509538321851081 0.10242183317105918 0.094904379141847295 -0.17328700129680386 -0.14866339556261687 0.066902687403702338 0.0050645908606853363 0.026720468338794219 0.09395364636536005 0.0062397278737273271 -0.18837342622188125 -0.12037204796988249 +leaf_weight=73471.274152393366 2.7308119712397447 0.84855188336223974 21.579656245594382 5.1301963720761696 5.0310378000140181 301.53472492979199 2.583141063572838 18.573188004316762 1.017823273316026 20.218907561386004 23.035818813601509 1.3340390977682544 18.961179119069129 26.301340517005887 3.6204618953634045 10.801865823799742 3.8637748667970326 17.324620477505967 1.9739080839790393 1.1248557865619648 21.370046889875088 4.0030473176157084 5.7795359574956819 21.138897942728363 39.838740272272844 596.41846261169121 39.194308654114138 7.7053896937286472 18.067506678402427 3.7073087947210288 5.2742343498393884 +leaf_count=23884465 893 276 7015 1667 1636 98033 838 6038 330 6572 7494 433 6171 8548 1178 3513 1256 5629 642 366 6944 1302 1881 6876 12954 193869 12741 2505 5870 1204 1714 +internal_value=0 0.00623192 0.00247739 -0.0535734 -0.0857278 0.0160905 0.0710849 0.0118525 -0.0819773 0.0972472 0.00460282 0.0460272 0.00510469 0.00363959 0.051449 0.00188765 -0.0510887 0.00773182 0.0430633 0.065504 0.00426639 0.0503055 -0.0635427 0.00235485 0.00665228 0.00893653 -0.0272573 -0.0593502 -0.0868767 -0.0268945 0.00927442 +internal_weight=0 1250.09 905.311 33.0749 27.9447 344.776 24.6684 320.108 3.60096 21.0675 872.236 6.36508 869.505 846.469 29.9218 816.548 35.089 24.2872 20.4234 18.4495 781.459 31.1526 9.78258 750.306 655.218 636.257 95.0876 50.6191 42.9137 21.7748 44.4685 +internal_count=24290853 406388 294301 10751 9084 112087 8016 104071 1168 6848 283550 2069 282657 275163 9726 265437 11406 7893 6637 5995 254031 10127 3183 243904 212994 206823 30910 16455 13950 7074 14455 +is_linear=0 +shrinkage=0.1 + + +Tree=70 +num_leaves=32 +num_cat=0 +split_feature=1 19 14 16 5 4 7 5 18 16 14 21 15 7 2 14 15 21 14 15 14 8 15 2 14 15 14 10 6 4 16 +split_gain=10.2674 5.77603 5.78871 12.6646 12.8546 11.4653 11.8713 10.5001 9.46654 10.8511 8.64747 15.9075 8.33016 7.06299 6.2276 7.87836 6.20863 6.0405 5.85926 8.86433 5.84217 6.73093 5.62691 5.59382 5.06485 4.95218 7.05961 5.80501 5.65002 5.60825 8.54556 +threshold=16619.500000000004 2.5000000000000004 58.500000000000007 52.500000000000007 16.500000000000004 5.5000000000000009 1.5000000000000002 18.500000000000004 2.5000000000000004 66.500000000000014 48.500000000000007 50488.500000000007 168.50000000000003 4.5000000000000009 100.50000000000001 2.5000000000000004 408.50000000000006 47069.500000000007 42.500000000000007 86.500000000000014 62.500000000000007 1.5000000000000002 229.50000000000003 238.00000000000003 36.500000000000007 283.50000000000006 27.500000000000004 1414.0000000000002 19.500000000000004 10.500000000000002 143.50000000000003 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 7 4 -4 6 17 24 9 13 14 -12 -11 -8 15 -9 18 -5 -17 -20 -14 -22 -7 -24 -2 27 28 -16 -27 30 -29 +right_child=1 -3 3 5 -6 22 8 10 -10 12 11 -13 20 -15 25 16 -18 -19 19 -21 21 -23 23 -25 -26 26 -28 29 -30 -31 -32 +leaf_value=-0.00016327518328932637 -0.17385497983685858 0.06602560280385894 0.1271749766062974 0.11067416714414464 -0.12643112462488079 0.11577532725752511 -0.1502030237382142 0.040873296642496726 0.089306328045238553 -0.16625409979918412 -0.17395753491015822 0.075020467959296705 0.10248369383169952 0.12926886175936556 0.0080329688593078138 -0.17704656722806333 0.090387907666241399 -0.15041428902175538 0.14087366622415518 -0.19554287860421382 -0.14849652161480195 0.029336507614956708 0.091507259741582622 -0.16999601380397472 0.038877970939719071 0.061037719919205341 -0.078657582150960026 -0.12456251424243647 -0.18194526679926057 0.025851661268975312 0.049923635555819787 +leaf_weight=73295.75214079376 4.7427577994531012 17.193350580579136 2.6181295973947263 13.179832306690511 8.4471076917252486 24.318820314714689 1.126472647418274 6.1263211489422273 6.5267771002836517 7.3443914109375354 6.9175219235476089 4.079474772704998 2.5896131861954919 4.5850289402296767 1220.1334072901082 7.1177910782862437 1.5101675591431547 0.95000253416947167 1.708771644625813 1.4460413698107002 4.7509487366187368 3.855713892611675 1.8689671299653121 1.4546801408287136 1.4648361171130089 38.130698340537492 4.4003671004902563 10.896586292365102 0.98160495271440495 7.6798641704954198 3.7807178426883175 +leaf_count=23828582 1544 5589 850 4283 2745 7905 365 1995 2122 2389 2251 1326 846 1491 396661 2315 492 309 556 470 1542 1252 610 472 476 12396 1432 3541 319 2498 1229 +internal_value=0 0.00841625 0.00771114 0.0332275 -0.0664258 0.0484262 0.0172378 0.00609615 -0.0175979 -0.0463681 0.0067087 -0.081596 -0.0834937 0.074149 0.00745345 -0.0511092 -0.0989344 0.0931202 -0.126767 -0.0133264 -0.0292055 -0.0688288 0.0990958 -0.0229464 -0.123655 0.008269 0.0414291 0.00710772 0.0549396 -0.0433875 -0.0796167 +internal_weight=0 1421.93 1404.73 83.6165 11.0652 72.5512 44.9088 1321.12 30.7789 24.2522 1314.91 10.997 18.5407 5.7115 1303.91 17.9091 11.7828 14.1298 10.2726 3.15481 11.1963 8.60666 27.6425 3.32365 6.20759 1286 43.5127 1242.49 39.1123 22.3572 14.6773 +internal_count=24290853 462271 456682 27181 3595 23586 14599 429501 10007 7885 427481 3577 6029 1856 423904 5828 3833 4592 3341 1026 3640 2794 8987 1082 2020 418076 14147 403929 12715 7268 4770 +is_linear=0 +shrinkage=0.1 + + +Tree=71 +num_leaves=32 +num_cat=0 +split_feature=1 14 15 11 15 20 16 14 10 10 20 14 16 15 1 14 14 6 20 7 15 10 11 14 1 16 14 15 11 10 20 +split_gain=9.53306 5.68317 4.55158 8.36962 8.25659 6.085 6.57901 10.3577 6.45178 5.30952 7.10285 5.10845 5.06547 7.21959 4.26784 4.0143 5.9343 10.946 10.4163 10.1992 8.66967 6.69232 6.56398 6.00918 21.7134 5.67786 5.48805 5.13961 4.43707 4.27524 4.02035 +threshold=22546.500000000004 103.50000000000001 209.50000000000003 9.5000000000000018 269.50000000000006 5745.0000000000009 164.50000000000003 24.500000000000004 70.500000000000014 1414.0000000000002 11930.000000000002 89.500000000000014 28.500000000000004 30.500000000000004 31531.500000000004 77.500000000000014 34.500000000000007 11.500000000000002 9231.5000000000018 9.5000000000000018 104.50000000000001 133.50000000000003 21.500000000000004 23.500000000000004 50065.500000000007 24.500000000000004 27.500000000000004 196.50000000000003 41.500000000000007 209.50000000000003 6717.5000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 9 4 11 -5 -7 8 -8 15 12 -4 13 -11 -6 16 23 18 22 -20 21 -19 25 27 -25 -18 -26 28 -2 30 -23 +right_child=1 -3 3 5 14 6 7 -9 -10 10 -12 -13 -14 -15 -16 -17 17 20 19 -21 -22 29 -24 24 26 -27 -28 -29 -30 -31 -32 +leaf_value=-0.00013940226777714581 0.0065198226068957678 0.048035929790646387 -0.1350518619460605 -0.19544501809816503 -0.092275331390335558 0.12107043951917085 -0.11258361787606859 -0.11800721757741649 0.075563995051804389 -0.13268186791055658 0.11650727516029909 0.098813596208269072 -0.14216025670161161 0.10527327165583994 0.036858208500109367 -0.090215326958248035 -0.15262084718199001 -0.15582061923920512 -0.15773221887295996 0.12831511938189319 -0.16706182376413409 -0.14436011528713158 -0.15414612615542095 -0.15629238977764184 -0.15071884922443746 0.098136381818605095 0.062486977884507237 -0.1613722702780537 0.090625778546565672 -0.15816410871051004 0.12007311243979808 +leaf_weight=73586.363413870786 906.40972875432635 36.37162071900093 6.3004047577269402 0.91534436598885704 2.9234242634847787 14.117665293859316 1.9778940354008288 3.7184307179122689 23.207028042583261 2.4736977722495777 2.0498131029307833 1.0965883700409902 6.7220378674683161 2.631314465746982 20.551205270807259 4.2345541421673252 0.9276285576634099 1.8223147871904055 3.7481622379273167 1.8675716274883596 3.8773268365766844 0.64739523828029621 1.1230363908689458 9.9368919672560896 1.3477376509690646 33.981285561603727 11.587290300056337 1.8143732189200807 6.3162356871180227 0.785370313678868 5.1381358737125993 +leaf_count=23926486 294719 11826 2048 296 952 4591 645 1209 7546 806 666 355 2187 853 6682 1376 300 592 1218 607 1261 212 365 3231 438 11051 3768 590 2053 255 1669 +internal_value=0 0.00915387 0.00784956 0.0316499 -0.00825363 0.0596879 0.0651163 0.0377858 0.0607879 0.00608575 -0.0553436 -0.100382 -0.0851281 -0.0100309 0.0207765 0.006942 0.00735702 0.0396177 0.0640743 -0.0626042 -0.043391 0.0137399 0.0838176 0.00550144 -0.0451267 0.0914731 0.0402725 0.00676761 0.00710185 0.0607643 0.0904833 +internal_weight=0 1120.62 1084.25 74.808 30.8716 43.9364 43.021 28.9034 25.1849 1009.44 13.8769 7.39699 11.8271 5.10501 23.4746 995.565 991.33 53.9182 41.6477 5.61573 12.2705 8.39322 36.032 937.412 22.8719 34.9089 12.935 914.54 912.726 6.5709 5.78553 +internal_count=24290853 364367 352541 24324 10037 14287 13991 9400 8191 328217 4512 2403 3846 1659 7634 323705 322329 17530 13541 1825 3989 2728 11716 304799 7437 11351 4206 297362 296772 2136 1881 +is_linear=0 +shrinkage=0.1 + + +Tree=72 +num_leaves=32 +num_cat=0 +split_feature=1 14 5 14 9 5 5 11 20 20 13 13 20 20 14 2 6 8 5 17 4 4 17 17 5 2 14 20 14 2 2 +split_gain=9.01361 5.56221 7.48487 7.18414 5.71213 4.94014 9.43931 7.47645 5.63353 8.30132 5.57214 6.9598 5.70653 5.05526 6.74033 7.35777 5.59874 4.45767 4.25789 4.23614 12.0631 5.07877 4.33327 4.47205 4.23521 4.01578 3.97146 3.86179 12.8261 20.0388 19.9966 +threshold=20447.500000000004 34.500000000000007 18.500000000000004 32.500000000000007 8.5000000000000018 32.500000000000007 54.500000000000007 21.500000000000004 5522.5000000000009 11369.500000000002 2.5000000000000004 14.500000000000002 3074.0000000000005 9231.5000000000018 62.500000000000007 242.50000000000003 5.5000000000000009 1.5000000000000002 127.50000000000001 262.50000000000006 231.50000000000003 235.50000000000003 781.00000000000011 568.50000000000011 59.500000000000007 2419.0000000000005 37.500000000000007 7075.5000000000009 1.0000000180025095e-35 633.50000000000011 923.50000000000011 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=27 2 -2 19 5 8 -7 10 -3 26 12 -12 -8 16 15 -15 -14 -13 -17 -4 -21 -22 23 -23 -16 -18 -10 -1 -29 -30 -31 +right_child=1 4 3 -5 -6 6 7 -9 9 -11 11 17 13 14 24 18 25 -19 -20 20 21 22 -24 -25 -26 -27 -28 28 29 30 -32 +leaf_value=-5.1477496353778027e-05 -0.15376315800281748 0.11378431455427965 0.0085527168805145781 -0.16160834667561458 -0.1684255063807005 -0.16177871297915469 -0.1385438075899722 -0.094625447219129993 0.090970251874092342 0.11340664397103913 -0.11004738787217844 -0.17940606100422649 -0.099285789739738381 -0.14962726315943936 0.079589632609876851 -0.15480190684668771 0.073609012200077817 0.10527678861064199 0.093810401006134789 -0.15086978913181476 0.21426375673950995 -0.0090376607276792826 0.10889474617650308 -0.1648117588105415 -0.14483155107726664 -0.14681443021715618 -0.13819617893672329 0.034790135254638949 -0.0062308583088746469 -0.15903019701089072 -0.011268682913849802 +leaf_weight=72420.555742170487 2.909544200054369 13.807027714094149 1033.153105634934 2.525853195344097 1.4356459250557225 2.7150686804670832 1.6674495224142436 4.730986432376084 0.97692699497565438 4.0036178614245728 5.5286497888155264 0.6515368652762844 2.0303416739334343 1.7065122775966326 1.0703696217387904 0.74476818231050756 50.670716683845967 3.5304001400363632 9.1816739465866686 6.316548721457365 1.0570989623665799 33.229408006111044 2.930117815732955 1.9511767919320835 3.9225353907677345 0.84022628219099615 3.3472851177211851 71.756636352627538 975.51343689559144 17.449651224422269 19.275839948328212 +leaf_count=23549249 943 4493 335961 821 465 886 542 1536 318 1302 1797 211 660 555 352 244 16472 1149 2981 2052 339 10813 954 635 1273 274 1089 23337 317207 5674 6269 +internal_value=0 0.00860918 0.00641229 0.00684334 0.0297673 0.0323277 0.0218123 0.0275899 0.074604 0.00964551 0.0346804 -0.0364174 0.0442914 0.0486363 0.000468406 0.0421823 0.0635935 0.0609238 0.0751573 0.00723781 -0.0226297 -0.00194855 -0.00794576 -0.0176772 -0.0967206 0.0700136 -0.0864228 -0.000140156 -0.00606469 -0.00896085 -0.0814757 +internal_weight=0 1196.63 1084.07 1081.16 112.562 111.126 88.9912 86.2762 22.1349 8.32783 81.5452 9.71059 71.8346 70.1671 16.6259 11.633 53.5413 4.18194 9.92644 1078.64 45.4844 39.1678 38.1107 35.1806 4.99291 51.5109 4.32421 73504.6 1084 1012.24 36.7255 +internal_count=24290853 389117 352518 351575 36599 36134 28932 28046 7202 2709 26510 3157 23353 22811 5405 3780 17406 1360 3225 350754 14793 12741 12402 11448 1625 16746 1407 23901736 352487 329150 11943 +is_linear=0 +shrinkage=0.1 + + +Tree=73 +num_leaves=32 +num_cat=0 +split_feature=1 14 14 15 6 16 16 2 1 16 15 21 10 16 10 9 0 15 15 0 2 16 0 18 7 1 1 16 14 21 14 +split_gain=8.63737 6.42205 9.34743 7.61548 8.62354 5.22032 10.9156 7.1696 5.79554 6.58716 5.79093 18.0667 6.8812 5.75319 13.2805 11.0592 9.53034 5.98222 15.9976 11.4462 10.0842 7.76086 11.9423 9.31504 7.63042 7.36501 7.79983 6.74881 5.46864 8.49608 5.10963 +threshold=16619.500000000004 54.500000000000007 48.500000000000007 67.500000000000014 1.5000000000000002 132.50000000000003 128.50000000000003 85.500000000000014 17216.000000000004 196.50000000000003 157.50000000000003 41057.500000000007 188.50000000000003 112.50000000000001 136.50000000000003 6.5000000000000009 8379.0000000000018 128.50000000000003 137.50000000000003 38097.000000000007 1736.5000000000002 94.500000000000014 7379.5000000000009 142.50000000000003 6.5000000000000009 18331.000000000004 47037.500000000007 92.500000000000014 5.5000000000000009 9050.5000000000018 36.500000000000007 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 5 4 -3 6 7 -2 9 -7 13 -12 -13 17 15 16 -15 21 -19 20 -20 27 -23 25 -21 -24 -27 30 -18 -30 -9 +right_child=1 3 -4 -5 -6 8 -8 10 -10 -11 11 12 -14 14 -16 -17 28 18 19 24 -22 22 23 -25 -26 26 -28 -29 29 -31 -32 +leaf_value=-0.00014873254524469672 -0.1165546366491813 0.10842477602576657 -0.15225459357236082 0.046189979486171796 -0.10787754933566528 -0.2046684505396168 -0.16909171733104392 0.004298875052090703 0.020769706395463709 0.15737773162282373 -0.17250046360147525 0.069576481494549383 -0.15954913873896057 -0.15385272427040461 -0.17179665647982237 -0.17208235373759687 0.13497526032027682 -0.18246827234635588 -0.18787619067446282 0.075589324910013439 0.13128520654983764 -0.15529930148985782 -0.19057095709472641 -0.16974958691596662 -0.16978244045488589 0.10506595111915168 -0.16226566568308065 -0.16937024982331297 0.13809866186863809 -0.16982055768539397 0.055599060273621451 +leaf_weight=73282.34327096105 4.8364806141471481 2.521764391451141 3.7171523156284811 71.654888508506701 6.8493934627622366 0.5556032401509573 3.6147223820880745 1049.2536363599502 129.38854372684727 5.2618689374066889 7.238161122659224 8.5566812092438322 1.5478449900401745 1.4504585582180975 2.3962330297799772 1.6813108596834343 17.078427622327585 6.1931186456931782 4.4630637257359949 12.525414944626389 1.2721540094935333 2.5066011640010393 0.94004157162272051 1.5150894526741456 1.4100296068936575 26.499941662303176 1.1382814568933088 2.2179288123734286 1.9934237413108342 1.6277971388772128 19.78168195602484 +leaf_count=23834958 1574 818 1209 23307 2227 181 1176 341267 42088 1707 2353 2785 503 470 780 549 5554 2014 1451 4075 414 812 309 492 458 8620 370 721 649 528 6434 +internal_value=0 0.0077759 0.00609931 0.0351031 -0.0496709 0.00654627 0.00441665 0.00494901 0.0251597 0.1228 0.00544987 -0.0519066 0.0344783 0.00631039 0.0526115 0.0751756 0.0939437 0.00523546 -0.0423042 0.0018251 -0.117082 0.00634934 0.0544152 0.0718832 0.0507619 0.0846935 0.0940559 0.00488661 0.111307 -0.000315989 0.00524815 +internal_weight=0 1401.69 1320.66 81.026 9.37116 1316.94 1181.74 1178.12 135.206 5.81747 1173.29 17.3427 10.1045 1155.94 26.2277 23.8314 22.1501 1129.72 25.8638 19.6707 5.73522 1103.85 32.6 30.0934 13.9354 28.5783 27.6382 1071.25 20.6996 3.62122 1069.04 +internal_count=24290853 455895 429543 26352 3045 428334 384358 383182 43976 1888 381608 5641 3288 375967 8530 7750 7201 367437 8412 6398 1865 359025 10603 9791 4533 9299 8990 348422 6731 1177 347701 +is_linear=0 +shrinkage=0.1 + + +Tree=74 +num_leaves=32 +num_cat=0 +split_feature=0 12 6 6 6 0 13 14 0 4 4 8 8 7 13 0 0 13 4 12 4 17 12 2 16 16 14 14 4 2 9 +split_gain=5.77942 13.0549 12.9006 10.5807 10.1134 11.9509 8.14541 14.3745 7.58691 13.4431 24.7622 32.9726 16.5544 15.9914 11.2646 14.0885 12.7401 9.83296 9.33903 10.7675 13.1974 11.1101 18.6404 14.9243 14.3034 9.92607 9.47579 8.2983 12.8399 8.28928 14.3611 +threshold=8440.5000000000018 2832.5000000000005 8.5000000000000018 114.50000000000001 90.500000000000014 10735.000000000002 81.500000000000014 5.5000000000000009 7743.5000000000009 115.50000000000001 391.50000000000006 21.500000000000004 16.500000000000004 979.00000000000011 10.500000000000002 7826.0000000000009 7826.0000000000009 147.50000000000003 102.50000000000001 82.000000000000014 2.5000000000000004 187.50000000000003 16.500000000000004 12.500000000000002 28.500000000000004 7.5000000000000009 4.5000000000000009 6.5000000000000009 15.500000000000002 15345.500000000002 369.00000000000006 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=8 6 -3 4 5 -4 -2 -8 29 18 11 13 14 -11 15 -12 -16 -14 19 21 -21 23 -23 -10 -25 -20 -26 -28 -29 -1 -31 +right_child=1 2 3 -5 -6 -7 7 -9 9 10 12 -13 17 -15 16 -17 -18 -19 25 20 -22 22 -24 24 26 -27 27 28 -30 30 -32 +leaf_value=-0.0001762530858390036 0.0078824011583343376 -0.17895145922912931 0.058877311335565168 -0.19868132838366814 0.15597616206293352 -0.1986334437563686 -0.13583672498792398 0.11202674727127973 -0.18722385729072241 -0.17712496751849321 0.14150636563770833 0.18181786419841597 -0.18316996428821791 0.060220700749690485 -0.17684413382494168 -0.11694320569364301 0.073094890910023205 0.20460916923461434 -0.19340163911864333 0.15181805969579665 -0.18788911624626148 0.157743944284266 -0.18049277709818393 0.053438251276697907 -0.18542652057790246 0.16696176785686492 0.112876824115133 -0.19070125644768102 0.11017153170650847 0.010002998924016814 0.14973583917306368 +leaf_weight=72425.890853897989 1486.8549004193192 7.3364190297434133 4.6061268409248424 2.572726532816886 3.9228219522628933 2.9606228753691539 9.2937574252136965 3.1269662474514908 4.3096986720338455 26.130278689379342 2.7011410176055497 3.3056348627433172 6.5156707843998438 3.1847031875513485 2.2087501003406969 9.6240778362262045 26.600048289226834 0.72685253620147694 0.87691560061648388 1.4356748437858176 5.6215808696579188 9.5299159686546755 1.9653701562201593 25.603369551012293 4.8264798634918398 5.9548710135277361 3.3641012765001488 3.9415219131624326 2.2157587120309472 576.54119345700019 7.4501796828117213 +leaf_count=23557292 483617 2380 1500 840 1276 962 3023 1018 1403 8500 877 1075 2120 1036 719 3130 8655 237 288 465 1826 3100 643 8325 1567 1933 1091 1286 722 187524 2423 +internal_value=0 0.00610178 -0.0714543 -0.0153722 0.0256741 -0.0418783 0.00720872 -0.0734361 -0.000126829 -0.0225456 -0.0502461 -0.11758 -0.0048427 -0.15134 0.0197034 -0.0603025 0.0539322 -0.144253 0.00967002 -0.00240662 -0.118782 0.0123234 0.099915 -0.0104256 0.00864633 0.120706 -0.0712836 -0.0134235 -0.0824292 -8.05725e-05 0.0117856 +internal_weight=0 1520.67 21.3987 14.0623 11.4896 7.56675 1499.28 12.4207 73160.5 150.642 80.9972 32.6206 48.3765 29.315 41.134 12.3252 28.8088 7.24252 69.6453 62.8135 7.05726 55.7562 11.4953 44.2609 39.9512 6.83179 14.3479 9.52138 6.15728 73009.9 583.991 +internal_count=24290853 494616 6958 4578 3738 2462 487658 4041 23796237 48998 26349 10611 15738 9536 13381 4007 9374 2357 22649 20428 2291 18137 3743 14394 12991 2221 4666 3099 2008 23747239 189947 +is_linear=0 +shrinkage=0.1 + + +Tree=75 +num_leaves=32 +num_cat=0 +split_feature=0 0 2 18 3 2 4 6 20 11 19 2 17 18 18 18 17 2 0 18 0 6 19 4 17 13 18 17 3 4 4 +split_gain=8.1626 6.6708 13.8417 18.3066 15.6535 20.1908 16.2754 12.6785 12.6334 10.6037 10.0382 7.64493 7.52822 7.55505 7.01052 6.84569 6.4015 6.30733 17.2805 18.0102 13.4709 12.5838 12.3217 13.411 12.3004 11.6954 8.01286 20.6718 18.492 16.7539 14.8953 +threshold=7481.5000000000009 7414.5000000000009 807.50000000000011 52.500000000000007 1294.0000000000002 17657.500000000004 1385.5000000000002 12.500000000000002 5522.5000000000009 3.5000000000000004 1.0000000180025095e-35 50964.500000000007 42.500000000000007 31.500000000000004 38.500000000000007 51.500000000000007 28.500000000000004 85.500000000000014 7826.0000000000009 169.00000000000003 8254.0000000000018 25.500000000000004 2.5000000000000004 178.50000000000003 595.00000000000011 1.5000000000000002 110.50000000000001 66.500000000000014 49.500000000000007 36.500000000000007 21.500000000000004 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 -1 3 7 14 6 9 8 12 -6 11 -7 -3 -14 -4 -15 -11 18 19 25 -20 22 24 -24 -19 -2 27 29 -29 30 -22 +right_child=17 2 4 -5 5 10 -8 -9 -10 16 -12 -13 13 15 -16 -17 -18 21 20 -21 26 -23 23 -25 -26 -27 -28 28 -30 -31 -32 +leaf_value=-0.00013085710631418373 -0.19512028660095859 -0.1935437523979977 -0.19730126716960705 -0.19052942114450833 -0.18888208643285487 0.032394368414392298 0.19779748581942957 0.13545238409831101 0.12748978895997254 0.19234126692472464 -0.16994409173671429 -0.15242515879397203 0.17705305784385184 -0.18902739000125424 0.12775089522580871 0.1603183222550883 -0.17163830631001267 0.0041876454257546349 -0.17545455236962038 -0.19444832384285379 0.021743602952222066 -0.16418038948788163 -0.17523792556137585 0.10767829476166664 -0.17133187330223132 0.093219124809820419 0.042203171759789382 -0.025832883473932446 -0.19317221146475924 0.13673966862616715 -0.19431678633093033 +leaf_weight=72727.0434195243 1.4483252423815418 5.8895135670900398 0.70927386870607634 10.465763684827833 12.17039016028866 41.264390017371625 1.4549472844228137 3.9179756520315996 2.5933937160298219 1.2922607390210026 2.8941923340316853 2.3664404333103439 1.3421872947365043 2.3814992634579544 10.282277961028738 0.7337513007223605 0.77178570209071029 1570.5466533427534 3.9373174546635701 2.4180216070963061 60.891816396440845 4.4222849511133964 1.8423191666370282 18.504606630187482 4.0028990578866788 48.968872831086628 58.485363260144368 46.535248287691502 7.695819988846778 12.550736148492431 3.3672443127725264 +leaf_count=23657780 471 1915 230 3405 3959 13428 473 1274 842 420 942 771 437 775 3341 239 251 510893 1280 788 19803 1440 600 6019 1302 15929 19026 15139 2503 4084 1094 +internal_value=0 -0.00016644 -0.0259084 -0.0866445 -0.00323866 -0.0226751 -0.120776 -0.0221519 -0.0698701 -0.153338 0.0104068 0.0223702 -0.119337 -0.0212895 0.106776 -0.106744 0.0562425 0.00656763 0.0214643 0.0721499 0.00762194 0.00427352 0.0047406 0.0820616 0.00374143 0.0849361 0.0114253 -0.00231133 -0.0495797 0.0310622 0.0104218 +internal_weight=0 72827.6 100.53 27.3241 73.206 62.2144 15.6894 16.8583 12.9403 14.2344 46.525 43.6308 10.347 4.45744 10.9916 3.11525 2.06405 1845.62 246.299 52.8352 193.464 1599.32 1594.9 20.3469 1574.55 50.4172 189.526 131.041 54.2311 76.8098 64.2591 +internal_count=24290853 23690482 32702 8887 23815 20244 5103 5482 4208 4630 15141 14199 3366 1451 3571 1014 671 600371 80117 17188 62929 520254 518814 6619 512195 16400 61649 42623 17642 24981 20897 +is_linear=0 +shrinkage=0.1 + + +Tree=76 +num_leaves=32 +num_cat=0 +split_feature=1 14 7 14 4 5 14 14 14 6 6 14 20 10 3 10 6 7 6 14 7 1 20 1 14 10 1 3 14 14 1 +split_gain=6.82572 4.15876 7.41262 8.80733 8.58579 6.24412 4.99845 5.51685 4.79756 4.8058 5.42503 4.7614 4.65402 7.97863 4.84115 9.5375 5.74364 10.2266 7.3458 6.87681 13.347 7.59681 5.63117 5.18551 4.76765 4.65395 8.87843 8.18112 4.94223 8.72585 8.43448 +threshold=22546.500000000004 21.500000000000004 6.5000000000000009 31.500000000000004 12.500000000000002 18.500000000000004 37.500000000000007 165.00000000000003 19.500000000000004 21.500000000000004 1.5000000000000002 241.50000000000003 9231.5000000000018 1414.0000000000002 686.00000000000011 85.500000000000014 5.5000000000000009 15.500000000000002 2.5000000000000004 6.5000000000000009 14.500000000000002 84498.000000000015 9852.0000000000018 49748.000000000007 1.0000000180025095e-35 133.50000000000003 53338.500000000007 1475.5000000000002 24.500000000000004 42.500000000000007 38269.500000000007 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 5 11 -4 6 -2 -5 -8 12 10 -10 25 13 -7 23 -16 18 19 -17 21 -21 -18 -20 24 -14 27 28 -3 -27 30 -30 +right_child=1 2 3 4 -6 8 7 -9 9 -11 -12 -13 14 -15 15 16 17 -19 22 20 -22 -23 -24 -25 -26 26 -28 -29 29 -31 -32 +leaf_value=-0.00011726067338088359 -0.15184272463896695 0.024715020186376561 -0.14849164840580456 0.06860603661231035 0.054289301048054295 0.0049521789970830036 -0.13293119392257213 0.014411634203648986 0.087111596445886896 0.076853201258594434 -0.16666368855468983 -0.13288860039748293 -0.090100909402191717 -0.096535121913444627 -0.19069504547792296 -0.20310825088719056 0.022483360903448581 0.076380190604325424 -0.16961984868307972 -0.15583275878709477 0.18536918572474217 -0.16010748265862004 0.099585297546430029 -0.16211773367236781 0.065004135974546268 0.12776227855842165 -0.034107345129644917 -0.16389560721478461 -0.16967921312904599 0.07927652806958016 0.040266611084196942 +leaf_weight=73536.836913859908 2.5183632348271194 45.787860289798118 4.4545014570467165 2.687015613482802 15.281707814952822 841.00363289823144 7.5095000960573071 3.8409064210136421 1.0344212588388462 1.6047186143696306 4.5371908380766399 1.6448244296770997 2.0839625257067373 7.8185048669693051 2.324545345734804 0.94425785355269631 16.964841588865973 11.360267920885233 0.82286527787800956 7.9371317093609814 1.3400220209732649 2.632166563300415 13.946198524441568 1.1040774948196475 40.41251540259691 12.391774444025939 9.1649124993418791 2.4213636873755595 3.0049968555686046 31.858518589666346 5.2685664031887427 +leaf_count=23931027 816 14904 1451 872 4971 273689 2441 1250 338 522 1476 537 676 2544 755 307 5523 3698 268 2583 436 857 4538 360 13152 4031 2981 787 977 10369 1717 +internal_value=0 0.00779855 0.0235648 -0.0174802 0.00242455 0.00541296 -0.0540375 -0.0830713 0.0058264 -0.0756302 -0.119548 0.0359927 0.00644128 0.00401738 0.0266376 0.00778117 0.0160276 -0.00399583 0.067298 -0.035619 -0.106548 -0.00204128 0.0845864 0.0518393 0.057398 0.0385203 0.0567122 0.0152418 0.0725593 0.0555141 -0.0359867 +internal_weight=0 1105.71 145.316 33.7736 29.3191 960.39 14.0374 11.3504 957.871 7.17633 5.57161 111.543 950.695 848.822 101.873 58.2723 55.9478 40.2344 15.7133 28.8742 9.27715 19.597 14.7691 43.6006 42.4965 109.898 61.6888 48.2092 52.5239 40.1321 8.27356 +internal_count=24290853 359826 47288 10985 9534 312538 4563 3691 311722 2336 1814 36303 309386 276233 33153 18965 18210 13097 5113 9399 3019 6380 4806 14188 13828 35766 20075 15691 17094 13063 2694 +is_linear=0 +shrinkage=0.1 + + +Tree=77 +num_leaves=32 +num_cat=0 +split_feature=1 15 0 15 21 5 20 21 20 20 5 5 5 20 20 21 14 15 14 12 7 7 20 20 7 20 6 5 4 15 6 +split_gain=7.23288 6.85168 5.28093 9.72372 10.5724 7.49166 12.1905 7.61148 7.68559 11.4268 9.16937 8.04094 7.61829 7.44729 8.0499 13.6848 7.36483 7.16256 6.19555 10.4834 6.18093 12.9504 6.17512 12.7464 5.97648 5.75957 5.64496 5.33883 5.20312 6.83785 7.72335 +threshold=22546.500000000004 86.500000000000014 37346.500000000007 82.500000000000014 30574.500000000004 127.50000000000001 633.50000000000011 28707.000000000004 5286.0000000000009 3964.5000000000005 329.50000000000006 134.50000000000003 408.50000000000006 8498.0000000000018 4450.5000000000009 53212.000000000007 4.5000000000000009 32.500000000000007 5.5000000000000009 1.0000000180025095e-35 3.5000000000000004 9.5000000000000018 2548.0000000000005 904.50000000000011 2.5000000000000004 9733.0000000000018 11.500000000000002 408.50000000000006 6.5000000000000009 106.50000000000001 13.500000000000002 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 3 4 5 6 -2 8 9 22 11 -10 -12 14 15 -4 -17 25 -18 -20 27 -22 23 -7 -14 -15 -6 -16 -3 30 -30 +right_child=1 28 13 -5 26 7 -8 -9 10 -11 12 -13 24 17 20 16 18 -19 19 -21 21 -23 -24 -25 -26 -27 -28 -29 29 -31 -32 +leaf_value=-0.00012043068680173725 0.14090661495054876 0.014734817292011662 -0.13968085145262932 -0.17910539334615524 0.081961055829467389 0.0057482016676636679 -0.12618816948131167 -0.18537895999732368 0.10823452263001328 -0.19475345214150397 0.12039675099811892 -0.16472248621175259 0.11927689671190078 -0.15639997188835975 0.0488118305731838 -0.1537793892403474 0.148214988140945 -0.13764061547323 -0.15151783907354271 0.080363552822725406 -0.14704788897913471 0.027925717655519669 0.13669633069281045 -0.16249217280021677 -0.051857726480846592 0.11475290059620709 -0.093763861746011659 -0.17375501535722751 -0.15999281740834712 0.024051012840973555 0.1124976055474277 +leaf_weight=73538.065723711406 2.1022103441646314 201.1036538957851 10.70256866537966 2.8147980630164957 24.686824150150645 668.7790561196307 9.1308315825008304 2.0698626349912947 1.6430681012570882 2.8669845182448617 16.430050664115701 3.1450557978241704 3.0916693247854692 1.1471544996602454 31.684930457151495 2.3107579771312876 3.7290575489168978 9.0785916117310972 3.1271001879358646 5.178351088805357 6.3446143842302289 12.691326364642007 3.5583936483599237 4.5337756254011756 6.0028132500592619 2.4701791206607595 1.9742714809253801 1.1157188464421768 1.1026331206085185 37.627405625447864 18.360898285754956 +leaf_count=23932666 687 65446 3483 916 8035 217655 2972 674 536 934 5344 1024 1004 373 10310 753 1215 2956 1016 1683 2066 4130 1157 1475 1952 807 643 362 358 12244 5977 +internal_value=0 0.00804663 0.00367851 0.0064097 0.00710593 0.00482656 -0.0762027 0.00610473 0.00666292 0.00446589 0.0559295 -0.071056 0.0797506 -0.0192743 -0.00755757 -0.0541064 0.00973802 -0.0902288 0.0411352 -0.00694252 0.0149352 -0.0303924 0.00530971 0.00461535 0.00631949 0.028763 0.0689485 0.0412412 0.0222985 0.0489416 0.0970607 +internal_weight=0 1100.6 842.41 752.83 750.015 723.354 11.233 712.121 710.051 679.738 30.3127 4.78812 25.5245 89.5804 76.8844 25.0478 14.3453 12.6959 12.0345 8.30545 51.8366 19.0359 676.871 673.313 9.09448 3.61733 26.6611 32.8006 258.195 57.0909 19.4635 +internal_count=24290853 358187 274162 245008 244092 235414 3659 231755 231081 221221 9860 1560 8300 29154 25018 8150 4667 4136 3914 2699 16868 6196 220287 219130 2956 1180 8678 10672 84025 18579 6335 +is_linear=0 +shrinkage=0.1 + + +Tree=78 +num_leaves=32 +num_cat=0 +split_feature=5 4 12 5 18 20 11 19 17 6 12 17 13 18 15 16 11 6 15 4 18 20 17 21 4 17 18 15 15 4 16 +split_gain=4.26089 19.9955 74.0888 15.1925 32.5192 29.1111 14.3164 14.2628 16.9934 15.4017 13.8974 13.3678 15.4005 15.0411 19.8049 18.4398 13.7488 16.2371 13.2525 15.8966 13.8238 18.5599 12.9119 12.7731 12.7139 12.6224 13.3584 19.8632 19.7625 19.6257 14.4976 +threshold=1.0000000180025095e-35 15.500000000000002 3.5000000000000004 1.5000000000000002 1.5000000000000002 8.5000000000000018 1.5000000000000002 10.500000000000002 529.00000000000011 136.50000000000003 7798.0000000000009 194.00000000000003 1894.5000000000002 4.5000000000000009 48131.500000000007 1631.5000000000002 318.50000000000006 94.500000000000014 806.50000000000011 50.500000000000007 3.5000000000000004 127.50000000000001 1896.5000000000002 774.50000000000011 44.500000000000007 1.5000000000000002 3.5000000000000004 37.500000000000007 45.500000000000007 197.50000000000003 2.5000000000000004 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 -1 -3 4 5 6 7 -2 10 22 -9 12 -6 15 16 -13 23 -18 25 20 21 -20 -10 -15 -22 -4 27 30 -29 -28 -27 +right_child=3 2 18 -5 11 -7 -8 8 9 -11 -12 13 -14 14 -16 -17 17 -19 19 -21 24 -23 -24 -25 -26 26 29 28 -30 -31 -32 +leaf_value=-0.00010296449576603552 0.10389587191338312 -0.15332223909581455 0.01526834754566197 0.00072486633316476662 0.013654636482987086 0.056679379172394476 -0.13103337766049572 -0.023506551791384958 0.070987783217020969 -0.19810661515569736 -0.16507845166614163 -0.13947762340468015 -0.2035402233476 -0.015594684032905771 0.12421184336431457 0.095984778538849638 -0.19902887674783121 0.1294488214363145 -0.02320833584069799 0.035061254643169941 0.048639852512029826 -0.19106579464930615 -0.19185105465132019 -0.096708897271750471 -0.19735533198634969 -0.1952858650080454 0.018028102134648168 0.097234860587261523 -0.025778103938729802 -0.19865801962506369 -0.048868769690651279 +leaf_weight=61775.371290918978 11.923156830016522 36.977390424755868 118.06227255077101 10988.009974472006 337.13935436651809 245.46867123479024 8.8464330385904741 23.20265499339439 29.398507972480733 2.6196982448454937 9.8892486169934255 18.652405197848569 3.2965590693056575 138.77908846805803 10.199739568401126 4.047668012790381 1.7548226832877833 10.564617003896272 22.385978889418769 125.55931762018008 23.158534460089871 9.333526495145632 1.9958963205572207 22.570832187775522 2.3106294638710088 11.495426415349359 114.09056096104905 13.417929089628158 489.43233032070566 4.3388356027426189 16.425389231881127 +leaf_count=20106693 3879 12035 38425 3576377 109733 79903 2877 7549 9566 855 3218 6072 1075 45172 3320 1317 570 3438 7283 40874 7536 3038 648 7347 752 3742 37133 4368 159299 1414 5345 +internal_value=0 -0.000328578 -0.0144497 0.00173758 0.0143777 0.0389979 -0.010393 0.0031114 -0.0147956 0.0348397 -0.0658142 -0.000625802 0.0115515 -0.0206945 -0.0112133 -0.0974921 -0.0191669 0.0826593 -0.00904432 0.0151565 -0.0285449 -0.0726007 0.0542778 -0.0269415 0.0263225 -0.0148085 -0.0202782 -0.0270541 -0.0224957 0.0100895 -0.109151 +internal_weight=0 62762.4 986.988 11868.4 880.349 333.344 87.8756 79.0292 67.106 34.0141 33.0919 547.005 340.436 206.569 183.869 22.7001 173.669 12.3194 950.011 182.748 57.1887 31.7195 31.3944 161.35 25.4692 767.263 649.2 530.771 502.85 118.429 27.9208 +internal_count=24290853 20427937 321244 3862916 286539 108495 28592 25715 21836 11069 10767 178044 110808 67236 59847 7389 56527 4008 309209 59483 18609 10321 10214 52519 8288 249726 211301 172754 163667 38547 9087 +is_linear=0 +shrinkage=0.1 + + +Tree=79 +num_leaves=32 +num_cat=0 +split_feature=1 16 11 12 9 16 0 15 7 1 7 0 0 8 0 16 16 1 2 16 6 16 0 0 0 1 7 12 0 2 0 +split_gain=6.8727 5.51883 6.05261 12.0469 10.0758 7.8642 6.08722 5.29253 8.55891 5.62095 4.88569 10.2678 7.11673 10.0095 10.3203 11.1697 16.3965 11.0584 8.42604 7.70164 10.5762 6.42306 6.10002 5.7847 9.64052 9.5282 5.73185 5.67446 12.3223 5.3007 5.28257 +threshold=22546.500000000004 58.500000000000007 6.5000000000000009 1.0000000180025095e-35 3.5000000000000004 54.500000000000007 6678.0000000000009 67.500000000000014 13.500000000000002 37408.500000000007 771.50000000000011 1785.0000000000002 1785.0000000000002 15.500000000000002 5053.5000000000009 6.5000000000000009 26.500000000000004 40420.000000000007 42546.500000000007 13.500000000000002 9.5000000000000018 2.5000000000000004 11022.500000000002 10735.000000000002 10503.000000000002 46669.000000000007 588.00000000000011 85.500000000000014 7357.5000000000009 24208.500000000004 920.00000000000011 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 3 -2 5 -4 -6 8 -5 -9 12 -12 18 -14 19 22 -17 -18 30 -15 -21 -20 26 24 29 -25 27 -16 -29 -13 -10 +right_child=1 -3 4 7 6 -7 -8 9 10 -11 11 23 13 14 15 16 17 -19 21 20 -22 -23 -24 25 -26 -27 -28 28 -30 -31 -32 +leaf_value=-0.00011702368281897796 -0.054377706707424471 0.017929970899240474 0.015018221563714211 -0.10844805390704676 -0.16930797241605589 -0.15253389546445159 0.097681786160610073 0.12722855665501367 -0.0022117896279729024 -0.15524458178755818 -0.16147711397337333 -0.12537220467868498 0.0011629408920788942 -0.18739483183365441 0.097879197094158973 -0.17797553379862693 -0.16718058587196644 0.10203339089241376 0.086643070809579892 -0.1656558197122443 0.19835603113805633 -0.1220118167140877 -0.13437172025820363 -0.15620692287373411 0.1524484456749681 0.093974572931363784 -0.18707276415579116 0.073904090869035141 -0.1850605600832457 0.03168496085921748 0.11365641773465739 +leaf_weight=73489.729363855906 39.710607534012524 363.93098109010316 91.801591708383057 6.9866254610387832 0.89169798488728957 2.8894389556371598 20.169825438060798 5.6965816589072338 5.4231738594826373 0.80386779987020407 2.8887496711686245 2.2138370840111739 391.4510964214569 7.6293805288150889 11.294364131987104 8.8252537771477382 2.1504532442195332 5.2527787672588602 2.2629881035536519 1.7880908329971135 1.441749095916748 4.2385875280015171 2.2371282384265205 4.0012682748492798 6.6760728307999662 2.4571258863434196 1.0598475148435671 5.6136721112998185 2.7314757480053231 73.269376610871404 14.336430331226437 +leaf_count=23935152 12934 118532 29902 2276 295 941 6562 1855 1765 262 941 721 127495 2482 3681 2874 700 1709 738 582 470 1380 729 1305 2175 800 345 1830 890 23861 4669 +internal_value=0 0.00787452 0.00284909 -0.00111444 0.0238199 0.00990547 0.086378 0.00257861 0.00154862 0.092297 0.00292281 0.0240547 -0.00121135 -0.00421981 -0.0463411 -0.0224242 -0.0859128 0.0238335 0.0493636 -0.1326 -0.00316673 -0.0493858 0.0224966 0.0301026 0.0372659 -0.0610242 0.0394505 0.0516748 -0.0108584 0.0270787 0.0818555 +internal_weight=0 1092.12 728.193 612.441 115.753 94.691 21.0615 572.73 566.23 6.50045 559.243 91.5064 467.736 441.475 50.0242 39.165 16.2285 7.40323 26.2612 10.8592 3.22984 6.50158 22.9365 88.6177 82.1593 6.45839 20.6994 19.6395 8.34515 75.4832 19.7596 +internal_count=24290853 355701 237169 199469 37700 30843 6857 186535 184418 2117 182142 29803 152339 143787 16292 12758 5283 2409 8552 3534 1052 2118 7475 28862 26757 2105 6746 6401 2720 24582 6434 +is_linear=0 +shrinkage=0.1 + + +Tree=80 +num_leaves=32 +num_cat=0 +split_feature=1 0 0 7 6 1 1 7 8 0 18 3 0 0 1 0 12 0 17 7 1 0 18 18 15 7 16 16 0 16 0 +split_gain=7.18549 3.9769 14.5406 10.7384 8.53421 8.53034 8.41012 8.14467 7.81722 9.27838 6.21651 5.93477 5.68916 5.67699 7.33356 6.7129 17.2599 12.655 11.6151 9.19895 6.62622 6.20378 5.4791 6.95177 6.93016 8.5138 5.8308 6.41195 7.54179 5.94356 5.42263 +threshold=22546.500000000004 10261.000000000002 9372.5000000000018 236.50000000000003 1.0000000180025095e-35 40420.000000000007 29522.000000000004 94.500000000000014 14.500000000000002 10582.000000000002 187.50000000000003 24767.000000000004 10503.000000000002 9181.5000000000018 42263.500000000007 7520.5000000000009 811.00000000000011 7826.0000000000009 37.500000000000007 139.50000000000003 23306.000000000004 8881.5000000000018 140.50000000000003 133.50000000000003 1.0000000180025095e-35 209.50000000000003 19.500000000000004 14.500000000000002 8440.5000000000018 4.5000000000000009 7598.5000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 11 7 5 8 -5 -4 -3 -10 -7 -2 -6 15 -15 20 17 18 30 -19 -13 -18 23 24 25 -21 27 28 29 -27 -17 +right_child=1 4 3 6 12 10 -8 -9 9 -11 -12 13 -14 14 -16 16 21 19 -20 22 -22 -23 -24 -25 -26 26 -28 -29 -30 -31 -32 +leaf_value=-0.00011925125139653286 -0.093892741720804862 0.10848604318894313 -0.054220943583495718 -0.16355814727204954 -0.16209877764822314 0.02330802894964501 0.015171949715305867 -0.18362854637047152 0.10929447710575049 -0.1532147298444721 -0.15963413747452798 -0.14371739074378817 0.010293219624887781 0.08025397196583979 -0.15623735403600011 -0.16571373739342254 0.070476549077215636 -0.095447077108045453 -0.15849499063698302 -0.17953771377924002 0.012819906778971622 -0.17003071585013196 -0.15089272168307036 0.12512943923929118 0.066767550372553497 0.076705626892836748 -0.15667501986872359 0.12231388272104951 -0.17092410151243001 -0.17230171287191606 0.08666217880996159 +leaf_weight=73494.724599185793 5.7770598203642285 25.925880731199872 11.941951764340049 3.0171834421926205 1.9218586920760561 28.430643906001936 20.661823840928264 8.2052936272812058 2.6535817408002904 2.7333096858346835 1.987294328471761 2.7238096969667813 487.90103273042769 17.194338952424008 1.4194971703691397 1.1295675155706728 20.70244081289275 11.307723709789569 14.229714255154247 3.428608567803173 374.6175610270584 1.1311051010852669 2.0446131443604818 5.1096406729193395 9.7730685069691372 5.207577037625013 2.1811464541824526 4.1719527351669958 2.6092080494854599 1.1748167374171314 3.456696048961021 +leaf_count=23937541 1886 8446 3891 983 626 9264 6727 2674 865 889 644 887 158909 5600 462 367 6743 3682 4637 1118 122009 369 664 1664 3185 1695 708 1358 851 383 1126 +internal_value=0 0.00807937 0.00192129 -0.0532611 0.0140327 0.0490721 -0.00760187 -0.106924 0.0857105 -0.0239027 0.0113559 0.00686303 0.00961683 0.00806662 0.0622191 0.0058989 -0.0190299 -0.0445858 -0.11389 -0.0168454 0.01169 0.0580169 0.00805073 0.0177066 -0.00152149 -0.0370716 -0.0052391 0.0198532 -0.0276868 0.0308705 0.0245036 +internal_weight=0 1084.77 533.216 43.8263 551.554 61.7307 23.679 20.1472 31.3128 5.38689 30.4179 489.39 489.823 483.613 18.6138 464.999 87.6579 65.8243 18.816 47.0084 377.341 21.8335 35.7006 33.656 28.5464 18.7733 15.3447 13.1636 8.9916 6.38239 4.58626 +internal_count=24290853 353312 173669 14275 179643 20108 7710 6565 10200 1754 9908 159394 159535 157508 6062 151446 28550 21438 6130 15308 122896 7112 11626 10962 9298 6113 4995 4287 2929 2078 1493 +is_linear=0 +shrinkage=0.1 + + +Tree=81 +num_leaves=32 +num_cat=0 +split_feature=1 5 15 8 11 15 15 1 1 6 6 8 8 6 6 1 19 15 17 16 4 1 8 18 18 4 3 14 1 13 1 +split_gain=7.318 4.68229 6.2906 3.83701 3.69331 3.55407 7.3651 3.3171 12.8276 3.68102 3.37226 3.01186 21.4855 15.1234 20.2884 11.9464 25.1773 21.3631 11.6916 11.3265 9.74508 9.79884 8.97295 13.6073 15.9695 15.8758 17.6119 18.5522 13.2874 15.1738 10.5804 +threshold=22546.500000000004 28.500000000000004 18.500000000000004 75743.500000000015 41.500000000000007 30.500000000000004 61.500000000000007 30.500000000000004 81.500000000000014 7029.5000000000009 9305.0000000000018 7939.0000000000009 7727.5000000000009 10462.500000000002 10119.500000000002 109.50000000000001 3.5000000000000004 45.500000000000007 35.500000000000007 18.500000000000004 178.50000000000003 20.500000000000004 8372.0000000000018 5.5000000000000009 1.5000000000000002 26.500000000000004 148.50000000000003 1.0000000180025095e-35 132.50000000000003 61.500000000000007 100.50000000000001 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=3 2 -2 11 -3 -4 -7 -5 -9 -10 -11 12 -1 14 18 16 -15 -18 22 20 21 -14 23 24 25 -13 -27 -28 29 -26 -31 +right_child=1 4 5 7 -6 6 -8 8 9 10 -12 13 19 15 -16 -17 17 -19 -20 -21 -22 -23 -24 -25 28 26 27 -29 -30 30 -32 +leaf_value=-9.4207886256890996e-05 -0.16049727527806629 0.0064104756959522339 0.12088826726155531 0.10549212123176457 0.077939155171381988 -0.11288586623780757 0.05247504751341632 -0.16178849953994934 -0.14068753921800595 0.08741466394990588 -0.13073573387249166 0.12340566206948196 0.10540344056478151 0.13838712453474683 -0.20053534084020075 0.075923576064131665 -0.19849996487607283 0.18438316757970546 -0.19671068905637024 -0.20033879966327769 -0.18564493696973161 -0.15941689499681932 -0.0039945101629059315 -0.19931581137356194 -0.1925237804827904 -0.1953132912928694 0.099440519081105863 -0.19717537890687759 -0.20060305297556727 0.026759647458261999 0.19068935180163363 +leaf_weight=72355.983847489391 1.4719370370730747 1029.3187031193083 5.9844115669839075 5.9047115539433461 7.2696162681968408 2.9755879217409555 28.409449320053682 3.1982953869155635 0.84872354616527346 11.910298610047899 0.75343934691045511 3.0461367396637824 34.553550197859295 5.6804528776556245 5.4490883047692469 38.572363033075817 7.8211892280960438 1.7909226752817629 3.2403758779400951 1.4446200558450062 1.284204056370071 1.4561250295955677 951.00436162407277 5.089331383816897 2.7661645498592398 12.858706211205574 5.5760544985532743 3.391010481165722 2.665522555471397 25.314735563122671 4.6623416393995276 +leaf_count=23569112 477 335282 1948 1920 2367 967 9269 1040 279 3883 245 990 11257 1850 1775 12562 2549 584 1056 471 417 474 309781 1658 900 4186 1818 1106 869 8243 1518 +internal_value=0 0.00818938 0.0422768 -0.000119832 0.00691211 0.0502638 0.0367973 0.041064 0.0182985 0.0609237 0.0744356 -0.000132508 -5.43464e-05 -0.00537706 -0.00809104 0.0462706 -0.0285229 -0.127161 -0.00706256 0.0743992 0.0850415 0.0946949 -0.00645793 -0.0422959 -0.0290391 -0.0904516 -0.120299 -0.0127286 0.0140984 0.0315766 0.0522557 +internal_weight=0 1075.43 38.8414 73496.3 1036.59 37.3694 31.385 22.6155 16.7108 13.5125 12.6637 73473.7 72394.7 1078.93 1025.06 53.8649 15.2926 9.61211 1019.61 38.7385 37.2939 36.0097 1016.37 65.37 60.2807 24.8719 21.8258 8.96706 35.4088 32.7432 29.9771 +internal_count=24290853 350310 12661 23940543 337649 12184 10236 7367 5447 4407 4128 23933176 23581731 351445 333900 17545 4983 3133 332125 12619 12148 11731 331069 21288 19630 8100 7110 2924 11530 10661 9761 +is_linear=0 +shrinkage=0.1 + + +Tree=82 +num_leaves=32 +num_cat=0 +split_feature=1 2 5 15 20 14 20 14 15 11 11 20 7 11 5 6 17 4 4 4 5 4 5 5 5 4 4 1 1 17 17 +split_gain=6.51857 4.74448 4.28324 3.59864 3.31494 7.93555 6.99621 3.63984 4.06999 3.2299 3.19478 4.60626 3.14705 2.97125 11.1651 16.4709 14.3032 11.7844 10.6008 14.9 21.5276 14.2612 10.679 13.9609 10.0376 13.6906 8.77802 8.69121 8.25565 18.1022 9.9453 +threshold=22546.500000000004 24208.500000000004 28.500000000000004 18.500000000000004 5286.0000000000009 12.500000000000002 7813.5000000000009 1.0000000180025095e-35 242.50000000000003 6.5000000000000009 41.500000000000007 5745.0000000000009 1839.0000000000002 55868.000000000007 233.50000000000003 130.50000000000003 378.50000000000006 72.500000000000014 215.00000000000003 134.50000000000003 81.500000000000014 112.50000000000001 48.500000000000007 30.500000000000004 28.500000000000004 26.500000000000004 13.500000000000002 16.500000000000004 1.0000000180025095e-35 42.500000000000007 13.500000000000002 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 13 3 -2 5 -5 -6 9 -9 -8 12 -12 -4 -1 15 16 18 -18 19 20 21 22 23 24 25 28 -17 -24 30 -30 -15 +right_child=2 -3 10 4 6 -7 7 8 -10 -11 11 -13 -14 14 -16 26 17 -19 -20 -21 -22 -23 27 -25 -26 -27 -28 -29 29 -31 -32 +leaf_value=-0.00013566287644376795 -0.14006936067963147 -0.14846343849162999 0.0055551103898303453 -0.1484087288074486 -0.10063533314485391 0.096666175011868305 -0.14574730902497776 -0.050017960501433402 0.076396694430578574 0.14110278625785608 0.098977004992532938 -0.16461523127172717 0.0677748589100403 -0.19612601868307478 -0.18734409738144431 -0.18247105762360805 -0.15962141919913767 0.12801718340115847 -0.19038430322540972 0.11358627870206316 -0.187701108865722 -0.19651129208062695 0.1323743255202964 -0.19206120010425654 0.18694258021275187 0.0388768185055803 0.16563483873417928 -0.1699680394399383 0.13457769621856872 -0.18612456415975373 0.11512780447001268 +leaf_weight=73358.702463886992 1.071290625783148 2.155750820413231 1022.1227753349522 1.4370475938194456 4.0003155704471309 16.394036870333366 0.42861521890154008 5.0757601286750269 5.1116478414623998 4.6631358169252053 6.5730931090074582 0.73731955396942783 8.1943548505660129 6.2642690693028253 2.673364282352849 0.78313272399827738 7.9388094166060892 1.7357629679609088 2.3677635827916665 15.667456888011655 5.2943076804513103 2.9339633237104854 12.441910420544444 3.1997883369913316 3.4870995883829883 47.209714347147383 9.6576424572849628 1.0294539427850384 4.3334391210228205 2.9638488418422639 1.2277731231879441 +leaf_count=23896957 347 703 332963 468 1305 5338 140 1654 1666 1519 2142 239 2669 2041 870 256 2587 565 769 5103 1723 957 4052 1044 1136 15384 3145 335 1410 966 400 +internal_value=0 -0.000113123 0.00772775 0.0406213 0.0458374 0.076915 0.0170945 0.0479179 0.013412 0.116956 0.00651736 0.0723914 0.00604996 -0.000108772 0.0149261 0.019133 0.00848922 -0.108015 0.0188851 0.0235573 0.00795161 0.020125 0.0278614 0.0118947 0.0218604 0.0125755 0.139524 0.10927 -0.0713823 0.00432204 -0.145119 +internal_weight=0 73492.1 1075.81 38.1818 37.1106 17.8311 19.2795 15.2792 10.1874 5.09175 1037.63 7.31041 1030.32 73489.9 131.21 128.536 118.095 9.67457 108.421 106.053 90.3856 85.0913 82.1573 68.6859 65.4861 61.999 10.4408 13.4714 14.7893 7.29729 7.49204 +internal_count=24290853 23940403 350450 12437 12090 5806 6284 4979 3320 1659 338013 2381 335632 23939700 42743 41873 38472 3152 35320 34551 29448 27725 26768 22381 21337 20201 3401 4387 4817 2376 2441 +is_linear=0 +shrinkage=0.1 + + +Tree=83 +num_leaves=32 +num_cat=0 +split_feature=3 3 18 14 16 16 5 6 20 6 9 16 4 9 10 14 16 10 21 16 16 3 15 21 16 6 15 20 14 16 15 +split_gain=3.62066 11.8035 13.5077 17.6512 9.99178 13.2827 8.41758 10.2292 7.975 7.91274 10.519 19.0476 10.4728 8.1303 7.46229 6.85866 6.68337 6.5022 6.26094 6.06536 12.5967 9.12188 9.03012 8.7502 17.095 8.93493 10.3933 10.3731 8.65331 8.28368 11.3675 +threshold=1174.0000000000002 1240.5000000000002 8509.0000000000018 216.50000000000003 86.500000000000014 83.500000000000014 89.500000000000014 2.5000000000000004 14029.500000000002 24.500000000000004 6.5000000000000009 121.50000000000001 899.00000000000011 5.5000000000000009 188.50000000000003 241.50000000000003 128.50000000000003 3.5000000000000004 69778.000000000015 193.50000000000003 232.00000000000003 1367.5000000000002 3.5000000000000004 12019.500000000002 138.50000000000003 10.500000000000002 218.00000000000003 8399.5000000000018 60.500000000000007 138.50000000000003 3.5000000000000004 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 -2 -4 5 -3 7 14 18 10 12 -12 19 -14 -6 -5 17 -13 -9 23 -21 -22 -23 25 -25 26 29 -27 -24 30 -8 +right_child=1 4 3 15 6 -7 9 8 -10 -11 11 16 13 -15 -16 -17 -18 -19 -20 20 21 22 28 24 -26 27 -28 -29 -30 -31 -32 +leaf_value=-0.00013319824039446343 0.094134731186276369 0.0010436093680280035 -0.20318400804468686 0.19364049596999189 -0.18878829387411766 -0.1651269686149931 0.043464324943142167 -0.18261971808837776 0.15415412552578922 0.15048915700403537 -0.17403263044210904 0.14647954787146919 -0.1623368394262652 0.037524825602341647 0.08478920416051576 -0.21292249017626541 -0.015407832817701618 -0.19307390924814108 0.0020577889168147318 -0.14141387978020473 -0.18161833673160521 -0.14905178687613449 0.056035821255701591 0.032757679597307465 -0.12833707009034848 0.13974093466968107 -0.19308240405200758 -0.17164822893969045 -0.16813260288499304 0.085935443770074282 -0.093303643768089375 +leaf_weight=71933.081139866816 26.961406694317702 2303.4450266038621 3.8813187337946138 2.3919625431299214 1.1762286687735493 4.8204435580410054 47.154801013879478 7.2672681650146824 1.0497579689254042 4.9335124467615961 7.1455395144876084 4.8959864510688957 4.8941522601671732 3.4845554971252568 6.5446265627979301 0.50202597957104433 22.311925469955895 0.63737188570667047 2.4561826051212847 5.3657138443959402 2.1000411935383445 2.5663859487394793 34.897116519394331 49.996108891209587 7.5869213399419086 18.14800791209564 1.7730722783599038 1.1368055531638663 1.8113841296872113 39.799261000473052 6.9761592990253112 +leaf_count=23434660 8785 750419 1266 779 384 1570 15361 2369 343 1607 2331 1597 1593 1134 2134 162 7262 209 801 1746 685 840 11366 16289 2472 5913 577 370 592 12966 2271 +internal_value=0 0.00364569 0.0624148 -0.0638102 0.00288147 0.000696589 0.0205086 -0.0447393 -0.107699 0.0250177 0.0226612 -0.0283855 0.0305059 -0.0792179 0.0431112 0.123113 0.0089898 0.107367 -0.135969 0.0346979 0.0027432 0.0214382 0.0322957 0.0433527 0.0115324 0.0592875 0.0467744 0.121385 0.0449742 0.0513021 0.0258383 +internal_weight=0 2628.11 33.7367 6.77531 2594.37 2308.27 286.109 18.4941 10.7732 267.615 262.681 34.9908 227.69 8.37871 7.72086 2.89399 27.8453 5.53336 9.72345 219.312 46.7406 41.3749 39.2749 172.571 57.583 114.988 95.7033 19.2848 36.7085 93.9302 54.131 +internal_count=24290853 856193 10992 2207 845201 751989 93212 6031 3513 87181 85574 11399 74175 2727 2518 941 9068 1806 3170 71448 15229 13483 12798 56219 18761 37458 31175 6283 11958 30598 17632 +is_linear=0 +shrinkage=0.1 + + +Tree=84 +num_leaves=32 +num_cat=0 +split_feature=8 3 13 2 5 12 12 16 13 19 19 16 3 13 8 13 13 13 20 13 10 19 16 12 3 16 9 6 4 2 16 +split_gain=3.91187 3.65672 22.7924 41.9993 19.2488 21.2452 20.7574 12.6871 9.93121 9.82745 8.55021 8.07022 7.67438 14.2354 7.59636 7.06523 6.85335 11.949 18.0531 7.22634 6.6275 16.1738 10.4611 16.1044 6.60605 12.518 6.51152 6.12038 6.55617 6.43961 7.34015 +threshold=75743.500000000015 1174.0000000000002 133.50000000000003 16.500000000000004 6.5000000000000009 20.500000000000004 38.500000000000007 86.500000000000014 183.50000000000003 3784.0000000000005 133519.50000000003 83.500000000000014 1240.5000000000002 69.500000000000014 365.50000000000006 33.500000000000007 246.00000000000003 439.00000000000006 4.5000000000000009 212.50000000000003 6.5000000000000009 1612.0000000000002 35.500000000000007 1.5000000000000002 1367.5000000000002 106.50000000000001 1.5000000000000002 24.500000000000004 778.00000000000011 47250.500000000007 143.50000000000003 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 -1 3 4 5 -3 -7 11 9 -4 -6 12 13 -5 16 24 19 20 -19 -10 21 22 23 -18 25 -9 -17 28 -26 -30 -31 +right_child=-2 2 8 7 10 6 -8 15 14 -11 -12 -13 -14 -15 -16 26 17 18 -20 -21 -22 -23 -24 -25 27 -27 -28 -29 29 30 -32 +leaf_value=-0.00014632846084785056 0.042419025985604014 -0.12065809873732054 0.12998505261945201 0.074775531356369218 -0.18853605732163087 0.12471630244104895 -0.20020398801099137 -0.17282239515853437 0.0099426948636855261 -0.16501223498075945 -0.025350965762811664 -0.15138677188874911 0.00079553247795822752 -0.19157360145542632 -0.20526408348888558 0.088325222670653014 0.1399347534633231 0.18344152891355567 -0.085227435011614339 -0.19264094195150561 0.17053039255190305 0.14368509227298557 0.16730188171284288 -0.20100934723699659 0.022362411104874816 0.081916282749593824 -0.1839607992102672 0.13043188018446936 -0.12883865580353132 -0.12246787756368223 0.04745212522367797 +leaf_weight=71909.61102573041 21.733830527722603 13.202108803328885 20.507891497807581 25.481958471238617 18.104221448418688 10.061770707252434 2.4436795341316602 2.0515548104885957 14.206084821256811 1.1950980713590968 3.9030316406860939 3.4599135978496625 2187.5310082027208 2.1781561034731558 1.290649116039275 1.241224460187367 1.9822546069044671 3.3490901514887801 9.876658485591177 2.0099230158375567 8.7042236047273018 11.413930552080272 1.9130490273237226 4.601278601679951 221.29617476297426 32.308925820107106 3.0035735835554078 4.9071627503726623 5.6390993839595493 3.53454324681661 9.0553124455618654 +leaf_count=23426822 7080 4302 6680 8302 5899 3278 795 671 4625 390 1272 1127 712659 711 420 405 647 1092 3216 655 2835 3720 623 1499 72094 10522 977 1599 1838 1150 2948 +internal_value=0 -1.23692e-05 0.00364972 0.00198999 -0.0909477 -0.0321815 0.0612239 0.00376259 0.055856 0.113741 -0.159595 0.00121904 0.00145739 0.0538013 0.0346877 0.0237008 0.0400221 0.0614116 -0.0171937 -0.0151669 0.097743 0.0659227 -0.0385398 -0.0983535 0.0256503 0.0667066 -0.104341 0.019879 0.0176141 -0.0400302 -0.000252123 +internal_weight=0 74540.1 2630.45 2549.4 47.7148 25.7076 12.5055 2501.69 81.0501 21.703 22.0073 2218.65 2215.19 27.6601 59.3471 283.038 58.0565 41.8405 13.2257 16.216 28.6147 19.9105 8.49658 6.58353 278.793 34.3605 4.2448 244.432 239.525 18.229 12.5899 +internal_count=24290853 24283773 856951 830549 15546 8375 4073 815003 26402 7070 7171 722799 721672 9013 19332 92204 18912 13632 4308 5280 9324 6489 2769 2146 90822 11193 1382 79629 78030 5936 4098 +is_linear=0 +shrinkage=0.1 + + +Tree=85 +num_leaves=32 +num_cat=0 +split_feature=0 0 6 20 17 17 4 16 7 17 13 6 0 17 1 12 6 13 1 12 17 4 17 7 1 17 13 4 0 13 12 +split_gain=7.02774 6.8438 20.4944 18.1775 17.1059 19.6207 18.7435 18.307 17.4196 20.0802 24.5769 22.1587 19.1036 19.0165 17.3024 16.6211 16.5361 14.2013 16.3383 14.1043 13.6902 13.5166 13.4468 14.1983 13.2486 13.2061 13.0335 14.0572 12.8042 12.673 11.4087 +threshold=7379.5000000000009 5209.5000000000009 22.500000000000004 7813.5000000000009 35.500000000000007 53.500000000000007 290.50000000000006 153.50000000000003 106.50000000000001 87.500000000000014 32.500000000000007 1.5000000000000002 5954.0000000000009 114.50000000000001 8532.0000000000018 256.50000000000006 10.500000000000002 78.500000000000014 15914.500000000002 93.500000000000014 84.500000000000014 778.00000000000011 339.50000000000006 209.50000000000003 2.5000000000000004 447.50000000000006 2.5000000000000004 306.50000000000006 7336.5000000000009 69.500000000000014 1234.0000000000002 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 -1 3 4 -3 6 24 8 12 11 15 28 19 -12 20 22 21 18 -8 -7 -13 -16 23 -11 -6 -4 -25 -28 -10 -15 -14 +right_child=-2 2 25 -5 5 7 17 -9 9 10 13 14 30 29 16 -17 -18 -19 -20 -21 -22 -23 -24 26 -26 -27 27 -29 -30 -31 -32 +leaf_value=-7.4035471934863332e-05 0.0059193767544194441 0.022107463644619699 -0.14699450671526923 -0.1742229443412914 0.13543968171181803 -0.17772117047723335 -0.18802115767469713 -0.16069950701693178 0.060457303265338894 -0.13472157702213924 -0.18891407294768806 -0.18858936127941917 0.062733984880278662 0.089696718480604773 -0.17988161481378975 -0.16006791304341725 0.13567033159819203 0.18019742850231968 0.00059012347829149573 0.00066657339885868919 0.10593823788257009 0.054088525565358259 -0.18660388153641905 -0.18496855608979218 -0.15177946548548563 0.10705892755719426 0.023869655952829003 -0.086918351085864598 -0.19192896814983607 -0.18045467409846083 -0.17189541043335857 +leaf_weight=72050.946903537522 1953.1472656720725 156.6948694944731 16.133051036624241 6.4726371846627435 1.7177812093868876 6.2496621415484723 5.9117957614362302 7.6240854064817531 57.557263070251793 12.657340344274415 3.2491255763452527 10.52499782317318 71.570886145462282 22.358155785361308 5.225311000132935 16.554799650068162 6.5283181914128354 3.2432816210202864 20.584097060374916 15.241196175920775 1.8565706512890763 4.6811665371060371 6.639776428055483 4.3777717858320093 24.679031305655371 2.3432780005969098 42.42049470404163 15.688465768238528 2.0828641168773165 1.8826765320263792 2.1341807458084068 +leaf_count=23473752 636324 51050 5256 2109 555 2038 1929 2482 18755 4125 1064 3428 23316 7280 1702 5389 2129 1056 6703 4962 605 1526 2162 1427 8045 763 13824 5109 679 613 696 +internal_value=0 -0.000159236 -0.0112224 -0.00765567 -0.00562121 -0.0172623 -0.0717557 -0.00761555 -0.00384432 -0.0196573 -0.0453236 0.0168527 0.0317507 0.0382652 -0.0551514 -0.0686904 0.012106 -0.017316 -0.0414931 -0.0512096 -0.144426 -0.0693223 -0.0501936 -0.0381403 -0.133089 -0.114774 -0.0185768 -0.00604129 0.051643 0.0687153 0.0559401 +internal_weight=0 72605.8 554.885 536.409 529.936 373.241 56.136 317.105 309.481 214.285 125.829 88.4565 95.1959 27.49 28.8164 98.3386 16.4348 29.7392 26.4959 21.4909 12.3816 9.90648 81.7838 75.1441 26.3968 18.4763 62.4867 58.109 59.6401 24.2408 73.7051 +internal_count=24290853 23654529 180777 174758 172649 121599 18288 103311 100829 69817 40993 28824 31012 8957 9390 32036 5357 9688 8632 7000 4033 3228 26647 24485 8600 6019 20360 18933 19434 7893 24012 +is_linear=0 +shrinkage=0.1 + + +Tree=86 +num_leaves=32 +num_cat=0 +split_feature=1 16 16 11 7 17 11 15 7 20 16 16 15 20 1 18 16 8 6 7 17 18 0 16 16 1 17 16 13 7 16 +split_gain=5.70114 3.49151 11.3812 9.39892 9.42468 8.21229 7.73515 7.89222 13.0845 15.2516 8.49536 7.9911 7.94686 7.72757 7.22091 6.25417 6.12183 6.32623 5.76119 10.5842 5.38731 5.32769 8.88469 11.2729 9.72093 7.50961 7.29728 6.87295 11.4499 5.97648 6.41739 +threshold=16619.500000000004 86.500000000000014 83.500000000000014 17.500000000000004 3.5000000000000004 73.500000000000014 6.5000000000000009 93.500000000000014 6.5000000000000009 7413.5000000000009 77.500000000000014 60.500000000000007 51.500000000000007 7485.5000000000009 23653.500000000004 224.50000000000003 77.500000000000014 3.5000000000000004 13.500000000000002 1.5000000000000002 1.0000000180025095e-35 13.500000000000002 22403.000000000004 13.500000000000002 10.500000000000002 20447.500000000004 17.500000000000004 2.5000000000000004 8.5000000000000018 1098.5000000000002 35.500000000000007 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 3 6 -5 -6 7 8 9 -2 21 12 13 -11 -9 18 -8 -18 -3 -20 -16 22 23 24 25 -10 -23 28 -27 30 -25 +right_child=1 15 -4 4 5 -7 16 14 10 11 -12 -13 -14 -15 20 -17 17 -19 19 -21 -22 26 -24 29 -26 27 -28 -29 -30 -31 -32 +leaf_value=-0.00011922992926617447 -0.10748702729831379 0.024804111611259692 -0.1569184334768701 0.00083099848850537558 -0.10857450105664342 0.062098912059079109 0.033195337559335493 -0.16683960583677415 -0.1802648658528658 0.12673166721436754 -0.15939898020925117 -0.14783979361838406 0.068768224690325383 -0.14744684999243052 0.069819237002192058 -0.14176498363254336 -0.16835145832867485 0.093506754289557709 -0.17566418746510912 0.0010569610403071822 -0.091486739328906316 -0.17580927336449267 -0.029323144499901716 0.11033841607682134 -0.18215842856183939 0.035597937414474277 0.0022383552248598569 0.055635967288991763 -0.16276333619853153 -0.15597322281610138 -0.0063696555961116471 +leaf_weight=73176.272708370932 23.305553077610966 230.69396050315117 4.3931343292351803 29.33393002091907 19.93838353903266 3.2835274549433953 126.61434903310146 1.4760016122600053 1.797257785219698 1.3995770377805516 3.161508672812487 2.879741564509458 14.47129485051846 3.8714859262108803 31.326141194440424 2.4958515033504218 2.9965973582002325 1.3330053593963382 3.5719660890754303 66.193975744041381 2.2170139041263601 2.309744933852925 22.74580381186388 28.546657202241477 2.518665889510884 8.5337324240244907 678.64731431213295 28.877773367799819 4.4157233363948754 1.0080849151127029 5.6427869491744778 +leaf_count=23847640 7593 75182 1432 9555 6503 1069 41263 479 587 456 1030 938 4719 1263 10209 815 977 434 1167 21568 722 754 7413 9302 821 2780 221166 9409 1439 328 1840 +internal_value=0 0.00641523 0.00370267 0.00437301 -0.0368469 -0.0844417 0.00653915 0.00312558 0.0011731 -0.0507121 0.00419638 0.00777811 0.0304775 -0.0746467 0.0496324 0.0158797 0.029197 -0.0877301 0.0171892 -0.00799103 0.0591578 0.0048552 0.0259262 0.0413759 0.00886219 0.0198908 0.00163443 0.0284912 -0.0320426 0.0840007 0.0910764 +internal_weight=0 1360 1057.04 1052.65 52.5558 23.2219 1000.1 869.152 834.133 45.9277 788.205 22.6221 19.7424 5.27106 35.0192 302.956 130.944 4.3296 300.46 69.7659 33.5432 785.044 104.086 81.3407 46.1432 43.6245 680.957 41.8272 12.9495 35.1975 34.1894 +internal_count=24290853 443213 344481 343049 17127 7572 325922 283248 271838 14969 256869 7376 6438 1719 11410 98732 42674 1411 97917 22735 10931 255839 33919 26506 15036 14215 221920 13628 4219 11470 11142 +is_linear=0 +shrinkage=0.1 + + +Tree=87 +num_leaves=32 +num_cat=0 +split_feature=1 18 1 6 10 3 6 17 17 17 12 11 20 20 4 18 18 4 8 12 12 6 1 1 11 18 18 10 12 12 1 +split_gain=6.06812 7.44124 5.95444 10.6124 12.4065 8.56845 11.2111 9.39902 10.3545 8.70762 9.09695 17.0719 14.1217 17.2647 12.5509 12.0209 15.1192 13.285 12.362 10.2115 9.17451 8.85893 8.44698 13.0279 15.1731 12.7644 14.6323 18.3604 13.8599 12.6055 9.74534 +threshold=5045.0000000000009 10060.000000000002 858.00000000000011 5007.5000000000009 54.500000000000007 388.50000000000006 21.500000000000004 120.50000000000001 123.50000000000001 236.00000000000003 1.5000000000000002 17.500000000000004 11844.500000000002 9852.0000000000018 19.500000000000004 9023.0000000000018 10192.500000000002 13.500000000000002 749.50000000000011 20.500000000000004 5.5000000000000009 20.500000000000004 2703.0000000000005 1770.0000000000002 263.50000000000006 7145.5000000000009 4437.5000000000009 3.5000000000000004 6.5000000000000009 4.5000000000000009 2216.5000000000005 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=2 -2 -1 5 -5 22 9 -8 -9 10 12 15 13 -7 -15 18 -17 -18 21 20 -20 -12 23 -4 25 26 28 -28 29 -25 -27 +right_child=1 -3 3 4 -6 6 7 8 -10 -11 11 -13 -14 14 -16 16 17 -19 19 -21 -22 -23 -24 24 -26 30 27 -29 -30 -31 -32 +leaf_value=-3.5566765687300671e-05 0.0058177049578052647 -0.17534464952904522 -0.02669979810151319 0.16898164843562147 -0.20728428163847357 0.017162447142296818 -0.20083533300912532 0.20450366475506351 -0.19479779574698938 0.042417340173734303 -0.0081564444272614663 -0.18366576168643431 0.086166305358023873 -0.16274472082723623 0.16591675189969399 -0.183110837128244 0.070193012392245499 -0.14226057936304068 -0.19993704519202918 -0.19763386578898592 0.14187486603784144 0.17112145353562358 -0.057052729097180038 0.0071012090322815748 -0.20937716871462497 0.087874915837390821 -0.18440278170995283 0.13440602106224203 -0.10987367724387552 0.15533957990128092 0.0018684970757936006 +leaf_weight=71591.38495360082 1883.9615771147946 2.270035537891089 251.32398835883942 6.0485358997248104 1.024783140979707 95.051570871553849 5.061671237461268 1.501047730445862 1.1446553141577167 45.618178722041193 406.16612791013904 5.5728749867994329 26.017900694918353 8.8705631408374739 1.3370536658912886 8.6149034707777918 7.7150121543090773 4.7587882481748238 0.84412876970600237 1.1195821865985625 11.258200534997739 2.775141109712421 52.651468164593098 25.038911754032597 2.9409494438441461 33.606531804311089 8.644977371033745 2.283606879063881 7.4859761723782858 7.441143588628619 21.6694530476816 +leaf_count=23332724 614009 740 81909 1972 336 30978 1652 490 370 14871 132370 1816 8480 2891 436 2809 2515 1552 275 364 3670 904 17160 8157 960 10954 2817 745 2441 2424 7062 +internal_value=0 0.00559968 -0.000145397 -0.00760839 0.114468 -0.00843349 -0.0011263 -0.120997 0.0317471 0.000350224 -0.00295786 -0.0097304 0.0201969 0.00389059 -0.119695 -0.00754357 -0.081225 -0.0108586 -0.00386289 0.0913042 0.118034 -0.00693984 -0.0196383 -0.014173 0.014681 0.0208874 -0.0152478 -0.117785 0.0127908 0.0410625 0.0541584 +internal_weight=0 1886.23 72645 1053.59 7.07332 1046.51 633.427 7.70737 2.6457 625.72 580.102 448.825 131.277 105.259 10.2076 443.252 21.0887 12.4738 422.163 13.2219 12.1023 408.941 413.087 360.436 109.112 106.171 50.8946 10.9286 39.966 32.4801 55.276 +internal_count=24290853 614749 23676104 343380 2308 341072 206443 2512 860 203931 189060 146275 42785 34305 3327 144459 6876 4067 137583 4309 3945 133274 134629 117469 35560 34600 16584 3562 13022 10581 18016 +is_linear=0 +shrinkage=0.1 + + +Tree=88 +num_leaves=32 +num_cat=0 +split_feature=1 20 21 20 15 5 7 3 10 7 3 13 0 3 3 8 12 21 21 12 10 8 7 10 6 0 6 13 6 8 0 +split_gain=5.28938 3.41402 14.6132 9.66994 6.14277 8.4596 8.47389 6.50871 5.68692 4.04312 3.91705 3.3588 13.3392 14.7997 13.3522 17.6049 21.1119 15.2462 14.5968 18.2861 11.7564 11.6133 12.1801 10.5405 7.47331 7.82427 6.43355 5.00509 27.2492 19.8419 22.5163 +threshold=16619.500000000004 10909.500000000002 22150.000000000004 10740.500000000002 120.50000000000001 252.50000000000003 16.500000000000004 2181.5000000000005 94.500000000000014 4.5000000000000009 160.50000000000003 63827.000000000007 361.50000000000006 1.0000000180025095e-35 5.5000000000000009 74.500000000000014 4720.5000000000009 2.5000000000000004 4.5000000000000009 4634.5000000000009 10734.000000000002 12.500000000000002 35.500000000000007 11117.500000000002 7.5000000000000009 242.50000000000003 58.500000000000007 52600.000000000007 6.5000000000000009 31.500000000000004 94.500000000000014 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=11 3 -3 -2 7 -6 -7 -4 10 -9 -5 27 13 20 24 16 18 -17 21 -20 -13 -16 -23 -22 -15 -26 -19 -1 -29 30 -30 +right_child=1 2 4 8 5 6 -8 9 -10 -11 -12 12 -14 14 15 17 -18 26 19 -21 23 22 -24 -25 25 -27 -28 28 29 -31 -32 +leaf_value=-0.00010452047756965741 0.0061495951860147044 -0.16752579909631124 -0.15071228871994699 0.16115459113739053 0.048754630414285877 -0.14036073190101125 0.082074450534403098 -0.16848935136332399 0.10264393168470927 0.087341154065729482 -0.15514820274163146 -0.13406880862420281 0.12348367977326324 -0.19512623270242299 -0.19722440982898684 -0.10808225050299469 -0.19100976650809448 0.11855347033633819 -0.2000016761612384 0.15506446551565009 0.1803771909885811 0.14277198338196098 -0.19511032401819756 -0.18714621651796148 0.10784122180128151 -0.18298511761495784 -0.17587654226316815 -0.15485424481033383 -0.15551952753237208 0.042713689290952185 -0.010947333509322574 +leaf_weight=72941.121473349369 1290.5796375599457 5.8946343670831984 4.8544482581200992 0.52568334341049494 24.994439230475109 5.4056980636087237 2.5069508124142885 0.80262175691314075 15.195760235830674 2.6819569580256939 1.5340573004214091 11.522207135159986 10.744411677820606 0.91754724760540085 1.4552292255102717 4.1817178537603494 8.3569398708641511 16.208364223944958 5.681368547724559 1.9476937479339538 2.6072726118727583 9.3328456917079148 1.204593552276491 1.1136737178894689 22.38079451490194 0.964959092263597 0.77775305660907079 13.386074713664128 13.493841515621169 50.967649071244523 53.422578128986061 +leaf_count=23774034 420646 1921 1583 171 8146 1761 818 260 4953 875 500 3755 3502 294 473 1361 2726 5278 1852 635 850 3046 391 364 7302 314 256 4365 4400 16611 17410 +internal_value=0 0.00619088 -0.0202481 0.00714387 0.000799845 0.0202268 -0.069887 -0.0758617 0.0815081 0.0284143 -0.074422 -0.000114642 0.0182554 0.00550209 0.0241204 -0.00584556 -0.0579044 0.0629634 -0.00121455 -0.109354 -0.0841621 0.0675774 0.104147 0.070378 0.0848179 0.0958204 0.105072 -0.00013963 -0.0196485 -0.00429553 -0.0401006 +internal_weight=0 1354.98 47.1407 1307.84 41.2461 32.9071 7.91265 8.33903 17.2555 3.48458 2.05974 73171.8 99.3974 88.653 73.4098 49.1465 27.9787 21.1678 19.6217 7.62906 15.2432 11.9927 10.5374 3.72095 24.2633 23.3458 16.9861 73072.4 131.27 117.884 66.9164 +internal_count=24290853 441634 15364 426270 13443 10725 2579 2718 5624 1135 671 23849219 32399 28897 23928 16018 9123 6895 6397 2487 4969 3910 3437 1214 7910 7616 5534 23816820 42786 38421 21810 +is_linear=0 +shrinkage=0.1 + + +Tree=89 +num_leaves=32 +num_cat=0 +split_feature=0 9 18 10 3 4 20 10 4 0 5 18 10 10 15 3 19 19 18 16 4 19 21 16 10 12 9 17 11 21 21 +split_gain=4.48363 9.2411 7.39653 7.38602 15.8636 25.1473 22.6839 15.878 15.1843 19.5235 14.99 14.0779 18.8616 13.1019 17.7341 16.2855 14.5829 29.416 23.8354 17.5427 16.5498 14.2898 13.8598 11.5586 12.5425 11.4184 11.2757 13.9512 11.0182 16.5645 17.0218 +threshold=115.50000000000001 13643.500000000002 14524.500000000002 102.50000000000001 293.50000000000006 240.50000000000003 6.5000000000000009 9259.5000000000018 290.50000000000006 3.5000000000000004 10.500000000000002 257.00000000000006 183.50000000000003 111.50000000000001 2.5000000000000004 54.500000000000007 1174.0000000000002 2262.0000000000005 7016.0000000000009 9840.5000000000018 3.5000000000000004 943.00000000000011 141.50000000000003 3499.5000000000005 118.50000000000001 3.5000000000000004 274.50000000000006 32008.000000000004 192.50000000000003 547.00000000000011 1258.5000000000002 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=3 2 -2 -1 5 13 7 8 10 -10 11 -7 -13 14 -5 22 21 -18 -19 20 28 -15 -16 24 -11 -24 -25 -28 29 -20 -31 +right_child=1 -3 -4 4 -6 6 -8 -9 9 23 -12 12 -14 16 15 -17 17 18 19 -21 -22 -23 25 26 -26 -27 27 -29 -30 30 -32 +leaf_value=3.21971799916833e-06 0.0023658026268730888 0.045600041685679993 -0.19929755559220305 -0.19383352207882021 0.036457745071425136 -0.17046769907666093 -0.16370372830941177 -0.19736564872038356 -0.19850257466497967 -0.20088068296877892 -0.19834302443997093 -0.19936588605761996 0.13923453260976792 -0.0045777724670720251 0.03678022565191693 -0.19827399721033689 0.1037978588882307 -0.19681800800389287 -0.19658201004826578 -0.19490670793598608 -0.19383007966905774 -0.16601684810345246 -0.15136878001100393 -0.15984423420685578 0.055220007066253296 0.19543881945130476 -0.20343324657929826 0.094167020859422593 0.096673794997068782 0.1124686015632288 -0.095729225200935159 +leaf_weight=65559.467244964646 5732.2110887508024 49.72024801504449 1.8193306936882425 7.9089136257607597 89.973547844914719 4.505308288382369 16.029458999691997 5.609782562009058 4.5258765036705872 2.0058445142931296 8.5936141805723292 2.2459538257680816 6.1498585697263479 2839.3640496734297 23.606555169681087 4.9375289181480175 37.465268566680606 6.14031114836689 3.732908958008923 3.7716167786566066 3.0869647864019489 5.4934951914474359 7.8546634629601595 6.351376304490258 41.016062818292994 1.0798695571720598 2.1220190407475439 6.1132947792066261 21.23280139756389 10.71392142865807 6.1990446101408443 +leaf_count=21369746 1868471 16202 593 2577 29327 1468 5222 1827 1476 657 2808 731 2006 925513 7694 1613 12212 2001 1217 1228 1008 1791 2558 2073 13369 352 691 1988 6922 3492 2020 +internal_value=0 0.00267403 0.00230182 -0.000225002 -0.00493327 -0.00613932 -0.054175 -0.0345011 -0.0235762 0.00148588 -0.0960229 -0.0278661 0.0486559 -0.00444393 -0.0577613 -0.0290467 -0.00362003 0.0354896 -0.0111448 0.0122478 0.0296234 -0.00488952 -0.00336952 0.0171975 0.0432796 -0.109452 -0.0597291 0.0174834 0.0460946 -0.00592238 0.0361587 +internal_weight=0 5783.75 5734.03 68737.3 3177.83 3087.86 105.268 89.239 83.6292 62.1345 21.4947 12.9011 8.39581 2982.59 45.3875 37.4786 2937.2 92.3428 54.8776 48.7373 44.9656 2844.86 32.5411 57.6086 43.0219 8.93453 14.5867 8.23531 41.8787 20.6459 16.913 +internal_count=24290853 1885266 1869064 22405587 1035841 1006514 34316 29094 27267 20254 7013 4205 2737 972198 14794 12217 957404 30100 17888 15887 14659 927304 10604 18778 14026 2910 4752 2679 13651 6729 5512 +is_linear=0 +shrinkage=0.1 + + +Tree=90 +num_leaves=32 +num_cat=0 +split_feature=13 4 19 6 6 18 18 19 8 8 19 13 2 5 5 13 7 8 7 2 16 6 4 4 7 7 19 8 16 20 4 +split_gain=4.97464 14.3786 27.5321 21.211 17.4672 18.6481 16.5369 33.0945 17.2439 18.2679 16.3758 13.3795 12.6298 12.2364 20.6886 11.718 19.3892 26.1257 16.468 13.1131 11.9551 23.8599 11.1867 10.6545 10.5204 13.4683 18.8341 11.1073 10.4703 10.0559 9.86912 +threshold=44073.000000000007 2.5000000000000004 56.500000000000007 48.500000000000007 202.00000000000003 1.5000000000000002 4.5000000000000009 357.00000000000006 42.500000000000007 129.50000000000003 420.50000000000006 120236.50000000001 389.00000000000006 212.50000000000003 193.50000000000003 40752.000000000007 147.50000000000003 80.500000000000014 21.500000000000004 322.00000000000006 2.5000000000000004 30.500000000000004 11.500000000000002 61.500000000000007 35.500000000000007 57.500000000000007 166.50000000000003 74.500000000000014 328.50000000000006 19.500000000000004 9.5000000000000018 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=15 3 4 6 13 30 12 -8 -9 -10 -5 -12 -2 14 -3 -1 17 18 20 -20 21 -17 -15 -24 -14 -26 27 -27 -21 -7 -6 +right_child=1 2 -4 10 5 29 7 8 9 -11 11 -13 24 22 -16 16 -18 -19 19 28 -22 -23 23 -25 25 26 -28 -29 -30 -31 -32 +leaf_value=-2.3109987424907215e-05 0.03337063247733809 0.0041087500568118291 -0.15511212062831767 0.075876790605013011 -0.19292176480205636 -0.19448077014408816 -0.16281406121141909 -0.18438800745493528 0.089313941357487797 -0.19157502373048163 -0.19673324513432558 0.096425517875505809 -0.1986636130557401 -0.19162383701217042 -0.19671182927572539 0.027895486034458503 -0.19547981302213302 0.091508818046653478 -0.16974190187751093 0.17626547452104591 0.012922603889999607 -0.19725372601028546 0.11387705712786483 -0.18923360995100169 0.085625019381498885 -0.20014802789196715 -0.1946205988078811 0.047294159872723798 -0.20271134663180793 0.20355485275275165 0.17794414590273219 +leaf_weight=74047.950208119393 138.39468588074669 164.6374494129559 12.306982239708303 84.883726382278837 0.95638945745304171 10.491772974317424 16.014995309989899 3.5044321669265655 18.86096848547459 2.6393793139141044 4.1623604185879222 2.4869766860501841 3.5265045412816098 1.4492984933895083 5.2949445525882757 9.3641913209576177 6.9718035523546851 16.457845655269921 15.005663898075001 2.2001328982878467 36.306855107890442 9.4636188410222513 19.42488983459771 1.2332895072177041 9.056973197031768 2.157912150258201 6.9508889175485811 11.385809222701935 1.0902691446244719 0.67558045592159111 2.8731036211829633 +leaf_count=24089081 45019 53566 4001 27612 310 3412 5210 1139 6138 858 1354 809 1142 468 1723 3045 2267 5355 4881 716 11813 3078 6321 402 2944 701 2265 3711 355 220 937 +internal_value=0 0.00971515 -0.00979888 0.0237938 -0.00116096 -0.105101 0.00645808 -0.0505789 0.0213052 0.0548321 0.0640385 -0.0870864 0.0201025 0.00695598 -0.00214864 -6.85778e-05 -0.0348278 -0.0223676 -0.0478904 -0.130099 -0.0206102 -0.0852736 0.07694 0.0957814 -0.0354099 -0.0159282 -0.0608065 0.00786936 0.0506921 -0.170401 0.0853229 +internal_weight=0 523.369 219.344 304.026 207.037 14.9968 212.493 41.0198 25.0048 21.5003 91.5331 6.64934 171.473 192.04 169.932 74144.8 96.8604 89.8886 73.4307 18.2961 55.1347 18.8278 22.1075 20.6582 33.0781 29.5516 20.4946 13.5437 3.2904 11.1674 3.82949 +internal_count=24290853 170262 71360 98902 67359 4879 69127 13345 8135 6996 29775 2163 55782 62480 55289 24120591 31510 29243 23888 5952 17936 6123 7191 6723 10763 9621 6677 4412 1071 3632 1247 +is_linear=0 +shrinkage=0.1 + + +Tree=91 +num_leaves=32 +num_cat=0 +split_feature=1 14 8 7 7 15 7 19 3 9 16 8 20 8 19 16 20 7 20 20 19 21 7 16 7 12 12 7 8 7 7 +split_gain=4.83298 3.2374 11.2298 9.88609 18.5953 16.5273 12.8551 11.1831 10.5017 9.98813 25.4894 15.7778 12.9268 10.8978 9.12885 8.61004 18.1102 14.7437 9.19483 11.5444 12.4334 7.61401 7.57907 8.12481 14.0503 7.8546 8.0529 7.37463 10.8512 18.3653 8.26935 +threshold=16619.500000000004 43522.000000000007 6.5000000000000009 267.50000000000006 378.00000000000006 54659.000000000007 241.50000000000003 166.50000000000003 19.500000000000004 1.0000000180025095e-35 904.50000000000011 133.50000000000003 1.5000000000000002 138.50000000000003 597.50000000000011 544.50000000000011 4.5000000000000009 152.50000000000003 18.500000000000004 10.500000000000002 312.50000000000006 11.500000000000002 122.50000000000001 175.50000000000003 52.500000000000007 5732.5000000000009 7798.0000000000009 232.50000000000003 150.50000000000003 173.50000000000003 85.500000000000014 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 -1 -3 6 -5 -6 9 -7 -8 12 11 14 -4 -12 21 16 -14 18 19 22 -21 -11 23 -18 -25 -24 -27 28 29 30 -17 +right_child=-2 2 3 4 5 7 8 -9 -10 10 13 -13 15 -15 -16 27 17 -19 -20 20 -22 -23 25 24 -26 26 -28 -29 -30 -31 -32 +leaf_value=-0.00015168251124880803 0.0059211184858016666 -0.18759911476475302 -0.1930608781129188 -0.18557273493262844 -0.19205288450681712 0.13770543663548981 0.13849499019213793 -0.17990898930431121 -0.19189307882463466 0.14444533837875728 -0.1862361515533634 -0.19279405605506139 -0.18988040115855603 0.08209163329123853 -0.19466529659879231 -0.0011413915011743343 0.079854185678596742 -0.18745714287618051 0.1890645719031149 -0.0013214581348001495 -0.19560938159552055 -0.19474081858794512 -0.20295042112170322 0.020449308194416128 -0.19000400917473184 0.17694755123305664 -0.18931334907926411 -0.19742687789851254 0.13165092004642939 -0.18710300323749027 0.045810129123895836 +leaf_weight=73010.457362056433 1353.5111430319957 2.839433286804705 3.2665909975767127 7.0995178683660951 2.7598714997293419 7.2711073819082213 12.31172513857018 1.3079908573999999 1.0436312176752824 24.567651740508165 5.0454674970824263 1.6689154460327689 6.0191807083319864 2.1622391175478697 0.86721050855703552 79.729233292746358 8.6997382063418645 4.1611785421846426 2.6702137840911737 8.2925619718153012 5.464165086974389 0.68013802566565473 0.7126289550214987 10.838678092695771 4.4849857431836417 6.7894377794582397 0.65852964622899879 1.5591086051426817 8.4203105052001757 4.3658368898904873 70.843683145125397 +leaf_count=23753991 440362 927 1063 2311 897 2361 4004 428 341 7994 1642 542 1958 702 282 25940 2830 1355 869 2697 1777 221 234 3526 1459 2209 213 507 2739 1421 23051 +internal_value=0 -0.000109326 0.010317 0.01223 -0.0586581 0.0208053 0.0169774 0.0892811 0.112677 0.0120986 0.0618294 0.1053 0.00443182 -0.105741 0.124351 0.0073156 -0.0255422 -0.00679779 0.00866696 -0.0018183 -0.0784925 0.135308 0.0309553 0.00267192 -0.0411469 0.114217 0.144564 0.019029 0.0210949 0.0150866 0.020949 +internal_weight=0 73307.1 296.601 293.762 18.4385 11.339 275.323 8.5791 13.3554 261.968 34.9916 27.7839 226.976 7.20771 26.115 223.709 58.7913 52.7721 48.6109 45.9407 13.7567 25.2478 32.184 24.0234 15.3237 8.1606 7.44797 164.918 163.359 154.939 150.573 +internal_count=24290853 23850491 96500 95573 5997 3686 89576 2789 4345 85231 11383 9039 73848 2344 8497 72785 19127 17169 15814 14945 4474 8215 10471 7815 4985 2656 2422 53658 53151 50412 48991 +is_linear=0 +shrinkage=0.1 + + +Tree=92 +num_leaves=32 +num_cat=0 +split_feature=13 4 19 5 5 13 19 16 15 6 6 10 9 13 4 16 5 5 11 10 15 10 19 11 10 18 6 1 1 21 6 +split_gain=7.55099 8.7995 8.98281 12.6547 26.6858 10.0463 9.99543 9.25303 11.3193 8.3707 16.2307 16.1891 8.1499 18.0425 15.673 9.94232 8.75967 14.4762 24.9855 11.7754 10.7001 11.3985 10.5621 9.00789 8.03427 7.8137 6.97672 6.97463 12.9676 7.26286 10.7878 +threshold=44073.000000000007 192.50000000000003 1.5000000000000002 50.500000000000007 11.500000000000002 145327.00000000003 2.5000000000000004 1.0000000180025095e-35 684.50000000000011 130.50000000000003 148.50000000000003 5794.5000000000009 1.0000000180025095e-35 62774.500000000007 1.5000000000000002 193.50000000000003 257.50000000000006 161.50000000000003 88990.500000000015 12261.500000000002 4877.5000000000009 9016.5000000000018 65.500000000000014 111517.50000000001 3376.0000000000005 4.5000000000000009 141.50000000000003 109.50000000000001 23.500000000000004 7.5000000000000009 1.5000000000000002 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 3 4 -2 -6 9 -5 -9 11 -11 24 16 14 15 -14 17 23 19 22 21 -18 -19 27 -4 -10 -17 28 29 -8 -31 +right_child=1 -3 6 7 5 -7 12 8 25 10 -12 -13 13 -15 -16 26 20 18 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 30 -32 +leaf_value=-8.3664949288942e-05 0.15528997762407284 -0.17839074309997899 -0.17429515677476612 0.12493129419785212 -0.18895931758434406 0.17158730501398733 0.0046117435648128013 -0.17672576887513111 -0.20185892234973052 0.12215127805887249 -0.18672889744561755 -0.18819143405632624 -0.19840492271091312 -0.17389630098138875 -0.10147229622473058 0.12003856238582603 -0.18167239703387819 -0.19666094243450727 0.11522212547128251 0.11968083267627652 -0.2011506846987571 0.10630664159191838 0.17706653953037549 -0.16306011033362164 0.049131575671682991 0.075952490789684149 -0.19421059616131908 0.17518284747298363 -0.19003633101289386 -0.18395064731767474 0.052485310261408395 +leaf_weight=74141.333122032971 4.5842460331041348 2.4138471453916273 2.0526825636625343 21.154031019890681 6.9279929781332612 0.86986368196085084 286.51057989604305 2.3496072492562261 1.0831811889074767 5.8487875421997142 2.398985386826098 8.6477503762580437 1.0660729976370955 3.1299153969157478 4.3316674712114027 29.061193155823279 1.4779786802828372 12.511538041901076 4.9633315717801443 1.5204247913788993 1.3813567070756096 19.620931865356397 0.80485495738685209 3.0206004157662383 7.4535294505767524 15.495354460086672 0.7240879776654755 2.5490692058810955 3.2374646676471448 2.0014398430939755 53.89128494868055 +leaf_count=24123908 1492 786 667 6884 2254 283 93225 765 352 1904 781 2815 347 1019 1407 9456 478 4070 1615 495 449 6387 262 983 2425 5041 236 829 1053 646 17539 +internal_value=0 0.0120896 0.0129899 0.0521853 -0.0361783 -0.14874 0.00850208 0.0794823 0.0286888 -0.0512286 0.032309 -0.0891817 0.0121542 0.056182 0.07665 0.101659 0.00786734 0.00419465 -0.0789977 -0.14397 0.0684808 0.0861337 -0.174073 0.00888478 0.000886896 0.0578013 0.112399 0.0103764 0.00916099 0.0110444 0.0440189 +internal_weight=0 513.084 510.67 52.4643 12.3821 7.79786 458.206 40.0822 18.9281 26.4017 8.24777 18.154 431.804 38.3129 35.183 30.8514 393.491 371.011 19.8001 14.8368 22.4803 21.0989 13.3164 351.21 9.50621 16.5785 29.7853 348.19 345.641 342.403 55.8927 +internal_count=24290853 166945 166159 17071 4029 2537 149088 13042 6158 8592 2685 5907 140496 12465 11446 10039 128031 120717 6442 4827 7314 6865 4332 114275 3092 5393 9692 113292 112463 111410 18185 +is_linear=0 +shrinkage=0.1 + + +Tree=93 +num_leaves=32 +num_cat=0 +split_feature=13 10 15 10 13 8 6 3 8 9 2 18 10 5 19 19 0 6 20 6 19 16 6 18 5 11 5 6 5 6 2 +split_gain=10.3579 7.55851 11.0441 9.47278 8.53747 8.49875 15.159 14.4199 8.55937 9.09345 6.88045 12.6774 8.719 10.0664 6.67539 10.9961 9.64352 7.55489 7.34013 9.4298 6.65531 10.0485 8.4269 8.19604 21.6348 8.35181 7.52276 6.2707 6.08889 15.6609 9.51984 +threshold=44073.000000000007 9425.5000000000018 635.00000000000011 7520.0000000000009 52108.000000000007 62.500000000000007 4.5000000000000009 4.5000000000000009 192.50000000000003 1.5000000000000002 1.0000000180025095e-35 15.500000000000002 6642.0000000000009 59.500000000000007 249.50000000000003 282.50000000000006 3.5000000000000004 3.5000000000000004 8.5000000000000018 194.50000000000003 1.5000000000000002 2.5000000000000004 41.500000000000007 14.500000000000002 92.500000000000014 46692.500000000007 100.50000000000001 54.500000000000007 86.500000000000014 83.500000000000014 1.0000000180025095e-35 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 3 10 5 6 7 -4 14 -10 12 -12 -2 -14 16 -16 -7 -17 19 -19 21 22 -8 24 25 -22 -25 -20 -6 -30 -31 +right_child=1 -3 4 -5 28 8 20 -9 9 -11 11 -13 13 -15 15 17 -18 18 27 -21 23 -23 -24 26 -26 -27 -28 -29 29 30 -32 +leaf_value=-9.7175686032393562e-05 -0.1761834278293084 0.039890958880460632 0.034850865675486682 -0.18776230219926671 0.0047324055351844283 0.10122921110767712 -0.1871607497311156 -0.18674511729169632 -0.17279589403775009 0.11937444921513535 0.11516657694217661 -0.19562505688012746 0.13480377586746381 -0.099183535768836054 -0.18734209423820447 -0.18560303130904529 -0.16710899187317663 -0.18702947108543447 0.097595757409312422 0.14549095047030344 0.015819342782953504 -0.20126220054374167 0.15951033651725949 -0.18668810298763255 -0.12931872047479268 0.14473131574388148 0.15342078897943442 -0.090043171125638699 -0.18604061126083904 -0.09901222767505985 0.050263499012393055 +leaf_weight=74146.845754465423 4.9461324778385514 93.802586287434679 5.0184425350744259 4.4868740072706705 172.88364592863945 36.8630867281463 0.76307522668503125 7.0786439101211727 3.16625724104233 1.6053790117148308 6.0132735981605938 1.6789285710547117 3.0575717519968748 4.6118869991623797 2.1962789269164231 1.3429902431089455 1.3897694679908443 2.4467974530998648 19.463801371632147 1.3091409862972794 71.208826542715542 1.0050579942762841 8.6452032101806235 0.72459788166452099 10.31948174088029 5.4072991856373838 6.3460427520330995 1.9604135907720763 6.1898939109523807 6.6092506013228585 12.081875742529517 +leaf_count=24126655 1608 30521 1634 1462 56258 11998 249 2308 1031 521 1955 547 993 1501 715 443 451 796 6332 426 23162 327 2814 233 3357 1759 2067 635 2012 2151 3932 +internal_value=0 0.0142784 0.00843026 -0.0562643 0.0125856 0.027828 0.0111556 -0.0948166 0.0549049 -0.0744975 -0.0272107 0.047332 -0.0726623 -0.00590016 0.0641245 0.0276882 0.0914802 0.045494 0.0578196 -0.0711287 0.0234326 0.0992863 0.131393 0.0150301 0.00660926 0.0249175 0.118566 0.0804259 -0.00192418 -0.0481769 -0.00252094 +internal_weight=0 504.623 410.82 24.7947 386.025 188.261 116.517 12.0971 71.7439 4.77164 20.3078 7.6922 12.6156 7.66946 66.9723 28.7194 38.2529 26.5231 25.1802 3.75594 104.42 10.4133 9.40828 94.0062 86.9356 76.6161 7.07064 21.4242 197.765 24.881 18.6911 +internal_count=24290853 164198 133677 8066 125611 61258 37910 3942 23348 1552 6604 2502 4102 2494 21796 9347 12449 8632 8189 1222 33968 3390 3063 30578 28278 24921 2300 6967 64353 8095 6083 +is_linear=0 +shrinkage=0.1 + + +Tree=94 +num_leaves=32 +num_cat=0 +split_feature=13 19 7 7 7 7 20 8 4 13 0 16 18 18 4 13 20 4 0 14 4 18 13 4 8 7 18 14 14 4 20 +split_gain=11.7514 7.80189 9.26436 23.0696 13.084 12.7375 8.71403 8.42937 6.87153 16.3116 6.74447 6.38235 5.90666 8.99126 10.3534 9.80086 9.27017 8.6894 7.84935 7.45322 18.0768 11.7343 12.4743 8.47598 7.39146 19.3642 13.5757 7.92122 13.467 7.37131 13.247 +threshold=44073.000000000007 1.5000000000000002 119.50000000000001 106.50000000000001 101.50000000000001 94.500000000000014 5.5000000000000009 74.500000000000014 87.500000000000014 63827.000000000007 231.50000000000003 6.5000000000000009 14.500000000000002 2.5000000000000004 14.500000000000002 62774.500000000007 18.500000000000004 28.500000000000004 361.50000000000006 174.50000000000003 33.500000000000007 10.500000000000002 49703.500000000007 42.500000000000007 40.500000000000007 47.500000000000007 1.5000000000000002 72276.500000000015 58412.500000000007 26.500000000000004 2.5000000000000004 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 -2 3 4 5 8 -6 -4 12 11 -11 -10 13 17 16 19 -15 18 24 -16 29 22 -22 -24 25 27 -26 28 -3 30 -21 +right_child=1 2 7 -5 6 -7 -8 -9 9 10 -12 -13 -14 14 15 -17 -18 -19 -20 20 21 -23 23 -25 26 -27 -28 -29 -30 -31 -32 +leaf_value=-0.00010321845315981989 0.051374731485617435 0.015162235849390266 0.15026461100868471 -0.18703885609689522 -0.18442801543145254 -0.16881896401584026 0.1282598846906001 0.030136741770310723 -0.18107416502928964 0.15926020014837128 -0.17576440667099413 0.016891281447210454 0.041372190904764045 -0.043458497771271552 0.12598546140718195 -0.14718028899312749 0.11787509973513632 -0.18573719279606127 0.13928501121298481 -0.063941265567872219 -0.19211832387275016 -0.19186896780695392 -0.10239612915670088 0.091462987584434191 0.068988537695822139 -0.18911532083202093 -0.17335995136885596 0.092396387391658966 -0.16276104900048216 0.12360179084945298 0.095659493434398862 +leaf_weight=74145.643072564082 53.4248771823477 47.032484571449459 6.2218971836846313 6.2618262023897833 0.96257347078062494 4.2704859033692619 12.027203651494345 95.484003782388754 7.3503602524287981 3.427636715816333 0.72862140930374142 2.092074682645034 45.462254038546234 82.225776381063042 6.7083445582538834 3.427254903712309 3.7227913881652048 1.9577922162134189 5.8776666133198878 9.4274929197272304 3.1132284528575864 5.166278804652392 3.1861331252148357 7.7204439775086939 38.212424606201239 4.9940928874420925 2.4602447166107586 11.035239450866355 4.6771294154459602 11.526882879261391 11.598813403979873 +leaf_count=24127566 17384 15305 2024 2038 312 1389 3915 31077 2392 1116 237 679 14793 26759 2184 1116 1211 635 1913 3068 1012 1679 1038 2511 12435 1625 801 3591 1522 3752 3774 +internal_value=0 0.0152518 0.0109475 0.00316145 0.00666036 0.00275517 0.105089 0.0374856 0.00502269 -0.0645505 0.100528 -0.137213 0.00807924 0.00234754 -0.0140157 0.0171755 -0.0364705 0.0231556 0.0267339 0.026813 0.0139547 -0.0630389 -0.0155652 0.034831 0.0206318 0.000398883 0.0543291 0.015483 -0.000930907 0.0593329 0.0240998 +internal_weight=0 501.784 448.359 346.654 340.392 327.402 12.9898 101.706 323.131 13.5987 4.15626 9.44243 309.533 264.071 147.823 61.8749 85.9486 116.247 114.289 58.4476 51.7393 19.1861 14.0198 10.9066 108.412 67.7389 40.6727 62.7449 51.7096 32.5532 21.0263 +internal_count=24290853 163287 145903 112802 110764 106537 4227 33101 105148 4424 1353 3071 100724 85931 48104 20134 27970 37827 37192 19018 16834 6240 4561 3549 35279 22043 13236 20418 16827 10594 6842 +is_linear=0 +shrinkage=0.1 + + +Tree=95 +num_leaves=32 +num_cat=0 +split_feature=13 6 4 5 4 0 13 4 20 21 4 6 0 2 15 0 4 19 6 6 4 4 6 2 14 13 21 7 2 19 6 +split_gain=11.9251 8.46856 11.8725 15.0407 11.6307 7.54437 7.50959 12.2706 11.4112 9.7676 14.2621 8.01594 9.98486 8.95347 14.0356 12.0041 9.01486 7.82932 7.15502 9.17889 13.2354 9.64731 8.67114 6.6959 12.3921 9.07058 8.83903 6.51416 16.1816 6.28239 7.99427 +threshold=44073.000000000007 51.500000000000007 18.500000000000004 11.500000000000002 17.500000000000004 710.00000000000011 46610.500000000007 1.5000000000000002 1.5000000000000002 2.5000000000000004 1.0000000180025095e-35 53.500000000000007 3617.0000000000005 1.5000000000000002 1329.0000000000002 546.50000000000011 10.500000000000002 5.5000000000000009 65.500000000000014 184.50000000000003 34.500000000000007 44.500000000000007 259.00000000000006 389.00000000000006 50800.000000000007 49798.000000000007 7.5000000000000009 87.500000000000014 90.500000000000014 1143.0000000000002 24.500000000000004 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 4 -4 5 23 7 -3 -9 11 -11 -8 13 14 16 -15 -13 -18 -12 20 -20 -22 -21 26 29 -26 -2 28 -28 -25 -31 +right_child=1 6 3 -5 -6 -7 9 8 -10 10 18 12 -14 15 -16 -17 17 -19 19 22 21 -23 -24 24 25 -27 27 -29 -30 30 -32 +leaf_value=-0.00010380956793940125 0.0062383487852688711 0.12320650605354809 0.10999285714266363 -0.095406764050204401 0.14491024833098887 -0.12218096131664194 0.11969368254721287 0.056171260316248195 -0.17130004118605058 -0.18049327299146978 0.06346622862190944 -0.12156033107628919 -0.18394440205573809 0.077614943598941111 0.037980881720494686 -0.18266238762313694 0.023841537974132035 -0.1823079832540305 -0.15999266172796442 0.11092417314761105 0.142238944177427 -0.18742561450503703 -0.16002355915440467 -0.18497229774761642 0.038627462233264687 -0.10604360407156982 0.096507533380186097 0.086926468135630625 -0.13581180230753795 0.16135996272530029 -0.19947997320787864 +leaf_weight=74145.604238328131 173.19786348642083 19.660904823802415 4.2816145117394653 21.302647655480541 6.2720119775040066 4.4381813706131643 10.165964723564683 7.5990609079599363 3.1070622155675665 5.0015049283392718 11.668043004465291 10.231048446265051 2.1932923403219311 44.184879385313252 66.300477816781495 1.8459978692699213 14.536836257873803 2.1096584858023553 8.3682865650625917 5.7404491496272376 3.2726438860408953 1.2180940718390045 1.487143882201053 6.4603340985486266 18.520689204102386 5.6577323914971194 7.1357182492502016 26.523830620571971 5.1706301395315677 1.3901866041123867 1.0996175184845924 +leaf_count=24128100 56363 6406 1391 6939 2041 1443 3306 2466 1008 1632 3793 3326 714 14375 21578 601 4732 688 2725 1870 1064 394 483 2103 6026 1840 2323 8632 1681 453 357 +internal_value=0 0.0153895 0.00391928 -0.0610324 0.0104138 0.0070341 0.0301514 0.0762986 -0.009844 0.0227103 -0.0235363 0.0339254 0.0277591 0.0310946 0.0132696 0.0671769 -0.0476873 -0.00228439 0.00118515 -0.0349931 -0.0856728 0.0528188 0.0551742 0.00937334 -0.0324365 0.00477454 0.015906 0.0590273 -0.00110366 -0.13296 0.00199565 +internal_weight=0 500.142 281.451 25.5843 255.867 249.595 218.691 30.367 10.7061 188.324 36.7562 151.568 141.402 139.209 93.178 46.0309 26.8775 16.6465 31.7547 20.0866 12.859 4.49074 7.22759 245.157 33.1286 24.1784 212.028 38.8302 12.3063 8.95014 2.4898 +internal_count=24290853 162753 91592 8330 83262 81221 71161 9880 3474 61281 11961 49320 46014 45300 30324 14976 8746 5420 10329 6536 4183 1458 2353 79778 10779 7866 68999 12636 4004 2913 810 +is_linear=0 +shrinkage=0.1 + + +Tree=96 +num_leaves=32 +num_cat=0 +split_feature=13 6 6 10 20 1 10 4 6 6 14 13 7 7 4 2 20 0 2 2 2 4 6 7 2 15 13 0 15 1 18 +split_gain=12.8624 6.57994 8.06011 10.224 11.2576 9.51145 7.4975 6.82772 6.30364 8.11467 5.22913 5.00188 12.5852 44.626 26.0735 24.9136 22.0698 20.3193 18.0177 16.0638 14.1711 34.3644 16.0347 14.6757 18.7094 13.534 13.5916 19.419 12.5169 12.1058 17.8621 +threshold=44073.000000000007 52.500000000000007 279.00000000000006 11782.500000000002 1.5000000000000002 16.500000000000004 11117.500000000002 74.500000000000014 111.50000000000001 175.50000000000003 47296.000000000007 540.50000000000011 227.00000000000003 173.50000000000003 2.5000000000000004 7.5000000000000009 18.500000000000004 7.5000000000000009 6157.0000000000009 749.00000000000011 807.50000000000011 3.5000000000000004 48.500000000000007 96.500000000000014 2617.0000000000005 10.500000000000002 37934.500000000007 1.5000000000000002 15.500000000000002 413.00000000000006 372.00000000000006 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=10 7 3 -3 -5 -4 -7 -2 -6 -10 11 -1 13 20 16 -15 -14 19 28 25 29 23 -23 24 -22 -17 -27 -28 -16 30 -13 +right_child=1 2 5 4 8 6 -8 -9 9 -11 -12 12 14 15 18 17 -18 -19 -20 -21 21 22 -24 -25 -26 26 27 -29 -30 -31 -32 +leaf_value=6.9291793070702789e-05 0.0074718901599009685 0.03862659272082912 -0.16982417177146902 -0.18558024310400087 -0.18550471956238529 -0.16712357799333422 0.12022474914430355 -0.15865753752591669 0.12580132164190308 -0.18235925859006433 -0.19295305378750138 -0.0032246004842934996 0.0041327289455398101 0.11694303663109962 -0.19553256750732212 0.14030562994240017 -0.15290106678008258 0.032540462176213238 -0.17662090857090296 0.014225141227265238 -0.19655341371403245 -0.1948432230402776 0.15950038121867371 -0.017738900293702942 -0.031599443474621465 -0.16847976891320612 -0.1897740149082689 0.10629053264242339 0.077942969262564388 0.11234551899433046 -0.19798099956833581 +leaf_weight=71006.190927225092 277.29204659577226 194.17483184498269 4.3398610910226108 4.3476598763372731 1.2113901400589382 1.2706897231983019 3.1815358446328901 2.4961727233603588 4.4398003793030512 1.0581712953862732 1.4060486827511329 2575.618736457749 324.70293305814266 5.9251556533854446 1.7107936929678533 1.7054716907441605 9.2034566079964844 10.50629103858955 3.0268564083380616 8.0539272970054281 18.072792537510399 1.486793967662378 9.0529991277726349 17.392121955403127 11.098539976926984 44.381612288940232 4.184540175017899 4.7079047517618164 77.052957487117965 9.0396230465848912 4.7178565986687309 +leaf_count=23107318 90244 63191 1413 1414 395 413 1034 809 1446 343 458 838175 105675 1931 557 556 2995 3418 985 2619 5880 487 2944 5662 3610 14440 1362 1531 25074 2942 1532 +internal_value=0 0.0160857 0.0292838 0.0333005 -0.0602326 -0.0644757 0.038214 0.00598974 0.0209926 0.066491 -0.000107127 -0.00010347 -0.00400817 -0.00647978 0.0121995 -0.0803182 -0.000195591 -0.0962116 0.062802 -0.117672 -0.00426266 -0.0535386 0.109515 -0.0904463 -0.133795 -0.136993 -0.145871 -0.0330293 0.0720029 -0.00317599 -0.00358069 +internal_weight=0 493.812 214.024 205.232 11.057 8.79209 4.45223 279.788 6.70936 5.49797 74149.2 74147.8 3141.64 2725.94 415.697 79.4649 333.906 73.5397 81.7906 63.0335 2646.48 57.1032 10.5398 46.5635 29.1713 54.9795 53.2741 8.89244 78.7638 2589.38 2580.34 +internal_count=24290853 160702 69649 66789 3598 2860 1447 91053 2184 1789 24130151 24129693 1022375 887089 135286 25857 108670 23926 26616 20508 861232 18583 3431 15152 9490 17889 17333 2893 25631 842649 839707 +is_linear=0 +shrinkage=0.1 + + +Tree=97 +num_leaves=32 +num_cat=0 +split_feature=1 11 6 6 15 2 9 2 3 2 18 16 9 16 1 20 1 15 14 3 1 15 5 5 5 14 15 14 14 15 15 +split_gain=4.27655 5.71838 6.06929 6.66223 6.09004 5.20527 26.5497 17.0027 14.7282 5.24324 7.08213 4.52108 12.0791 10.0796 8.95952 28.5549 11.4015 23.0752 19.1229 8.00354 6.62278 9.79517 9.45951 8.13611 14.7901 11.2886 6.5698 5.72177 28.6767 14.6555 6.05651 +threshold=5045.0000000000009 118.50000000000001 54.500000000000007 27.500000000000004 712.00000000000011 12256.000000000002 369.00000000000006 11757.000000000002 14.500000000000002 11670.000000000002 3.5000000000000004 58.500000000000007 18.500000000000004 56.500000000000007 6985.5000000000009 6717.5000000000009 8532.0000000000018 129.50000000000003 13.500000000000002 324.50000000000006 14908.000000000002 315.00000000000006 1520.0000000000002 110.50000000000001 71.500000000000014 7.5000000000000009 269.50000000000006 38.500000000000007 12.500000000000002 192.50000000000003 100.50000000000001 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=5 2 3 4 11 7 -7 9 -9 -1 -11 13 14 -2 15 -13 17 -16 19 -19 21 22 23 24 25 -18 -27 28 29 -25 -29 +right_child=1 -3 -4 -5 -6 6 -8 8 -10 10 -12 12 -14 -15 16 -17 20 18 -20 -21 -22 -23 -24 27 -26 26 -28 30 -30 -31 -32 +leaf_value=-0.00012593706659182438 0.0023329210466644808 -0.16925846485842685 0.11126023400146928 -0.14609182378708815 -0.16680194355155567 -0.19249850317363865 0.11491629531772091 0.16529485127970264 -0.17809925651750502 -0.18459172866294321 0.11509535697426249 0.11233049026010572 -0.15656241215335584 -0.1080490638391411 -0.19225771659036126 -0.17629036381458055 -0.19326627222172241 -0.18556261875305935 -0.1821926873304153 0.12439624899869919 0.008533631599709211 -0.15477867401968007 -0.17203305324486479 0.082056370012022151 -0.19247035995829512 0.1297621132507005 -0.19882106555970802 -0.18772085309221884 -0.15483157582040005 -0.19854436284143021 0.12013495440501842 +leaf_weight=72690.279250937776 1264.0938122071166 1.8878194335848082 5.3470464287674977 2.9304945124167707 2.0657466659322372 3.5022678629611645 14.200177146994973 1.4567229407839466 8.7593514674226736 0.88854095037095149 7.006985571119003 23.08511975849979 4.2158242258155942 8.3271932383067888 8.2321891292231175 4.0256378178019068 1.6215810803696493 0.92222405737265845 3.3864146645646533 8.6158922741888073 460.77665055639955 2.5604085167869917 1.9883004159200925 47.110111406887881 3.7411247058771542 6.4869752288796008 0.67148947622627009 0.6626393585465874 6.3528871284797779 1.9379004281945515 17.943259690422565 +leaf_count=23664240 411525 611 1740 956 674 1142 4620 474 2852 291 2279 7513 1371 2711 2678 1309 528 302 1099 2804 150012 833 647 15336 1217 2112 219 217 2071 631 5839 +internal_value=0 0.00469747 0.00487149 0.00456918 0.00480418 -0.000122013 0.0540971 -0.000135214 -0.129134 -0.000117086 0.0813694 0.00499306 0.0121149 0.00161054 0.0132999 0.0694736 0.0106421 -0.0614002 0.0219485 0.0944269 0.0134041 0.0380446 0.0436222 0.0485776 -0.0259716 0.0449725 0.0989398 0.0611906 0.0450768 0.0709698 0.109171 +internal_weight=0 1888.99 1887.1 1881.75 1878.82 72726.1 17.7024 72708.4 10.2161 72698.2 7.89553 1876.76 604.337 1272.42 600.121 27.1108 573.01 21.1567 12.9245 9.53812 551.853 91.0767 88.5163 86.528 12.5212 8.78005 7.15846 74.0068 55.4009 49.048 18.6059 +internal_count=24290853 614955 614344 612604 611648 23675898 5762 23670136 3326 23666810 2570 610974 196738 414236 195367 8822 186545 6883 4205 3106 179662 29650 28817 28170 4076 2859 2331 24094 18038 15967 6056 +is_linear=0 +shrinkage=0.1 + + +Tree=98 +num_leaves=32 +num_cat=0 +split_feature=13 16 5 1 17 7 17 13 4 13 4 4 9 7 7 7 7 1 4 7 1 16 4 10 4 4 10 5 5 0 0 +split_gain=10.1882 5.622 12.5003 11.1587 10.4813 9.74201 8.01865 6.53512 8.1685 8.49037 6.97207 6.46287 5.99514 6.38217 12.2952 15.9227 8.42469 11.5966 10.1042 9.91345 8.10224 7.95793 7.22053 6.95135 6.35382 5.95094 8.27471 5.61901 10.1383 11.6278 7.99243 +threshold=44073.000000000007 1.0000000180025095e-35 61.500000000000007 23.500000000000004 10.500000000000002 1.0000000180025095e-35 40.500000000000007 49798.000000000007 44.500000000000007 62774.500000000007 39.500000000000007 72.500000000000014 1.0000000180025095e-35 260.50000000000006 241.50000000000003 139.50000000000003 70.500000000000014 16.500000000000004 18.500000000000004 57.500000000000007 1.0000000180025095e-35 11.500000000000002 14.500000000000002 7412.0000000000009 1.5000000000000002 1.0000000180025095e-35 2598.5000000000005 329.50000000000006 46.500000000000007 61.500000000000007 5.5000000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 5 7 -4 6 -2 12 10 -10 -9 -11 13 14 15 16 17 18 19 22 24 -19 27 -24 -18 26 -8 28 29 30 -3 +right_child=1 3 4 -5 -6 -7 25 8 9 11 -12 -13 -14 -15 -16 -17 20 21 -20 -21 -22 -23 23 -25 -26 -27 -28 -29 -30 -31 -32 +leaf_value=-9.5431887214335692e-05 0.035771531421403172 -0.0052539814227233819 -0.19539410131602522 -0.15589379006978801 0.059457580231150144 0.098184240192394773 0.16840727912836451 -0.00044496661973831945 -0.17966172421164467 -0.17811802378946362 0.10262263949937817 0.065739237265726641 0.072487584642014644 -0.15444608185439104 0.13477317267048949 -0.10596093098951316 0.075706447194639162 0.063872509309437581 -0.17611683880185214 -0.17222202336614559 -0.17199257869779611 -0.1798184732458071 0.1609428515437209 -0.19048083830204737 -0.16970200045114955 -0.14205086522804244 -0.17932593461623797 0.14972877811194224 -0.14696792711003517 0.14488256255627069 -0.16677474212061799 +leaf_weight=74120.361348884835 5.801902143750346 33.81390152790118 1.6480902737239365 4.0686006642063139 77.466997810726753 4.7871476310538119 1.6324278770480298 190.56159170717001 5.1162917095352878 1.4929159327584782 6.7973394108703351 3.9953222975018434 22.777240620693192 2.1272112185833967 8.7962496648542565 10.440616274252532 38.080520593794063 31.787083966657519 3.8701776892994522 3.5061494484543791 1.4477552138268936 1.3990287433844049 4.3654828872531644 0.64618506375700224 1.0850703505566333 10.003128625336101 1.1782503701397216 2.1001868913881472 5.3582911480916655 4.8404902280308297 3.3687354692956424 +leaf_count=24129915 1891 11010 538 1326 25215 1558 531 62036 1666 485 2212 1303 7414 690 2865 3399 12398 10348 1258 1141 472 456 1421 210 354 3258 382 683 1746 1576 1096 +internal_value=0 0.0143081 0.0351568 0.00885346 0.0541486 -0.0290463 -0.0617644 0.010582 -0.0014892 -0.0869881 0.00310484 -0.000595053 0.0245433 0.0175891 0.0199515 0.0130389 0.0221967 0.00590815 -0.0196728 -0.00923358 0.0603201 0.0535992 0.00125325 0.115632 0.0689075 -0.105927 0.0226358 -0.0103314 -0.0174261 -0.000908428 -0.0198877 +internal_weight=0 494.36 102.518 391.842 79.1151 23.4029 18.6157 387.774 207.963 10.6045 197.359 5.48824 179.81 157.033 154.906 146.11 135.669 95.0557 61.8696 57.9994 40.6133 33.1861 54.4933 5.01167 39.1656 12.8138 2.81068 49.4816 47.3814 42.0231 37.1826 +internal_count=24290853 160938 33373 127565 25753 7620 6062 126239 67702 3454 64248 1788 58537 51123 50433 47568 44169 30945 20141 18883 13224 10804 17742 1631 12752 4171 913 16111 15428 13682 12106 +is_linear=0 +shrinkage=0.1 + + +Tree=99 +num_leaves=32 +num_cat=0 +split_feature=13 19 4 4 11 10 5 14 11 6 5 5 4 4 13 5 4 10 10 4 5 6 6 10 4 10 14 5 19 14 14 +split_gain=12.2158 5.10637 5.33578 10.1409 16.4682 5.41208 7.7328 7.22735 5.28251 8.86735 9.0764 7.57357 5.74285 5.01889 5.04728 11.2407 9.66611 13.9329 10.8107 8.32496 11.1737 9.73085 15.8801 12.4619 7.93231 10.7767 7.25848 8.74949 7.19766 7.16435 22.4992 +threshold=44073.000000000007 1.5000000000000002 1.5000000000000002 10.500000000000002 88990.500000000015 10064.000000000002 100.50000000000001 1218.5000000000002 45424.000000000007 9.5000000000000018 178.50000000000003 193.50000000000003 14.500000000000002 159.50000000000003 49798.000000000007 278.50000000000006 21.500000000000004 4730.0000000000009 10857.500000000002 4.5000000000000009 222.50000000000003 24.500000000000004 22.500000000000004 12261.500000000002 7.5000000000000009 9492.5000000000018 873.50000000000011 48.500000000000007 3.5000000000000004 658.00000000000011 1744.5000000000002 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 -2 4 -4 7 -7 8 -5 -10 12 -12 -11 14 -3 16 19 -18 26 29 21 22 -21 24 25 -23 -19 -28 -27 -16 -31 +right_child=1 13 3 5 -6 6 -8 -9 9 10 11 -13 -14 -15 15 -17 17 18 -20 20 -22 23 -24 -25 -26 28 27 -29 -30 30 -32 +leaf_value=-0.0001040814372844702 0.10074534503050692 0.02462369044056964 -0.15926172506790451 0.13937897203145705 0.11426794196601134 -0.18207983578917467 0.13620433261905648 -0.1996005454931491 0.1445953865725918 0.036936602031905484 0.16347902685281623 -0.16961871300729209 -0.17343057723013727 -0.14220384150966384 0.076149021180503873 -0.16649385435400008 -0.16633668983626365 -0.024375036085302716 -0.17767344162960122 -0.17011690057180182 0.097441703251140738 -0.15516339510784233 0.14454837759542058 0.15265662438998479 -0.17874463846814692 0.17397632026380658 -0.16598776296825138 0.096336089037160594 -0.18171282844395895 -0.17326056746744251 0.0080350144930724631 +leaf_weight=74125.457114880424 13.002472143620251 199.91472737601725 6.2158380008768273 4.1453229011967876 3.4078343224246055 0.82620538957416911 10.028782556182703 1.348626487655564 3.6232717107050112 1.8360493460204432 2.8809146862477069 0.89453063951805223 4.4257310521788895 2.0953356069803695 13.018001869379075 3.847780611133202 2.916311767534352 6.9734654725179999 1.849586784839629 2.5304834629641872 4.4109645937569431 4.0357333752908806 4.3796366711612791 1.6963869922328729 12.30046467221109 2.4382745781913404 1.3188393786549557 35.401825176435523 0.74206226353999216 7.2234980133362106 130.74348107870901 +leaf_count=24131184 4233 65079 2024 1350 1110 272 3265 439 1179 594 938 294 1442 679 4235 1250 951 2272 602 823 1436 1315 1424 552 4006 796 431 11525 240 2350 42563 +internal_value=0 0.0157297 0.0451581 0.0269215 -0.0624023 0.0555666 0.111979 0.0235973 0.0405025 0.0104981 -0.0379088 0.0845569 -0.111748 0.012192 0.0129344 0.00302516 0.00583693 0.0455607 0.0591291 -0.00465252 -0.0505352 -0.0737447 0.0293181 -0.107317 -0.129914 -0.0466789 0.0691531 0.0869146 0.090984 0.00523421 -0.00145703 +internal_weight=0 490.472 52.6356 39.6331 9.62367 30.0094 10.855 19.1544 17.8058 13.6605 10.0372 3.77545 6.26178 437.837 435.742 235.827 231.979 48.46 45.5437 183.519 32.534 28.123 6.91012 21.2129 19.5165 7.21607 43.6941 36.7207 3.18034 150.985 137.967 +internal_count=24290853 159669 17140 12907 3134 9773 3537 6236 5797 4447 3268 1232 2036 142529 141850 76771 75521 15781 14830 59740 10592 9156 2247 6909 6357 2351 14228 11956 1036 49148 44913 +is_linear=0 +shrinkage=0.1 + + +Tree=100 +num_leaves=32 +num_cat=0 +split_feature=13 17 5 5 17 5 4 4 0 14 5 0 17 4 5 12 11 19 5 5 14 2 18 5 0 5 11 0 5 17 4 +split_gain=13.3334 7.04845 6.62377 12.8436 14.8918 8.36929 15.2807 11.5185 8.52621 8.1194 7.91501 14.5845 6.1371 6.04266 15.4296 9.44329 9.36195 5.92454 6.38496 10.0768 5.89424 9.80763 5.86169 5.70741 11.2777 9.46491 7.02554 5.52221 5.46434 8.42618 11.1173 +threshold=44073.000000000007 63.500000000000007 96.500000000000014 104.50000000000001 279.50000000000006 54.500000000000007 26.500000000000004 1.5000000000000002 242.50000000000003 172.50000000000003 47.500000000000007 115.50000000000001 487.50000000000006 10.500000000000002 178.50000000000003 47154.500000000007 45424.000000000007 3.5000000000000004 28.500000000000004 32.500000000000007 267.50000000000006 1.0000000180025095e-35 12.500000000000002 161.50000000000003 336.00000000000006 212.50000000000003 66987.000000000015 132.50000000000003 79.500000000000014 45.500000000000007 1.0000000180025095e-35 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 20 5 4 -4 10 7 8 -7 -9 11 -3 27 14 -5 -15 -16 18 -13 -20 21 -2 -12 -17 25 26 -25 -8 -22 30 -30 +right_child=1 2 3 13 -6 6 12 9 -10 -11 22 17 -14 15 16 23 -18 -19 19 -21 28 -23 -24 24 -26 -27 -28 -29 29 -31 -32 +leaf_value=-0.00010826796678132963 -0.077082780548641913 0.012854727601003483 -0.17404235102444321 -0.16220139909675393 0.13729099347632676 -0.17557071320248438 -0.15353349330576685 0.050491759370262047 0.092612475407103462 -0.17467466423544908 0.10751404539998864 -0.16009002368670511 -0.17419539548548635 -0.15712185296261313 0.081317067486612785 0.071183816201029398 -0.099018143702125194 -0.15789186536532915 0.13438078559704547 -0.1583898062153114 0.10063225878031357 0.091771390983955634 -0.10069406308314646 -0.17133401372891863 -0.13373029810068035 0.070006825710817386 0.067301583179806931 0.064050076654812216 0.1114965978932656 0.083723667493248263 -0.17158980000041177 +leaf_weight=74130.280968201536 8.0274536904180405 266.21932089675101 1.6879983770195419 6.1331794346915585 17.103877633984666 1.6319851187290648 1.2657278963597551 1.8351659813197319 4.3328967596171424 12.575530719477682 12.394051178300286 2.5380139928311092 1.3603062548791047 2.5033416005317113 9.7889318213565257 30.599267977115232 4.0780408571590661 6.8193240916007198 3.4077320033684373 1.7948181423125786 23.900636334554292 6.0191244774032375 1.5177397419465695 3.2882649482926416 4.6355115706101051 18.700594944006298 1.9744935807539148 14.869216323015278 2.1827907995320848 9.2812528100330365 3.8064982870419044 +leaf_count=24132552 2612 86670 553 1994 5566 531 411 600 1410 4094 4035 827 443 815 3189 9964 1329 2221 1109 582 7776 1959 494 1070 1510 6082 643 4840 705 3022 1245 +internal_value=0 0.0165048 0.0122843 0.0347826 0.109325 0.00548584 -0.0387668 -0.0976274 0.0192379 -0.146 0.0111728 0.00752478 0.0297843 0.0176373 -0.0301299 0.0331207 0.0282835 -0.0899302 -0.030057 0.0333784 0.0508488 -0.00472678 0.0847991 0.0411656 0.00904777 0.036667 -0.0818022 0.0469814 0.0707779 0.0240514 -0.0684192 +internal_weight=0 486.273 433.055 100.494 18.7919 332.562 37.8708 20.3756 5.96488 14.4107 294.691 280.779 17.4953 81.7016 20.0002 61.7015 13.867 14.5599 7.74056 5.20255 53.2178 14.0466 13.9118 59.1981 28.5989 23.9634 5.26276 16.1349 39.1712 15.2705 5.98929 +internal_count=24290853 158301 140982 32715 6119 108267 12329 6635 1941 4694 95938 91409 5694 26596 6512 20084 4518 4739 2518 1691 17319 4571 4529 19269 9305 7795 1713 5251 12748 4972 1950 +is_linear=0 +shrinkage=0.1 + + +Tree=101 +num_leaves=32 +num_cat=0 +split_feature=13 10 17 17 13 4 13 17 19 7 8 0 10 5 13 17 5 5 19 11 17 4 4 10 4 19 7 18 8 8 16 +split_gain=13.8402 7.01429 7.97625 6.68809 7.1994 8.90686 8.72619 6.35285 6.07316 10.9636 5.80759 5.71836 4.94596 6.45849 4.92346 4.68696 5.62323 5.38711 6.15009 4.32291 11.4656 9.99993 9.11337 14.4656 4.54611 8.87116 4.73386 10.2543 8.74378 9.80632 11.1502 +threshold=44073.000000000007 9425.5000000000018 722.50000000000011 30.500000000000004 49798.000000000007 87.500000000000014 50488.500000000007 101.50000000000001 858.00000000000011 13.500000000000002 1.5000000000000002 4.5000000000000009 4602.0000000000009 44.500000000000007 52108.000000000007 45.500000000000007 813.00000000000011 335.50000000000006 7.5000000000000009 55868.000000000007 244.50000000000003 36.500000000000007 26.500000000000004 5060.5000000000009 2.5000000000000004 3.5000000000000004 18.500000000000004 13.500000000000002 15.500000000000002 28.500000000000004 1183.0000000000002 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 3 10 12 5 8 -6 -8 -5 -10 15 -11 -2 -14 -15 16 -3 -17 -19 20 -9 22 23 24 26 -26 27 -22 -28 30 -30 +right_child=1 2 -4 4 6 -7 7 19 9 11 -12 -13 13 14 -16 17 -18 18 -20 -21 21 -23 -24 -25 25 -27 28 -29 29 -31 -32 +leaf_value=-0.00010959783919781664 0.12020061913752317 -0.16179210850006001 -0.16184935623942867 0.029308216302203163 -0.16637849256444379 -0.16417420771503421 -0.10507316752210727 -0.12094822978773807 0.14333233445186921 -0.21112709381826017 -0.16566979060183071 -1.4775273938103735e-09 -0.15992443908714246 0.065268961694211716 -0.15059168658501731 0.058692616202542937 0.14113144348031745 -0.15714396700878608 0.091028682686705317 0.034127007178599934 -0.15497865304044056 -0.16182364164830798 0.10401330671568061 -0.16057221096300187 0.16910474548719551 -0.13854705795214481 0.1432942885918643 0.1717465585466639 0.040775658500936431 0.017167866466192259 -0.17483224931772695 +leaf_weight=74131.613848817753 8.9801525222137588 2.1231413876812435 1.887630556419025 160.82186510576867 3.326834322186186 2.520328518934547 5.9044307763979296 8.3012141581566521 1.9138821167871345 3.5245410976931453 1.2799846544221498 2.0170402601361275 1.6460682977922285 13.820703954435883 1.1441010950366024 82.527213852270506 0.86143727251328517 2.4975346734572659 1.6637597144581375 31.314554570708424 4.2674581076134919 3.72026310826186 8.5987890426767972 5.5280512139433986 1.1588718672282983 4.9012904796982184 4.9630283314036197 1.2396295021171679 4.0442586353747156 97.633510632032994 5.8944560649688356 +leaf_count=24134575 2923 690 615 52361 1083 818 1923 2703 628 1145 416 655 535 4498 372 26871 280 811 542 10195 1389 1212 2799 1800 379 1593 1619 403 1316 31785 1919 +internal_value=0 0.0169253 0.0416112 0.011006 0.00750959 0.0224232 -0.00584076 -0.00299186 0.0252179 -0.0630149 0.0458338 -0.13428 0.0604096 0.0280855 0.0487658 0.0488528 -0.0743595 0.0530949 -0.0579201 0.000327777 -0.0067165 -3.62235e-05 0.00431807 -0.00229501 0.00475532 -0.0797155 0.00909196 -0.0814338 0.013522 0.00753471 -0.0870971 +internal_weight=0 480.026 92.8407 387.185 361.594 170.798 190.797 187.47 168.277 7.45546 90.9531 5.54158 25.591 16.6109 14.9648 89.6731 2.98458 86.6885 4.16129 181.565 150.251 141.95 138.229 129.631 124.103 6.06016 118.042 5.50709 112.535 107.572 9.93871 +internal_count=24290853 156278 30225 126053 117725 55607 62118 61035 54789 2428 29610 1800 8328 5405 4870 29194 970 28224 1353 59112 48917 46214 45002 42203 40403 1972 38431 1792 36639 35020 3235 +is_linear=0 +shrinkage=0.1 + + +Tree=102 +num_leaves=32 +num_cat=0 +split_feature=13 19 19 17 19 10 17 17 4 4 7 18 17 19 12 7 12 13 18 12 19 7 7 7 12 19 12 10 7 12 12 +split_gain=14.509 7.10657 5.07367 5.78676 5.58051 8.49867 7.23065 9.52903 7.00085 8.36499 14.7635 6.90154 6.42629 7.39526 6.07607 7.05972 6.14735 16.8167 6.09832 9.49697 6.22962 10.1029 9.39291 9.27917 6.66887 6.15226 5.30982 6.28771 9.67804 6.69183 5.60108 +threshold=44073.000000000007 1.5000000000000002 2.5000000000000004 84.500000000000014 4.5000000000000009 2535.5000000000005 63.500000000000007 244.50000000000003 44.500000000000007 13.500000000000002 18.500000000000004 20.500000000000004 1231.0000000000002 6.5000000000000009 11147.500000000002 143.50000000000003 10776.000000000002 47328.000000000007 1.5000000000000002 9618.0000000000018 507.50000000000006 42.500000000000007 37.500000000000007 87.500000000000014 4634.5000000000009 33.500000000000007 2188.5000000000005 9425.5000000000018 77.500000000000014 8110.5000000000009 1785.5000000000002 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 -2 3 -3 5 -4 -6 26 9 10 12 -10 13 -9 16 -16 18 -18 25 20 22 -22 -20 -24 -23 -12 30 28 -28 -30 -8 +right_child=1 2 4 -5 6 -7 7 8 11 -11 14 -13 -14 -15 15 -17 17 -19 19 -21 21 24 23 -25 -26 -27 27 -29 29 -31 -32 +leaf_value=-0.00011107102061126738 0.054259650533139872 -0.17328688139296428 -0.17480386958558256 -0.01472728739073492 0.075692754275258872 0.048898770138710461 -0.19022437915512291 -0.1569177578250166 -0.1462514557897302 0.065821037255531831 -0.18590967806022979 0.088190513242694016 -0.15868518567796377 -0.0085268974052202361 -0.17361524820448676 -0.0024135955298415198 0.12798222069935952 -0.18709237842112614 0.055019639583139934 -0.17319595590795445 -0.16350446651160547 -0.16178670663194794 -0.16456305729738885 -0.022689798972906804 0.060928474952925651 0.033882545303912597 -0.14773906284531677 1.299297060718966e-05 0.016136874596920005 -0.20012248218769996 0.11739549955980425 +leaf_weight=74132.078356813145 47.324737228103913 2.6918444106704582 1.7458151116734359 15.881189733423524 16.271667546971003 62.369533721168409 0.70604679873213072 4.0951357286248884 4.9873160393326534 30.557395653391723 1.286976984119973 1.6781917775515465 5.1739941867999724 18.669188529398525 3.3354802289977661 8.6680974893970397 12.64012037438806 1.9561597276478995 9.4093685937696154 3.2316581758204839 2.3944701630680383 1.4128041363437684 6.5118487703730379 15.785331134626174 27.798585050739348 121.90349382872228 11.32392760057701 15.603987583890556 9.4751297194161435 1.6853614075807843 3.6608683289960027 +leaf_count=24137741 15407 875 566 5173 5294 20311 230 1336 1621 9953 418 546 1686 6075 1085 2824 4117 636 3062 1053 783 458 2118 5145 9051 39698 3686 5080 3086 548 1191 +internal_value=0 0.01751 0.0133977 -0.0377078 0.0157452 0.0428075 0.0106452 0.00737799 0.0140386 0.0164946 0.0103241 -0.0872254 -0.058086 -0.0352213 0.0191588 -0.0499861 0.0232208 0.0857567 0.0184099 -0.0059831 0.00255195 0.0339698 -0.0287662 -0.0641236 0.0501569 0.0315864 -0.0367846 -0.0487593 -0.0826068 -0.0165208 0.0676592 +internal_weight=0 470.236 422.911 18.573 404.338 64.1153 340.223 323.951 281.496 274.83 244.273 6.66551 27.9383 22.7643 216.334 12.0036 204.331 14.5963 189.735 66.5441 63.3124 31.6059 31.7065 22.2972 29.2114 123.19 42.4553 38.0884 22.4844 11.1605 4.36692 +internal_count=24290853 153112 137705 6048 131657 20877 110780 105486 91665 89498 79545 2167 9097 7411 70448 3909 66539 4753 61786 21670 20617 10292 10325 7263 9509 40116 13821 12400 7320 3634 1421 +is_linear=0 +shrinkage=0.1 + + +Tree=103 +num_leaves=32 +num_cat=0 +split_feature=13 17 12 17 10 21 10 17 12 1 8 7 16 1 17 21 12 8 3 13 21 1 14 7 3 7 10 7 5 3 5 +split_gain=15.911 5.4345 4.48663 13.6716 17.3787 15.576 14.5333 13.6229 16.7902 48.5434 12.6858 12.4274 20.6897 16.9564 15.5852 16.5199 14.5539 15.1498 17.516 17.9126 12.3577 12.1563 11.012 10.6407 12.1641 10.3153 10.3796 9.52016 12.3078 9.34508 9.91672 +threshold=44073.000000000007 63.500000000000007 4813.5000000000009 62.500000000000007 11530.500000000002 37.500000000000007 11782.500000000002 182.50000000000003 5191.5000000000009 1.0000000180025095e-35 341.00000000000006 402.50000000000006 180.50000000000003 1.0000000180025095e-35 1709.0000000000002 1.5000000000000002 14446.500000000002 365.50000000000006 11.500000000000002 8445.0000000000018 21.500000000000004 118.50000000000001 92.500000000000014 393.00000000000006 6.5000000000000009 508.50000000000006 15537.500000000002 70.500000000000014 9.5000000000000018 1.5000000000000002 308.50000000000006 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=2 -2 -1 5 7 10 22 -5 9 -9 20 21 -13 14 15 -14 17 18 -17 -20 25 23 -6 -10 -25 26 -4 28 -11 30 -8 +right_child=1 -3 3 4 6 -7 29 8 11 27 -12 12 13 -15 -16 16 -18 -19 19 -21 -22 -23 -24 24 -26 -27 -28 -29 -30 -31 -32 +leaf_value=1.7330341678451623e-05 0.04925663862800251 0.014678235049776526 -0.029129514393509793 0.016004175148812531 0.10104941829271226 -0.19549651630515308 -0.072633714490117615 -0.19374704146365757 -0.00062571896064971498 -0.19653260935848671 -0.19850512925548547 -0.19953704623155918 -0.19813216294625585 -0.20324759813741855 -0.20101879397806785 -0.19681061110895956 -0.20599938815307561 0.099506718113678047 -0.19794503308292899 0.121959239719789 0.085639004703741028 -0.17871099277438829 -0.19688211190732874 0.16573262009022524 -0.19868434025533427 0.066350400056776851 0.14804028882638695 0.098787597727831591 0.0064068315157318687 -0.19632228548367794 0.06206019288357828 +leaf_weight=72043.385478758981 51.108851006953046 410.6230379252811 268.79133738798555 411.10127853065205 1.4095708176027972 5.221133218612521 10.714844363508751 17.916890827938914 1219.3142864018155 3.4709106134250787 3.9850014760158947 7.7840980649925759 3.4234456350095561 4.7870337865315369 3.7338763679144895 4.4854764232877633 2.422071903012692 21.72462681226898 2.3217356833629292 7.1118962136097252 10.89398155082017 3.8129516760818651 10.349795601796357 6.7631631868425748 1.0594622078351674 12.389455120312048 3.3479468449950209 8.8730739823076856 21.499428884242661 2.8538355983328065 11.158278291346504 +leaf_count=23459055 16641 133711 87525 133870 460 1701 3488 5834 397046 1131 1298 2535 1115 1558 1218 1460 787 7074 754 2317 3546 1239 3370 2203 344 4034 1091 2890 6997 931 3630 +internal_value=0 0.0185057 -0.000115258 -0.00467969 -0.00134355 -0.024262 -0.0696507 7.9302e-05 -0.00480448 -0.0606488 -0.0212759 -0.00256159 -0.047881 -0.0242757 -0.00533083 0.0122803 0.0312038 0.0473223 -0.034126 0.0432268 -0.0188853 -0.0004338 -0.16117 0.000120142 0.116378 -0.0228873 -0.0269499 0.00981419 -0.021802 -0.0261272 -0.00392144 +internal_weight=0 461.732 74136.1 2092.72 1788.09 304.629 36.4863 1751.61 1340.5 51.7603 299.408 1288.74 57.7943 50.0102 45.2231 41.4893 38.0658 35.6437 13.9191 9.43363 295.423 1230.95 11.7594 1227.14 7.82263 284.529 272.139 33.8434 24.9703 24.727 21.8731 +internal_count=24290853 150352 24140501 681446 582251 99195 11879 570372 436502 16852 97494 419650 18818 16283 14725 13507 12392 11605 4531 3071 96196 400832 3830 399593 2547 92650 88616 11018 8128 8049 7118 +is_linear=0 +shrinkage=0.1 + + +Tree=104 +num_leaves=32 +num_cat=0 +split_feature=17 10 10 16 17 18 14 14 9 8 14 14 5 2 10 15 6 8 0 0 8 3 8 8 8 8 19 16 9 19 10 +split_gain=3.36162 17.7654 28.2954 20.2703 15.531 25.8482 23.8001 22.8923 33.1944 15.1938 15.8425 13.6051 13.5989 13.2166 13.6159 12.2492 11.9694 17.2042 10.7608 10.0211 13.3998 9.84498 9.70229 15.3223 21.5827 16.5455 14.2883 11.0946 13.8322 10.0069 15.0334 +threshold=30403.500000000004 29.500000000000004 41.500000000000007 5847.5000000000009 37648.500000000007 33642.000000000007 7360.0000000000009 5654.0000000000009 26.500000000000004 35.500000000000007 2089.0000000000005 9058.5000000000018 11.500000000000002 1.0000000180025095e-35 5.5000000000000009 81983.000000000015 1.0000000180025095e-35 5.5000000000000009 9.5000000000000018 5.5000000000000009 129.50000000000003 23.500000000000004 365.50000000000006 17.500000000000004 8.5000000000000018 3.5000000000000004 1541.5000000000002 11872.000000000002 17.500000000000004 43288.000000000007 10.500000000000002 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 4 9 -4 5 13 7 8 21 11 -11 -3 -13 -2 -15 19 17 18 -16 20 -5 22 23 24 25 27 -26 29 -29 30 -6 +right_child=1 2 3 15 6 -7 -8 -9 -10 10 -12 12 -14 14 16 -17 -18 -19 -20 -21 -22 -23 -24 -25 26 -27 -28 28 -30 -31 -32 +leaf_value=6.2056783097412643e-05 -0.19746593754435371 0.083835046794060814 -0.20070623817159294 0.025859965981393881 0.070207218482231368 -0.19355298255342207 -0.1984047390609496 0.12004777736774541 -0.19622864408943527 0.11540465168179913 -0.19833650342008835 -0.15050008010364491 0.08346101350489965 -0.19596360818308017 0.13845288378544132 -0.19916219913415242 0.087667761680336978 -0.19784712939672144 -0.19737583005788456 -0.019083151476906438 -0.19512960209722371 0.1426339175986526 0.19520305087131998 -0.19473397182014734 -0.19445661060514499 -0.19661374103095197 0.16597432742076504 0.098236880848589003 -0.19176255380145774 -0.032407164581692786 -0.19822701461379708 +leaf_weight=73966.494412626038 4.0379703983198878 71.426201251510065 5.3883083626860744 109.00569115963299 19.228220913559202 13.936790290492352 6.9375670449808231 12.697328363195991 9.9775283838389424 2.5174221391789606 4.4624154656485189 5.300056866835801 4.6764008107129484 3.2146395931486067 4.2471401176881063 3.2041161147062658 12.901059360359794 4.6957601816393435 1.2305913888849316 153.07671906700125 2.8146642762585534 4.1766718845465212 2.2218261952511957 4.6267955626826724 1.2395386623684306 5.0954109267331651 9.7601755984360334 14.340648010955194 1.8578176986193273 131.49975896987598 2.3402414019219568 +leaf_count=24085019 1315 23255 1752 35497 6261 4538 2261 4134 3247 819 1453 1726 1523 1047 1382 1044 4200 1529 401 49851 915 1359 724 1509 403 1659 3180 4667 605 42815 763 +internal_value=0 -0.00726142 0.00722627 -0.00866992 -0.0266599 -0.0808269 -0.0160508 -0.0102757 -0.0182944 0.0564152 -0.0851793 0.0685562 -0.0408323 -0.0290238 -0.00315142 -0.00481037 0.0237103 -0.0573945 0.0630078 -0.00245955 0.0202974 -0.00925433 -0.0125548 -0.0149844 -0.0104977 -0.0190683 0.125358 -0.0137236 0.0649766 -0.0220521 0.0410813 +internal_weight=0 632.135 361.872 273.489 270.263 44.264 226 219.062 206.365 88.3825 6.97984 81.4027 9.97646 30.3272 26.2892 268.101 23.0746 10.1735 5.47773 264.897 111.82 196.387 192.21 189.989 185.362 174.362 10.9997 169.267 16.1985 153.068 21.5685 +internal_count=24290853 205834 117835 89059 87999 14412 73587 71326 67192 28776 2272 26504 3249 9874 8559 87307 7512 3312 1783 86263 36412 63945 62586 61862 60353 56770 3583 55111 5272 49839 7024 +is_linear=0 +shrinkage=0.1 + + +Tree=105 +num_leaves=32 +num_cat=0 +split_feature=13 16 5 8 1 4 5 13 4 8 8 17 4 20 19 19 20 8 0 19 20 8 21 4 4 8 3 4 3 0 13 +split_gain=12.7405 5.06876 7.53799 5.56745 5.50624 4.54627 5.47292 4.47996 5.64027 5.67503 9.17688 8.30757 8.1684 7.69347 6.37122 5.9289 8.55312 5.50164 8.11909 10.7759 4.52452 4.70116 6.74113 5.23216 4.76829 7.9386 7.41781 5.75756 4.94179 5.77779 4.80038 +threshold=44073.000000000007 1.0000000180025095e-35 61.500000000000007 1.0000000180025095e-35 23.500000000000004 1.0000000180025095e-35 50.500000000000007 49798.000000000007 44.500000000000007 44.500000000000007 31.500000000000004 3778.5000000000005 1.0000000180025095e-35 15.500000000000002 95.500000000000014 2616.0000000000005 17.500000000000004 66.500000000000014 8.5000000000000018 45.500000000000007 2.5000000000000004 74.500000000000014 9.5000000000000018 1.5000000000000002 1.0000000180025095e-35 102.50000000000001 19.500000000000004 2.5000000000000004 21.500000000000004 1.5000000000000002 132471.00000000003 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 3 5 7 6 -2 -3 9 10 11 13 14 -9 -12 16 17 18 19 -11 -19 22 23 -22 25 28 -27 30 -23 -30 -26 +right_child=1 4 -4 -5 -6 -7 -8 8 -10 15 12 -13 -14 -15 -16 -17 -18 20 -20 -21 21 24 -24 -25 27 26 -28 -29 29 -31 -32 +leaf_value=-0.00010343358055666693 -0.13723585286020692 0.024115626655783581 0.051633665571063553 0.081508429899258231 -0.13111277235400035 -0.079717498229047612 0.13541991550573892 0.017550715728036067 -0.07488670374889321 0.10891611813074083 -0.162774064059582 -0.15678483675752108 -0.15956637824203926 -0.13924967420571274 0.10475488493256774 0.090509084177710156 -0.19450753782495644 0.07975493326262538 0.17288312676396322 -0.16679287702294521 -0.1706390293754787 -0.18421290906379248 0.12554571131418443 0.12118147349036597 0.04541663750389037 0.013184060327424044 -0.1754061786797981 -0.1420789079523124 -0.1714121464131807 0.094112613553985383 -0.20298243973351468 +leaf_weight=74097.13040392744 1.0630607131752214 171.67700809345115 74.506313650490483 4.2503770003677337 2.6961281811818472 11.114908125135115 2.3942594926338643 81.619621821067994 9.0995117896818538 1.7533765267580728 0.93151594436495244 3.0370391769683911 1.3650130893220183 3.2539098989509503 20.063628152362071 4.9695910294540218 2.776925155776552 5.8464736230671397 1.1056883322889905 7.4023718499811366 1.7687245398992666 5.7803247291012685 5.1540027952287346 0.9414154445985331 15.745502971578393 14.956471292302014 2.4235931956209233 2.1146657970966762 1.5462527257041077 1.743605288211256 0.8184316838160165 +leaf_count=24139714 346 55928 24276 1384 881 3621 778 26592 2961 571 303 989 446 1060 6536 1619 906 1903 361 2413 576 1882 1681 308 5128 4873 791 688 503 568 267 +internal_value=0 0.0165202 0.0373492 -0.0191935 0.0112747 -0.0485658 0.0515834 0.0123182 0.00199621 0.00573501 0.0202732 0.00572417 0.0774738 0.0115392 0.0928851 -0.0151263 -0.0224299 -0.0155147 -0.0830816 -0.113993 -0.00373117 -0.0129418 0.0584084 -0.06927 -0.0253753 -0.0526909 -0.0131142 0.0133057 -0.128527 -0.0306856 0.0331431 +internal_weight=0 463.92 93.3289 18.8226 370.591 14.5722 3.45732 367.895 196.218 187.118 110.271 87.9106 22.3602 84.8735 20.9951 76.8474 71.8778 69.1009 10.2614 9.15575 58.8395 52.993 7.86414 2.71014 45.1288 26.4502 17.3801 18.6786 9.07018 3.28986 16.5639 +internal_count=24290853 151139 30405 6129 120734 4745 1124 119853 63925 60964 35926 28641 7285 27652 6839 25038 23419 22513 3345 2984 19168 17265 2565 884 14700 8617 5664 6083 2953 1071 5395 +is_linear=0 +shrinkage=0.1 + + +Tree=106 +num_leaves=32 +num_cat=0 +split_feature=13 19 1 2 9 8 12 20 13 8 5 21 19 8 13 13 5 2 1 20 5 20 13 21 8 1 13 9 8 20 13 +split_gain=12.0259 4.87884 4.14364 7.10245 12.9966 7.80171 5.36623 16.2491 18.5268 25.9586 20.1003 15.2662 12.7484 11.6234 13.5168 10.8339 10.4522 11.2895 9.22213 8.04978 11.4992 10.9297 12.6089 28.5122 21.7847 17.2536 14.6353 13.6645 12.2105 10.9007 9.91513 +threshold=44073.000000000007 1.5000000000000002 5045.0000000000009 12256.000000000002 369.00000000000006 1.0000000180025095e-35 8.5000000000000018 11844.500000000002 98.500000000000014 243.50000000000003 697.00000000000011 32525.500000000004 41174.000000000007 31.500000000000004 48.500000000000007 58.500000000000007 1.5000000000000002 11.500000000000002 19.500000000000004 9052.5000000000018 1520.0000000000002 10353.500000000002 110.50000000000001 32525.500000000004 53.500000000000007 20.500000000000004 209.50000000000003 3.5000000000000004 19.500000000000004 5745.0000000000009 240.50000000000003 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=2 -2 3 6 -5 -6 7 -1 10 16 11 18 13 14 -13 -16 -10 -18 -9 29 21 -21 23 25 26 -23 28 30 -24 -8 -26 +right_child=1 -3 -4 4 5 -7 19 8 9 -11 -12 12 -14 -15 15 -17 17 -19 -20 20 -22 22 24 -25 27 -27 -28 -29 -30 -31 -32 +leaf_value=-1.7600001670676185e-05 0.046540655114469198 0.012639040728137877 0.0045246254249069456 -0.17141967935655406 -0.19715832759112562 0.1143623573639734 -0.0022589189273401348 -0.19087658947574945 -0.19350407060818955 -0.15878460208043663 -0.18172933006001382 -0.19131978891118351 0.030998701343068175 -0.18492714205167254 0.16446565712154698 -0.19334524707832804 0.034840506128707968 -0.19854942920777982 0.06977475304059183 -0.1271339876878563 0.13916189853418862 0.1704220405037917 0.03071659235859486 0.0037676073744682317 0.14027253347873628 -0.08982003957912485 -0.19380881438972289 0.011622424109110952 -0.1769594988641435 0.051145458475850981 -0.18817690664109765 +leaf_weight=63421.801786609663 47.327726859773975 411.86616504103586 1888.0890301929612 2.0566403754637603 0.85238667018711467 14.140389451466033 8201.7389089951175 1.3730329945683468 2.3701372947252812 11.399266731328678 3.9307559852022669 2.4846847895532829 41.185500801220769 5.9856798988766968 4.1733783156378195 1.0614283900940789 39.032721905299695 2.1887973318807772 119.29942658299115 9.440880654845385 4.5356909781694403 2.6396951060742131 10.816005375701936 84.590577951952582 13.269651780079581 72.984255103219766 7.7162407078722017 83.797010426438646 3.8349511469423296 38.399686010263395 0.98749398929067034 +leaf_count=20663429 15419 134190 615154 672 280 4604 2672215 447 773 3715 1280 811 13417 1949 1360 344 12718 715 38868 3077 1478 859 3521 27562 4325 23776 2515 27302 1250 12507 321 +internal_value=0 0.0161332 -9.99826e-05 -0.000220906 0.0643145 0.0966514 -0.000236148 7.95474e-05 0.0263553 -0.0244281 0.0419136 0.0469209 0.00319851 -0.080344 0.000749519 0.0919146 0.0107063 0.0224479 0.066809 -0.00259075 -0.0188327 -0.0213031 -0.0177429 -0.0361196 0.00670639 -0.0807361 -0.0823472 0.0270205 -0.0236435 -0.00201005 0.117523 +internal_weight=0 459.194 74096.2 72208.1 17.0494 14.9928 72191 63656.3 234.485 54.9909 179.494 175.563 54.8907 13.7052 7.71949 5.23481 43.5917 41.2215 120.672 8534.75 294.612 290.077 280.636 160.215 120.421 75.624 22.3672 98.0542 14.651 8240.14 14.2571 +internal_count=24290853 149609 24141244 23526090 5556 4884 23520534 20739826 76397 17921 58476 57196 17881 4464 2515 1704 14206 13433 39315 2780708 95986 94508 91431 52197 39234 24635 7286 31948 4771 2684722 4646 +is_linear=0 +shrinkage=0.1 + + +Tree=107 +num_leaves=32 +num_cat=0 +split_feature=15 14 14 14 8 8 8 14 4 7 2 7 16 8 8 6 21 14 10 8 8 8 6 0 5 17 6 6 6 7 4 +split_gain=3.74141 21.5024 8.22881 9.82486 11.3172 7.80886 6.73458 13.3236 9.46519 5.96926 9.52171 8.66489 5.41353 20.6844 30.3997 14.1987 12.7593 10.7496 6.63899 5.00909 5.36355 10.7372 11.3541 10.1097 12.6014 11.9465 8.99899 14.1334 15.6056 8.38283 7.87951 +threshold=44167.000000000007 1842.0000000000002 1908.5000000000002 3469.0000000000005 102.50000000000001 107.50000000000001 1.0000000180025095e-35 6109.5000000000009 112.50000000000001 267.50000000000006 228.50000000000003 378.00000000000006 41351.500000000007 29.500000000000004 1.5000000000000002 1.5000000000000002 123.50000000000001 7437.5000000000009 44.500000000000007 293.00000000000006 66.500000000000014 44.500000000000007 9.5000000000000018 19.500000000000004 4.5000000000000009 52108.000000000007 79.500000000000014 45.500000000000007 16.500000000000004 70.500000000000014 1.0000000180025095e-35 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=12 -2 -3 4 -4 -6 7 8 -5 19 -11 -12 -1 14 17 16 -15 -14 -18 20 21 26 -23 24 25 -22 27 28 -8 -24 -29 +right_child=1 2 3 6 5 -7 9 -9 -10 10 11 -13 13 15 -16 -17 18 -19 -20 -21 23 22 29 -25 -26 -27 -28 30 -30 -31 -32 +leaf_value=-3.4730763206100269e-05 -0.19553309278206568 0.18214064146458128 -0.1917041277318195 0.15216081767380826 0.17764552468755027 -0.1971942125146231 0.019776525692198385 -0.19287464861151707 -0.18229415808636359 -0.16143794334856118 -0.15639367720753411 0.067339294100878244 -0.19264755287567603 -0.1893495460537159 -0.19537389936132524 -0.19138379421350626 -0.20091609816472183 0.13306660265969439 0.13460951795264314 -0.18552670885701372 0.027250979661978137 -0.12362088814706534 -0.13202939865371563 -0.19608937652330652 0.10330080792317438 -0.1926992635413857 -0.15853044772575936 0.12815101531694559 -0.093715134633431812 0.024556129675761987 -0.088974314829550677 +leaf_weight=74134.261497215804 5.0589992569293818 2.819336690939962 4.927166338893584 2.9316140869632346 1.8466063346713784 0.7950604804791509 162.05074808193604 4.5283873401640449 1.1894894414581356 4.9197385773295528 2.3141436874866468 6.8696623211435508 1.2583129187114561 1.5247752314899106 13.115895421011372 2.1801458531990638 0.62142511457204719 5.2027401914820075 11.561258578905834 1.216551647055893 124.10073990400997 11.618958474224199 4.1458044415339854 1.9132085893070314 23.023892595985672 2.5195437744259825 2.8690039455541401 15.786565449321644 13.09483636455843 19.499562334909569 1.8693021705839772 +leaf_count=24141908 1648 919 1603 956 601 260 52777 1471 387 1602 754 2236 407 495 4270 710 201 1697 3768 396 40412 3785 1349 623 7500 822 934 5139 4265 6350 608 +internal_value=0 0.00939024 0.0118772 0.0107178 -0.102169 0.0648303 0.0128199 -0.0744751 0.0556258 0.014718 -0.0491757 0.0109629 -5.34167e-05 -0.0391141 -0.107913 0.0456608 0.0833623 0.0696326 0.117495 0.0170664 0.0177108 0.00811769 -0.0426743 0.0323282 0.0352485 0.0228743 0.0172716 0.0198876 0.0112913 -0.00289842 0.105163 +internal_weight=0 421.909 416.85 414.031 7.56883 2.64167 406.462 8.64949 4.1211 397.812 14.1035 9.18381 74169.7 35.4646 19.5769 15.8876 13.7075 6.46105 12.1827 383.709 382.492 230.935 35.2643 151.557 149.644 126.62 195.67 192.801 175.146 23.6454 17.6559 +internal_count=24290853 137397 135749 134830 2464 861 132366 2814 1343 129552 4592 2990 24153456 11548 6374 5174 4464 2104 3969 124960 124564 75207 11484 49357 48734 41234 63723 62789 57042 7699 5747 +is_linear=0 +shrinkage=0.1 + + +Tree=108 +num_leaves=32 +num_cat=0 +split_feature=12 8 5 12 10 15 6 6 16 5 10 6 3 16 10 19 5 10 5 10 16 11 16 7 0 5 18 16 6 6 6 +split_gain=7.02385 6.15966 4.40439 9.97765 7.02145 4.72335 3.89431 21.3481 8.95551 7.41561 7.48281 9.91801 7.16336 16.5913 10.5534 6.74892 5.36028 8.25931 7.53901 4.87777 7.54582 4.85214 4.82289 4.30529 4.26793 3.93831 5.93747 4.37759 3.88105 3.85968 3.74962 +threshold=47154.500000000007 1.5000000000000002 38.500000000000007 50858.500000000007 5374.5000000000009 3849.5000000000005 73.500000000000014 83.500000000000014 11.500000000000002 96.500000000000014 9813.0000000000018 53.500000000000007 1.0000000180025095e-35 2.5000000000000004 7520.0000000000009 3.5000000000000004 161.50000000000003 10734.000000000002 266.50000000000006 11117.500000000002 13.500000000000002 55868.000000000007 3.5000000000000004 1.0000000180025095e-35 242.50000000000003 280.50000000000006 6.5000000000000009 1.5000000000000002 202.00000000000003 391.50000000000006 68.500000000000014 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 3 -2 5 -5 8 -8 9 10 23 15 14 28 -9 -12 19 -18 24 21 -21 -16 -11 30 -19 -20 27 -27 -14 -30 -4 +right_child=1 -3 6 4 -6 -7 7 12 -10 22 11 -13 13 -15 16 -17 17 18 25 20 -22 -23 -24 -25 -26 26 -28 -29 29 -31 -32 +leaf_value=-4.9943844258159523e-05 -0.14586021029192361 -0.15601758211450226 0.052476933062799415 0.064784601219531099 -0.14946622963280376 -0.15348097664424976 -0.16712557521393623 -0.15642130382164851 -0.1580695577215438 0.11562629921128878 -0.18087028695715723 -0.17603512946755348 0.088908779924271208 -0.15607883926287983 0.084184414878274272 0.07740982478500795 -0.12273478456968843 -0.16886747396005442 0.13237277180382154 -0.17041919754857376 0.12653264546122298 -0.14468739634955752 -0.19098153346927196 -0.14198516120510077 0.02223618582280848 -0.18101930490155418 -0.16009982474326845 0.13074825158163525 -0.14379354552294932 0.086301412841522387 -0.14941801800280527 +leaf_weight=74390.8460860013 5.0211761078098816 1.9938616304425512 55.987958348967368 11.48935850523412 2.1535940942703737 1.0851095925318066 6.2602071627916294 4.0419239461771204 2.2099613218015284 16.053623246902138 1.2534257654333467 3.7844157770741731 30.535125116934069 3.3175316163687958 16.120987629983581 5.2460100492462516 5.9423813133034882 2.6806982634589067 4.8091914411634189 2.2348912960733278 1.3866739119403066 0.98276234773220394 0.52996422862633963 1.2032655805815 2.0718445508973673 0.53866800415562499 1.5065539539791641 2.7476691659539938 1.4978014400694513 1.420316651521716 0.9352563985157748 +leaf_count=24226683 1635 648 18236 3740 702 352 2039 1316 720 5228 409 1232 9948 1079 5254 1708 1933 872 1565 729 452 319 172 392 676 176 490 892 488 463 305 +internal_value=0 0.0188553 0.0206429 -0.024127 0.017375 0.0459494 0.0256867 0.0108575 0.0406675 0.0458349 0.0312918 -0.0473368 0.0244729 0.057226 -0.00225253 0.0276001 0.0129377 -0.0235896 0.0174535 0.0487102 -0.0567183 0.0710337 0.105828 0.0452029 -0.0855569 0.0684383 0.00428647 0.079646 0.0783793 -0.0318009 0.0491598 +internal_weight=0 197.042 195.048 19.7492 14.7281 12.5745 175.299 88.0952 87.2039 84.9939 68.4103 10.2839 81.835 36.7708 45.0642 6.49944 41.0223 20.297 14.3546 20.7253 3.62157 17.1037 16.5836 58.1265 4.75254 9.60208 4.79289 3.28634 33.4532 2.91812 56.9232 +internal_count=24290853 64170 63522 6429 4794 4092 57093 28691 28402 27682 22282 3349 26652 11978 14674 2117 13358 6604 4671 6754 1181 5573 5400 18933 1548 3123 1558 1068 10899 951 18541 +is_linear=0 +shrinkage=0.1 + + +Tree=109 +num_leaves=32 +num_cat=0 +split_feature=13 6 6 12 8 6 13 6 12 10 19 12 6 20 6 6 5 5 5 10 6 17 15 21 6 13 10 6 12 5 12 +split_gain=12.4346 5.20927 9.60825 5.96984 4.97616 4.87337 7.20647 6.77761 6.23086 4.83277 4.77818 5.26054 12.9793 6.76702 4.95671 4.88437 4.84929 5.54476 4.71108 7.68828 5.73224 10.9607 8.17112 4.64161 4.68968 4.26411 13.0315 10.6967 10.122 14.6747 14.2741 +threshold=44073.000000000007 48.500000000000007 46.500000000000007 4883.5000000000009 192.50000000000003 73.500000000000014 132471.00000000003 76.500000000000014 5732.5000000000009 11782.500000000002 420.50000000000006 7343.0000000000009 83.500000000000014 6.5000000000000009 255.50000000000003 136.50000000000003 335.50000000000006 813.00000000000011 96.500000000000014 10734.000000000002 59.500000000000007 67.500000000000014 1187.0000000000002 7.5000000000000009 1.5000000000000002 40752.000000000007 16059.000000000002 105.50000000000001 6427.0000000000009 1.0000000180025095e-35 8853.0000000000018 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=25 2 23 4 -3 6 8 -7 -5 18 11 15 -13 16 -15 -9 -14 -18 19 20 22 -22 -10 -2 -25 -1 27 28 29 -27 -30 +right_child=1 3 -4 5 -6 7 -8 10 9 -11 -12 12 13 14 -16 -17 17 -19 -20 -21 21 -23 -24 24 -26 26 -28 -29 30 -31 -32 +leaf_value=-7.4162269263735819e-05 0.0025492922802270172 -0.17231287569866183 -0.16382451494406444 -0.16486344706645956 0.13512240551184487 -0.15248982722665161 -0.16410113207969979 0.10206354254039096 -0.062387367481616468 -0.16249061486792762 -0.14008875184649469 -0.15735581117988551 0.034960806280274613 -0.15391360904557347 0.066094253135945566 -0.14975152872984993 -0.13661922615381378 0.092252961860315855 0.10291586782493152 -0.1543237535602241 0.09732611987140731 -0.063572177308424904 0.088833557342399713 -0.13865786851671616 0.044556409556263589 -0.178757013902484 0.18161016934842822 -0.11566076072833086 0.07856571548908102 0.048466986301583394 -0.028734218626854793 +leaf_weight=74045.992369380692 197.44778148278419 2.5045077447430226 3.2570970188826314 1.2849495463306073 0.6666201720945536 2.2956834416545453 1.5505075546680007 11.520609003957357 3.9755020073789629 0.98932001343928178 1.8121334557072248 4.1382636881317003 87.988479234481929 3.5343047967762677 1.4417899289401246 0.82546385703608471 3.4536756327288449 1.5263145200442521 19.196769447735278 1.8582692543277506 7.3394463049480718 10.005837936681926 35.310447530355304 1.4446970278513607 42.396392729016952 7.9254372679861245 3.0334705269196993 12.279589243757071 17.382432142389007 4.4314722667913884 43.235787400975823 +leaf_count=24116264 64305 817 1061 419 217 752 505 3756 1297 323 590 1352 28645 1152 469 266 1125 497 6252 605 2391 3259 11500 471 13808 2580 988 4003 5659 1443 14082 +internal_value=0 0.0166144 0.00678184 0.0284465 -0.107685 0.0306044 0.0494264 0.0176616 0.0535669 0.0571344 0.021022 0.0235734 0.0161168 0.0234463 -0.0901678 0.0852271 0.0295274 -0.0664723 0.0599313 0.0458233 0.0523909 0.00450998 0.0735309 0.00908481 0.0385189 -0.00010035 -0.022064 -0.029311 -0.0147808 -0.0972693 0.00203432 +internal_weight=0 447.765 244.546 203.219 3.17113 200.048 81.511 118.537 79.9605 78.6756 116.241 114.429 102.083 97.9446 4.97609 12.3461 92.9685 4.97999 77.6863 58.4895 56.6312 17.3453 39.2859 241.289 43.8411 74134.3 88.2882 85.2547 72.9751 12.3569 60.6182 +internal_count=24290853 145834 79645 66189 1034 65155 26551 38604 26046 25627 37852 37262 33240 31888 1621 4022 30267 1622 25304 19052 18447 5650 12797 78584 14279 24145019 28755 27767 23764 4023 19741 +is_linear=0 +shrinkage=0.1 + + +Tree=110 +num_leaves=32 +num_cat=0 +split_feature=13 4 17 4 19 3 5 10 5 4 17 1 14 8 5 15 5 5 8 19 12 12 5 14 1 20 14 1 1 15 20 +split_gain=11.1942 3.74118 3.49848 7.10709 9.62664 13.5907 8.12125 3.60233 4.48906 4.19171 3.57193 3.29716 4.20522 6.78523 13.0058 9.31009 6.47952 6.09777 5.17841 4.59472 5.58962 5.14837 4.40576 11.726 12.7807 12.5062 11.2986 10.936 9.50843 9.66962 9.01714 +threshold=44073.000000000007 192.50000000000003 63.500000000000007 2.5000000000000004 4.5000000000000009 2.5000000000000004 11.500000000000002 10064.000000000002 266.50000000000006 82.500000000000014 84.500000000000014 5045.0000000000009 151.50000000000003 1.5000000000000002 569.00000000000011 25.500000000000004 995.50000000000011 597.50000000000011 749.50000000000011 2.5000000000000004 334.50000000000006 6.5000000000000009 408.50000000000006 24.500000000000004 8532.0000000000018 8399.5000000000018 22.500000000000004 7848.0000000000009 10028.000000000002 136.50000000000003 7932.5000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=11 2 3 -2 5 6 -5 8 10 -9 -4 -1 18 14 15 -14 -15 -16 19 22 21 -21 -13 26 -25 28 27 30 -26 -30 -24 +right_child=1 -3 7 4 -6 -7 -8 9 -10 -11 -12 12 13 16 17 -17 -18 -19 -20 20 -22 -23 23 24 25 -27 -28 -29 29 -31 -32 +leaf_value=-0.00020265629619405632 0.089172728826397124 -0.12863433442701119 -0.084899597139914088 0.13280213435290664 0.073194498400272479 0.11013638344877236 -0.13231901372829871 0.040917967801149702 -0.13945785914521827 -0.15563660596888454 0.011471972861082294 0.0088357245400492897 0.10386473455482817 0.0870151517321371 0.12428980378811486 -0.14952390223237169 -0.14839515516525556 -0.097416532469478415 -0.15900546479700195 -0.13396190692765528 -0.18237288325681456 0.05777393937135588 0.04463710335864074 -0.18054845318385737 0.15154310504905513 -0.11798839400997184 0.10499722237496596 -0.0026953277834016781 0.0054282187536769388 -0.15970269137406129 -0.17536225461060204 +leaf_weight=72241.333578899488 19.130897517548874 1.7850357368297398 3.8909529565135008 1.309200339019301 13.132326360326259 4.1872191462898618 9.8354997244896349 54.238740892324131 2.0120128134731194 1.107135520374867 332.61734209570568 734.80442890792619 1.8397553286049531 20.492844253283696 6.3932118541561058 6.8452608323423192 1.2399529584508844 1.5392269990406928 1.9625089624896634 1.4698210216010945 1.0830526289355464 29.665734225709457 67.096477439044975 6.3397049891063935 3.7651402568444601 11.749623387149766 10.370190926769281 952.91798087318602 26.139217260963054 4.1026980643800917 1.9162656790576864 +leaf_count=23530309 6236 580 1263 423 4277 1365 3207 17667 653 359 108342 239335 604 6679 2082 2226 404 500 640 476 349 9671 21849 2066 1226 3824 3377 310391 8514 1335 624 +internal_value=0 0.0158445 0.0164287 0.0420374 0.0103576 -0.0434645 -0.101174 0.0133342 0.00946722 0.0369861 0.0103577 -9.47365e-05 0.0040265 0.036803 -0.0113001 -0.0958483 0.0735839 0.0812694 0.00334828 0.00352038 0.0409542 0.0487226 0.00285741 -0.00119358 -0.0474828 -0.0290463 0.00114246 8.85827e-05 0.00168372 -0.0169739 0.0385284 +internal_weight=0 443.246 441.461 47.5951 28.4642 15.3319 11.1447 393.866 338.52 55.3459 336.508 74133.1 1891.73 38.3503 16.6175 8.68502 21.7328 7.93244 1853.38 1851.42 32.2186 31.1356 1819.2 1084.4 52.0964 45.7567 1032.3 1021.93 34.0071 30.2419 69.0127 +internal_count=24290853 144372 143792 15508 9272 4995 3630 128284 110258 18026 109605 24146481 616172 12495 5412 2830 7083 2582 603677 603037 10496 10147 592541 353206 16965 14899 336241 332864 11075 9849 22473 +is_linear=0 +shrinkage=0.1 + + +Tree=111 +num_leaves=32 +num_cat=0 +split_feature=13 6 6 16 11 16 6 6 7 7 7 19 12 2 8 19 10 0 7 6 18 21 6 6 6 7 19 8 12 6 12 +split_gain=9.96483 5.32962 6.31739 4.52089 5.67969 6.9808 5.11965 16.3751 5.37897 5.99009 5.84172 6.51107 4.9943 4.67373 4.37433 6.05726 5.16079 5.01665 3.9253 3.84029 3.90186 3.78692 4.4547 5.95051 5.0965 4.86578 8.78497 6.63882 3.72284 5.33593 3.68507 +threshold=44073.000000000007 48.500000000000007 46.500000000000007 1.0000000180025095e-35 47802.500000000007 6.5000000000000009 86.500000000000014 104.50000000000001 75.500000000000014 119.50000000000001 122.50000000000001 138.50000000000003 4883.5000000000009 1.0000000180025095e-35 102.50000000000001 338.50000000000006 10857.500000000002 135.50000000000003 260.50000000000006 202.00000000000003 1.5000000000000002 7.5000000000000009 26.500000000000004 38.500000000000007 19.500000000000004 223.50000000000003 306.50000000000006 74.500000000000014 15757.500000000002 6.5000000000000009 4813.5000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 21 19 6 -6 8 -8 12 -10 -11 -12 -5 -7 16 18 17 -9 -16 20 -3 30 24 -24 25 26 27 -23 -28 -30 -2 +right_child=1 3 -4 4 5 13 7 14 9 10 11 -13 -14 -15 15 -17 -18 -19 -20 -21 -22 22 23 -25 -26 -27 28 -29 29 -31 -32 +leaf_value=-8.9238987325543075e-05 0.027716460483952984 -0.14658540342519455 -0.15331611550160495 -0.17550540381173979 -0.074981353240857695 0.054520938039726713 -0.16200012858222898 -0.010880286675090153 -0.16229059452174244 0.13294030756395964 0.032565639862655392 -0.16006402762768587 0.069873384113950818 -0.13110309039645054 0.10944394510820257 -0.15141979907679026 0.046358326284704446 -0.15969842665436654 -0.14587857314220429 -0.081762897522667022 0.054932692450995128 0.065384715788354864 0.10308402330296702 -0.15235347202454291 -0.16132920861550523 0.10774495957499884 -0.11904323519041907 -0.17836635244577248 0.14207155789053541 -0.18947318828531734 -0.0061195016651037597 +leaf_weight=74128.664379338137 40.482451086514629 0.97496802872046728 2.4918784729670724 0.84658421241328952 13.638179255445722 10.266026896744732 5.3790596773033021 12.456443883493195 2.1026096570421933 3.3408200638368717 6.0823422670364362 2.4661934056784958 41.032189180550631 1.5629273845115665 10.648884987691414 1.0941663089615747 13.884372375570818 2.7686515642853911 0.63822337036253962 2.2168110664206315 66.226671307318611 14.707130465190867 11.712891308532559 0.98898241622373539 1.591888204042333 5.4477865489898241 6.2133470643893798 1.2092443570727471 1.4686693400144579 0.7250862456858157 157.09043400570226 +leaf_count=24146955 13189 316 812 275 4441 3342 1753 4056 685 1089 1980 805 13371 511 3468 356 4522 903 207 721 21573 4788 3816 323 520 1776 2022 394 477 237 51170 +internal_value=0 0.0149745 0.005092 0.0271826 0.0160532 -0.026222 0.0265323 0.0021601 0.0469781 -0.00670225 0.0208133 -0.0230067 0.064913 0.0299949 0.0234426 0.0732295 0.00226652 -0.0379426 0.0950069 0.0477372 0.052009 0.00672557 0.0332336 0.0831954 0.0129994 0.0223208 0.00318819 0.0468658 -0.0795024 0.0324885 0.000813447 +internal_weight=0 441.756 244.13 197.626 128.208 25.4671 102.741 46.8698 55.8707 13.992 11.8894 8.54854 41.8788 11.829 41.4907 12.3813 29.1095 15.2251 11.2871 69.4185 67.2016 241.638 44.065 12.7019 31.3632 29.7713 24.3235 15.9164 8.4071 2.19376 197.573 +internal_count=24290853 143898 79524 64374 41764 8294 33470 15265 18205 4559 3874 2785 13646 3853 13512 4031 9481 4959 3675 22610 21889 78712 14353 4139 10214 9694 7918 5182 2736 714 64359 +is_linear=0 +shrinkage=0.1 + + +Tree=112 +num_leaves=32 +num_cat=0 +split_feature=12 7 1 14 6 3 21 19 20 2 3 3 14 7 6 12 7 21 6 18 2 19 6 14 2 19 6 20 6 18 19 +split_gain=6.78007 4.35809 3.67013 3.60104 6.14923 11.0426 10.9091 9.15608 8.75982 7.83856 9.58703 6.27799 7.19405 10.7364 9.53924 6.69892 6.65667 7.44991 7.24863 6.36141 6.06233 8.32634 6.53323 5.2957 6.39914 5.82325 5.14355 11.7276 4.84378 4.76829 4.4582 +threshold=47154.500000000007 1.5000000000000002 3.5000000000000004 43522.000000000007 12.500000000000002 15.500000000000002 1.5000000000000002 507.50000000000006 4.5000000000000009 197.50000000000003 3.5000000000000004 14.500000000000002 50800.000000000007 103.50000000000001 24.500000000000004 6293.0000000000009 128.50000000000003 1.5000000000000002 132.50000000000003 7.5000000000000009 131.50000000000003 264.50000000000006 9.5000000000000018 47554.500000000007 344.00000000000006 325.50000000000006 5.5000000000000009 18.500000000000004 11.500000000000002 5.5000000000000009 110.50000000000001 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=3 2 -2 -1 5 6 -5 8 -8 10 -10 -6 16 -14 -15 -16 17 -13 -18 -17 21 -7 26 -11 -25 -26 27 -23 30 -19 -22 +right_child=1 -3 -4 4 11 20 7 -9 9 23 -12 12 13 14 15 19 18 29 -20 -21 28 22 -24 24 25 -27 -28 -29 -30 -31 -32 +leaf_value=-8.786411371093372e-05 -0.19219240003034249 -0.14694154719233898 0.021611456051900412 -0.15749993948504859 0.039696363866996938 -0.15524220292746704 -0.17989369745904005 -0.17221998593907062 0.068720460798924896 -0.16433778549200465 -0.15604509062665306 -0.17491837699441401 -0.14800681507804156 -0.17259845474552571 0.090012154387628107 -0.15972173664677114 -0.15959942503677713 0.075353959515653601 0.059669618983990237 0.082514169694987705 0.086285052070707066 -0.14665942234003584 -0.17744630732950589 0.12807000887004874 0.038123594474338288 -0.14152360984805401 0.091392635863800378 0.15545063010051952 -0.18075892847243394 -0.16908781349567115 0.017605075598858412 +leaf_weight=74128.092522659499 0.8064659716328596 1.5648284307098936 180.57808079045208 7.1546184443868688 97.860483434633352 3.8957035302300964 2.6803900621598578 3.6949471490224814 2.9647025605663675 1.2175308164441982 5.2727285616565496 1.356534568592912 7.0677905505872358 2.628892670501954 8.2690178244374675 2.6768536489689714 3.4616714236326507 21.72342453640886 2.6709019080153666 1.8220366386813109 17.822067471395712 3.553302094107492 1.6477246033027757 7.0730150623712715 12.500932939816265 2.1087354417541055 6.9982777242548764 2.0127759845927358 0.93311599059961636 0.82845049723982711 20.123328043031506 +leaf_count=24149148 264 511 58826 2331 31884 1269 874 1202 964 396 1719 443 2300 856 2698 872 1133 7073 870 593 5806 1158 537 2305 4072 687 2279 655 304 269 6555 +internal_value=0 0.0192273 0.0206608 -4.72935e-05 0.011886 -0.00711192 -0.0443393 -0.0227568 -0.00642656 0.00850584 -0.0751506 0.0247294 -0.00316626 -0.045971 0.000867805 0.0365842 0.028843 0.0526837 -0.0641018 -0.0616169 0.022068 -0.0257241 0.00977833 0.0385979 0.0499932 0.0121936 0.0343315 -0.037412 0.0443274 0.0663743 0.0498625 +internal_weight=0 182.949 181.385 74380.1 252.02 101.654 44.6676 37.513 33.818 31.1376 8.23743 150.366 52.5056 22.4646 15.3968 12.7679 30.041 23.9084 6.13257 4.49889 56.9863 18.1078 14.2121 22.9002 21.6827 14.6097 12.5644 5.56608 38.8785 22.5519 37.9454 +internal_count=24290853 59601 59090 24231252 82104 33113 14550 12219 11017 10143 2683 48991 17107 7319 5019 4163 9788 7785 2003 1465 18563 5898 4629 7460 7064 4759 4092 1813 12665 7342 12361 +is_linear=0 +shrinkage=0.1 + + +Tree=113 +num_leaves=32 +num_cat=0 +split_feature=13 6 12 15 17 0 10 6 19 12 6 12 16 17 2 18 17 0 5 5 15 10 10 16 0 21 17 7 14 7 0 +split_gain=10.5635 3.14022 4.96124 4.12955 7.79334 7.82339 4.61133 3.84816 4.79874 4.08866 8.73344 7.56512 5.55241 5.23942 5.97031 9.35736 11.9014 6.97017 18.6219 8.21811 5.84619 4.89779 4.74962 4.56498 4.05799 3.76344 3.57723 5.77191 4.77295 8.21629 5.79045 +threshold=44073.000000000007 52.500000000000007 4883.5000000000009 242.50000000000003 33.500000000000007 374.00000000000006 9492.5000000000018 57.500000000000007 420.50000000000006 7393.5000000000009 59.500000000000007 9492.5000000000018 175.50000000000003 487.50000000000006 1.0000000180025095e-35 10.500000000000002 99.500000000000014 264.50000000000006 256.50000000000006 139.50000000000003 635.00000000000011 7568.5000000000009 10734.000000000002 10.500000000000002 1.0000000180025095e-35 3.5000000000000004 228.50000000000003 85.500000000000014 487.50000000000006 18.500000000000004 210.50000000000003 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 3 -3 6 -5 26 -2 24 9 25 -11 12 -12 14 15 17 -17 18 -13 21 -15 -19 -23 -16 -4 -9 27 -6 -28 30 -30 +right_child=1 2 7 4 5 -7 -8 8 -10 10 11 13 -14 20 23 16 -18 19 -20 -21 -22 22 -24 -25 -26 -27 28 -29 29 -31 -32 +leaf_value=-9.0477397769529169e-05 -0.094552829799165475 -0.15202187689427832 -0.17426801585256588 0.082485582580402075 -0.011056453628630641 -0.12390645867690592 0.070907225865737569 0.09315879761528878 -0.13124916415752416 -0.17645348819275264 0.047176230664178122 -0.11303647017612971 -0.16141018823832357 -0.14140267327188524 0.048146106355099742 0.09946937299143116 -0.15369409750239013 -0.14380347353382283 0.075926286001518226 -0.090342393185997349 0.063146916859325816 0.11047204784313697 -0.16580906899711051 -0.14427922841273808 0.069780151221168957 -0.03393529618197013 0.06549635690073656 -0.17436871536640675 -0.10740933373271813 0.016113389220518758 0.0049783325865899244 +leaf_weight=74129.403479547269 7.3048327203141508 1.5596296698495269 0.70055489894002931 14.273819499183444 21.847750564222228 4.5237236364337141 2.1891650294419378 13.29185294674244 2.0376753643795373 2.2796936439117408 1.9189215868245799 11.59167629631702 3.8100076634145807 1.4493271796382021 37.618584508716594 2.4198639907408488 7.9824068718589842 0.88364971987903373 9.480672074496395 3.2160062159528016 38.88974036325817 14.325657271197993 0.65049221529625434 1.274635454872622 24.833037765696645 2.8250793855986549 16.183467793816817 2.4020581177901468 9.7361752067517973 158.1351347384043 8.6636309315217641 +leaf_count=24151315 2380 510 230 4649 7116 1475 714 4331 664 743 627 3777 1240 470 12251 791 2596 286 3089 1047 12669 4669 212 417 8091 921 5273 784 3171 51523 2822 +internal_value=0 0.0156595 0.0255712 0.00826236 0.0108662 0.00625081 -0.0564004 0.0270975 0.0212052 0.0232236 0.0176493 0.0209147 -0.0915436 0.0258789 0.0123855 -0.0102765 -0.0948011 0.0116235 -0.0280201 0.0554165 0.0557977 0.084973 0.0984717 0.0418398 0.0630843 0.0708809 0.00896455 -0.0272333 0.0135193 0.00875446 -0.0544911 +internal_weight=0 428.299 183.039 245.26 235.766 221.492 9.494 181.48 155.946 153.908 137.791 135.512 5.72893 129.783 89.4436 50.5504 10.4023 40.1482 21.0723 19.0758 40.3391 15.8598 14.9761 38.8932 25.5336 16.1169 216.968 24.2498 192.718 176.535 18.3998 +internal_count=24290853 139538 59631 79907 76813 72164 3094 59121 50800 50136 44884 44141 1867 42274 29135 16467 3387 13080 6866 6214 13139 5167 4881 12668 8321 5252 70689 7900 62789 57516 5993 +is_linear=0 +shrinkage=0.1 + + +Tree=114 +num_leaves=32 +num_cat=0 +split_feature=13 4 17 4 19 3 5 5 13 20 4 17 4 13 16 5 5 13 5 5 21 13 13 5 5 5 5 19 13 5 5 +split_gain=11.3694 3.05953 3.06554 3.48193 6.0587 8.87648 5.72741 3.12775 3.0395 8.56016 21.7996 25.2224 20.4184 13.5483 17.6143 11.0498 10.8105 30.3369 20.4607 12.2128 16.9905 13.7332 11.3337 10.729 10.4674 11.6712 21.0416 12.7508 10.2184 12.213 9.69837 +threshold=44073.000000000007 192.50000000000003 63.500000000000007 2.5000000000000004 4.5000000000000009 2.5000000000000004 11.500000000000002 40.500000000000007 540.50000000000011 1.5000000000000002 52.500000000000007 502.50000000000006 16.500000000000004 830.00000000000011 10.500000000000002 589.50000000000011 189.50000000000003 1627.5000000000002 303.50000000000006 156.50000000000003 1.5000000000000002 3968.5000000000005 30203.000000000004 19.500000000000004 589.50000000000011 531.00000000000011 362.50000000000006 13.500000000000002 41754.000000000007 44.500000000000007 66.500000000000014 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=8 2 3 -2 5 6 -5 -6 -1 10 12 23 24 -13 -15 16 19 18 -18 20 -14 -21 -23 -12 25 26 28 -28 -10 -30 -25 +right_child=1 -3 -4 4 7 -7 -8 -9 9 -11 11 13 15 14 -16 -17 17 -19 -20 21 -22 22 -24 30 -26 -27 27 -29 29 -31 -32 +leaf_value=4.1041681916165484e-05 0.075425955227476318 -0.12457328843778669 0.01399707327217911 0.12145599098194211 -0.14532680327992001 0.098544029554101742 -0.11259459088871018 0.078863970417634849 0.0084996756462918151 -0.006889818294336306 0.1613687852864002 0.087607774125411778 -0.02827861913612403 -0.18576230338778063 0.018412348832274567 0.13196958045520008 -0.19826473897283795 -0.190178054076207 0.12951155137400053 -0.19225562912429994 -0.19110831759936819 0.1181240880976763 -0.1910093288356923 -0.024125147853486267 -0.19731019821657891 0.14202302444847167 -0.19646449098107219 0.14665784563187131 -0.1958949661061378 0.013748184242271389 0.059959969883643098 +leaf_weight=71027.666126583354 18.18066172482213 1.5334004907053884 373.62968650009134 1.2009618170559431 0.65598621335811813 3.8358789139892897 8.0787750338786264 12.116778918862108 698.90065419346502 2062.2956563354237 9.1009708694764395 4.9364157738164094 115.8570937194163 10.076932484458668 7.2764149598078793 3.952340244781225 2.6274093240499514 14.517796162283046 6.9209392135962844 1.9684378518722976 6.7834400334395459 12.865393742802551 1.3064207513816644 15.840344773590912 2.5347705362364641 6.2770370687358072 7.6328134989598748 1.2621143087744711 5.404462510137817 5.7198038269998506 102.33242862991756 +leaf_count=23142140 5921 502 121726 396 212 1251 2630 3953 227711 671937 2963 1608 37746 3282 2372 1289 854 4730 2256 641 2212 4192 425 5158 827 2045 2488 411 1762 1862 33351 +internal_value=0 0.0164216 0.0169392 0.0418838 0.0183281 -0.0294122 -0.0823043 0.0673499 -9.28663e-05 -0.00315467 0.00422298 0.0395607 -0.00168541 -0.0585681 -0.10015 -0.0332428 -0.0372526 -0.0991246 0.0393177 -0.0265233 -0.037285 0.0552489 0.0896268 0.0567463 0.00554769 0.00625673 0.00507133 -0.147778 0.00698618 -0.088102 0.0486889 +internal_weight=0 419.232 417.699 44.069 25.8884 13.1156 9.27974 12.7728 74134.1 3106.39 1044.09 149.564 894.531 22.2898 17.3533 166.799 162.847 24.0661 9.54835 138.781 122.641 16.1403 14.1718 127.274 727.732 725.197 718.92 8.89493 710.025 11.1243 118.173 +internal_count=24290853 136591 136089 14363 8442 4277 3026 4165 24154262 1012122 340185 48734 291451 7262 5654 54345 53056 7840 3110 45216 39958 5258 4617 41472 237106 236279 234234 2899 231335 3624 38509 +is_linear=0 +shrinkage=0.1 + + +Tree=115 +num_leaves=32 +num_cat=0 +split_feature=13 6 6 4 11 4 5 4 12 6 14 4 8 4 17 4 6 6 5 13 21 6 6 0 9 2 12 12 8 12 8 +split_gain=8.99195 3.98841 6.23676 4.66429 7.12833 7.02701 4.40992 4.17977 7.70601 6.25236 5.07007 4.93325 4.16017 4.13071 4.10942 3.66352 9.02649 8.9094 7.09838 3.55274 4.1613 4.20424 6.33527 3.53506 3.18081 3.10498 7.55047 11.5355 7.16457 6.9817 5.40037 +threshold=44073.000000000007 48.500000000000007 46.500000000000007 18.500000000000004 46692.500000000007 17.500000000000004 11.500000000000002 2.5000000000000004 47154.500000000007 202.00000000000003 174.50000000000003 65.500000000000014 181.50000000000003 67.500000000000014 14.500000000000002 9.5000000000000018 73.500000000000014 70.500000000000014 66.500000000000014 46610.500000000007 1.5000000000000002 62.500000000000007 194.50000000000003 2.5000000000000004 4.5000000000000009 400.50000000000006 8856.0000000000018 9682.5000000000018 44.500000000000007 15757.500000000002 42.500000000000007 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 3 5 -5 14 -6 10 11 15 -3 12 24 -13 -2 16 17 18 -10 -12 -21 23 -23 -22 -9 -16 28 -28 30 -29 -27 +right_child=1 7 -4 4 6 -7 -8 8 9 -11 19 13 -14 -15 25 -17 -18 -19 -20 20 21 22 -24 -25 -26 26 27 29 -30 -31 -32 +leaf_value=-8.2712431667438207e-05 -0.19159858948092423 -0.15883727435166939 -0.15301529769907218 -0.1538519142494193 0.096945743067049034 0.11530638662461508 -0.043711103599025844 -0.13312140952251802 0.10607230085474534 -0.094966648948210622 0.084602760858823733 0.12769859119171437 0.031300453020795226 -0.13331004545206612 0.013920870891682442 0.036442576271375371 -0.14183185847608581 0.1254017908954064 -0.15830050778933977 0.055002692949634217 0.058019686146131649 -0.14477930806964487 0.055514237455498272 -0.16515589913070894 0.11097268911963885 -0.056326190663918953 0.10696126497842587 -0.16446963361353056 -0.15995323920960514 0.1374571544954479 0.12663115294391655 +leaf_weight=74109.927964260743 1.0241634661797423 1.1932321486528952 2.4429604224860659 4.3672065045684567 3.1587196455802751 6.3117725929478175 7.5729731330793584 8.2309538174886221 1.5439142130780945 4.2584120716055613 19.792437355441507 2.2513562859967347 2.3974415246630079 0.82982944627292443 182.47592993231956 85.856388293468626 4.1761740295041809 5.0638086950639263 2.9679892190615647 31.009424651885638 7.7702717971988005 4.1309366974455761 2.5564664750127122 0.78109282458899532 0.57088285114150572 7.536036910023542 8.0171553549007495 3.3487803211901328 5.5459239222400347 0.99297010898590077 2.0527997757308185 +leaf_count=24153893 337 390 796 1422 1025 2057 2474 2691 505 1388 6451 734 781 264 59473 27974 1360 1653 967 10107 2536 1346 830 254 186 2455 2612 1093 1806 324 669 +internal_value=0 0.0145867 0.00593108 0.00760187 -0.0461425 0.0113362 -0.00231078 0.0255519 0.0142246 0.0236943 0.0454569 -0.0546517 -0.0854808 0.0574034 0.00822594 0.0287673 -0.0191517 0.0343518 -0.0678356 0.0491481 0.0339748 -0.00881484 -0.0682109 0.0376345 -0.11729 0.00920062 -0.0221278 0.0358643 -0.0694835 -0.0954182 -0.0171583 +internal_weight=0 420.228 234.847 232.404 15.0989 217.306 10.7317 185.381 118.147 103.867 67.2339 14.2805 11.1993 3.08119 210.994 99.6083 13.7519 9.57571 4.5119 66.0406 46.2482 15.2388 6.6874 8.55136 8.80184 209.97 27.4937 12.3589 15.1348 4.34175 9.58884 +internal_count=24290853 136960 76543 75747 4921 70826 3499 60417 38503 33847 21914 4656 3658 998 68769 32459 4485 3125 1472 21524 15073 4966 2176 2790 2877 68432 8959 4029 4930 1417 3124 +is_linear=0 +shrinkage=0.1 + + +Tree=116 +num_leaves=32 +num_cat=0 +split_feature=13 1 2 10 14 14 2 21 21 21 4 6 6 6 6 12 0 13 14 14 21 21 0 0 0 10 12 18 6 6 18 +split_gain=10.0142 3.02634 8.70902 8.8939 6.47219 5.3581 5.31749 5.27025 9.73217 5.31522 8.66558 6.54013 5.58434 5.06916 4.44632 4.38958 4.29146 4.23142 4.11447 3.98874 5.06023 5.89235 3.60472 3.5679 8.84093 7.05474 6.70012 4.44423 3.81447 3.5154 3.53221 +threshold=44073.000000000007 5045.0000000000009 12256.000000000002 13.500000000000002 100.50000000000001 294.50000000000006 11853.500000000002 4583.5000000000009 11052.000000000002 14179.000000000002 7.5000000000000009 3.5000000000000004 10.500000000000002 54.500000000000007 27.500000000000004 27.500000000000004 48692.000000000007 2.5000000000000004 129.50000000000003 89.500000000000014 38521.000000000007 41057.500000000007 5143.0000000000009 1150.0000000000002 1463.0000000000002 2.5000000000000004 93.500000000000014 5.5000000000000009 25.500000000000004 24.500000000000004 257.00000000000006 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 2 6 -4 13 -5 -1 -6 -9 -10 11 17 -13 14 19 16 -12 -11 -14 23 -21 -22 -15 25 -25 26 27 -3 29 30 -26 +right_child=-2 4 3 5 7 -7 -8 8 9 10 15 12 18 22 -16 -17 -18 -19 -20 20 21 -23 -24 24 28 -27 -28 -29 -30 -31 -32 +leaf_value=-0.00020075990092280699 0.015541992852603215 0.091965131786966192 -0.16018902025152379 0.10812116320825071 0.11739936236356086 -0.19265961366685624 -0.13069238250573048 -0.17027017507701522 0.12877948404502723 0.090335186527478586 0.091559403015103374 -0.14169761502122 -0.15019082375914816 0.13939225593573226 -0.13231522520180636 -0.1652907775026938 -0.13128112069948925 -0.1303282129759695 0.0098503755477369979 -0.16039661164707714 0.12980475813956135 -0.14840874003468971 -0.13604004855586604 -0.16462393298515232 0.0019767620801953555 -0.16072495446662646 0.07893782709065976 -0.17609371774654026 -0.19032878968546638 0.091067899644521361 0.030746021515365346 +leaf_weight=72201.941323554696 412.28283069406461 0.92525394215045453 1.4935151875834005 14.361024796846319 7.0544856488995711 0.61773330904543478 3.1229145796387447 2.4258006416494018 5.3752524980809531 6.5854077596450251 18.600819347921068 4.8095649451715898 1.8975552648480505 4.0869253946002564 2.4416289763321393 0.75022173525940061 0.90631222311640058 1.0011182856978846 10.468891543569042 3.3751220210106103 1.8587530650547708 1.2892907864297738 0.53767416556365777 3.1597749827196813 1740.3875029024493 1.5952420493122179 18.60737799666822 1.8655195381725205 1.0221520548220713 4.511358934454619 43.749383961468993 +leaf_count=23534269 134382 301 488 4679 2300 202 1018 792 1755 2143 6062 1567 616 1332 796 244 295 328 3412 1101 606 419 175 1030 567286 519 6068 608 333 1470 14257 +internal_value=0 -8.64621e-05 -0.000189818 0.0725142 0.0038645 0.0957168 -0.000206404 0.036217 0.0253747 0.0347922 0.0235704 -0.0161113 -0.0502664 0.00280562 0.00254062 0.0720772 0.0812061 0.0612164 -0.014707 0.00272131 -0.0753354 0.0158616 0.107369 0.00300172 0.00249972 0.0421432 0.0572671 -0.0872214 0.00279479 0.00290515 0.00268222 +internal_weight=0 74110.8 72221.5 16.4723 1889.29 14.9788 72205.1 59.8754 52.8209 50.3951 45.0199 24.7625 17.176 1829.41 1824.79 20.2574 19.5071 7.58653 12.3664 1822.35 6.52317 3.14804 4.6246 1815.82 1792.83 22.9934 21.3982 2.79077 1789.67 1788.65 1784.14 +internal_count=24290853 24156471 23540656 5369 615815 4881 23535287 19514 17214 16422 14667 8066 5595 596301 594794 6601 6357 2471 4028 593998 2126 1025 1507 591872 584376 7496 6977 909 583346 583013 581543 +is_linear=0 +shrinkage=0.1 + + +Tree=117 +num_leaves=32 +num_cat=0 +split_feature=12 14 8 12 12 14 9 12 6 0 2 8 8 12 2 20 0 14 1 0 1 2 14 9 6 20 11 20 11 20 6 +split_gain=5.10948 3.95588 5.36603 5.15472 5.1665 5.61003 4.10118 3.80438 3.59423 3.39444 3.32395 3.16638 3.01962 3.03962 5.27447 3.57838 3.38702 3.15458 3.26555 5.33977 2.89912 9.17211 7.04471 5.79652 5.26274 5.23674 6.838 6.50292 5.56297 6.05174 5.56213 +threshold=47154.500000000007 43522.000000000007 6.5000000000000009 11147.500000000002 10776.000000000002 53612.000000000007 1.5000000000000002 15757.500000000002 7.5000000000000009 2.5000000000000004 31.500000000000004 1.5000000000000002 75743.500000000015 14.500000000000002 90.500000000000014 1.5000000000000002 5.5000000000000009 8.5000000000000018 30.500000000000004 166.50000000000003 5045.0000000000009 12256.000000000002 80.500000000000014 369.00000000000006 54.500000000000007 8399.5000000000018 17.500000000000004 6717.5000000000009 28.500000000000004 3361.0000000000005 18.500000000000004 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 12 -3 4 10 6 9 -5 -2 -6 -4 -10 20 14 15 17 -17 18 -14 -20 21 -1 24 -23 -22 27 30 28 29 -24 -27 +right_child=8 2 3 7 5 -7 -8 -9 11 -11 -12 -13 13 -15 -16 16 -18 -19 19 -21 22 23 25 -25 -26 26 -28 -29 -30 -31 -32 +leaf_value=-0.00021061759539868857 -0.14159821674764803 -0.14261615507595923 -0.18589088307579149 -0.097501238921185993 0.11568723441675766 -0.15236615918172822 -0.19606919109386192 0.03883401200826013 0.019561806177550389 -0.16419890720578209 0.014261255950893781 -0.13839881156010903 0.10578451496347706 0.11186540400191476 -0.13300685133884516 -0.16280574313027132 0.1098746108287829 -0.13667759415551761 -0.12550037759075569 0.10239433950951909 0.0022641435405087486 -0.14472228123474953 0.079357196930158094 0.091148210162258367 0.10487774570978788 0.073850886088601062 -0.091161599544931526 -0.059410374358390056 0.11767128502217067 -0.067783383421744434 -0.095346180771935712 +leaf_weight=72175.164814694799 1.4156129377661262 2.2009024764411143 0.83297952171414991 6.8620216922718082 11.870616360101842 0.99253591801971275 0.46818516077473837 2.9167508742539212 171.71777925048082 0.44973378354916427 212.30724929222197 1.2784596361743741 2.8417354095727259 4.3454766869544974 2.0331600596837225 0.48946414981037278 6.569096960593015 1.503668652672786 2.5231030457653105 1.7352420203387735 1809.3268600931624 1.1189003417384835 5.0450490557414067 15.13703401875682 5.0119153327541417 40.08968908136012 2.9772989107295862 11.062267349654574 5.8906623355287584 6.2680128758947831 2.0418810833943999 +leaf_count=23528258 453 715 270 2237 3870 323 153 951 55986 146 69215 417 926 1417 665 158 2142 490 822 565 589816 367 1643 4931 1632 13075 971 3607 1920 2046 666 +internal_value=0 -4.01103e-05 0.0128072 0.0142524 0.0173158 0.0766564 0.0944312 -0.056836 0.0170959 0.10547 0.013479 0.0183945 -8.153e-05 0.0369265 0.0185238 0.0381944 0.090966 -0.00509983 0.022766 -0.0326351 -9.25411e-05 -0.000193701 0.00377611 0.0749132 0.0025476 0.0341534 0.0553008 0.000404804 0.0388669 -0.00216618 0.0656509 +internal_weight=0 74340.1 238.901 236.7 226.921 13.7811 12.7885 9.77877 174.412 12.3204 213.14 172.996 74101.2 22.0409 17.6955 15.6623 7.05856 8.60375 7.10008 4.25835 74079.1 72191.4 1887.71 16.2559 1814.34 73.3749 45.1089 28.266 17.2037 11.3131 42.1316 +internal_count=24290853 24233997 77880 77165 73977 4492 4169 3188 56856 4016 69485 56403 24156117 7185 5768 5103 2300 2803 2313 1387 24148932 23533556 615376 5298 591448 23928 14712 9216 5609 3689 13741 +is_linear=0 +shrinkage=0.1 + + +Tree=118 +num_leaves=32 +num_cat=0 +split_feature=12 8 17 14 8 12 12 14 19 10 5 6 6 4 4 20 4 2 17 8 8 6 3 6 12 12 6 12 17 8 6 +split_gain=4.53028 3.95101 3.58785 3.49724 3.78243 3.55237 5.74769 4.69206 4.56624 3.42042 4.10738 5.0051 4.96043 7.70066 4.88313 4.53797 4.25392 4.06325 3.5619 3.35165 3.34257 4.0267 3.24484 3.58933 10.5559 10.0446 9.28972 8.60022 6.37562 6.10311 7.83362 +threshold=47154.500000000007 75743.500000000015 10.500000000000002 43522.000000000007 6.5000000000000009 11147.500000000002 10776.000000000002 53612.000000000007 15.500000000000002 9813.0000000000018 96.500000000000014 53.500000000000007 73.500000000000014 61.500000000000007 10.500000000000002 1.5000000000000002 2.5000000000000004 4.5000000000000009 30.500000000000004 105.50000000000001 112.50000000000001 38.500000000000007 41.500000000000007 12.500000000000002 4813.5000000000009 6293.0000000000009 8.5000000000000018 6542.0000000000009 228.50000000000003 18.500000000000004 13.500000000000002 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 3 -2 -1 -5 6 8 -8 -6 17 11 15 -12 14 16 -11 -14 18 -4 -7 -21 -22 23 24 26 28 -10 -27 -26 30 -25 +right_child=2 -3 9 4 5 19 7 -9 22 10 12 -13 13 -15 -16 -17 -18 -19 -20 20 21 -23 -24 29 25 27 -28 -29 -30 -31 -32 +leaf_value=-8.8797648939385447e-05 -0.16454512001597713 0.043903897487855087 0.05382484954004553 -0.13433731722686348 -0.1787548382457439 -0.14750911802624214 0.095774755484474172 -0.14663425577821054 0.051045992940618651 -0.1517205572582353 0.10468224587056396 -0.14706338362485741 0.017425067611510758 -0.16038900951519405 0.063957157635261844 0.064373306238571307 -0.13742543412005379 -0.1426656921620193 0.002485385018412797 0.11702643675162361 -0.12543997022331188 0.055032610347139355 0.10831199636411698 0.10169607792064983 0.091622818317057292 0.10345642749784947 -0.15633031326463309 -0.034007174667985147 -0.15687032184542166 0.025643742825444227 -0.16059179982919489 +leaf_weight=74074.72721877294 1.0925865651806805 20.456699948088499 15.686601453926412 1.7524053337983776 1.2521636233432207 2.4714542285073531 12.222200270538448 0.85429540724726472 31.017093345697504 1.2115792187105379 11.785336062079294 2.4687358592636883 7.6746780311223102 2.0037255545903454 25.894575988175347 4.9103765253676102 2.3074114059854764 1.7801775388361409 97.566361195626087 1.5584157602861521 2.9716279049171135 2.1171010349062271 3.4962930592009789 1.6216643393272523 1.1726732393726695 5.3961688672425216 2.3218547714641309 29.068524991802406 8.638311298156621 126.83066602570761 3.8233486314420588 +leaf_count=24149548 356 6669 5116 573 414 805 3985 278 10117 395 3837 804 2500 653 8444 1606 753 580 31808 507 967 691 1142 526 383 1759 758 9475 2816 41340 1248 +internal_value=0 -3.77686e-05 0.0160991 -4.98647e-05 0.0120378 0.0131209 0.0155716 0.079938 0.0116502 0.0172381 0.0369802 -0.0268645 0.0480234 0.0303957 0.0410511 0.0216068 -0.0183694 0.00724008 0.00959638 -0.0480817 -0.0111139 -0.0503566 0.0127675 0.011176 -0.00589578 -0.0378971 0.0366035 -0.0124844 -0.127169 0.0211931 -0.0824758 +internal_weight=0 74333.8 174.382 74313.3 238.586 236.834 227.715 13.0765 214.639 173.29 58.2564 8.59069 49.6657 37.8804 35.8767 6.12196 9.98209 115.033 113.253 9.1186 6.64714 5.08873 213.387 209.89 77.6146 44.2757 33.3389 34.4647 9.81098 132.276 5.44501 +internal_count=24290853 24234001 56852 24227332 77784 77211 74241 4263 69978 56496 18992 2805 16187 12350 11697 2001 3253 37504 36924 2970 2165 1658 69564 68422 25308 14433 10875 11234 3199 43114 1774 +is_linear=0 +shrinkage=0.1 + + +Tree=119 +num_leaves=32 +num_cat=0 +split_feature=12 10 14 12 6 0 12 6 12 6 12 3 0 21 21 19 12 18 6 6 17 10 17 8 3 5 3 17 8 12 8 +split_gain=5.32035 3.9658 3.75371 3.68294 8.33371 7.86054 7.97897 8.8718 11.0203 8.32043 7.6479 6.11433 5.41179 5.31871 5.16878 4.04268 3.94892 3.82801 7.86276 4.42563 3.79946 3.74945 4.15877 4.04487 3.89577 3.73642 3.45316 3.3915 3.3849 3.38378 9.29413 +threshold=47154.500000000007 9813.0000000000018 43522.000000000007 5657.0000000000009 4.5000000000000009 3.5000000000000004 4813.5000000000009 19.500000000000004 1110.0000000000002 18.500000000000004 1672.5000000000002 5.5000000000000009 2.5000000000000004 4.5000000000000009 8.5000000000000018 117.50000000000001 11147.500000000002 1.5000000000000002 5.5000000000000009 1.5000000000000002 15305.500000000002 1.5000000000000002 636.00000000000011 22.500000000000004 11.500000000000002 3.5000000000000004 14.500000000000002 1231.0000000000002 75743.500000000015 3764.5000000000005 24.500000000000004 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=2 -2 28 5 11 6 7 10 -9 -8 -4 -5 14 -14 20 -11 21 29 19 -19 -13 22 24 -24 -6 -25 -26 -17 -1 30 -12 +right_child=1 -3 3 4 16 -7 9 8 -10 15 17 12 13 -15 -16 27 -18 18 -20 -21 -22 -23 23 25 26 -27 -28 -29 -30 -31 -32 +leaf_value=-9.1743235709636602e-05 0.0069821204313922167 0.039438751772605425 -0.13551391111028857 -0.13838216233884412 -0.13180453970527248 -0.11528165667278414 -0.15497865288359772 0.12371497674786386 -0.14493663915834673 -0.17432439006355466 0.088135414954654018 -0.1306592450083969 0.11877418289917385 -0.14977772694878277 0.053826347735427854 -0.17320497846107918 -0.052385700853042887 -0.15389866845524813 -0.14427890476776825 0.066919908736552722 0.10301778255628116 -0.093827909551533792 -0.14497317496152537 0.045523471701556531 0.11646743754880323 -0.14234990855814378 -0.014959313227491104 0.036759112393889591 0.04044171903539754 0.10850708168534824 -0.083390424666786728 +leaf_weight=74077.11886890982 114.75206472701393 56.02705424677697 2.3378529903129666 4.7654803303303224 2.3294423147453953 6.0274086472491026 5.6753712933859815 2.0685127719771108 5.8316155182255898 1.2423002969007928 11.116078199585901 5.8061033886770002 4.3672572240466243 0.88731672067660827 3.1041833806666546 0.84607336518820275 5.6283615871798238 1.004763388307764 2.7611188291339195 9.3874793689756171 0.79054917930625368 2.2875307126669204 1.1466268689837296 114.04742453602375 2.4208971858024588 1.0685012552421529 11.475981280222184 8.4793391518760455 20.608186585566727 13.024081736220976 4.4131384228821835 +leaf_count=24152051 37412 18267 763 1553 760 1965 1846 672 1901 406 3625 1892 1423 290 1013 276 1836 327 900 3062 258 746 370 37190 789 346 3741 2768 6719 4247 1439 +internal_value=0 0.0176301 -4.05065e-05 0.0125958 0.0211305 -0.00581859 0.00385732 0.022986 -0.0745948 -0.0573152 0.0404887 -0.0397413 -0.0083098 0.073425 -0.0525824 -0.00486523 0.0296804 0.0503545 0.00571765 0.0455703 -0.102655 0.0331076 0.0352992 0.0419181 -0.0121252 0.0437796 0.0079358 0.0177096 -8.047e-05 0.0709169 0.0393907 +internal_weight=0 170.779 74332.1 234.341 160.126 74.2151 68.1877 51.9446 7.90013 16.2431 44.0445 19.7209 14.9554 5.25457 9.70084 10.5677 140.405 41.7067 13.1534 10.3922 6.59665 134.776 132.489 116.263 16.2263 115.116 13.8969 9.32541 74097.7 28.5533 15.5292 +internal_count=24290853 55679 24235174 76404 52207 24197 22232 16936 2573 5296 14363 6429 4876 1713 3163 3450 45778 13600 4289 3389 2150 43942 43196 37906 5290 37536 4530 3044 24158770 9311 5064 +is_linear=0 +shrinkage=0.1 + + +Tree=120 +num_leaves=32 +num_cat=0 +split_feature=13 20 10 11 12 18 4 18 18 4 15 9 14 12 14 0 10 13 17 10 14 13 6 17 11 4 4 4 16 10 6 +split_gain=8.89739 2.76638 2.84862 4.5522 2.64141 3.35176 7.0364 6.19994 3.21266 6.36365 5.92789 3.59342 2.96715 2.9056 2.54592 6.67483 5.93328 4.20092 2.43502 4.19042 3.64733 3.69673 3.29805 3.10506 4.78423 2.60164 2.87265 4.58352 3.50559 2.99126 2.8621 +threshold=44073.000000000007 50132.000000000007 19.500000000000004 2.5000000000000004 19.500000000000004 7230.0000000000009 7.5000000000000009 8587.0000000000018 9948.0000000000018 11.500000000000002 1.5000000000000002 10.500000000000002 62.500000000000007 10.500000000000002 72.500000000000014 1596.5000000000002 2.5000000000000004 58.500000000000007 63.500000000000007 4416.0000000000009 1218.5000000000002 52600.000000000007 13.500000000000002 84.500000000000014 66987.000000000015 159.50000000000003 156.50000000000003 87.500000000000014 6.5000000000000009 9701.5000000000018 52.500000000000007 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 -1 3 4 8 14 -7 -8 9 11 -11 -3 -10 -13 15 16 -6 -17 19 22 21 -21 -2 24 -20 26 27 30 -29 -30 -25 +right_child=18 2 -4 -5 5 6 7 -9 12 10 -12 13 -14 -15 -16 17 -18 -19 23 20 -22 -23 -24 25 -26 -27 -28 28 29 -31 -32 +leaf_value=-9.9647707536586479e-05 -0.15053082805334761 -0.083564001012160993 0.10564922421687557 -0.17067836519953702 -0.16161529412755404 -0.17255546422434873 -0.17357894321299794 0.062319381926128625 -0.14826729706994651 -0.15462565131956491 0.11075196955510998 0.035704257808138143 0.06700137608256182 -0.12642267548696964 0.099536760818510484 0.10259995190757594 0.12575396320451063 -0.18588906212768594 -0.18415904115628498 0.0077607058121742711 0.072170771035232881 -0.15599247156292811 0.10413231870328239 0.0073408605499981419 -1.8066239267286949e-08 -0.12145484682733834 0.11714118779602925 -0.14425787164245316 -0.15208860789285045 0.11700153978467903 0.02609433280501134 +leaf_weight=74022.675701938249 0.53509873989969392 3.6154696380981539 3.6177112902514628 1.3005949817888893 2.606728394282984 2.0470276203122912 1.2825157025363285 8.4860920247301674 0.69189825875218858 4.9020060179173006 1.0162237118929622 17.395235171279641 8.5846072297426854 1.1804266541730601 9.8955170905683172 7.207499212352559 0.99186062219087023 0.54277262231335033 2.011635533970547 19.896147906256381 9.0439929983986058 1.4812328916159447 10.245659773470832 207.56930367380846 4.7220202595926821 1.4218543298193242 2.6841869927011421 3.2155009095877167 0.61716038602753531 1.2494081570766864 133.86405104355072 +leaf_count=24136324 176 1180 1180 424 849 666 417 2771 223 1599 331 5670 2802 386 3223 2352 324 177 659 6491 2950 483 3340 67675 1544 463 875 1047 201 408 43643 +internal_value=0 -8.01514e-05 0.0190691 0.0147035 0.018126 0.0387177 -0.0039774 0.0313484 -8.30347e-05 -0.0169232 -0.109057 0.00764847 0.0509453 0.0254016 0.0624638 0.0301384 -0.0824092 0.0823963 0.0149012 0.0379207 0.018936 -0.00358571 0.0914922 0.0122471 -0.0550163 0.0135389 0.0140886 0.0132903 -0.0809792 0.0280298 0.0146934 +internal_weight=0 74098 75.3642 71.7465 70.4459 33.06 11.8156 9.76861 37.3859 28.1094 5.91823 22.1911 9.27651 18.5757 21.2444 11.3489 3.59859 7.75027 398.557 41.2021 30.4214 21.3774 10.7808 357.355 6.73366 350.621 349.2 346.515 5.08207 1.86657 341.433 +internal_count=24290853 24160898 24574 23394 22970 10779 3854 3188 12191 9166 1930 7236 3025 6056 6925 3702 1173 2529 129955 13440 9924 6974 3516 116515 2203 114312 113849 112974 1656 609 111318 +is_linear=0 +shrinkage=0.1 + + +Tree=121 +num_leaves=32 +num_cat=0 +split_feature=14 12 20 12 16 19 10 12 12 1 19 14 0 11 1 0 12 4 18 18 0 14 16 5 21 21 14 0 14 19 5 +split_gain=4.49452 3.96636 3.79481 3.72419 3.42205 3.14094 14.4011 20.1083 17.2327 19.2183 17.0483 22.6049 30.2893 13.9373 13.3685 22.4228 15.4598 16.025 18.1258 16.2757 17.9486 15.442 21.0446 22.5402 17.8265 16.69 15.2924 19.1233 17.579 14.9236 14.6701 +threshold=43522.000000000007 47154.500000000007 50132.000000000007 19.500000000000004 14.500000000000002 627.50000000000011 76.500000000000014 280.00000000000006 4.5000000000000009 2328.0000000000005 684.50000000000011 7483.0000000000009 14.500000000000002 4.5000000000000009 413.00000000000006 149.50000000000003 1.0000000180025095e-35 10.500000000000002 9815.5000000000018 7468.5000000000009 431.00000000000006 6227.0000000000009 11872.000000000002 23.500000000000004 258.50000000000006 547.00000000000011 23.500000000000004 175.50000000000003 14.500000000000002 2772.5000000000005 8.5000000000000018 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 2 5 -4 -3 -1 8 -8 9 10 11 -7 -13 14 21 -16 -17 19 26 20 -18 -12 23 -23 -24 -26 28 -28 -19 -22 -27 +right_child=-2 4 3 -5 -6 6 7 -9 -10 -11 13 12 -14 -15 15 16 17 18 -20 -21 29 22 24 -25 25 30 27 -29 -30 -31 -32 +leaf_value=2.4092036428463977e-05 0.014053848041584502 0.016407453007679464 0.00047878842770091181 0.046264879063060982 -0.15244381420059805 -0.1937217278834904 0.021714295031206938 -0.20420729137663507 -0.0014387946423886832 -0.1909207259566664 0.0091229020655489105 -0.19547294464154497 0.09058744188194745 -0.19893716107198961 -0.19984459400150226 0.014744700825771695 -0.15926799888921642 -0.19097614074057162 0.09271306758644314 -0.1952607852685867 0.17370133525879636 -0.19790491511574576 0.028496832275537828 0.0065340596817530325 -0.15898244570311862 0.06805055068471004 0.15866946831388332 -0.16286922767207773 0.095317471526658204 -0.0559084106770582 -0.12753926186064779 +leaf_weight=71426.62253990199 226.86552139988635 168.93481949063425 36.0889155613404 34.98805320179963 1.2088556158996642 12.798289891798051 265.61155384604353 3.9989919195213579 1808.4680140122655 6.6570317596197119 254.58688987603819 5.8715923678828394 10.014840443618594 4.1056342403171575 8.3513197158463282 46.607194910407998 9.3960235653794388 2.8011347522260648 11.20339898415841 13.247952833306043 4.3316674954257897 14.395130506367424 43.945212830643868 8.6238367180339974 12.612564326322174 10.154837437439712 2.1283372794277993 14.127541849913539 9.1522165790665877 8.1691879249410686 6.1615911392727858 +leaf_count=23291209 73977 55088 11772 11405 394 4174 86610 1302 589711 2170 83015 1913 3266 1340 2725 15197 3062 913 3654 4321 1415 4693 14331 2813 4113 3312 695 4606 2986 2662 2009 +internal_value=0 -4.29327e-05 -7.79527e-05 0.0230173 0.0152078 -0.000100128 -0.00351594 0.0183633 -0.00604992 -0.0221038 -0.0199122 -0.0948179 -0.0151399 -0.0154738 -0.0139045 -0.0413577 -0.0304339 -0.0586759 -0.0148968 -0.107771 -0.0548387 -0.00375949 -0.037961 -0.121314 -0.0116321 -0.0725908 -0.0576344 -0.120771 0.0282278 0.0236536 -0.00581025 +internal_weight=0 74265.4 74095.2 71.077 170.144 74024.1 2597.52 269.611 2327.91 519.443 512.786 28.6847 15.8864 484.102 479.996 129.516 121.165 74.5575 39.4126 35.1448 21.8969 350.48 95.8932 23.019 72.8742 28.929 28.2092 16.2559 11.9534 12.5009 16.3164 +internal_count=24290853 24216876 24161394 23177 55482 24138217 847008 87912 759096 169385 167215 9353 5179 157862 156522 42236 39511 24314 12854 11460 7139 114286 31271 7506 23765 9434 9200 5301 3899 4077 5321 +is_linear=0 +shrinkage=0.1 + + +Tree=122 +num_leaves=32 +num_cat=0 +split_feature=14 14 7 7 20 2 19 9 20 2 6 7 2 0 5 5 12 19 19 7 7 12 4 3 12 12 19 1 7 4 19 +split_gain=4.04527 3.92959 19.8183 17.6525 18.4125 18.3512 15.6668 22.826 17.6369 17.5589 19.248 14.6384 13.9644 19.1059 18.9023 24.0617 18.301 20.2604 16.5652 15.138 19.5195 21.1159 20.3354 16.7165 16.5742 22.6391 13.8174 15.5511 17.8538 16.9969 13.3982 +threshold=43522.000000000007 9736.0000000000018 137.50000000000003 124.50000000000001 2.5000000000000004 9.5000000000000018 120.50000000000001 688.00000000000011 21.500000000000004 1.0000000180025095e-35 44.500000000000007 142.50000000000003 25.500000000000004 1.0000000180025095e-35 4.5000000000000009 6.5000000000000009 11803.500000000002 597.50000000000011 420.50000000000006 821.50000000000011 341.50000000000006 11903.000000000002 6.5000000000000009 5.5000000000000009 5252.0000000000009 11607.000000000002 5.5000000000000009 1.0000000180025095e-35 13.500000000000002 222.50000000000003 295.00000000000006 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 -1 3 6 -5 -6 7 9 30 10 -3 -4 18 14 16 -16 17 -14 -13 20 22 -22 23 24 -15 -26 -11 28 -28 -29 -8 +right_child=-2 2 11 4 5 -7 8 -9 -10 26 -12 12 13 19 15 -17 -18 -19 -20 -21 21 -23 -24 -25 25 -27 27 29 -30 -31 -32 +leaf_value=3.8875361329461187e-05 0.013322420628686117 -0.19992673453926388 0.11208622206894542 0.072040845694612402 0.13256062889555978 -0.1943243042284635 0.019934937018096906 0.15934433835369871 -0.1386087622992995 -0.1969794487900931 0.19910184337338555 0.091837865228554194 -0.11843514562389899 0.15591234761435499 0.12233616168267002 -0.2026917504710003 0.049753682817706299 0.073210496383059959 -0.19747247525450817 0.16928835374244597 -0.16440707495195162 0.10503977530666608 -0.19838253820323407 0.073594603192492572 -0.19556891245845509 0.072238489039387399 0.091911532984183683 0.029159024541413076 -0.063286479300446619 -0.20066614429310864 -0.015044685522509392 +leaf_weight=73363.116464524326 227.22391082558897 11.26299405423924 15.756443114951251 4.5528156862128553 1.9499509558081651 14.401080177980473 196.2043770266464 5.7325839321129015 9.3170048559550178 5.0862441256176671 1.3542096614837653 29.939088707789779 40.000319682527333 3.4368726944085237 12.21960060065612 2.7994324116734779 11.330811148509381 6.3987716280389568 2.1191875024233005 7.1517197338398537 10.358106422703711 4.0439839946920975 3.4852708044927558 59.137481148936786 8.0490551383700204 5.1931034834124139 8.0142665975727123 50.123383895028383 98.698845225619152 3.4386712096747933 247.79265583306551 +leaf_count=23929904 74119 3675 5138 1486 631 4702 63999 1869 3039 1660 442 9766 13048 1118 3986 914 3696 2087 691 2333 3378 1318 1138 19291 2627 1696 2615 16344 32189 1124 80830 +internal_value=0 -4.07752e-05 -0.00668594 -0.015395 -0.105818 -0.155341 -0.0124278 -0.0370623 -0.00244435 -0.0433884 -0.157099 0.0191923 0.0120754 0.000877897 -0.0381828 0.0617535 -0.0641823 -0.0920058 0.0727132 0.0290531 0.01835 -0.0887487 0.0378003 0.0486576 -0.0397596 -0.0905444 -0.0347122 -0.0295628 -0.0516309 0.0144043 0.000412974 +internal_weight=0 74242.5 879.348 657.929 20.9038 16.351 637.025 183.711 453.314 177.979 12.6172 221.419 205.663 173.605 72.7489 15.019 57.7299 46.3991 32.0583 100.856 93.7039 14.4021 79.3018 75.8165 16.679 13.2422 165.361 160.275 106.713 53.5621 443.997 +internal_count=24290853 24216734 286830 214605 6819 5333 207786 59918 147868 58049 4117 72225 67087 56630 23731 4900 18831 15135 10457 32899 30566 4696 25870 24732 5441 4323 53932 52272 34804 17468 144829 +is_linear=0 +shrinkage=0.1 + + +Tree=123 +num_leaves=32 +num_cat=0 +split_feature=13 21 2 2 10 12 8 18 18 1 4 12 8 12 5 2 18 13 16 16 16 5 15 12 1 1 15 18 18 8 12 +split_gain=6.02984 2.85256 2.97579 4.16417 3.09506 3.30516 2.80608 2.75 7.42274 5.24642 3.19342 2.67964 3.27728 4.76138 5.11466 4.9015 2.42451 2.41882 2.40533 2.76864 3.02158 2.51113 3.596 3.28214 2.40214 2.84811 13.7082 7.72747 15.3149 13.2764 12.6481 +threshold=44073.000000000007 54465.500000000007 749.00000000000011 3498.0000000000005 343.00000000000006 1.5000000000000002 75743.500000000015 6259.0000000000009 8179.0000000000009 9247.0000000000018 20.500000000000004 383.50000000000006 552.50000000000011 16.500000000000004 2.5000000000000004 5.5000000000000009 21.500000000000004 590.50000000000011 499.00000000000006 128.50000000000003 77.500000000000014 202.50000000000003 104.50000000000001 1.0000000180025095e-35 16619.500000000004 13961.000000000002 168.50000000000003 120.50000000000001 319.00000000000006 3.5000000000000004 31.500000000000004 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 6 7 4 18 -6 24 10 9 -9 -3 12 13 14 -10 -15 -2 -8 19 20 -4 23 -23 -22 25 -1 27 -27 29 -29 -31 +right_child=16 2 3 -5 5 -7 17 8 11 -11 -12 -13 -14 15 -16 -17 -18 -19 -20 -21 21 22 -24 -25 -26 26 -28 28 -30 30 -32 +leaf_value=-0.00014801871512642482 0.012574634004799915 0.042478003293961036 -0.050576073882321916 0.078090103678820535 -0.1477083996158228 0.032030606200942245 0.041797201045432518 -0.15711404108218679 -0.15391868734894396 0.073164466262768568 -0.1429804768297894 0.10665254049841509 0.064825247128126651 -0.16567926404620048 0.043764927296647126 0.032886593130506091 -0.17709127574260564 -0.18880607694226004 0.10580538937627737 -0.10596422115260745 0.079673044861994044 0.099470964811194551 -0.09369019467179375 -0.13751737608673709 0.0041642447141518853 0.0013387349258464347 -0.16162245197461872 0.11915425955041735 0.080749659083075742 0.067965192569626853 -0.18031051918421592 +leaf_weight=72446.88708327715 398.72773492835404 52.431468297145329 8.4304011845088223 4.7223607950727446 3.5030006956658317 1.4451379859237929 20.329555343196265 4.2308140687237019 1.4767099891905768 1.2913402249105272 0.94519567594397713 3.3897267808206371 8.182489121827528 3.3126811763213473 11.510938072577117 1.989875205705175 0.67511761907371592 0.465263876831158 1.7075113850296464 3.0562960101815397 7.465662162518127 1.3999232598580409 3.0935753866797313 0.76729639980476338 1296.7136047506356 137.40266177779995 6.0014643634203813 2.256025040522224 5.7476229049498206 2.4414004096761337 12.861340137547812 +leaf_count=23635750 130085 17106 2752 1540 1144 471 6632 1380 482 421 308 1106 2669 1085 3750 651 221 152 557 998 2437 459 1004 249 423055 44829 1958 738 1875 798 4191 +internal_value=0 -6.60907e-05 0.0150669 -0.00936259 -0.0227413 -0.0952144 -9.15445e-05 0.0248626 0.0032442 -0.103264 0.0391939 0.0229397 0.0122206 -0.0113132 0.0212881 -0.091164 0.012254 0.0366377 -0.00890645 -0.0169959 -0.00414367 0.0266146 -0.033512 0.0594313 -0.000101878 -0.000178062 -0.0132337 -0.00769232 -0.0609348 -0.107313 -0.140701 +internal_weight=0 74055.5 124.352 35.5912 30.8688 4.94814 73931.1 88.7612 35.3846 5.52215 53.3767 29.8624 26.4727 18.2902 12.9876 5.30256 399.403 20.7948 25.9207 24.2132 21.1569 12.7265 4.4935 8.23296 73910.3 72613.6 166.711 160.709 23.3064 17.5588 15.3027 +internal_count=24290853 24160547 40569 11611 10071 1615 24119978 28958 11544 1801 17414 9743 8637 5968 4232 1736 130306 6784 8456 7899 6901 4149 1463 2686 24113194 23690139 54389 52431 7602 5727 4989 +is_linear=0 +shrinkage=0.1 + + +Tree=124 +num_leaves=32 +num_cat=0 +split_feature=13 21 3 8 6 11 14 12 14 14 14 0 12 4 6 14 14 0 2 2 14 3 3 3 15 15 6 13 13 14 0 +split_gain=5.07693 3.00079 3.30505 2.65513 2.3954 2.2966 3.90513 2.55421 2.60467 3.70345 3.30607 5.0013 3.29481 3.96029 3.26108 4.48148 6.55811 3.51715 3.49628 4.87239 3.78895 2.96456 3.02673 2.80653 2.46224 5.43192 3.10823 2.31091 2.42591 2.28468 2.25342 +threshold=44073.000000000007 54465.500000000007 45.500000000000007 75743.500000000015 9305.0000000000018 28.500000000000004 241.50000000000003 19.500000000000004 224.50000000000003 109.50000000000001 103.50000000000001 431.00000000000006 4.5000000000000009 4.5000000000000009 4.5000000000000009 6.5000000000000009 19.500000000000004 546.50000000000011 3498.0000000000005 1541.0000000000002 1.5000000000000002 79.500000000000014 309.50000000000006 248.50000000000003 37.500000000000007 74.500000000000014 10.500000000000002 78.500000000000014 75.000000000000014 29.500000000000004 3994.0000000000005 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 3 -3 30 -5 6 7 8 9 10 11 -4 14 -14 15 -13 -17 -18 19 20 -16 -19 -23 -22 26 -26 -25 28 -24 -21 -1 +right_child=-2 2 5 4 -6 -7 -8 -9 -10 -11 -12 12 13 -15 18 16 17 21 -20 29 23 22 27 24 25 -27 -28 -29 -30 -31 -32 +leaf_value=-0.00020323749084674497 0.011223195799737408 -0.16220925671690789 -0.1580587802930245 0.042809809230295863 -0.12646484489194001 0.10743682482386036 -0.13926308688390535 0.043838132850526013 0.10396026607334188 -0.096340300553562006 0.11243877688859949 0.098426222631803839 0.016365664877582228 -0.13600365153234742 -0.13128925059112631 -0.15374925069495884 -0.23112893443296323 -0.1424161207371509 0.082270015565197321 -0.14884797613079748 -0.16162789173191983 0.088894505136686897 -0.035178689684649656 -0.13676403839365858 -0.13940060235568136 0.017599801791305308 0.088078249409044046 -0.14927170092450492 0.13166085168648345 -0.004101565001345087 0.0027608097457428229 +leaf_weight=71245.948422634756 400.88806726083567 1.0353509145497799 1.8127014527854033 19.868331993289761 0.87269684986676921 2.7572632548399261 1.6161862248554815 27.537061136419652 2.7926035525742909 3.4069219535158473 3.0376933472580268 9.6348354825750011 3.5790672269358756 3.2591554560931399 1.9670806090580288 2.068676097784194 0.47724637435749073 0.80052623269148071 4.5526843831758006 3.4000360300415187 0.9066252025077145 9.1922138425288704 2.3982623028568915 0.67724394868128257 2.4650543645257157 20.784756582928821 6.6717308834777214 0.94734431093092997 1.3690152168273928 1.6053175679990088 2660.6917940131534 +leaf_count=23245778 130798 333 590 6481 286 901 526 8986 911 1111 991 3147 1167 1064 642 674 155 265 1486 1111 297 2998 782 222 803 6787 2175 308 446 521 868111 +internal_value=0 -6.07621e-05 0.0156905 -8.64901e-05 0.0356874 0.017229 0.0151024 0.0172654 0.00893189 0.00581033 0.0100749 0.00611726 0.00999446 -0.0562549 0.0164738 0.0437946 0.0132863 0.0360424 -0.000597867 -0.0104028 0.00335789 0.044712 0.0554838 0.0117648 0.0169023 0.00095388 0.0673579 -0.00965798 0.0254502 -0.102425 -9.65297e-05 +internal_weight=0 74048.1 120.753 73927.4 20.741 119.717 116.96 115.344 87.8068 85.0142 81.6073 78.5696 76.7569 6.83822 69.9186 26.8881 17.2533 15.1846 43.0305 38.4778 33.4725 14.7074 13.9068 31.5054 30.5988 23.2498 7.34897 4.71462 3.76728 5.00535 73906.6 +internal_count=24290853 24160055 39399 24120656 6767 39066 38165 37639 28653 27742 26631 25640 25050 2231 22819 8775 5628 4954 14044 12558 10926 4799 4534 10284 9987 7590 2397 1536 1228 1632 24113889 +is_linear=0 +shrinkage=0.1 + + +Tree=125 +num_leaves=32 +num_cat=0 +split_feature=13 21 8 0 0 0 9 2 2 0 15 0 11 9 0 8 13 18 7 18 18 18 0 0 0 18 0 6 0 18 18 +split_gain=4.37214 4.25434 2.50838 2.2448 2.88004 5.30053 4.3207 3.26726 2.14333 4.42456 3.46391 3.66753 7.39193 3.73576 3.49982 2.8013 2.76554 8.23447 11.1573 6.45951 4.89615 4.89525 4.2949 3.51625 6.58152 3.80393 2.92427 3.64632 2.90692 2.45017 4.3075 +threshold=44073.000000000007 54465.500000000007 75743.500000000015 175.50000000000003 145.50000000000003 20.500000000000004 97240.000000000015 90.500000000000014 50964.500000000007 10503.000000000002 1.5000000000000002 7942.5000000000009 3.5000000000000004 7.5000000000000009 9773.0000000000018 2.5000000000000004 4.5000000000000009 29.500000000000004 329.50000000000006 19.500000000000004 78.500000000000014 94.500000000000014 6189.0000000000009 5053.5000000000009 3265.5000000000005 20.500000000000004 5209.5000000000009 5.5000000000000009 7065.5000000000009 52.500000000000007 53.500000000000007 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 2 8 4 5 6 -4 -8 -1 10 11 16 13 -13 -14 -11 17 19 -19 28 -20 -22 -21 24 -18 -25 -27 -28 -10 -15 -31 +right_child=-2 -3 3 -5 -6 -7 7 -9 9 15 -12 12 14 29 -16 -17 23 18 20 22 21 -23 -24 25 -26 26 27 -29 -30 30 -32 +leaf_value=-0.00011746197272104479 0.010403787742585494 0.019154250500531239 -0.15131654267938907 -0.16220520804776836 0.12046730117300289 -0.13203663889544978 0.071228545639136318 -0.13955325507956892 -0.16009060589786608 0.029390501456912712 0.061192435603752676 -0.13954926477897631 -0.13652521925206679 -0.098380868465661228 0.1125418746854765 -0.14695370000505062 0.02568431457855665 -0.15876437925525302 -0.14956673055161482 0.10687153220861774 0.11561837394490448 -0.13981808906302909 -0.15975211168392633 -0.1454949892086137 -0.14385965295722988 0.11721483645655904 0.042175498593158292 -0.1417447477472297 0.067588722986441738 0.18901263118667333 -0.14170457741084913 +leaf_weight=73786.677247212385 401.75454328929482 115.09855607911595 1.0556560291443116 0.56232944247312744 3.6670421527232966 1.956190062657696 12.53263996983878 0.78123029170092273 1.8042776950096624 1.3434152995469038 15.427894995984387 5.651926114165688 0.67490043537691136 1.5102680927375334 3.4387272505555302 2.7342693362952559 8.6221776724560169 6.4362527225166541 1.2557596792466936 8.7078332023229414 4.2555242008529603 0.91083810431882728 0.64920829446055073 1.0598312714137126 3.1174461790360501 5.1409335106145582 39.9002187155711 1.1078715554322105 0.81365588912740339 1.1477558612823484 0.59956263157073397 +leaf_count=24077536 131098 37559 345 183 1196 637 4089 257 591 437 5034 1845 220 492 1122 892 2813 2098 410 2844 1389 296 212 346 1017 1677 13022 362 265 374 195 +internal_value=0 -5.64549e-05 -8.63659e-05 0.0348419 0.0403841 0.022396 0.0434197 0.0588603 -9.60807e-05 0.0134681 0.0171858 0.0101724 -0.0391967 -0.0903892 0.0716788 -0.0888561 0.0178464 -0.0101456 -0.0657161 0.0495244 0.0275369 0.0705845 0.0883727 0.0296386 -0.0193379 0.0418178 0.0461195 0.0372067 -0.0893277 -0.00509638 0.0755326 +internal_weight=0 74038.6 73923.5 20.5551 19.9928 16.3257 14.3695 13.3139 73903 116.311 112.233 96.805 13.0231 8.90951 4.11363 4.07768 83.7818 24.8333 12.8584 11.975 6.42212 5.16636 9.35704 58.9485 11.7396 47.2089 46.149 41.0081 2.61793 3.25759 1.74732 +internal_count=24290853 24159755 24122196 6707 6524 5328 4691 4346 24115489 37953 36624 31590 4248 2906 1342 1329 27342 8105 4193 3912 2095 1685 3056 19237 3830 15407 15061 13384 856 1061 569 +is_linear=0 +shrinkage=0.1 + + +Tree=126 +num_leaves=32 +num_cat=0 +split_feature=13 20 2 17 13 6 6 17 6 6 6 16 4 19 18 0 16 6 15 16 6 20 3 6 6 19 6 6 18 6 18 +split_gain=3.78713 3.10704 4.17357 2.43489 2.85809 3.88197 8.57482 5.16651 6.62954 5.82295 6.45658 5.86246 5.81478 5.11138 4.84233 5.86016 4.53139 4.4159 3.80777 3.38805 5.8746 4.73731 3.17363 3.16798 2.86549 5.58624 4.19582 2.95036 2.75399 2.62027 5.76747 +threshold=44073.000000000007 50132.000000000007 15.500000000000002 63.500000000000007 49798.000000000007 17.500000000000004 10.500000000000002 487.50000000000006 87.500000000000014 37.500000000000007 29.500000000000004 267.50000000000006 119.50000000000001 282.50000000000006 10.500000000000002 3.5000000000000004 544.50000000000011 2.5000000000000004 635.00000000000011 559.00000000000011 5.5000000000000009 12.500000000000002 21.500000000000004 190.50000000000003 202.00000000000003 325.50000000000006 184.50000000000003 171.50000000000003 9.5000000000000018 97.500000000000014 10.500000000000002 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 -1 -3 -2 5 6 14 8 9 10 -7 12 -11 -8 15 19 22 -16 -12 20 -5 -21 -15 -9 26 -26 27 -6 -28 -10 -31 +right_child=3 2 -4 4 24 7 13 23 29 11 18 -13 -14 16 17 -17 -18 -19 -20 21 -22 -23 -24 -25 25 -27 28 -29 -30 30 -32 +leaf_value=-7.2031364907114564e-05 0.032829673518989348 0.024770922028764457 -0.17280007939881403 -0.14168698317284789 -0.00074134667174641591 -0.13397023857666024 -0.16874832800035322 0.046350095227519594 0.093424657770895869 -0.15569353754143284 -0.19458720176786637 0.097089652540962426 0.061925997257606481 0.070905700053791987 -0.16960453212136628 -0.13436580290399358 -0.15757365206817586 0.10472953008916717 0.077419996984834566 0.047134943249121208 0.023953243914982259 -0.1564282692780104 -0.15224765227699533 -0.093874621887536036 -0.14104223492327533 0.078309089449508776 -0.15664776289250668 -0.15501658905454746 0.10289271967595681 0.037063008665047859 -0.14396166463365487 +leaf_weight=73963.815958097708 40.805955808114959 65.46748970655608 1.0869567717891175 3.471112065133644 182.25916845252505 2.2838744183536637 4.4191236344049667 70.677595650602598 5.9451299348438651 7.8399594611837493 0.55496282572858024 1.4118322711437947 1.4558272765134459 3.4506852775812149 0.63636857876553943 3.068351961526786 1.8600488193333147 7.5265940064564347 7.0843419780721888 18.351889868470611 5.5882445297320373 1.2191761214053247 0.78167568100616325 1.6487233200168692 3.7350519895262542 1.6846937068621626 0.44167501479387183 1.2480911824968632 5.4993142520543188 15.585782145033592 1.9840316542831704 +leaf_count=24137772 13320 21365 355 1132 59482 744 1443 23062 1942 2562 180 461 474 1125 207 1001 607 2456 2315 5991 1824 397 255 539 1218 550 144 407 1794 5081 648 +internal_value=0 -5.2598e-05 0.0215442 0.00967351 0.00706119 0.0166678 -0.00652645 0.0266991 -0.000259144 -0.0416316 0.0135551 -0.0927754 -0.121612 -0.0868712 0.0146604 -0.00302645 -0.0274817 0.083343 0.0576598 0.0110493 -0.0395122 0.034454 0.0296915 0.0431536 -0.00116393 -0.0728583 0.000887102 -0.00179062 0.0835975 0.0360389 0.0166212 +internal_weight=0 74030.4 66.5544 402.519 361.713 166.845 50.3733 116.472 44.1457 20.6308 9.92318 10.7076 9.29579 10.5115 39.8617 31.6988 6.09241 8.16296 7.6393 28.6304 9.05936 19.5711 4.23236 72.3263 194.868 5.41975 189.448 183.507 5.94099 23.5149 17.5698 +internal_count=24290853 24159492 21720 131361 118041 54446 16438 38008 14407 6736 3239 3497 3036 3430 13008 10345 1987 2663 2495 9344 2956 6388 1380 23601 63595 1768 61827 59889 1938 7671 5729 +is_linear=0 +shrinkage=0.1 + + +Tree=127 +num_leaves=32 +num_cat=0 +split_feature=13 21 8 12 18 5 5 5 7 17 17 5 5 5 15 12 5 17 17 5 7 3 19 5 19 17 12 2 15 7 5 +split_gain=4.33551 3.12087 2.64657 2.45941 4.15896 2.3467 5.10272 3.26278 4.02362 2.17833 6.44508 5.76186 3.64519 6.05638 6.19076 3.29866 4.98206 5.02507 4.28264 4.24483 4.23182 4.01962 3.11783 3.04545 7.38075 5.13677 2.75934 2.6099 2.60419 5.28027 5.51563 +threshold=44073.000000000007 54465.500000000007 75743.500000000015 132867.50000000003 3.5000000000000004 256.50000000000006 233.50000000000003 335.50000000000006 1.0000000180025095e-35 114.50000000000001 228.50000000000003 172.50000000000003 96.500000000000014 104.50000000000001 684.50000000000011 5732.5000000000009 120.50000000000001 201.50000000000003 157.50000000000003 4.5000000000000009 1.0000000180025095e-35 3.5000000000000004 12.500000000000002 69.500000000000014 6.5000000000000009 384.50000000000006 47154.500000000007 1.0000000180025095e-35 1158.0000000000002 18.500000000000004 48.500000000000007 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 2 -1 5 -5 6 9 8 -7 -2 15 12 23 27 -15 -11 17 -17 26 -19 -21 -20 -13 28 -25 -26 -18 -14 -12 30 -30 +right_child=3 -3 -4 4 -6 7 -8 -9 -10 10 11 22 13 14 -16 16 18 19 21 20 -22 -23 -24 24 25 -27 -28 -29 29 -31 -32 +leaf_value=-9.1229977945911687e-05 0.029048333299422625 0.016351043638059327 0.036701427921708912 0.030516680449895073 -0.15553155897262161 0.070085671995932158 -0.15145350973117899 -0.043526731644539608 -0.15721513375917162 0.048872025355211089 0.043860769726355886 -0.12632573226703586 0.098115151114101712 -0.15319138266628785 0.026571619926442636 -0.136578127381349 -0.133518120139724 -0.11327030002598318 -0.17070960742659763 -0.056015556733175015 0.090320657525728557 0.10258638332548396 0.03498617559344807 -0.15677983416582972 0.089336539431951217 -0.13628069530261222 0.081402021253748319 -0.15138991916102237 -0.09478821653293612 0.013044641225080717 0.018637661410311791 +leaf_weight=73899.818198961191 52.904721524377237 115.75656248821178 19.555849511612905 2.4521024631103492 2.3559255771688186 17.633787969709381 1.9612613919307467 3.6419223377015433 0.81476457443204608 4.1674334760173215 22.646172333756112 4.5259425149997687 15.021191048435863 2.0942083697882508 22.484268059721217 7.8264178973331626 0.66621891432441582 4.3872893828374808 1.874238121963572 4.4393716985941847 3.5615889859618619 0.75494267576141272 1.6295819767110513 3.6611028427723777 3.7704043225385249 1.377916441997513 5.7813923503854312 0.43127977714175436 9.6891149492585118 185.53783541262965 7.6896180224721311 +leaf_count=24117525 17269 37777 6382 799 770 5753 639 1191 267 1357 7390 1476 4903 683 7339 2555 217 1434 613 1448 1162 246 532 1195 1232 448 1887 141 3162 60550 2511 +internal_value=0 -5.58034e-05 -8.14962e-05 0.0104384 -0.0606466 0.0113126 0.0094167 0.0429715 0.0600471 0.0102766 0.00711396 0.0120614 0.0142078 0.042096 0.0112549 -0.0343712 -0.0462147 -0.0738501 0.0153315 -0.034221 0.0091253 -0.0922354 -0.0836208 0.00944447 -0.0482365 0.0289515 0.0591947 0.0911514 0.0116972 0.00810765 -0.0446003 +internal_weight=0 74035.1 73919.4 395.782 4.80803 390.974 368.884 22.0905 18.4486 366.922 314.018 280.559 274.403 40.0309 24.5785 33.4589 29.2915 20.2147 9.07679 12.3883 8.00096 2.62918 6.15552 234.372 8.80942 5.14832 6.44761 15.4525 225.563 202.917 17.3787 +internal_count=24290853 24161684 24123907 129169 1569 127600 120389 7211 6020 119750 102481 91562 89554 13066 8022 10919 9562 6599 2963 4044 2610 859 2008 76488 2875 1680 2104 5044 73613 66223 5673 +is_linear=0 +shrinkage=0.1 + + +Tree=128 +num_leaves=32 +num_cat=0 +split_feature=12 17 17 10 14 18 18 6 6 10 11 0 5 10 4 16 20 7 12 5 0 4 0 18 10 4 10 10 10 4 18 +split_gain=3.23887 3.35811 3.36 3.03506 6.1787 3.0926 3.98889 4.12739 3.67122 3.96802 3.62556 3.32155 3.1431 2.68613 3.16929 2.63228 2.56531 3.39497 2.53221 5.8997 4.28105 4.07968 4.04212 5.25529 5.00557 5.99037 3.63042 2.79383 3.08596 2.7787 2.45802 +threshold=47154.500000000007 10.500000000000002 712.00000000000011 9425.5000000000018 172.50000000000003 15.500000000000002 7.5000000000000009 63.500000000000007 202.00000000000003 9701.5000000000018 45424.000000000007 242.50000000000003 1168.5000000000002 11117.500000000002 2.5000000000000004 1.0000000180025095e-35 50132.000000000007 1.5000000000000002 50694.000000000007 40.500000000000007 366.50000000000006 87.500000000000014 336.00000000000006 6.5000000000000009 6871.5000000000009 72.500000000000014 9158.0000000000018 6642.0000000000009 5794.5000000000009 74.500000000000014 20.500000000000004 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=16 -2 3 4 -3 6 10 -8 9 -9 12 30 -5 -11 -15 -4 -1 -18 19 -6 22 26 24 -24 27 -26 -21 28 -20 -27 -7 +right_child=1 2 15 5 18 11 7 8 -10 13 -12 -13 -14 14 -16 -17 17 -19 20 21 -22 -23 23 -25 25 29 -28 -29 -30 -31 -32 +leaf_value=-4.8640720609152904e-05 -0.16309886767215032 -0.14524291905088493 -0.027765720133315332 -0.17785067133240207 -0.12662813029039641 0.079228654383329183 -0.11166254224694697 -0.18197847812003545 -0.14689776517179465 0.059195120567884264 0.054765599388142738 -0.1620535557754246 0.12995331021244313 0.046861518506836182 -0.14350442945833836 -0.20266576909045139 0.022089528463370599 -0.18483981165204777 0.009995820273433112 0.047696110144742287 -0.10184177132307065 -0.13424845160395626 -0.21413309849880402 0.10731331644579511 -0.13716982583488888 0.14851635966365553 -0.14402771430548875 0.10107362025992772 -0.13612114454576987 -0.047161157374116601 -0.13895734680626817 +leaf_weight=74194.402428239817 1.0635075394457079 2.6259394053195129 4.5143540254503014 1.2493955757236133 2.3363717890606486 19.368287787539892 3.5523535351967466 0.90140231390251102 1.4155030727561086 10.742156768101266 24.948052725332673 0.61767778427019848 0.45168372988700856 1.8840981847606593 1.6321438592276534 1.0631569451070388 65.744420972972875 0.80252904153894533 21.511622256119161 38.024917806556914 4.23206615509116 1.3476968229806505 0.55912594683468242 5.6287227747961879 5.5617666067992086 1.2996746461139994 1.0139911603182543 3.1151762324734582 1.5495185515028413 1.6432569624739697 0.53047777138999563 +leaf_count=24215480 350 858 1473 406 765 6321 1161 295 463 3504 8144 201 147 616 530 348 21458 262 7017 12406 1382 443 183 1837 1815 424 330 1016 507 538 173 +internal_value=0 0.0140212 0.0151746 0.0178717 0.00590731 0.0339532 0.0197557 -0.0138456 0.00711812 0.0214989 0.0451342 0.066323 -0.0961202 0.0343626 -0.0415012 -0.0611043 -3.10385e-05 0.019594 0.0104267 0.0278731 -0.0060998 0.036811 0.0038145 0.0782678 -0.0094696 -0.0761205 0.0427163 0.0121853 0.000177965 0.0392551 0.0734121 +internal_weight=0 164.384 163.321 157.743 90.4498 67.2932 46.7768 20.1277 16.5753 15.1598 26.6491 20.5164 1.70108 14.2584 3.51624 5.57751 74260.9 66.547 87.8239 42.723 45.1009 40.3866 40.8689 6.18785 34.681 8.5047 39.0389 26.1763 23.0611 2.94293 19.8988 +internal_count=24290853 53653 53303 51482 29521 21961 15266 6569 5408 4945 8697 6695 553 4650 1146 1821 24237200 21720 28663 13944 14719 13179 13337 2020 11317 2777 12736 8540 7524 962 6494 +is_linear=0 +shrinkage=0.1 + + +Tree=129 +num_leaves=32 +num_cat=0 +split_feature=13 1 2 17 6 2 19 9 10 19 15 2 15 11 11 11 0 17 0 6 0 17 17 15 15 15 17 6 19 8 0 +split_gain=5.60218 2.31809 7.44534 5.47503 4.8176 3.67568 3.45573 3.45928 3.50501 7.20879 4.94807 3.39335 8.66004 5.54441 3.38791 4.61701 3.41203 4.97538 4.87179 3.94333 3.34403 3.31414 5.66809 3.2269 6.07837 4.18618 4.05923 3.18969 3.20645 4.44546 4.38418 +threshold=44073.000000000007 5045.0000000000009 12256.000000000002 1.0000000180025095e-35 54.500000000000007 11853.500000000002 33.500000000000007 1960.5000000000002 18.500000000000004 163.00000000000003 15.500000000000002 11670.000000000002 12.500000000000002 5.5000000000000009 118.50000000000001 103.50000000000001 1150.0000000000002 114.50000000000001 1463.0000000000002 25.500000000000004 5143.0000000000009 110.50000000000001 45.500000000000007 70.500000000000014 1.0000000180025095e-35 269.50000000000006 73.500000000000014 24.500000000000004 2.5000000000000004 23.500000000000004 3994.0000000000005 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 2 5 6 14 11 -4 8 9 10 -8 -1 13 -13 15 16 17 21 -18 27 -6 22 -3 24 26 -25 -17 28 29 30 -20 +right_child=-2 4 3 -5 20 -7 7 -9 -10 -11 -12 12 -14 -15 -16 23 18 -19 19 -21 -22 -23 -24 25 -26 -27 -28 -29 -30 -31 -32 +leaf_value=-0.00017277497301172395 0.01204950813611296 0.056582942668300121 -0.19822779251300185 -0.17998544703431837 0.12461748430731522 -0.1222371962678177 -0.17539874500305053 -0.226335240844873 0.13732527610651266 -0.18543227723172376 0.12449296511400521 0.10832145123092909 -0.16227104570221787 -0.17924925365591876 -0.15386598726568945 -0.1851755443935818 -0.14748990779290064 -0.15417227102602335 -0.014645448308389212 -0.12517352077679719 -0.1308229603420217 0.1769572479583128 -0.14420381317085024 0.13279985739154068 -0.18349793565104999 -0.077408395556232773 0.15690443321202022 0.086920537479657833 0.032589093983573569 -0.14510275819735849 0.003572839925151527 +leaf_weight=72126.121741035953 383.85995749714493 17.520041777628659 0.43370311090257097 0.85294122909545045 4.842636010143905 2.4666743514826512 0.62355190387461057 0.34225852787494737 8.5195128459017706 1.1660209878755265 4.675926187337609 9.4033900515059905 1.6039586717961345 0.72192090738099146 1.3734845428843971 0.43247630074620147 2.1661429068190037 1.2241804808145378 143.58641121862456 2.4134233175136606 0.5731540749548002 1.9623239694628853 1.52860678709112 6.6696086172014475 1.3057248615659771 1.1042132317088542 1.7527941793669013 4.4896418685093513 34.809385148953879 2.0535108083859077 1649.9195353369796 +leaf_count=23541961 125293 5716 142 277 1577 805 205 112 2781 380 1526 3068 523 237 451 141 708 401 46864 788 190 641 502 2177 427 361 571 1465 11365 671 538527 +internal_value=0 -6.24745e-05 -0.000152787 0.0667827 0.00340436 -0.000168203 0.0801371 0.0880138 0.0951935 0.039677 0.0892068 -0.000164029 0.0536188 0.0878181 0.00313223 0.00324736 0.00286115 0.0417995 0.00239046 0.00256717 0.0975842 0.0532175 0.0404703 0.0670749 -0.0127931 0.102941 0.089205 0.00273519 0.0025287 0.00194593 0.0021143 +internal_weight=0 74036.7 72156.9 16.6139 1879.73 72140.3 15.761 15.3273 14.985 6.4655 5.29948 72137.9 11.7293 10.1253 1874.31 1872.94 1861.67 22.2352 1839.44 1837.27 5.41579 21.011 19.0486 11.2648 3.491 7.77382 2.18527 1834.86 1830.37 1795.56 1793.51 +internal_count=24290853 24165560 23552017 5423 613543 23546594 5146 5004 4892 2111 1731 23545789 3828 3305 611776 611325 607648 7260 600388 599680 1767 6859 6218 3677 1139 2538 712 598892 597427 586062 585391 +is_linear=0 +shrinkage=0.1 + + +Tree=130 +num_leaves=32 +num_cat=0 +split_feature=13 20 2 7 13 8 7 12 13 7 8 7 8 8 2 6 16 12 12 15 12 12 12 2 6 2 12 13 6 15 7 +split_gain=4.86499 2.35726 2.84762 2.23977 5.48114 4.85803 4.1828 3.7393 2.83927 5.65481 4.748 4.22459 3.93887 4.74427 3.88749 3.65111 3.59935 4.92065 5.17859 5.91022 5.43167 4.20223 3.96772 3.92007 3.91547 3.7888 4.08885 6.02402 3.90265 3.61921 3.18869 +threshold=44073.000000000007 50132.000000000007 15.500000000000002 267.50000000000006 58602.000000000007 177.50000000000003 440.50000000000006 9682.5000000000018 52600.000000000007 127.50000000000001 105.50000000000001 194.50000000000003 155.50000000000003 112.50000000000001 3262.5000000000005 26.500000000000004 1.0000000180025095e-35 7343.0000000000009 5846.0000000000009 69930.500000000015 3898.0000000000005 5553.5000000000009 7564.5000000000009 238.00000000000003 7.5000000000000009 242.50000000000003 7615.0000000000009 56979.500000000007 148.50000000000003 684.50000000000011 59.500000000000007 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 -1 -3 5 -5 8 -6 -7 -2 16 -11 12 13 15 -15 -12 29 18 19 20 24 -21 -19 -20 -18 26 -24 28 -28 -10 -25 +right_child=3 2 -4 4 6 7 -8 -9 9 10 11 -13 -14 14 -16 -17 17 22 23 21 -22 -23 25 30 -26 -27 27 -29 -30 -31 -32 +leaf_value=-7.5177366032612493e-05 0.017725236115616792 0.021223145936531074 -0.15795224075385161 -0.10801430759968145 -0.13438501283580553 0.093034821148474597 0.088313237143484533 -0.1782492992708852 -0.18552273830807112 -0.16486191024528307 -0.10014895197107448 -0.13146789759629707 0.13855037567755241 -0.12046624504911969 0.15004569433259091 0.11119350702220916 -0.13442387724063107 -0.14588976014379293 0.091334958492671786 0.040142334324865303 -0.14961435698120901 -0.17350610037031899 0.13478771093432232 0.052376861578085389 0.034605059167883069 -0.15269143394852144 -0.15421235226074706 -0.0028236388334963837 0.029927131767238442 0.045056018542674263 -0.15309166505518698 +leaf_weight=73963.283611635386 218.71571801936807 65.379006752205896 0.89920238945342035 5.1078270282014255 1.0789047419675624 10.034546948969361 3.8638123262790032 0.53518993617035526 0.69859906767669855 3.1525303376256515 1.1203106621978767 3.147439354332163 1.7273731715977199 4.6114080714760348 0.60041706589981902 3.0235639619058929 1.8218795184511671 2.5032305878703474 13.078809998114595 19.63947044120869 4.2196415706275729 0.96589640737511118 1.8430429520085443 1.8031753221730462 5.5307861405308358 1.9722816944122303 4.6694640889036227 39.759836772893323 1.5274817189419989 26.613872955640545 1.2997220664983613 +leaf_count=24143654 71392 21342 293 1669 352 3275 1262 175 228 1029 366 1028 564 1506 196 987 598 821 4268 6413 1376 316 602 587 1804 643 1523 12973 498 8687 426 +internal_value=0 -5.82855e-05 0.0187923 0.0112169 -0.0353694 0.0124668 0.0397023 0.0792986 0.0105264 -0.000307592 -0.0538236 -0.0292249 -0.000189304 -0.0258053 -0.0893025 0.0540564 0.00696315 -0.00177468 0.0212157 -0.00199034 -0.0591783 0.0301274 -0.0230428 0.0673613 -0.00727776 -0.0168644 -0.01126 -0.017117 -0.108824 0.0391583 -0.0336885 +internal_weight=0 74029.6 66.2782 384.666 10.0505 374.616 4.94272 10.5697 364.046 145.33 17.383 14.2305 11.0831 9.3557 5.21183 4.14387 127.947 100.635 48.3594 32.1777 11.5723 20.6054 52.2753 16.1817 7.35267 49.7721 47.7998 45.9568 6.19695 27.3125 3.1029 +internal_count=24290853 24165289 21635 125564 3283 122281 1614 3450 118831 47439 5676 4647 3619 3055 1702 1353 41763 32848 15788 10507 3778 6729 17060 5281 2402 16239 15596 14994 2021 8915 1013 +is_linear=0 +shrinkage=0.1 + + +Tree=131 +num_leaves=32 +num_cat=0 +split_feature=13 21 9 16 15 4 6 6 1 2 2 4 1 6 2 2 2 15 2 15 12 4 4 4 2 2 1 4 4 9 4 +split_gain=5.76359 2.42904 2.19955 9.91199 9.24099 7.99495 8.39669 14.1822 15.3286 11.9304 11.8253 10.2033 7.7124 19.3284 9.26435 10.2441 8.3869 8.32404 18.9463 9.31172 9.69721 7.98684 13.6471 7.92087 7.86495 6.49514 6.45207 6.15907 10.2867 10.5407 7.94641 +threshold=44073.000000000007 54465.500000000007 48668.000000000007 18.500000000000004 761.00000000000011 215.00000000000003 7781.5000000000009 8727.0000000000018 124.50000000000001 50.500000000000007 367.50000000000006 1.0000000180025095e-35 16.500000000000004 1.5000000000000002 70.500000000000014 50.500000000000007 134.50000000000003 100.50000000000001 4.5000000000000009 283.50000000000006 1574.0000000000002 74.500000000000014 137.50000000000003 41.500000000000007 22.500000000000004 29.500000000000004 4.5000000000000009 41.500000000000007 101.50000000000001 55371.500000000007 34.500000000000007 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 2 -1 4 5 6 12 11 9 -9 -10 -8 13 14 15 17 -14 18 27 21 -21 -19 -23 -16 -24 -5 -25 30 -29 -30 -4 +right_child=-2 -3 3 25 -6 -7 7 8 10 -11 -12 -13 16 -15 23 -17 -18 19 -20 20 -22 22 24 26 -26 -27 -28 28 29 -31 -32 +leaf_value=-0.00011225390230300127 0.0123210690812635 0.01442667157158751 -0.18606886434521297 0.14295664205956068 -0.18013855802441722 -0.18614859162941941 -0.18803728975874548 -0.1742711362017545 -0.18502133500815027 0.097652613750057335 0.15105506430793611 0.10405690192677577 0.057478260832832445 -0.19260555678525787 -0.19799751171311586 -0.19435329423905787 -0.19112113697624586 0.11486116674266085 -0.19038240738354906 0.0096475568943133073 -0.1886685406939648 -0.19469711141519094 0.15444669576828596 0.11042523002058807 -0.20409910248166385 -0.20496064157800664 -0.20403570740569227 -0.19913732548229618 0.19069486869325405 -0.19839754316798799 0.17825240173407481 +leaf_weight=73738.921814205212 377.73396300179593 115.51753823817126 0.71462869457900691 7.6174739901907724 2.6303089174907646 2.0779355537379152 7.5483064075815509 1.8406032518250892 5.3121962940786025 13.074828256620092 1.3039672625018282 1.421040628920309 52.874918539077044 4.7866269816877312 0.96620912849903362 2.7037477111443868 1.3928153537563037 14.861420646077024 6.3250477351248255 13.98586497991346 2.9933636523783198 2.6291828183457246 3.6184299455489963 15.183310116874051 0.73628526460379351 0.57724399655126024 0.68177564698271531 2.3399852942675352 2.3322875499725346 0.99254978727549303 3.6902310401201248 +leaf_count=24071981 123312 37711 233 2489 858 680 2466 602 1734 4266 425 464 17259 1565 315 883 454 4852 2065 4567 977 857 1181 4955 240 186 223 766 763 323 1201 +internal_value=0 -6.28672e-05 -8.55117e-05 0.011042 0.00583446 0.00877438 0.0112395 -0.0361092 0.00790133 0.0640965 -0.118785 -0.14176 0.0220323 0.0022021 0.0146759 -0.00430087 0.0510978 0.00500483 -0.0547431 0.030235 -0.0253147 0.0734109 -0.0147933 0.0799824 0.0938246 0.118449 0.0969118 0.0304558 -0.0385083 0.0745407 0.119146 +internal_weight=0 74031.7 73916.1 177.213 169.018 166.388 164.31 30.5009 21.5316 14.9154 6.61616 8.96935 133.809 79.5409 74.7543 57.923 54.2677 55.2193 16.3947 38.8245 16.9792 21.8453 6.9839 16.8313 4.35472 8.19472 15.8651 10.0697 5.66482 3.32484 4.40486 +internal_count=24290853 24167541 24129830 57849 55174 54316 53636 9957 7027 4868 2159 2930 43679 25966 24401 18908 17713 18025 5351 12674 5544 7130 2278 5493 1421 2675 5178 3286 1852 1086 1434 +is_linear=0 +shrinkage=0.1 + + +Tree=132 +num_leaves=32 +num_cat=0 +split_feature=10 21 8 3 3 0 2 8 8 3 14 8 3 3 2 15 8 2 8 8 8 2 3 14 2 15 8 14 15 14 8 +split_gain=2.98001 8.35902 11.9407 9.84196 12.5587 10.8548 5.86505 5.50108 14.1465 4.81498 3.22156 5.37049 9.65346 2.76609 2.34785 7.59421 4.73884 2.33435 8.47024 2.30951 3.59985 18.7352 10.6905 15.8262 4.69396 13.7133 3.09381 2.54444 2.24334 7.76232 5.2804 +threshold=63908.500000000007 7.5000000000000009 4198.0000000000009 68.500000000000014 63.500000000000007 1.5000000000000002 1.0000000180025095e-35 9637.0000000000018 13779.500000000002 17.500000000000004 1.0000000180025095e-35 4875.0000000000009 309.50000000000006 225.50000000000003 2.5000000000000004 136.50000000000003 5431.5000000000009 18.500000000000004 9334.0000000000018 75743.500000000015 57621.000000000007 52.500000000000007 1.0000000180025095e-35 19.500000000000004 17.500000000000004 3.5000000000000004 65910.500000000015 2.5000000000000004 44167.000000000007 1842.0000000000002 87.500000000000014 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=19 3 -3 4 5 -2 -4 10 -9 -7 11 -8 -13 14 15 -12 -16 -18 -19 20 28 22 23 -22 -25 -26 -24 -21 -1 -30 -31 +right_child=1 2 6 -5 -6 9 7 8 -10 -11 13 12 -14 -15 16 -17 17 18 -20 27 21 -23 26 24 25 -27 -28 -29 29 30 -32 +leaf_value=-5.2829775787989931e-05 -0.1861363211733385 -0.18545876764713978 -0.18294096480736546 -0.19163739703161486 0.16944223275726264 0.18536638327766974 0.20110688881963379 -0.17507487402209421 0.12997668261765097 -0.18785646933728073 0.17193598256892018 -0.1731250717978729 0.14167727623104992 -0.17947895480464976 -0.17582132309571644 -0.19999354961214544 0.16398208241948575 -0.18620508573745642 0.1468752103077042 -0.20733526283245041 -0.19739634829264308 -0.19601038346191438 -0.20123459745906902 0.18470132167065911 0.16700338958522187 -0.20526748967194128 0.18934281691543628 0.040622217702243675 -0.16307297268091236 0.0011410601387323344 0.026990153816871809 +leaf_weight=73937.817838020404 3.5358690856955928 2.009429203812032 0.91986963758245366 3.3508262179675503 3.1534936712123454 1.5865743160247805 1.297885537147528 2.8381193570094174 3.2737751316744834 0.44195627281442273 1.3672281391918724 2.1945019336417322 1.7516327835619447 0.31826751329935987 0.49013756425120192 0.91731923399493087 11.627651761053132 0.95082005625590582 3.8749576443806291 0.42338352242950605 3.6878824454033765 5.6635683173662974 0.21546357311308328 2.1294200494885445 2.2183793252333999 1.7863148897886274 3.4520733081735671 18.369455975655001 2.6448685071663922 265.33751035474415 112.54800026110024 +leaf_count=24139243 1154 652 301 1096 1029 518 424 927 1069 144 445 715 573 103 158 300 3800 311 1267 139 1206 1845 70 695 726 584 1127 5997 865 86623 36747 +internal_value=0 0.0254722 0.0509604 -0.0459775 0.0100087 -0.0803466 0.0658893 0.0732962 -0.0116771 0.104052 0.0942458 0.0246485 -0.0333888 0.112918 0.117758 0.0225943 0.130589 0.139716 0.0812486 -1.57251e-05 -2.45863e-05 -0.0433723 0.0207126 -0.033686 0.0647381 0.000950008 0.166397 0.035036 -1.34149e-05 0.00764498 0.00883986 +internal_weight=0 45.9003 33.8316 12.0687 8.71789 5.5644 31.8222 30.9023 6.11189 2.02853 24.7904 5.24402 3.94613 19.5464 19.2281 2.28455 16.9436 16.4534 4.82578 74356.3 74337.5 19.1531 13.4895 9.822 6.13411 4.00469 3.66754 18.7928 74318.3 380.53 377.886 +internal_count=24290853 14986 11045 3941 2845 1816 10393 10092 1996 662 8096 1712 1288 6384 6281 745 5536 5378 1578 24275867 24269731 6253 4408 3211 2005 1310 1197 6136 24263478 124235 123370 +is_linear=0 +shrinkage=0.1 + + +Tree=133 +num_leaves=32 +num_cat=0 +split_feature=9 0 8 13 16 8 1 16 1 8 20 0 0 0 14 0 18 0 1 10 13 6 9 8 8 20 18 1 1 9 6 +split_gain=7.06699 6.71472 5.94786 4.89976 4.73837 6.1656 12.8342 8.1508 7.29108 6.50241 6.46495 10.4193 10.0538 15.822 6.76137 6.42879 13.8666 11.1982 7.72462 7.58413 5.56409 5.51161 5.33204 5.12384 5.11909 5.10417 5.00157 4.60824 12.0942 5.08232 4.4702 +threshold=48668.000000000007 175.50000000000003 2660.0000000000005 44073.000000000007 20.500000000000004 6947.5000000000009 1.0000000180025095e-35 2.5000000000000004 1.5000000000000002 10178.500000000002 1.5000000000000002 44.500000000000007 64.500000000000014 31.500000000000004 3.5000000000000004 10.500000000000002 238.50000000000003 5.5000000000000009 41.500000000000007 1363.0000000000002 92.500000000000014 7029.5000000000009 53130.500000000007 8173.5000000000009 49104.000000000007 1.0000000180025095e-35 555.00000000000011 85.500000000000014 115.50000000000001 55371.500000000007 6673.5000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=3 2 -2 -1 5 7 9 -4 -9 -7 12 20 13 15 -14 16 -8 25 -17 27 21 -12 -11 -6 -16 30 -22 -20 -29 -30 -18 +right_child=1 -3 4 -5 23 6 10 8 -10 22 11 -13 14 -15 24 18 17 -19 19 -21 26 -23 -24 -25 -26 -27 -28 28 29 -31 -32 +leaf_value=-0.00010412003109870406 -0.080659356487848596 -0.17286201506515358 -0.17828892401343233 0.011303112810647548 -0.16230971717371512 -0.092969252949458306 -0.17424873419132406 0.083411466470046006 -0.17684101993354773 0.072716659627777125 0.17456998528045561 -0.1818594502175333 -0.17772476554101782 -0.17604172103198157 -0.1998316934073027 -0.11061506616732009 0.091680553050070446 -0.17851512783006127 0.13133310785539143 -0.14910544155468208 0.061218634465181068 -0.16681615287078394 -0.15085558258296661 0.12969970771368647 0.11531865925952656 -0.18463341253366336 -0.17902495946992758 -0.19434846896781 0.1370151643767514 -0.173756313160771 -0.037051567996647099 +leaf_weight=73860.179920400871 5.3774444326991206 1.7736110801051825 1.3448717137798691 378.47244468935241 0.65895484969951112 15.259714020416142 5.1442176855052848 28.201881442219022 1.1191897012758989 5.1559086162596932 6.9076286596246055 1.6588676879182447 0.9232157050864761 5.2724300571717313 0.53758852911414479 2.7328625016380128 10.762733887415378 2.6310074501088812 10.116026546689687 1.4654977581812989 24.312900613411333 0.50767628755420435 1.3450169357238335 6.8204462565190624 12.495877476729218 0.91135278582805845 0.89859628537669678 1.8423652250785369 4.7636611950583756 0.59158646687865246 3.5996175287291416 +leaf_count=24113382 1754 579 440 123561 214 4982 1679 9203 365 1682 2255 542 302 1721 175 890 3513 860 3310 477 7939 166 439 2228 4079 298 294 603 1552 193 1176 +internal_value=0 0.0206642 0.0227653 -4.59654e-05 0.0262857 0.022425 0.0121861 0.0624362 0.0734776 -0.05729 0.0276012 0.0626217 0.00877854 -0.0122311 0.0837952 0.00715085 -0.0295438 0.0120315 0.0464671 0.0693268 0.075052 0.151198 0.0264604 0.103973 0.10232 0.0448546 0.0526558 0.0878158 0.0266534 0.102685 0.0594166 +internal_weight=0 165.133 163.359 74238.7 157.982 150.502 119.836 30.6659 29.3211 21.7606 98.0757 34.2857 63.79 49.8334 13.9567 44.5609 23.0489 17.9047 21.512 18.7791 32.6268 7.4153 6.50093 7.4794 13.0335 15.2737 25.2115 17.3136 7.19761 5.35525 14.3624 +internal_count=24290853 53910 53331 24236943 51577 49135 39127 10008 9568 7103 32024 11196 20828 16272 4556 14551 7526 5847 7025 6135 10654 2421 2121 2442 4254 4987 8233 5658 2348 1745 4689 +is_linear=0 +shrinkage=0.1 + + +Tree=134 +num_leaves=32 +num_cat=0 +split_feature=9 4 8 1 2 2 2 9 2 1 8 0 7 14 2 14 1 0 7 21 12 13 0 15 14 2 0 2 12 1 4 +split_gain=7.44068 6.48502 5.65162 5.05161 11.6067 13.6985 4.736 4.58884 18.0837 12.2826 11.5034 10.6665 12.0453 15.3747 12.7865 10.8874 11.1018 10.5294 16.4542 11.161 21.8344 21.221 10.2151 11.5606 10.2503 10.0752 15.0692 10.0747 11.1315 9.35609 8.47783 +threshold=48668.000000000007 215.00000000000003 1887.5000000000002 124.50000000000001 126.50000000000001 134.50000000000003 367.50000000000006 32795.500000000007 39.500000000000007 41.500000000000007 847.00000000000011 5.5000000000000009 33765.500000000007 3.5000000000000004 50.500000000000007 15.500000000000002 124.50000000000001 4.5000000000000009 41808.500000000007 4.5000000000000009 811.00000000000011 212.50000000000003 34.500000000000007 3.5000000000000004 18.500000000000004 85.500000000000014 132.50000000000003 40.500000000000007 7.5000000000000009 1.0000000180025095e-35 127.50000000000001 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=7 2 -2 -4 -5 -6 -7 -1 9 10 -9 17 14 -14 -13 -16 -17 18 19 -10 21 29 24 -24 30 26 27 -15 -29 -21 -12 +right_child=1 -3 3 4 5 6 -8 8 11 -11 22 12 13 25 15 16 -18 -19 -20 20 -22 -23 23 -25 -26 -27 -28 28 -30 -31 -32 +leaf_value=2.9018045620110984e-06 -0.14989340680903993 -0.15754037967756052 0.031436124091719357 -0.16679389737457392 0.13689505682225525 -0.1484749962178491 0.067152074787899904 0.16776113290108863 0.06295489377138494 -0.19526745002295495 -0.032164159123232401 0.085688805687292313 -0.19249138133962485 0.18163984208392558 -0.17308500854123723 0.10829717903618014 -0.11557305193046519 0.12780430442351207 -0.1988314861357903 0.15669410277190499 0.099818383587424078 -0.1941417704557325 -0.1768381486476556 0.10814922752823652 -0.19617867461955638 0.056819871617114749 0.12792556580290076 -0.19634950651214708 0.030066356687406849 -0.18643174624555367 -0.13558719937147343 +leaf_weight=73944.763022177562 1.8553341545630235 2.0005371114239088 143.38134085666388 4.3680278152169185 5.4808293544920144 3.7641178873600438 1.3965102809015659 2.7945404872298232 42.565375028760172 4.832807332975789 108.92587073962204 4.187847287743355 3.4501539037446483 1.1577391698956478 11.072016777005048 3.6921771867200714 5.537206678185612 10.960944380029103 3.2096949128317638 3.3332967651076642 8.522951365914194 8.8817443081643415 1.7159859123639751 8.3485026177950186 4.340348716126754 33.447670533205383 3.9529855104628941 7.3337270056363177 3.0847634733654559 1.0434285269584505 8.5479077299823967 +leaf_count=24141590 602 654 46814 1425 1789 1231 456 913 13893 1578 35563 1369 1127 377 3613 1206 1807 3578 1049 1090 2783 2897 560 2725 1417 10917 1291 2397 1009 342 2791 +internal_value=0 0.0213917 0.0236255 0.025658 -0.0295387 0.0268008 -0.0901245 -4.67513e-05 -0.0124953 -0.0386322 -0.0330113 0.0109632 -0.0155042 0.0115422 -0.0734056 -0.106224 -0.0260147 0.0368909 0.0221403 0.0331626 -0.0250576 -0.105332 -0.0372657 0.0595591 -0.0452655 0.0259152 -0.040649 -0.0982129 -0.129311 0.0748916 -0.0396897 +internal_weight=0 162.247 160.246 158.391 15.0095 10.6415 5.16063 74239.7 294.94 139.506 134.673 155.434 76.9163 52.427 24.4892 20.3014 9.22938 78.5174 67.5565 64.3468 21.7814 13.2585 131.879 10.0645 121.814 48.9769 15.5292 11.5762 10.4185 4.37673 117.474 +internal_count=24290853 52971 52317 51715 4901 3476 1687 24237882 96292 45547 43969 50745 25113 17118 7995 6626 3013 25632 22054 21005 7112 4329 43056 3285 39771 15991 5074 3783 3406 1432 38354 +is_linear=0 +shrinkage=0.1 + + +Tree=135 +num_leaves=32 +num_cat=0 +split_feature=9 11 8 2 1 1 14 1 6 4 2 10 8 15 15 8 8 2 1 2 17 4 8 20 2 18 6 4 2 8 6 +split_gain=5.19456 6.09301 5.28581 7.97578 3.72805 3.01291 2.70179 7.17851 8.39957 6.54212 3.25196 2.97039 6.62211 5.16772 3.98903 3.53416 6.4773 2.91076 6.2869 2.78627 4.77458 9.4407 7.13182 10.0868 5.09929 4.74208 3.96789 3.79915 3.67426 3.23865 3.23614 +threshold=53130.500000000007 703.50000000000011 2660.0000000000005 154.50000000000003 220.50000000000003 213.50000000000003 10.500000000000002 68.500000000000014 8822.0000000000018 80.500000000000014 3.5000000000000004 86711.000000000015 8372.0000000000018 209.50000000000003 1187.0000000000002 2734.5000000000005 3827.0000000000005 74.500000000000014 185.50000000000003 168.50000000000003 2.5000000000000004 42.500000000000007 7097.0000000000009 2.5000000000000004 2.5000000000000004 187.50000000000003 1650.0000000000002 101.50000000000001 9.5000000000000018 14240.000000000002 6673.5000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 -2 3 -3 5 6 7 11 -9 10 -8 14 -13 -14 15 -4 -17 -12 -19 20 22 28 -18 25 27 -24 -27 -25 -22 -23 -28 +right_child=1 2 4 -5 -6 -7 9 8 -10 -11 17 12 13 -15 -16 16 19 18 -20 -21 21 29 23 24 -26 26 30 -29 -30 -31 -32 +leaf_value=-3.2488617944494732e-05 -0.16024304102101508 -0.15327634179346272 0.16892269721835959 0.13024735725383815 -0.13345960038321364 0.15511172834577849 -0.15721737442759171 -0.16391959204972925 0.018555011119098672 -0.16229035145927592 0.081153236481621344 -0.16919882218526305 0.095005048789196661 -0.15870252662078199 0.13907392294036794 -0.18136664072807249 0.12559409335832392 -0.15606466738297564 0.10878028013904587 0.10529799274473083 0.10556864880565867 -0.12371852217800035 -0.15173688942267533 -0.16466816455030578 -0.17222886283024766 -0.14971479384611111 0.10180881291188458 0.15207043933782247 -0.15709075836112374 0.13393625961138553 -0.014302852712690883 +leaf_weight=74281.640633364499 1.8149851177586231 3.6383555701468131 1.8166619110852469 1.3642126009799538 1.3897330515901547 1.9325615012785409 0.65001177531667664 4.3716473391978052 5.9642271631164476 1.3368441078346212 24.881944300257604 2.2932106965454286 3.606619318947196 1.0327375356573609 3.7334860209375611 1.4493700186721969 8.2465005079284328 1.6833365128259168 1.9170321802666874 4.8206702806055537 4.5309527425561082 6.4176315667573363 1.0512099511688635 0.58579621510580082 3.5651984140276909 0.79873131029307742 12.196034135995431 1.0711187678389249 0.60351730126421888 0.52798812463879574 2.988538470468483 +leaf_count=24254192 592 1188 592 445 456 631 216 1428 1946 437 8120 748 1179 336 1219 474 2692 550 626 1575 1478 2096 344 192 1162 262 3985 350 197 172 973 +internal_value=0 0.0214928 0.0244788 -0.0759587 0.029243 0.0314156 0.0290752 0.0184709 -0.0586242 0.0540194 0.0639456 0.0314624 -0.030185 0.0385288 0.0393181 0.0319678 0.026875 0.0689929 -0.0150468 0.033242 0.0250848 -0.0281243 0.0461571 0.0167242 -0.104863 0.053998 0.067529 0.0400887 0.0746951 -0.104132 0.0789564 +internal_weight=0 112.281 110.466 5.00257 105.463 104.074 102.141 71.6718 10.3359 30.4692 29.1323 61.336 6.93257 4.63936 54.4034 50.6699 48.8533 28.4823 3.60037 47.4039 42.5832 12.0801 30.5031 22.2566 5.22211 17.0345 15.9833 1.65691 5.13447 6.94562 15.1846 +internal_count=24290853 36661 36069 1633 34436 33980 33349 23400 3374 9949 9512 20026 2263 1515 17763 16544 15952 9296 1176 15478 13903 3943 9960 7268 1704 5564 5220 542 1675 2268 4958 +is_linear=0 +shrinkage=0.1 + + +Tree=136 +num_leaves=32 +num_cat=0 +split_feature=9 8 3 13 11 16 8 1 3 3 8 8 21 9 3 8 3 8 20 1 1 1 8 10 11 14 10 6 10 6 10 +split_gain=6.16821 4.31105 4.6392 4.06519 3.68093 3.25772 4.71495 2.83293 3.09621 7.03165 7.76276 5.87291 3.79737 4.27617 2.86935 3.958 5.22625 4.99076 8.93154 5.23103 4.88504 6.54353 3.77503 3.18111 2.82166 2.73104 2.68788 5.9072 2.9469 2.84081 7.16379 +threshold=53130.500000000007 2660.0000000000005 63.500000000000007 44073.000000000007 703.50000000000011 17.500000000000004 6858.5000000000009 220.50000000000003 54.500000000000007 100.50000000000001 9334.0000000000018 9637.0000000000018 11.500000000000002 63109.500000000007 45.500000000000007 4431.5000000000009 13.500000000000002 6858.5000000000009 2.5000000000000004 16.500000000000004 22.500000000000004 26.500000000000004 4711.0000000000009 2236.5000000000005 58552.000000000007 7.5000000000000009 1363.0000000000002 5163.0000000000009 1606.5000000000002 6673.5000000000009 775.50000000000011 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=3 2 -2 -1 -3 7 -7 8 14 10 -10 12 -11 -14 15 16 -6 -17 19 -19 23 -22 -16 -21 -24 -12 29 -28 -29 -23 -31 +right_child=1 4 -4 -5 5 6 -8 -9 9 11 25 -13 13 -15 22 17 -18 18 -20 20 21 26 24 -25 -26 -27 27 28 -30 30 -32 +leaf_value=-8.7825949904623527e-05 -0.1413813050407044 -0.14210540843113556 0.074595402988263934 0.010287105824368 0.07570059274404671 -0.14962189761140238 0.08921669190531667 -0.12815989574448497 -0.16878322531517959 -0.053674113258617373 0.10551655254061142 -0.13638597073417513 -0.15593193975957348 0.10052194009060836 0.14266033675669651 0.13450375884377477 -0.16470087724519392 -0.14855556737724282 -0.14802914343744589 -0.15570835841692784 -0.18517098527795442 0.093612380603469603 0.10396531529946931 0.12818810236598122 -0.16419727882689453 -0.15447709486555758 -0.15310377224795058 -0.17851206040018555 0.038160906094508708 -0.1596330871808084 0.061233434440247883 +leaf_weight=73903.116099658917 3.0744040253339326 1.2568871598050453 1.4701385155785827 379.60774394567125 1.4923085796181106 0.88102839165367086 13.366613594756926 1.22391185350716 4.494435886153954 2.5415815031155935 1.9600970232859256 1.8195641227648582 0.69799637468531628 9.4919572597136703 4.8727682882454237 4.6794954896904519 2.2950674765743315 1.4782044854946423 2.7697846794035277 0.41626901552080919 1.3913728306069959 11.247644266928548 0.75418355781584956 7.6147891404107213 0.81792526226490736 0.50891872169449914 2.0568059931392808 0.65805179078597476 13.611910014878957 1.8104003819753396 7.7767088110558689 +leaf_count=24131463 1004 409 480 123951 486 290 4363 399 1469 831 637 594 228 3103 1590 1527 750 485 905 136 456 3672 246 2490 268 166 670 215 4443 590 2537 +internal_value=0 0.0238224 -0.0715138 -3.48068e-05 0.0279889 0.0300699 0.0744477 0.0229241 0.0250433 -0.00788532 -0.0905268 0.0316629 0.0556801 0.0829552 0.0358191 0.0289318 -0.0699775 0.0356801 0.0265826 0.0366453 0.0425221 0.0277421 0.0991886 0.113473 -0.0355524 0.0519261 0.0357139 0.00533283 0.0281691 0.0595214 0.0195257 +internal_weight=0 108.531 4.54454 74282.7 103.987 102.73 14.2476 88.4822 87.2582 21.5146 6.96345 14.5511 12.7315 10.19 65.7437 59.2988 3.78738 55.5114 50.8319 48.0622 46.584 38.5529 6.44488 8.03106 1.57211 2.46902 37.1615 16.3268 14.27 20.8348 9.58711 +internal_count=24290853 35439 1484 24255414 33955 33546 4653 28893 28494 7028 2272 4756 4162 3331 21466 19362 1236 18126 16599 15694 15209 12583 2104 2626 514 803 12127 5328 4658 6799 3127 +is_linear=0 +shrinkage=0.1 + + +Tree=137 +num_leaves=32 +num_cat=0 +split_feature=11 13 17 8 8 8 6 6 7 21 0 0 1 7 0 6 1 8 0 1 1 12 13 14 1 15 6 1 6 11 6 +split_gain=4.95351 5.4235 4.18199 6.21021 4.20749 3.67496 6.01421 13.2129 23.7551 10.8736 9.50363 13.8434 9.58188 11.1403 11.1291 7.41486 5.76809 18.2398 5.46473 5.12657 7.08855 9.92939 5.27561 5.01298 5.00947 15.5477 6.07804 7.00962 11.7602 6.85987 6.11982 +threshold=55868.000000000007 234.50000000000003 2.5000000000000004 6947.5000000000009 9334.0000000000018 39511.000000000007 5949.0000000000009 4085.0000000000005 49089.500000000007 2.5000000000000004 80.500000000000014 34.500000000000007 22.500000000000004 41915.000000000007 18.500000000000004 800.50000000000011 220.50000000000003 41482.500000000007 27.500000000000004 204.50000000000003 174.00000000000003 47.500000000000007 18.500000000000004 2.5000000000000004 66.500000000000014 4.5000000000000009 6311.0000000000009 140.50000000000003 7029.5000000000009 2561.0000000000005 7029.5000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=5 -2 3 -3 -5 -1 7 10 -9 16 11 12 13 -7 -14 -15 19 -18 -16 20 22 -22 -8 -19 25 -24 29 30 -29 -26 -28 +right_child=1 2 -4 4 -6 6 9 8 -10 -11 -12 -13 14 15 18 -17 17 23 -20 -21 21 -23 24 -25 26 -27 27 28 -30 -31 -32 +leaf_value=-6.2339441141440001e-05 -0.15341688305996609 0.12193722401795266 0.016507534758092496 -0.083044307159012815 0.091481161602431027 -0.18875798838938024 -0.18009359559892368 -0.18872106217710743 0.026157260425178377 -0.16408419344850025 -0.19343184741802391 0.13612280839592122 -0.18507636676666395 -0.17595318717601982 0.11625868769897214 0.098156811877774128 0.11236739666833043 0.014809549150094565 -0.18399887129662107 0.13555541394966017 -0.18303297986007225 0.11131676054239364 0.046682615313866402 -0.18133006412665875 0.16752994257016837 -0.17183583829691276 -0.15967267718677214 0.17556062083387794 -0.17307752351127734 -0.15738643573178282 0.069587613216685398 +leaf_weight=74127.478897124151 1.7235274661870779 9.7081650909967703 81.362011476965563 3.2902172410394988 2.3809810061939061 1.8095864744391317 1.0284042949788261 9.7050896203727479 10.949201396229908 2.8247222471982232 1.8720229652244587 10.074569734744726 4.9765504687093181 1.0743546681478608 2.6805196837522094 12.117021462880073 5.2337921503931266 1.7204714250110553 0.78327484347391862 5.6955647322465657 3.2122205947816829 1.7816809415817259 18.471035690105058 5.3709634797705803 7.9725639108801243 3.9528333444322916 1.1994938198768057 1.387601333670317 3.196037022251403 0.70744925926555868 39.733089689427288 +leaf_count=24206607 564 3169 26567 1075 778 589 336 3170 3575 920 611 3290 1627 350 875 3958 1709 563 256 1861 1049 582 6032 1750 2605 1291 393 454 1042 231 12974 +internal_value=0 0.0224144 0.025547 0.073369 -0.00977199 -2.97106e-05 0.0151317 -0.0112532 -0.0748103 0.02942 0.0258421 0.0380896 -0.00404294 0.0439144 -0.0892771 0.0758323 0.0348499 -0.0292352 0.0483608 0.0437913 0.0374671 -0.0780174 0.0448944 -0.133744 0.0479142 0.00816263 0.0643615 0.0497372 -0.0675346 0.141048 0.0628693 +internal_weight=0 98.4649 96.7414 15.3794 5.6712 74287 159.53 56.0422 20.6543 103.488 35.3879 33.5159 23.4413 15.001 8.44034 13.1914 100.663 12.3252 3.46379 88.338 82.6424 4.9939 77.6485 7.09143 76.6201 22.4239 54.1962 45.5162 4.58364 8.68001 40.9326 +internal_count=24290853 32153 31589 5022 1853 24258700 52093 18301 6745 33792 11556 10945 7655 4897 2758 4308 32872 4022 1131 28850 26989 1631 25358 2313 25022 7323 17699 14863 1496 2836 13367 +is_linear=0 +shrinkage=0.1 + + +Tree=138 +num_leaves=32 +num_cat=0 +split_feature=9 9 13 14 1 6 3 3 3 20 6 3 0 7 0 6 15 15 3 6 12 6 1 0 0 15 13 1 20 3 3 +split_gain=7.33647 5.15551 7.47293 7.4186 7.46446 8.43595 6.93067 10.8838 8.80643 5.87449 5.89153 5.3968 5.15851 5.1285 6.3313 6.40329 12.4886 5.88917 10.9106 5.3715 5.07343 12.6956 5.41176 4.93134 5.87076 6.01066 6.12466 12.1177 5.77282 8.4919 13.847 +threshold=40074.000000000007 35163.500000000007 60.500000000000007 11.500000000000002 118.50000000000001 5364.0000000000009 174.50000000000003 160.50000000000003 110.50000000000001 2.5000000000000004 9891.0000000000018 1.0000000180025095e-35 21.500000000000004 38230.000000000007 2.5000000000000004 5239.0000000000009 89.500000000000014 325.50000000000006 88.500000000000014 1.0000000180025095e-35 1.0000000180025095e-35 5092.5000000000009 140.50000000000003 175.50000000000003 2.5000000000000004 588.50000000000011 1280.0000000000002 2.5000000000000004 3361.0000000000005 60.500000000000007 160.50000000000003 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 -1 -3 4 5 6 7 8 13 10 20 -7 -8 14 15 16 -4 19 -19 -18 21 -6 -22 24 28 26 -26 -28 29 -2 -31 +right_child=23 2 3 -5 9 11 12 -9 -10 -11 -12 -13 -14 -15 -16 -17 17 18 -20 -21 22 -23 -24 -25 25 -27 27 -29 -30 30 -32 +leaf_value=-3.6673419290358705e-05 0.018456289053637772 -0.19714979773819197 -0.18681045442910926 -0.19501601848998054 0.19192025416747929 -0.18991421957619678 -0.17584924288450363 0.14432483071092841 -0.18275518415148384 -0.19302038524043386 -0.19897910505888161 0.093129952612561698 0.22124489282235654 0.20924466986578893 -0.18600465466605917 0.1881437968364168 0.035759822080919004 -0.17424903357144211 0.12756898778829245 -0.20648722348871318 -0.18616035454589711 -0.20551628498737778 0.13837764440796488 -0.103610759858244 0.033254019261147204 0.14155228348772828 0.066405341265020984 -0.17403397689991831 0.057268897596843782 -0.068303498333670762 0.03047728404709989 +leaf_weight=74006.571656602202 78.575672763836337 2.4713049363344899 3.7749905195087186 2.3538703690282992 1.4713472891598898 4.6720386112574479 3.816810183459892 4.2975767655298105 3.1789676925400263 0.93351923488080579 0.78802915057167489 0.78713403409346927 0.35781091451644886 1.0411712788045395 2.0652261696523047 1.6228490974754084 24.30819574021735 4.0674679491203269 1.6976153475698081 0.95114800217561324 0.54516227543353935 1.7713847304694352 8.9361218486446869 3.4329175797756752 128.21628225859604 4.812109629623591 4.1525833274645247 4.2325535167183261 17.782693534391001 37.944847758393735 22.668901168799493 +leaf_count=24167503 25663 807 1230 768 480 1526 1248 1403 1039 304 258 257 117 340 675 530 7939 1328 555 310 179 579 2916 1119 41869 1572 1358 1379 5807 12392 7403 +internal_value=0 -6.33907e-05 -0.0261108 -0.0203551 -0.0145714 -0.0309366 -0.0183322 -0.00736555 -0.0226298 0.0495941 0.0663559 -0.149103 -0.141814 -0.00975225 -0.0156766 -0.0060186 -0.0150732 0.0058234 -0.0853742 0.0266379 0.0827887 -0.0251846 0.119717 0.0155592 0.0169303 0.0317085 0.027839 -0.0549609 0.00361677 -0.00323777 -0.0313605 +internal_weight=0 74082.5 75.9097 73.4384 71.0846 56.639 51.1798 47.0052 42.7076 14.4456 13.512 5.45917 4.17462 39.5287 38.4875 36.4223 34.7994 31.0244 5.76508 25.2593 12.724 3.24273 9.48128 301.819 298.386 141.414 136.601 8.38514 156.972 139.189 60.6137 +internal_count=24290853 24192291 24788 23981 23213 18497 16714 15349 13946 4716 4412 1783 1365 12907 12567 11892 11362 10132 1883 8249 4154 1059 3095 98562 97443 46178 44606 2737 51265 45458 19795 +is_linear=0 +shrinkage=0.1 + + +Tree=139 +num_leaves=32 +num_cat=0 +split_feature=9 9 1 6 10 1 16 19 16 1 17 4 20 14 6 19 17 1 1 6 12 4 16 1 9 4 4 4 4 4 1 +split_gain=7.77349 4.64157 8.65874 5.2101 7.72837 6.888 10.5432 5.69282 5.35527 3.98695 3.73867 3.57608 3.73819 3.24081 3.19461 3.50262 3.16772 7.03783 4.86747 9.38072 5.20397 9.52278 9.35368 6.26652 5.20844 4.81372 4.67679 4.41931 8.90938 4.22503 4.10493 +threshold=40074.000000000007 42173.000000000007 220.50000000000003 8727.0000000000018 741.50000000000011 1.0000000180025095e-35 9.5000000000000018 1.0000000180025095e-35 1.5000000000000002 493.50000000000006 4.5000000000000009 290.50000000000006 63.500000000000007 1.0000000180025095e-35 2947.5000000000005 5.5000000000000009 1.5000000000000002 77.500000000000014 68.500000000000014 1.5000000000000002 866.50000000000011 3.5000000000000004 3.5000000000000004 2.5000000000000004 45258.000000000007 112.50000000000001 14.500000000000002 19.500000000000004 21.500000000000004 16.500000000000004 1.0000000180025095e-35 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 3 11 4 8 7 13 10 -2 -4 -6 16 -13 -7 -10 -16 25 18 19 20 21 -18 -23 24 -24 -3 30 -26 -29 -28 -22 +right_child=1 2 9 -5 5 6 -8 -9 14 -11 -12 12 -14 -15 15 -17 17 -19 -20 -21 26 22 23 -25 27 -27 29 28 -30 -31 -32 +leaf_value=-6.5021375353934755e-05 -0.17470892480809544 0.026891800662296808 -0.15647459260350996 0.096685226512033595 0.042682724367876838 -0.20870337869700484 -0.19598682790151445 -0.18804082998475283 -0.15996117906984986 0.035135070490914598 -0.16601146928967331 0.077274638590416847 -0.17182113135652657 0.1522921925092991 0.073594162214204603 -0.18150688374458712 0.16515711511045766 -0.1823898078985359 0.13657882568273638 -0.16193952047655483 -0.1526000711401598 -0.17779929047255857 -0.16325099049452821 0.13113200975961201 -0.19725729063791378 -0.17158550605967804 0.14875344615384237 0.15185389959089499 -0.035719284023779094 0.024075650729347781 0.058330093923001008 +leaf_weight=74085.803386790023 2.751654571999099 135.12495423201472 4.4174755629064739 13.830222373071591 10.590929782949388 0.25536922365426917 1.0137727131368581 1.3961679306812573 1.0384472906589506 1.4399133018741848 0.93412536953110326 0.85972719313576762 2.013291536248289 9.5014664148911816 3.6211232021450996 0.63219925691373635 2.4048988921567824 2.0263935137772924 2.7692820222582659 3.3672365303500547 2.7018161624437189 4.3126601907424655 2.7775196128059241 2.523519314127042 1.2685766066424573 1.2331161283073004 2.8414690436329684 3.0440287329256561 15.061520620016379 62.559357265243307 1.4010804391000418 +leaf_count=24192981 900 44120 1443 4515 3458 84 331 456 339 470 305 281 658 3104 1183 206 782 661 905 1098 883 1408 908 824 415 405 928 995 4920 20430 457 +internal_value=0 0.0160723 0.010803 0.0454627 0.0231399 0.0518936 0.110952 0.0026652 -0.061554 -0.109371 0.0257677 0.013638 -0.0972813 0.142844 -0.00271483 0.0356768 0.0149365 0.00223283 0.00572818 0.00225274 0.00773236 -0.02606 -0.0419239 -0.0181759 -0.0351851 0.0250969 0.0229954 -0.0168253 -0.0041832 0.0294925 -0.0805704 +internal_weight=0 299.713 254.148 45.5655 31.7353 23.6918 10.7706 12.9212 8.04342 5.85739 11.5251 248.29 2.87302 9.75684 5.29177 4.25332 245.417 109.059 107.033 104.264 100.896 31.3927 28.9878 24.6752 22.1516 136.358 69.5037 19.3741 18.1055 65.4008 4.1029 +internal_count=24290853 97872 82991 14881 10366 7738 3519 4219 2628 1913 3763 81078 939 3188 1728 1389 80139 35614 34953 34048 32950 10252 9470 8062 7238 44525 22698 6330 5915 21358 1340 +is_linear=0 +shrinkage=0.1 + + +Tree=140 +num_leaves=32 +num_cat=0 +split_feature=11 13 4 7 14 21 21 21 21 21 5 21 16 4 16 13 21 11 4 21 14 4 11 16 7 21 13 14 2 13 13 +split_gain=4.0324 3.67736 2.93217 2.74234 9.11409 8.9084 20.9318 10.6463 17.1811 11.7463 8.38422 7.87967 25.3644 40.5603 30.0755 23.9598 21.4918 10.7168 9.33789 9.037 10.43 6.83257 5.75321 5.74106 5.50813 5.45694 6.5805 8.78097 5.26359 14.8232 9.75134 +threshold=55868.000000000007 234.50000000000003 1.0000000180025095e-35 1028.0000000000002 3938.0000000000005 2615.0000000000005 547.00000000000011 492.50000000000006 406.50000000000006 403.00000000000006 26296.000000000004 218.50000000000003 24.500000000000004 8782.5000000000018 267.50000000000006 11.500000000000002 252.50000000000003 28.500000000000004 8944.0000000000018 212.50000000000003 1.0000000180025095e-35 9369.5000000000018 387.50000000000006 96.500000000000014 1156.5000000000002 244.50000000000003 5.5000000000000009 1.0000000180025095e-35 109.50000000000001 58.500000000000007 1164.5000000000002 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=3 -2 -3 -1 5 6 7 8 9 11 -9 19 21 25 15 17 -16 18 -15 28 -21 -13 -12 -17 -6 26 -14 -28 -5 -30 -31 +right_child=1 2 -4 4 24 -7 -8 10 -10 -11 22 12 13 14 16 23 -18 -19 -20 20 -22 -23 -24 -25 -26 -27 27 -29 29 30 -32 +leaf_value=-0.00013060912301378374 -0.13884407262904216 -0.092085590368111314 0.025377349577986732 1.2725301006003422e-05 -0.18559761008718997 0.13852201049815829 -0.19502411228742458 -0.19552303908004248 -0.19454843583038031 0.16728680161768827 0.17984825080706165 0.13092676246911025 -0.19856463476452924 0.20161445723530391 -0.19789609133288547 0.15160474784013139 0.13297067053549569 -0.19539943575378274 -0.1969939530754187 0.14821151164634941 -0.19516394126670464 -0.18299346643101672 -0.2007966797332281 -0.19707194024827707 0.14191171716084741 -0.19631678686254522 0.18436670605788424 -0.19521319513102103 0.0014732475124054865 0.038079279894649466 -0.094936136420803535 +leaf_weight=72173.280398486357 1.4299016894656222 2.1744402258482287 93.729454160551541 1324.6291657815018 0.55699595413170655 4.8494208658812559 5.3335206384072071 0.75167940370738406 4.3975726698990902 4.3599340850487343 6.4219045878853649 10.169105000444684 2.2807916211895671 1.4217478036880522 2.2242184900678721 4.0566872081253669 16.729968984378502 11.028136425535193 1.0018151807598767 7.4368954142555577 1.0040249410085378 0.7440703085158028 0.42324222717434157 0.53443300630897272 6.5788691150955856 16.019561034860089 1.5501121815759691 1.0043012453243134 483.20088026416488 188.45365964937082 5.6774145842937278 +leaf_count=23569098 464 712 30608 432569 182 1581 1744 246 1435 1424 2096 3318 744 463 726 1325 5465 3608 324 2429 327 243 139 174 2147 5230 507 330 157796 61544 1855 +internal_value=0 0.0203407 0.0227141 -2.66525e-05 0.00352444 0.0031421 0.0028296 0.00333318 0.00290324 0.00331998 0.1215 0.00297615 -0.0303103 -0.0566885 0.00617683 -0.0862341 0.0941444 -0.153557 0.0368438 0.0041147 0.107368 0.109523 0.156313 0.111017 0.116348 -0.168214 -0.0751051 0.0351299 0.00367935 0.01085 0.0341892 +internal_weight=0 97.3338 95.9039 74286.1 2112.84 2105.7 2100.85 2095.52 2087.92 2083.53 7.59683 2079.17 68.7649 57.8518 36.997 18.0428 18.9542 13.4517 2.42356 2010.4 8.44092 10.9132 6.84515 4.59112 7.13587 20.8548 4.83521 2.55441 2001.96 677.332 194.131 +internal_count=24290853 31784 31320 24259069 689971 687642 686061 684317 681836 680401 2481 678977 22457 18896 12085 5894 6191 4395 787 656520 2756 3561 2235 1499 2329 6811 1581 837 653764 221195 63399 +is_linear=0 +shrinkage=0.1 + + +Tree=141 +num_leaves=32 +num_cat=0 +split_feature=9 2 2 3 10 6 9 4 4 3 3 2 15 4 4 2 21 14 3 3 2 15 18 3 4 15 2 2 2 15 9 +split_gain=7.33681 6.94359 12.0989 10.8285 16.3617 8.09746 7.69381 6.02038 5.67952 5.08281 11.2882 8.75679 13.2032 3.98324 9.48395 5.8528 4.80004 3.74252 11.9832 11.4527 8.16483 4.45923 4.1666 3.83755 4.84317 4.2872 5.60105 4.84226 3.62981 4.19372 3.93844 +threshold=40074.000000000007 40.500000000000007 33.500000000000007 104.50000000000001 53880.500000000007 10633.500000000002 40213.500000000007 101.50000000000001 163.50000000000003 82.500000000000014 70.500000000000014 18.500000000000004 2111.5000000000005 50.500000000000007 42.500000000000007 30.500000000000004 24.500000000000004 1.0000000180025095e-35 41.500000000000007 47.500000000000007 1.0000000180025095e-35 93.500000000000014 1.5000000000000002 3.5000000000000004 19.500000000000004 269.50000000000006 10.500000000000002 1.5000000000000002 4.5000000000000009 59.500000000000007 46899.500000000007 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 3 9 28 -4 -3 -6 -9 10 13 12 -11 14 15 16 17 19 -19 20 -2 -20 -21 -22 -25 27 -27 -26 29 30 -5 +right_child=1 6 5 4 7 -7 -8 8 -10 11 -12 -13 -14 -15 -16 -17 -18 18 21 22 23 -23 -24 24 25 26 -28 -29 -30 -31 -32 +leaf_value=-6.2929737354495055e-05 -0.19493159093741014 0.14541351875526443 -0.16468297992477571 -0.17971891861600756 -0.14627939598266709 0.12869629716116299 0.026664320483207261 0.081166012662337153 -0.14521649199199757 -0.16304929428836773 -0.16404382177535065 0.14707195299701251 0.11261915924842072 0.098512578102223261 -0.16717679325933063 -0.15945493862930282 -0.18245808952532497 0.025643010691262104 0.019601287095477961 0.15330374635521055 0.16106006292053351 -0.18318300703469761 -0.19146276866702786 -0.16965368165288791 0.080706552375592835 -0.18368545917309942 0.16698916643594447 -0.17391944372870505 -0.17620799865413569 -0.16784961876269822 0.11750320914011624 +leaf_weight=74073.231550324213 1.9132823449326704 5.6778570495080194 6.5017015157500273 0.62069961661473128 1.7292061259504397 1.0999435235280541 139.67707934962527 9.4987717859912646 1.2545921554556105 3.0568225106690061 3.5266826818697146 7.8335575512610376 4.025331043638289 5.7866872020531437 2.8325848735403261 1.8105896537890647 1.1326487397309382 62.924578329606447 1.486458858475088 9.4705056634265894 1.8411534670740346 4.0092261335812509 0.36400826647877771 1.3769193355692548 1.4456450380384924 0.53923672181554172 2.9320607497356832 1.5451269042678175 7.9034611716051586 1.9404461298836393 1.5823855269700287 +leaf_count=24193734 625 1855 2123 202 567 359 45625 3103 409 998 1151 2559 1314 1889 925 591 370 20553 485 3092 599 1310 119 450 473 175 959 505 2581 637 516 +internal_value=0 0.0156768 0.000732302 0.00720626 -0.0533287 -0.122232 0.0313029 0.0269049 0.054754 0.0195956 0.0118316 0.0742179 -0.00636577 0.0179479 0.0130725 0.0185749 0.0221178 0.0246967 0.0132752 0.0611662 -0.00616767 -0.128334 0.140543 0.0311416 0.00062743 0.0369104 0.112515 -0.0508413 -0.136463 -0.0606534 0.0337635 +internal_weight=0 297.339 151.984 144.383 24.5296 7.60165 145.355 12.4826 10.7534 119.853 104.937 14.9157 7.08215 101.411 95.624 92.7914 90.9809 89.8482 68.4203 21.4279 11.5934 5.49568 9.83451 9.68014 7.83899 6.46207 3.4713 2.99077 12.047 4.14353 2.20309 +internal_count=24290853 97119 49639 47157 8015 2482 47480 4079 3512 39142 34271 4871 2312 33120 31231 30306 29715 29345 22348 6997 3786 1795 3211 3161 2562 2112 1134 978 3936 1355 718 +is_linear=0 +shrinkage=0.1 + + +Tree=142 +num_leaves=32 +num_cat=0 +split_feature=9 2 2 7 1 3 3 12 12 3 15 19 2 1 3 15 2 14 18 3 20 9 15 2 15 14 2 2 15 6 14 +split_gain=7.28813 3.55864 10.7835 10.3806 7.30724 4.49095 10.5156 6.08747 18.7985 5.91411 6.60186 4.95194 7.05732 4.85317 4.70873 11.7194 5.11326 4.73659 8.88355 4.41 4.3683 4.65438 4.28832 6.85168 6.77777 4.7436 5.01548 5.01411 4.56595 4.1661 4.12479 +threshold=40074.000000000007 153.50000000000003 134.50000000000003 39003.500000000007 213.50000000000003 104.50000000000001 132.50000000000003 238.50000000000003 1234.0000000000002 100.50000000000001 251.50000000000003 96.500000000000014 14.500000000000002 37.500000000000007 60.500000000000007 452.00000000000006 28.500000000000004 1.0000000180025095e-35 1.5000000000000002 169.00000000000003 8853.5000000000018 67223.500000000015 168.50000000000003 1.0000000180025095e-35 377.00000000000006 3.5000000000000004 3.5000000000000004 22.500000000000004 452.00000000000006 1.0000000180025095e-35 7.5000000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 5 -3 -5 9 -7 19 -9 11 -11 14 -13 29 17 20 -17 18 -2 -8 21 -16 -19 24 -24 -25 -27 28 -28 -14 -10 +right_child=1 3 -4 4 -6 6 7 8 30 10 -12 12 13 -15 15 16 -18 22 -20 -21 -22 -23 23 25 -26 26 27 -29 -30 -31 -32 +leaf_value=-6.204781589785798e-05 0.07445813441245569 0.084536490728462907 -0.16719575149238669 0.093312691626305511 -0.11009068774674352 -0.15190624459954452 0.13492485953712549 -0.13787369083904455 0.065315435624588733 -0.16953126845037644 0.13730625196502677 -0.17664727324659965 -0.17321349425186772 -0.16965897179750358 -0.16138606632976743 0.085927540006062841 -0.15620867848875525 0.018034480205275979 -0.17503169442623667 -7.3859832292178657e-05 0.11736336415784926 -0.015749056653642988 -0.15390139932189395 -0.17554524273010369 0.1318129770968349 -0.18006251566580989 0.067425619686836219 -0.16092925801871072 -0.16504965371860633 0.15567823213297405 -0.13236357608213764 +leaf_weight=74074.80606718552 23.208187534182802 21.806695840205069 3.3140217478503464 2.5586780026787874 5.702425648283679 5.0502790309255934 4.9991759583936055 8.9426718042814191 12.506054289638994 0.78070023935288091 6.8869943602476269 0.90339626534841855 0.40665452927350809 0.54829044773941849 7.2730181192746386 7.304704047390258 0.99036624818108965 140.60881106861052 1.5207020732341323 4.6898937789956108 0.85752513259649354 3.1426005256362259 1.0638571812305624 3.2145999586209646 3.7815481824800372 1.9794133189134289 5.888516004662959 1.631563530070707 0.98636212805286039 7.2813160507939756 1.1528611951216579 +leaf_count=24195807 7579 7120 1082 835 1865 1649 1634 2921 4084 254 2250 296 134 176 2376 2384 326 45931 497 1533 280 1027 347 1049 1236 646 1925 533 322 2378 377 +internal_value=0 0.0157952 0.012041 0.0483718 -0.0470914 0.0143469 -0.017721 0.00326562 -0.0251627 0.0197834 0.106065 0.0166714 0.0886794 0.117782 0.0134366 -0.0331989 0.0570184 0.0183994 0.0591158 0.0695801 -0.0995831 -0.117444 0.012073 -0.0331247 0.0690815 -0.0692717 -0.0366919 -0.00333021 0.0340716 0.138282 0.0486306 +internal_weight=0 290.982 260.914 30.0678 8.2611 257.6 37.3409 32.2907 22.6016 220.259 7.66769 212.591 9.13966 8.23626 203.452 19.5682 8.29507 183.884 24.7289 9.68907 11.2731 10.4156 159.155 18.5459 4.84541 13.7005 10.4859 8.50644 6.87488 7.68797 13.6589 +internal_count=24290853 95046 85226 9820 2700 84144 12198 10549 7382 71946 2504 69442 2984 2688 66458 6393 2710 60065 8076 3167 3683 3403 51989 6058 1583 4475 3426 2780 2247 2512 4461 +is_linear=0 +shrinkage=0.1 + + +Tree=143 +num_leaves=32 +num_cat=0 +split_feature=9 8 10 8 17 4 8 15 15 4 14 18 2 20 17 8 15 2 8 18 8 1 4 4 17 18 9 4 8 13 2 +split_gain=7.45284 5.20756 9.7899 6.82001 7.59707 10.135 4.74114 13.9617 22.4942 16.7443 14.5691 14.2298 12.2561 15.7048 10.9373 10.5721 11.3782 14.9543 13.8913 11.9222 11.9123 11.8483 17.3903 11.3172 10.8425 12.7827 11.0361 10.3527 12.8731 10.25 9.67751 +threshold=40074.000000000007 5431.5000000000009 50358.000000000007 7939.0000000000009 4.5000000000000009 28.500000000000004 9334.0000000000018 89.500000000000014 125.50000000000001 34.500000000000007 1.0000000180025095e-35 1.0000000180025095e-35 2.5000000000000004 6.5000000000000009 1.5000000000000002 13055.000000000002 1329.0000000000002 70.500000000000014 10972.000000000002 2.5000000000000004 10584.000000000002 3.5000000000000004 112.50000000000001 54.500000000000007 5.5000000000000009 5.5000000000000009 12972.000000000002 37.500000000000007 9482.0000000000018 294.50000000000006 164.50000000000003 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=6 2 -2 4 -3 -6 -1 10 -9 -10 -8 14 13 -13 -11 16 18 20 21 -20 -18 22 23 -14 26 -26 -25 -16 -29 -27 -12 +right_child=1 3 -4 -5 5 -7 7 8 9 11 30 12 15 -15 27 -17 17 -19 19 -21 -22 -23 -24 24 25 29 -28 28 -30 -31 -32 +leaf_value=1.100710552793916e-05 -0.17532032914566856 0.086267589164194289 -0.0019633541513098553 0.014196324148635378 0.09866864422018054 -0.11939028293546175 0.055784436497817128 -0.1914259680859168 -0.15788508655427247 -0.18968687166585133 -0.011816548886493618 0.18365363056409897 -0.18658902737516225 -0.19229389088833343 0.16454155649335578 -0.19207303746610332 0.11261980194014121 -0.19815699309552665 -0.17595893967817389 0.12612228204882245 -0.195357196590765 0.04571889863224822 -0.19222610335248991 0.10371024415346548 -0.18996246033401243 -0.19144054156291232 -0.18775892449448706 -0.19451343033258292 0.017733223165727043 0.12752381309593153 0.032297675281858507 +leaf_weight=73460.362196280548 3.6794316734885788 27.885218275070656 28.413139476091601 217.28789159320877 3.8777354499325138 4.7330057300860062 43.807087504770607 8.4115852164104563 8.8869518347782996 2.6105800969526163 325.74533491674811 1.2628182284533966 4.5946904222946641 9.2525187175488082 4.5671400418505064 3.8972459796641479 13.485497487708928 2.1510413391515604 1.6033812060486505 7.0560103266034275 1.3848871355876315 7.5653034557471974 11.725761748792136 8.6807521341834235 5.4735400103963894 1.4307579523883758 1.5276736933737982 2.9710869614500544 74.817277426365763 3.4055268869269639 58.6879645063309 +leaf_count=23996571 1203 9110 9284 70973 1265 1549 14311 2748 2909 852 106409 412 1503 3018 1495 1272 4405 703 524 2304 453 2472 3831 2837 1787 467 499 969 24436 1113 19169 +internal_value=0 0.0161152 -0.0218388 0.0209147 0.0609143 -0.0211903 -6.21938e-05 -0.00880584 -0.0316213 -0.0240844 0.00114434 -0.0170677 -0.0461255 -0.147145 0.0118297 -0.0317671 -0.0228529 0.0482887 -0.0456734 0.0701886 0.0839377 -0.0682681 -0.0916768 -0.0447282 -0.012961 -0.085296 0.0600924 0.0182176 0.00962657 0.033162 -0.00508203 +internal_weight=0 285.876 32.0926 253.784 36.496 8.61074 74075.4 615.002 186.762 178.35 428.24 169.463 84.4974 10.5153 84.9661 73.9821 70.0848 17.0214 53.0634 8.65939 14.8704 44.404 36.8387 25.1129 20.5183 10.3098 10.2084 82.3555 77.7884 4.83628 384.433 +internal_count=24290853 93384 10487 82897 11924 2814 24197469 200898 61009 58261 139889 55352 27600 3430 27752 24170 22898 5561 17337 2828 4858 14509 12037 8206 6703 3367 3336 26900 25405 1580 125578 +is_linear=0 +shrinkage=0.1 + + +Tree=144 +num_leaves=32 +num_cat=0 +split_feature=9 9 2 0 9 2 6 2 6 19 18 11 2 0 13 6 6 16 13 6 2 13 2 16 2 2 3 5 4 16 13 +split_gain=6.02862 3.2651 8.72416 10.0718 5.68618 4.12331 3.91783 8.02648 6.19356 9.48235 6.82097 7.39728 5.93319 5.22305 13.0652 8.92438 8.07206 6.26944 5.13951 12.0182 9.76199 4.77453 4.7099 4.66026 6.3146 5.91776 8.1024 5.88952 5.82413 6.12419 5.54411 +threshold=40074.000000000007 42173.000000000007 19.500000000000004 39.500000000000007 40578.000000000007 9.5000000000000018 7984.5000000000009 104.50000000000001 8727.0000000000018 30.500000000000004 1061.0000000000002 318.50000000000006 49.500000000000007 4.5000000000000009 105.50000000000001 6673.5000000000009 1750.5000000000002 1.0000000180025095e-35 113.50000000000001 2254.5000000000005 14.500000000000002 1074.0000000000002 367.50000000000006 18.500000000000004 1.5000000000000002 42.500000000000007 25.500000000000004 1.0000000180025095e-35 87.500000000000014 8.5000000000000018 7875.5000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 4 -4 -2 -6 13 8 9 -8 11 -10 -11 15 16 18 -15 -18 20 21 -3 -20 -9 24 -23 26 -26 28 -28 -30 -27 +right_child=1 6 3 -5 5 -7 7 22 10 12 -12 -13 -14 14 -16 -17 17 -19 19 -21 -22 23 -24 -25 25 30 27 -29 29 -31 -32 +leaf_value=-5.5982852275970657e-05 -0.15957725113134982 -0.18043926904295721 0.07480934209097613 -0.17385943049978794 0.045320663110485278 -0.15582696603791124 -0.17144718608000517 -0.1513072422546462 -0.18749966729603576 0.10167830561122655 -0.17638292863974248 0.049512784028626168 -0.15607429300640729 0.10177275610304831 0.05811251243589366 -0.16649827246820109 -0.15997241092318351 -0.0031851782244829668 -0.031478383532105796 -0.16988016067443429 0.10272449570644455 0.064625378537077005 0.06107619873988368 0.091841766512146039 0.043057413468127137 0.05228189183192522 -0.12126091740245047 0.046107439888994656 0.040433445039728233 -0.1460139185138907 -0.14862515464782242 +leaf_weight=74067.276547879796 2.9887228839215805 1.3755659626331169 33.166405590309296 1.7129064229084168 5.3168893703259528 1.260749058332294 4.5769556223531236 5.9082792871340635 1.3897744524874784 3.3252217806875706 1.5867391268257041 25.089011847710935 1.2209861413575707 2.6935840733349314 57.78245593556494 3.0052478100988074 5.9232100199442339 4.4789051485713571 24.302299691073131 3.9590921174531095 10.594015670707448 17.121757554938085 1.2683156856219282 7.9250660475809118 11.161926050728651 19.466656900243837 13.330091325740801 4.4525990365509633 5.9196120018605125 2.5081622474244796 1.4778134835651133 +leaf_count=24197320 975 450 10837 561 1736 410 1495 1929 453 1088 519 8199 398 879 18882 984 1934 1463 7939 1292 3461 5594 413 2589 3647 6360 4357 1454 1932 820 483 +internal_value=0 0.0144833 0.0393947 0.0625973 -0.0452028 0.0067663 0.00990516 -0.0169479 0.00173717 -0.0698407 0.0250047 0.0370729 0.0324531 0.0159379 0.0376731 0.0037693 -0.0525121 -0.0924635 0.00790942 0.00123183 0.0701828 0.00752396 -0.113773 0.018894 0.0112306 -0.00444606 -0.0282934 -0.0586788 -0.0801225 -0.0150545 0.0381062 +internal_weight=0 286.289 44.4457 34.8793 9.56636 6.57764 241.843 44.3653 37.1887 9.12316 28.0655 26.4788 4.54621 197.478 70.8782 126.6 13.0957 10.4021 123.595 111.625 11.9696 107.666 7.17659 83.3637 75.4386 58.3169 37.3724 26.2105 21.7579 8.42777 20.9445 +internal_count=24290853 93533 14519 11398 3121 2146 79014 14494 12152 2981 9171 8652 1486 64520 23158 41362 4276 3397 40378 36467 3911 35175 2342 27236 24647 19053 12210 8563 7109 2752 6843 +is_linear=0 +shrinkage=0.1 + + +Tree=145 +num_leaves=32 +num_cat=0 +split_feature=8 21 2 2 1 1 11 6 2 1 14 21 18 0 2 0 18 1 15 11 18 21 17 12 17 12 0 11 6 16 15 +split_gain=4.4435 6.68311 12.1299 4.38317 13.3356 7.1816 8.75583 7.36348 11.2053 12.5935 12.0799 9.19206 5.87756 5.66003 17.1502 7.14729 6.74702 6.48095 6.52908 9.87802 8.3691 8.33841 7.62965 6.33931 9.19467 5.94136 5.60017 5.06454 4.52915 4.32147 6.75406 +threshold=33372.000000000007 2.5000000000000004 15.500000000000002 367.50000000000006 301.50000000000006 204.50000000000003 417.00000000000006 9534.5000000000018 50.500000000000007 68.500000000000014 5.5000000000000009 1.0000000180025095e-35 319.00000000000006 50.500000000000007 95.500000000000014 44.500000000000007 1386.0000000000002 74.500000000000014 3.5000000000000004 225.50000000000003 25.500000000000004 1.0000000180025095e-35 1.0000000180025095e-35 1.0000000180025095e-35 1.0000000180025095e-35 7.5000000000000009 41.500000000000007 212.00000000000003 11196.500000000002 1.5000000000000002 5.5000000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 3 -3 4 5 7 -7 13 9 28 11 -10 -8 14 15 17 -16 25 19 -19 -20 -21 -22 24 -15 27 -5 -2 -9 30 -29 +right_child=1 2 -4 26 -6 6 12 8 10 -11 -12 -13 -14 23 16 -17 -18 18 20 21 22 -23 -24 -25 -26 -27 -28 29 -30 -31 -32 +leaf_value=-3.9655153309034845e-05 -0.1623921382193021 0.10813078432111951 -0.1601149742971178 0.14045218400907739 -0.17658137568581936 -0.17028542980612169 0.10222842022410442 -0.16930967108438094 -0.15540010258839188 0.15841312547698147 -0.18444051827309355 0.087969624683132353 -0.15285307521183422 -0.15503288731438356 -0.17156149347198993 -0.16952749306174278 0.050146789846681299 0.11252398847384387 -0.1729979993037826 -0.1618427299746043 0.10387209732300672 0.052519248413804821 -0.19672903611409145 0.083840278009811753 0.10859302618142737 -0.14995613992399298 -0.17188014328304224 0.012473910289461883 0.085723729106850388 0.074123994579742739 -0.15428214846506266 +leaf_weight=74152.736331940614 1.6324980473145831 2.4616636144928625 5.3481419128074776 5.068333846051245 3.5209623676055335 1.3761600867728727 19.393915495384139 2.3842490415554516 2.4118696001823974 4.7102307444438338 7.2746405618963763 4.3529084586189128 0.94744415703462337 2.9555999756994451 6.7050024609488883 1.9006589605705801 1.7259382462652864 4.4740864395862427 1.2244216863764474 4.4831039054843131 26.381751419045035 3.0486178999999538 0.87226932751946051 22.956891490641283 2.3951111846836284 2.3979584439075543 0.64740208256989706 38.084486006613588 0.98361600749194611 10.381197902810525 2.5943071332003447 +leaf_count=24227111 534 800 1750 1657 1150 449 6336 779 788 1539 2378 1422 310 966 2192 620 563 1459 401 1465 8619 997 285 7503 783 782 211 12443 321 3392 848 +internal_value=0 0.0150719 -0.0755635 0.0188514 0.0161371 0.0199482 0.0738321 0.0124626 -0.0409998 0.0528349 -0.0949911 0.00120018 0.0903474 0.0212729 0.0106559 0.0224908 -0.126175 0.0263094 0.0566862 -0.00516455 0.082761 -0.0750754 0.0942513 0.0609938 -0.0370274 0.00398635 0.105075 0.010992 -0.0948248 0.0165355 0.00183897 +internal_weight=0 195.095 7.80981 187.286 181.57 178.049 21.7175 156.331 22.1175 8.0781 14.0394 6.76478 20.3414 134.214 105.906 97.4754 8.43094 95.5747 40.4843 12.0058 28.4784 7.53172 27.254 28.3076 5.35071 55.0904 5.71574 52.6925 3.36787 51.06 40.6788 +internal_count=24290853 63742 2550 61192 59324 58174 7095 51079 7227 2639 4588 2210 6646 43852 34600 31845 2755 31225 13226 3921 9305 2462 8904 9252 1749 17999 1868 17217 1100 16683 13291 +is_linear=0 +shrinkage=0.1 + + +Tree=146 +num_leaves=32 +num_cat=0 +split_feature=6 21 12 1 0 10 19 10 14 18 20 14 6 10 20 0 3 10 6 0 0 0 14 20 18 14 14 6 10 14 0 +split_gain=3.99209 9.73094 13.1569 7.0059 12.7528 11.0417 7.62955 22.1957 12.5018 19.7765 10.1216 8.06889 12.8394 7.32015 6.85455 6.67296 6.0286 14.6747 5.9713 13.8841 32.6085 19.2708 19.0819 23.5596 14.6544 13.6319 14.4472 13.5917 12.601 11.357 18.9057 +threshold=6921.0000000000009 392.50000000000006 25.500000000000004 301.50000000000006 1.5000000000000002 1363.0000000000002 8.5000000000000018 165.50000000000003 10.500000000000002 52.500000000000007 1.0000000180025095e-35 15.500000000000002 11196.500000000002 6.5000000000000009 1.5000000000000002 149.50000000000003 1.5000000000000002 428.50000000000006 7781.5000000000009 40.500000000000007 12.500000000000002 64.500000000000014 20.500000000000004 4.5000000000000009 1061.0000000000002 12.500000000000002 4.5000000000000009 7504.0000000000009 4.5000000000000009 16.500000000000004 3.5000000000000004 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 3 15 16 -5 6 7 8 9 -6 -11 12 14 -10 -8 -3 18 -18 19 20 28 -21 23 24 -22 26 -23 -25 -2 -30 -31 +right_child=1 2 -4 4 5 -7 11 -9 13 10 -12 -13 -14 -15 -16 -17 17 -19 -20 21 22 25 -24 27 -26 -27 -28 -29 29 30 -32 +leaf_value=-9.7553870130866779e-05 -0.19167060301186412 0.10123998862493465 -0.20315417038971806 -0.19789195594990264 -0.19925995712382016 -0.18913360845741892 0.11397481319835559 -0.15738562505172396 -0.20501797317731515 -0.19793099916376711 0.15270257514038177 -0.057347750055575464 -0.18766484379042764 0.092244529688707555 -0.18707158408289423 -0.089841569667425195 -0.18868722298636151 0.048610534931663223 -6.8796590137311776e-05 0.11769181556409308 -0.19459183210977479 -0.19112314426815169 0.06532629195938798 -0.19372208337986896 0.070336011302472384 -0.19288887673135763 0.072042509082933665 0.1600073819244866 0.053760090171600439 -0.19648517308179847 0.035347060542213114 +leaf_weight=73052.779008426602 2.4457204148638985 22.029756730888046 1.6915624663233746 2.3431817160017081 4.6702536699012835 2.0730795372801358 31.414793261064915 6.320773050945717 0.85674276668578642 1.0240494571626184 4.1988725567935035 3.981344976622494 1.5529400934465218 25.040242396178655 0.77499004083801892 1.9929350395686913 5.3637577121262421 5.0687652332853759 994.74901817063801 28.650907779083354 19.314686674042612 2.5070507245836806 7.0890735080465666 1.4840179090388108 2.3409824296832076 3.4428268605843177 12.422507211216724 4.0527404649183154 79.571437809674535 5.1431745516310938 11.129301642329663 +leaf_count=23867830 787 7194 552 770 1525 676 10261 2065 277 335 1371 1301 507 8185 254 651 1750 1657 325016 9358 6311 820 2316 485 764 1126 4063 1323 26004 1681 3638 +internal_value=0 0.00550419 0.0664066 0.00427013 0.032133 0.0387135 0.04463 0.0153706 0.0458805 -0.0497421 0.0839544 0.0772919 0.0931781 0.0824103 0.106727 0.0853878 0.00228875 -0.0733933 0.00296109 0.0197432 0.00318384 0.0664285 -0.080794 -0.118888 -0.165953 -0.0135143 0.0278503 0.0651972 0.0324737 0.0381933 -0.0379272 +internal_weight=0 1294.74 25.7143 1269.03 84.2513 81.9081 79.835 42.1109 35.7902 9.89318 5.22292 37.7241 33.7427 25.897 32.1898 24.0227 1184.78 10.4325 1174.34 179.594 132.571 47.0233 34.2815 27.1924 21.6557 18.3724 14.9296 5.53676 98.2896 95.8439 16.2725 +internal_count=24290853 423023 8397 414626 27527 26757 26081 13758 11693 3231 1706 12323 11022 8462 10515 7845 387099 3407 383692 58676 43309 15367 11199 8883 7075 6009 4883 1808 32110 31323 5319 +is_linear=0 +shrinkage=0.1 + + +Tree=147 +num_leaves=32 +num_cat=0 +split_feature=9 6 1 6 21 4 6 11 21 11 20 1 10 6 14 21 11 6 11 1 0 14 21 10 1 0 20 17 0 6 3 +split_gain=7.19397 4.87434 13.2465 12.0657 14.8924 14.5159 15.2544 12.4179 11.3365 14.1441 13.7719 11.5351 13.5295 11.1602 11.746 10.9168 10.813 10.5012 27.0166 20.551 14.5175 16.7633 16.2845 14.3288 12.8681 16.8496 15.3982 11.796 11.3734 10.0056 10.9172 +threshold=40074.000000000007 6921.0000000000009 301.50000000000006 7781.5000000000009 208.50000000000003 5044.0000000000009 17598.500000000004 534.50000000000011 171.50000000000003 181.50000000000003 23.500000000000004 255.50000000000003 893.00000000000011 7029.5000000000009 6.5000000000000009 547.00000000000011 27.500000000000004 5539.0000000000009 55.500000000000007 162.50000000000003 14.500000000000002 12.500000000000002 4.5000000000000009 775.50000000000011 36.500000000000007 6.5000000000000009 45.500000000000007 68.500000000000014 1.0000000180025095e-35 7504.0000000000009 1.0000000180025095e-35 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 17 3 7 8 -6 -7 -3 11 -10 -11 -5 -13 -9 -15 16 -8 -1 27 20 21 24 -23 -21 26 -26 -20 28 -19 30 -16 +right_child=-2 2 -4 4 5 6 15 13 9 10 -12 12 -14 14 29 -17 -18 18 19 23 -22 22 -24 -25 25 -27 -28 -29 -30 -31 -32 +leaf_value=-0.00011324658664967332 0.016166793872049983 0.043264471457439564 0.047563758442988303 -0.0005976410633368478 0.12243596888164693 -0.10345638247985339 -0.19693976349391853 -0.196346768596496 -0.14500788569102491 0.037363078282089505 -0.13858722456174499 -0.1276055890203209 0.14003468252800014 -0.18433821506294845 0.064323372474098015 -0.18036207172694946 0.051151771059646869 0.047948783096936121 -0.19396011280950265 -0.19459120104826313 0.033253753746326546 -0.18540316120693356 0.18304929154324709 0.068085256347724984 0.091828760207235488 -0.18975848712228066 0.20234843607265371 0.12131735503727646 -0.19314834835328565 -0.19644758139084872 -0.19857897597362387 +leaf_weight=72743.243679659383 274.23051323481195 124.82317878573667 72.923891997954343 835.82565318398701 16.685996017651632 8.9210757125401852 1.8095503263175476 3.5293258796446052 12.606496343621982 15.806337573332714 6.1908392990007988 12.957004879252052 2.2110866991570211 3.2288178643211749 18.060756552149549 2.2488316782109896 60.273157332732808 2.2446693822275909 5.1258142471779164 9.0852243885165063 64.484020096657332 10.513991966145111 1.3540083859115828 2.6920096173416814 14.086874733446168 2.5025296318344763 1.2122640245361251 1.601768432650714 15.247246693455962 1.9434629089664657 1.7308872195426364 +leaf_count=23766102 89595 40781 23825 273070 5453 2915 585 1154 4120 5159 2027 4233 722 1056 5902 736 19697 734 1672 2970 21065 3433 442 880 4604 819 396 522 4984 635 565 +internal_value=0 -5.98515e-05 0.00625691 0.00358849 -0.000510072 0.0382608 0.0190868 0.0296672 -0.00444753 -0.0605554 -0.012156 -0.00216603 -0.0885911 -0.0298998 -0.00636808 0.0360803 0.0439205 -0.000164023 -0.0285438 -0.00965236 0.00516379 -0.0468934 -0.143367 -0.134549 0.00304439 0.049351 -0.118159 -0.138424 -0.162209 0.0200699 0.0413311 +internal_weight=0 74075.2 1201.78 1128.85 975.536 89.9386 73.2526 153.316 885.597 34.6037 21.9972 850.994 15.1681 28.4933 24.9639 64.3315 62.0827 72873.4 130.15 111.057 99.2795 34.7955 11.868 11.7772 22.9275 16.5894 6.33808 19.0937 17.4919 21.7351 19.7916 +internal_count=24290853 24201258 392635 368810 318717 29386 23933 50093 289331 11306 7186 278025 4955 9312 8158 21018 20282 23808623 42521 36281 32431 11366 3875 3850 7491 5423 2068 6240 5718 7102 6467 +is_linear=0 +shrinkage=0.1 + + +Tree=148 +num_leaves=32 +num_cat=0 +split_feature=9 8 20 17 15 3 14 3 18 3 16 14 15 9 3 4 0 12 16 0 0 14 0 20 0 12 0 19 18 7 15 +split_gain=5.68522 3.53942 5.46355 3.38133 3.35387 4.99487 6.72876 3.09644 6.49355 10.4127 7.90296 4.8382 6.23118 5.12771 3.85271 3.4 3.32247 3.15753 6.28908 4.93337 6.18389 10.9622 5.67072 4.32173 3.80143 3.44339 9.72574 4.81449 4.39404 3.57638 3.57777 +threshold=40074.000000000007 4431.5000000000009 122.50000000000001 1.5000000000000002 712.00000000000011 104.50000000000001 1.0000000180025095e-35 60.500000000000007 1.0000000180025095e-35 34.500000000000007 11.500000000000002 1.0000000180025095e-35 51.500000000000007 46899.500000000007 27.500000000000004 52.500000000000007 5.5000000000000009 14.500000000000002 6.5000000000000009 70.500000000000014 32.500000000000007 4.5000000000000009 132.50000000000003 262.50000000000006 145.50000000000003 4.5000000000000009 12.500000000000002 6.5000000000000009 31.500000000000004 38230.000000000007 7.5000000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 -2 -4 7 -6 -7 8 10 17 14 13 -13 -9 -3 -14 -17 18 19 20 25 -22 -21 -19 -24 28 -27 -28 -10 -30 -31 +right_child=1 4 3 -5 5 6 -8 11 9 -11 -12 12 15 -15 -16 16 -18 23 -20 22 21 -23 24 -25 -26 26 27 -29 29 30 -32 +leaf_value=-5.3379889747067173e-05 -0.14787250254370704 -0.17254761112618008 -0.16230227334521194 0.0058305848272814592 0.084448269359616857 0.094891984740126542 -0.15033851459712896 -0.1597655802081083 -0.15194452940133366 -0.15619388809492593 -0.1591752871869381 -0.14198254271813385 -0.14412999644746269 -0.019874464612989039 0.092471028334804597 0.029844771395238386 -0.1555072742850874 0.06505448564694688 -0.16118111064381926 -0.14730132942932228 -0.13983455018870203 0.080739491511814934 0.094522063573783524 -0.17820749638439495 -0.059324433029164569 -0.17137998458582085 -0.15038721262366381 0.11179824159610188 -0.17611498156922006 0.013112384841273332 0.091900357422594323 +leaf_weight=74063.421674239667 3.025365088600668 0.56328126473818585 1.2723392555490161 19.973256440483965 15.349381185951644 2.1054664886323726 2.3878233123105019 3.6815573404892339 1.5698936753906299 3.532665232080034 1.4058583974838246 2.5330601141322395 1.2259664548328135 9.0894137758878042 20.969218576326966 37.344653989188373 0.99279938312247296 18.41009423011565 2.0649455487437072 3.8646617117774422 2.4694337040418786 25.725491083692759 2.7679424487287179 0.76048011344391753 3.8263149040285498 3.7601559541799352 0.90479957044590253 3.1000019722559955 0.94814847497036581 74.064473113190616 6.2499533935042555 +leaf_count=24200688 989 184 415 6526 5015 688 780 1203 515 1156 459 826 396 2971 6850 12211 323 6015 675 1264 806 8406 905 249 1248 1232 294 1010 310 24202 2042 +internal_value=0 0.0143271 -0.0221423 -0.00423842 0.0178443 0.0573027 -0.0354284 0.0144669 0.0209023 0.0135096 0.0705401 -0.00628837 0.0100675 -0.0602016 0.0855383 0.0198025 0.0250448 0.0174934 0.0119589 0.014725 0.020522 0.0614207 -0.0511173 0.0554045 0.0052527 0.00779386 -0.0558807 0.052563 0.0137629 0.0169642 0.0192436 +internal_weight=0 275.939 24.271 21.2456 251.668 19.8427 4.49329 231.825 176.958 154.019 22.9384 54.8675 42.0965 12.771 21.5325 39.5634 38.3375 150.487 131.316 129.251 118.792 28.1949 10.4589 19.1706 6.59426 90.5974 7.76496 4.0048 82.8325 81.2626 80.3144 +internal_count=24290853 90165 7930 6941 82235 6483 1468 75752 57822 50329 7493 17930 13756 4174 7034 12930 12534 49173 42909 42234 38817 9212 3417 6264 2153 29605 2536 1304 27069 26554 26244 +is_linear=0 +shrinkage=0.1 + + +Tree=149 +num_leaves=32 +num_cat=0 +split_feature=9 6 2 0 2 11 6 6 2 0 11 12 2 2 14 0 0 7 14 0 11 9 2 14 6 11 11 7 20 12 20 +split_gain=6.34331 3.78287 11.7256 12.69 9.66683 16.9985 17.8771 23 15.626 14.1205 13.0647 10.2946 9.30726 22.5284 12.1923 10.4696 16.623 10.9794 16.2675 9.66886 12.5676 9.82722 9.2829 8.62498 9.22682 8.14715 7.91442 7.78351 7.76515 7.47677 13.0115 +threshold=40074.000000000007 6311.0000000000009 164.50000000000003 1.5000000000000002 129.50000000000003 212.00000000000003 8144.0000000000009 10119.500000000002 151.50000000000003 121.50000000000001 1874.0000000000002 2.5000000000000004 228.50000000000003 271.50000000000006 20.500000000000004 54.500000000000007 24.500000000000004 701.50000000000011 2.5000000000000004 115.50000000000001 2855.0000000000005 18895.500000000004 137.50000000000003 5.5000000000000009 10119.500000000002 1920.0000000000002 3651.5000000000005 13.500000000000002 5.5000000000000009 21.500000000000004 6.5000000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 -1 4 -4 26 6 22 -8 28 10 -7 -11 15 14 -14 16 17 18 -5 21 -21 -17 -6 24 -15 -12 29 -26 -9 -3 -31 +right_child=-2 2 3 12 5 9 7 8 -10 11 25 -13 13 23 -16 19 -18 -19 -20 20 -22 -23 -24 -25 27 -27 -28 -29 -30 30 -32 +leaf_value=-0.00014947843470349085 0.015332782225168266 0.0028208627488293574 -0.1851301342983018 0.15366255961680339 -0.19756882073614257 -0.18721834380364594 -0.19671798547663258 -0.19741926698002221 0.14553967690584788 -0.19635265194857557 0.18696806388791842 0.1456985022597006 -0.18951020647770356 -0.19869519598656346 0.074914794379870905 0.14630128764097058 -0.12113517465780864 0.07753601737499384 -0.19110565112659206 -0.18485113389742266 0.16930079747479504 -0.18460621582640127 0.12000299686412036 0.050965116867239924 0.18140541575702307 -0.16421128477531713 -0.18668803299170245 -0.18492948831510395 0.17037034230015591 0.079748443148776785 -0.056784389926307001 +leaf_weight=72809.717216895471 268.84410004633537 1027.6981561857974 2.6106705368729299 2.1155717698857179 0.99612960487138735 14.393934393301612 7.0684432142297764 2.8088805635925369 5.6921088830567896 1.2136394446715701 1.6907556671649211 3.1995778813725337 7.6803718783194181 3.0431647117366074 2.2559018353931606 23.054709205403931 6.1636123914504415 33.722686746914405 3.8758778022602201 2.3356455668108529 1.7548638507723806 0.93381580477580328 12.115266055159735 43.079410511651076 1.7209814582020047 1.0842565300408749 2.170816649682819 0.87480649189092208 0.72150559490546573 29.614816387882456 9.1323786427674332 +leaf_count=23792946 87852 335830 855 693 326 4702 2310 918 1859 396 552 1048 2510 996 738 7534 2012 11021 1267 762 573 306 3961 14076 562 354 709 285 236 9680 2984 +internal_value=0 -5.5657e-05 0.00538823 0.0332036 0.00202878 -0.0405117 0.0089584 -0.060995 0.0430278 -0.107907 -0.148916 0.0516341 0.0375018 0.00775377 -0.129476 0.0610948 0.0316597 0.0553734 -0.0693685 0.109188 -0.0329169 0.13342 0.0958757 0.0357422 -0.0805547 0.0497548 0.00405842 0.0579469 -0.122254 0.00444669 0.0475688 +internal_weight=0 74064.5 1254.82 135.222 1119.6 50.9845 29.4023 16.2909 9.2225 21.5822 17.1689 4.41322 132.611 58.6546 9.93627 73.9568 45.8777 39.7141 5.99145 28.079 4.09051 23.9885 13.1114 48.7184 5.63895 2.77501 1068.62 2.59579 3.53039 1066.45 38.7472 +internal_count=24290853 24203001 410055 44190 365865 16662 9610 5323 3013 7052 5608 1444 43335 19167 3248 24168 14993 12981 1960 9175 1335 7840 4287 15919 1843 906 349203 847 1154 348494 12664 +is_linear=0 +shrinkage=0.1 + + +Tree=150 +num_leaves=32 +num_cat=0 +split_feature=9 13 1 9 1 14 16 11 11 11 2 13 17 13 3 14 15 1 5 2 18 7 17 17 13 18 13 0 13 3 1 +split_gain=5.00412 3.00271 2.94758 31.9674 14.9456 12.2548 12.6682 10.7665 15.9812 10.7519 10.5781 10.429 19.5276 11.5027 9.56101 9.38277 13.8991 12.8473 23.7233 8.76834 8.74865 15.2084 8.57463 8.51528 8.28034 22.8458 31.1671 24.6572 15.6697 15.6648 28.7275 +threshold=40074.000000000007 44073.000000000007 153.50000000000003 600.50000000000011 41.500000000000007 241.50000000000003 11872.000000000002 158.50000000000003 49.500000000000007 494.50000000000006 228.50000000000003 294.50000000000006 24.500000000000004 485.00000000000006 1.0000000180025095e-35 362.00000000000006 13.500000000000002 605.00000000000011 8.5000000000000018 12256.000000000002 7898.0000000000009 119.50000000000001 71.500000000000014 64009.500000000007 150.50000000000003 5908.5000000000009 344.50000000000006 84.500000000000014 85.500000000000014 1.5000000000000002 96.500000000000014 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 2 4 5 19 6 -4 8 -8 -9 -5 12 -12 -13 -15 17 -17 20 -19 -1 21 -7 -14 24 28 29 -27 -28 -6 -26 -31 +right_child=-2 -3 3 10 23 15 7 9 -10 -11 11 13 22 14 -16 16 -18 18 -20 -21 -22 -23 -24 -25 25 26 27 -29 -30 30 -32 +leaf_value=-6.5259023536735095e-05 0.013578872476448543 0.0088004203141877919 -0.00049983791089369526 0.053219600846198437 -0.0096079119148434616 -0.19159942558777798 0.18193730963174193 0.11276130982082662 -0.2020646769991529 -0.19796543769298194 -0.18207367959105089 0.087925112188190324 0.093898528899522868 -0.18586431407610715 0.029027745355993052 -0.17940716744141819 0.017256978749861849 -0.15050331494757818 0.071553248090593949 0.075700427914817514 0.11279696634177371 0.19360114198015657 -0.20274446176522576 -0.1935869478409534 -0.021100640955973103 -0.19169032289035717 -0.18889880751347904 0.13411052047752126 0.027430028868915865 0.07593279055025276 -0.1912106313374364 +leaf_weight=67147.564215775565 270.40700687264325 381.40367316352786 4719.8528375434253 158.10375385654334 867.87540764833466 2.5862541071837759 1.6610596454702342 21.40794994332828 3.1184879668289796 1.1746983170160081 7.6560909482650441 15.322265690541828 6.1215764228254557 3.4347553155967017 5.21247710485477 3.958960021758684 38.945904359832639 7.1218822993396342 14.82820252800593 15.278151210542999 33.615112825180404 1.6978537160903213 1.1588922645896671 2.5280928008723995 408.36197786900448 17.230811061610442 3.5502765374258143 7.0684320402797312 131.53962079796474 34.757637758215424 4.5526560107246032 +leaf_count=21940972 88356 124626 1542246 51664 283590 844 541 6996 1021 382 2500 5005 2001 1123 1703 1298 12717 2323 4852 4992 10983 554 380 826 133434 5630 1159 2310 42980 11359 1486 +internal_value=0 -4.95743e-05 -9.53817e-05 0.00223706 -0.000266881 0.00063303 -0.000106515 0.0677399 -0.0686106 0.096598 0.0417249 -0.00498649 -0.0705735 0.0358839 -0.0563292 0.0347998 -0.00088981 0.0603851 -0.000494797 -4.80239e-05 0.0956448 -0.0389389 0.0466794 -0.0102157 -0.00990143 -0.0207638 -0.108644 0.026115 -0.0047331 -0.0152968 0.044994 +internal_weight=0 74068.7 73687.3 5046.98 68640.3 4849.97 4747.22 27.3622 4.77955 22.5826 197.01 38.9061 14.9366 23.9695 8.64723 102.754 42.9049 59.8493 21.9501 67162.8 37.8992 4.28411 7.28047 1477.46 1474.94 475.522 27.8495 10.6187 999.415 447.672 39.3103 +internal_count=24290853 24202497 24077871 1649133 22428738 1584757 1551186 8940 1562 7378 64376 12712 4881 7831 2826 33571 14015 19556 7175 21945964 12381 1398 2381 482774 481948 155378 9099 3469 326570 146279 12845 +is_linear=0 +shrinkage=0.1 + + +Tree=151 +num_leaves=32 +num_cat=0 +split_feature=7 0 1 1 1 13 11 13 14 0 15 0 15 17 11 16 16 16 21 0 7 15 20 18 3 13 18 20 17 1 13 +split_gain=3.81966 9.10734 21.7381 8.99086 12.5394 11.8273 12.9633 13.5076 11.1635 12.3667 11.1444 10.8522 10.1881 23.3703 10.1815 9.46295 12.6683 17.4395 9.4623 9.80615 9.44109 8.83287 8.06696 8.58899 20.6137 15.3113 9.82505 8.97472 7.94166 13.7833 9.95719 +threshold=1028.0000000000002 11494.500000000002 11.500000000000002 153.50000000000003 132.50000000000003 105.50000000000001 1079.5000000000002 439.00000000000006 3.5000000000000004 7282.5000000000009 3.5000000000000004 3.5000000000000004 45.500000000000007 208.50000000000003 1920.0000000000002 175.50000000000003 148.50000000000003 77.500000000000014 327.50000000000006 34.500000000000007 1098.5000000000002 42.500000000000007 6.5000000000000009 22.500000000000004 3161.5000000000005 12.500000000000002 11.500000000000002 19.500000000000004 13.500000000000002 1.0000000180025095e-35 12.500000000000002 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 3 -3 4 12 8 20 -8 9 -6 11 -5 13 18 15 16 17 -13 21 -20 -7 -2 -14 24 25 26 27 -24 29 -21 -30 +right_child=1 2 -4 10 5 6 7 -9 -10 -11 -12 14 22 -15 -16 -17 -18 -19 19 28 -22 -23 23 -25 -26 -27 -28 -29 30 -31 -32 +leaf_value=-0.00012134438200518772 0.0012005328868159261 0.10482922153539252 -0.17020648476853426 -0.13752650610885908 -0.17860281275178369 0.028755716322393734 -0.1769319655224865 0.092809641316136129 0.084866348505202804 0.12883450008962613 0.040689563328755604 0.013909917060719732 0.11259225304003631 -0.19102668522148469 -0.17195358912388939 -0.18953550138303998 0.13984077773709389 -0.12955270324821147 0.12743410333178382 -0.11790881858022169 -0.18489634853772319 -0.17246934588149126 -0.19692217395255379 0.079428553663437637 -0.17847844703935445 -0.19270550946945134 -0.19013118178023625 0.13201599008842915 0.086836917530102933 0.10449965404065349 -0.19725846894573343 +leaf_weight=72227.095197959541 1500.659305335641 4.2936013704165807 8.6898681392194685 5.1602876747492692 4.4499115400831206 2.450531397014859 2.7882035010261443 5.5552439030434471 9.1176915289834124 1.8533409517258403 141.57794450944493 245.96729153642082 13.638794323167529 6.2882432847982264 3.0776908119732971 2.3327373721549511 7.6175495964707798 8.7756712029804458 11.611931398743762 8.1549536412348989 13.259054333611855 2.9342789594084016 0.89374977070838824 15.611336694099007 5.842156450438778 2.3866192140849298 1.2102160342037667 11.530015607364474 15.26697672379669 4.2327060292009255 1.3421533950604487 +leaf_count=23614534 490645 1406 2837 1690 1453 800 913 1816 2977 607 46291 80411 4461 2057 1007 762 2490 2869 3798 2668 4338 959 292 5103 1910 781 397 3769 4988 1385 439 +internal_value=0 0.00423688 -0.0792529 0.00476421 0.00144041 -0.0542391 -0.0980677 0.00266771 0.0141232 -0.0882072 0.0179234 0.00611394 0.00281272 0.00136461 0.00888207 0.0109847 0.0127676 0.00896776 0.00214806 0.0497804 -0.151569 0.000861614 0.0467405 0.0227736 -0.0176815 0.0409556 0.0818578 0.108353 0.0186835 -0.0419147 0.0638797 +internal_weight=0 2068.57 12.9835 2055.59 1641.08 39.474 24.053 8.34345 15.4209 6.30325 414.509 272.931 1601.6 1550.49 267.771 264.693 262.361 254.743 1544.2 40.6087 15.7096 1503.59 51.1129 37.4741 21.8628 16.0206 13.634 12.4238 28.9968 12.3877 16.6091 +internal_count=24290853 676319 4243 672076 536556 12904 7867 2729 5037 2060 135520 89229 523652 506939 87539 86532 85770 83280 504882 13278 5138 491604 16713 12252 7149 5239 4458 4061 9480 4053 5427 +is_linear=0 +shrinkage=0.1 + + +Tree=152 +num_leaves=32 +num_cat=0 +split_feature=6 1 1 20 6 2 1 14 19 10 1 18 13 1 1 18 1 13 14 19 14 1 19 1 16 8 20 1 1 13 20 +split_gain=8.15927 12.055 16.5915 10.9936 10.934 12.7071 22.7221 19.0385 11.0569 12.1205 21.2314 11.2114 11.1983 12.1273 14.7194 11.3651 9.39335 9.23557 8.97462 15.1668 11.5487 9.87957 20.1203 11.9839 9.52123 8.83945 8.39182 8.49108 19.0707 14.4021 12.3259 +threshold=5949.0000000000009 128.50000000000003 124.50000000000001 5.5000000000000009 7781.5000000000009 95.500000000000014 109.50000000000001 3.5000000000000004 115.50000000000001 333.50000000000006 66.500000000000014 12190.500000000002 864.00000000000011 68.500000000000014 90.500000000000014 384.50000000000006 107.50000000000001 1975.0000000000002 9.5000000000000018 32.500000000000007 10.500000000000002 476.00000000000006 4.5000000000000009 159.50000000000003 3.5000000000000004 6.5000000000000009 46.500000000000007 41.500000000000007 50.500000000000007 14.500000000000002 5.5000000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 4 18 25 6 8 -8 9 16 11 -11 13 -12 -15 -16 17 -6 -3 20 -20 22 24 -24 -22 -2 27 -27 29 -29 -30 +right_child=1 3 -4 -5 5 -7 7 -9 -10 10 12 -13 -14 14 15 -17 -18 -19 19 -21 21 -23 23 -25 -26 26 -28 28 30 -31 -32 +leaf_value=-0.00014340400117416845 -0.16703063327861889 0.044878799214480299 -0.17995505906036086 -0.096902231810822248 0.0010989888497045987 0.11154572996939838 0.034736377293885221 -0.18136044655261743 0.096391723254004594 -0.1617070276593614 0.12270323080579354 0.19637905437675726 0.093801109059183249 -0.15592584225867229 0.044441977468122312 -0.18250735699730603 0.11822269372401749 -0.17620850912033137 -0.10688644203149351 -0.18243097671930286 -0.1382750990170544 0.088107626103316836 0.07700960825326543 0.0045928190276288122 0.0074954040500134975 0.057826130137766142 -0.19796030739696016 0.17130091721405194 0.028749372376528005 -0.1819045131682534 -0.18999134192567557 +leaf_weight=72928.915950782801 2.3854695435147724 153.19543678779155 4.9582573536317787 7.2374106258212114 742.7190708006965 9.8140713125758321 6.0074688415043083 12.686871424084528 11.630168940595466 15.668727596377721 4.12425464158878 0.92601999454200346 8.671503277611917 10.004538893233986 11.751501019229179 2.7166571641573674 6.8281703875400117 2.949381122365593 7.2999176601006175 3.965037561254575 11.804221813858023 19.535125051974319 34.512975886464119 67.632505778019549 7.2223081043921402 65.816652002133196 1.6501378328830458 1.3638864951208214 120.36593104433268 7.5176605778397061 2.6324270016048095 +leaf_count=23844371 778 50092 1621 2362 242831 3208 1965 4147 3805 5122 1348 303 2836 3270 3842 888 2232 964 2387 1298 3860 6388 11285 22110 2361 21521 540 442 39352 2463 861 +internal_value=0 0.00765837 0.00254121 0.0249095 0.00340444 -0.00158137 -0.00290831 -0.111917 -0.000417025 -0.0018133 -0.0476383 -0.141725 -0.00574374 -0.0359289 -0.0626623 0.00182815 0.00146682 0.000397675 0.0277984 0.0105805 0.0157512 0.0221137 0.0114743 0.029061 -0.0829419 0.0243256 0.0266155 0.02849 0.0138493 -0.127665 0.0240679 +internal_weight=0 1365.59 1053.19 312.405 1048.23 846.498 836.684 18.6943 817.99 806.36 53.8632 16.5947 37.2685 28.597 24.4727 14.4682 752.497 745.668 305.168 151.972 148.007 140.707 121.172 102.145 19.0265 201.732 199.347 197.697 131.88 8.88155 122.998 +internal_count=24290853 446482 344339 102143 342718 276761 273553 6112 267441 263636 17609 5425 12184 9348 8000 4730 246027 243795 99781 49689 48391 46004 39616 33395 6221 65957 65179 64639 43118 2905 40213 +is_linear=0 +shrinkage=0.1 + + +Tree=153 +num_leaves=32 +num_cat=0 +split_feature=9 13 5 0 19 9 20 5 7 20 19 2 9 19 14 18 0 19 0 13 20 7 0 5 5 20 1 13 14 4 4 +split_gain=4.11694 2.74187 2.76161 7.7057 10.6815 20.9897 16.2013 29.3858 16.6767 24.6031 13.3985 16.9441 12.2855 11.8875 10.8316 10.5297 9.87208 14.2708 9.50303 10.3802 19.5336 17.862 15.2225 13.1584 18.5235 12.8951 12.345 10.6084 10.4679 16.421 9.24516 +threshold=40074.000000000007 44073.000000000007 1576.5000000000002 45.500000000000007 146.00000000000003 40.500000000000007 138.50000000000003 2849.5000000000005 490.50000000000006 114.50000000000001 138.50000000000003 10684.500000000002 307.50000000000006 219.50000000000003 23.500000000000004 244.00000000000003 3.5000000000000004 199.50000000000003 44.500000000000007 6.5000000000000009 83.500000000000014 19894.000000000004 10.500000000000002 30083.500000000004 31527.500000000004 32.500000000000007 30.500000000000004 5.5000000000000009 1.0000000180025095e-35 9716.0000000000018 9541.0000000000018 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 2 -1 4 10 -6 8 -8 14 -10 15 -12 13 -11 -7 18 17 -9 19 21 28 -4 -23 -24 25 -25 -22 30 29 -21 -26 +right_child=-2 -3 3 -5 5 6 7 16 9 12 11 -13 -14 -15 -16 -17 -18 -19 -20 20 26 22 23 24 27 -27 -28 -29 -30 -31 -32 +leaf_value=-0.00018483396441102608 0.012308284520336339 0.0084099164319739669 -0.0033074813118189154 0.01063185698197879 -0.14822776805319657 -0.15606166968172228 -0.18613904226267899 -0.17356701092728571 -0.17292758930409455 -0.17012719401176224 -0.1867007216510701 0.10192038376183404 0.083112533678848313 0.14817084902819744 0.12907487570063195 -0.15113383554926377 0.12473897757293306 0.014931216791035252 -0.19094304553801789 -0.19032101153490921 -0.18959447835616017 -0.19759637196235028 -0.11208914128265333 -0.19970505466715505 -0.0066309219082002942 0.19007133403811208 0.21111330014320703 -0.1915617859076697 0.04654087971140683 0.19708298071517541 -0.1939978350162046 +leaf_weight=71863.407165512239 270.76559154446295 381.57590512165916 525.44431639156392 855.51694165216031 7.3027667265268965 1.393498276942412 7.7294101235456756 4.1834989179624236 5.7262373472913159 4.1008332413621273 8.4421843767049705 2.6796958877239367 25.013497529318556 1.6436075703240929 30.312798266590107 5.0003355043008915 7.6972370319999746 100.54150911059696 2.7525200324598691 4.3281797571107736 5.9407037702621883 6.9044452046509823 18.766378950211219 0.98580341436900099 32.282766553456895 6.1064730279613286 0.88313503796234716 4.1178553723730138 85.129288634983823 1.4643034394830463 2.8673772969050324 +leaf_count=23500268 88542 124781 171827 279767 2383 455 2532 1369 1872 1342 2760 877 8180 538 9914 1634 2516 32879 900 1418 1943 2254 6137 322 10559 1997 289 1347 27833 480 938 +internal_value=0 -4.50307e-05 -8.8848e-05 0.00381873 -0.00258831 0.0181132 0.0245629 0.00246763 0.0634947 0.0173934 -0.00826001 -0.117161 0.0528256 -0.0790553 0.116543 -0.00653708 0.015435 0.00740119 -0.00550118 -0.00476697 0.0254431 -0.00970928 -0.0564084 -0.0414403 -0.012842 0.135894 -0.137735 -0.0397054 0.03769 -0.0923877 -0.0219154 +internal_weight=0 74010.2 73628.7 1765.26 909.741 195.645 188.342 120.152 68.1905 36.4842 714.096 11.1219 30.7579 5.74444 31.7063 702.974 112.422 104.725 697.974 695.221 97.7456 597.475 72.0311 65.1267 46.3603 7.09228 6.82384 39.268 90.9218 5.79248 35.1501 +internal_count=24290853 24202311 24077530 577262 297495 63980 61597 39296 22301 11932 233515 3637 10060 1880 10369 229878 36764 34248 228244 227344 31963 195381 23554 21300 15163 2319 2232 12844 29731 1898 11497 +is_linear=0 +shrinkage=0.1 + + +Tree=154 +num_leaves=32 +num_cat=0 +split_feature=9 9 2 2 8 20 3 8 9 2 16 2 3 8 3 8 20 8 3 2 17 2 2 8 2 2 0 8 8 2 20 +split_gain=3.71839 2.50453 9.61316 5.29107 2.74167 2.65899 4.59179 5.80574 10.1504 5.33544 5.23593 5.50995 5.04381 3.70786 3.70706 3.50635 3.40264 3.14953 5.03585 3.07303 2.75233 2.58854 7.05274 4.39055 2.80564 6.54522 4.92878 2.46077 6.87337 5.2706 3.83337 +threshold=40074.000000000007 40578.000000000007 19.500000000000004 188.00000000000003 5706.5000000000009 3361.0000000000005 70.500000000000014 10369.000000000002 49054.500000000007 52.500000000000007 20.500000000000004 10.500000000000002 94.500000000000014 9803.5000000000018 45.500000000000007 9410.5000000000018 217.50000000000003 10743.500000000002 986.50000000000011 4.5000000000000009 3.5000000000000004 216.50000000000003 164.50000000000003 41482.500000000007 153.50000000000003 134.50000000000003 6.5000000000000009 6235.0000000000009 6310.5000000000009 1.0000000180025095e-35 122.50000000000001 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 -2 4 -4 6 21 8 -8 10 19 -12 -10 14 -7 -14 -17 -11 -19 20 -9 22 24 -23 25 27 -26 29 -29 -3 -31 +right_child=1 5 3 -5 -6 13 7 9 12 17 11 -13 15 -15 -16 16 -18 18 -20 -21 -22 23 -24 -25 26 -27 -28 28 -30 30 -32 +leaf_value=-4.2827587601756513e-05 -0.13821463770864845 -0.16080908334211416 -0.14288576310760878 -0.18154461672772748 0.10440094038000541 -0.13376654167658522 -0.13734004035416625 0.083009648555460336 -0.13843516323332294 0.13454393716871296 -0.1678456536740508 0.12183826281532735 0.032835195505880703 -0.13160634846150365 0.071593229913011355 0.085521565028552848 -0.14526249813420883 -0.10319343732104054 0.13342268533401599 -0.14815134358378643 -0.14079356854764472 0.10808587577332154 -0.14857828105790852 -0.052914783320777065 0.11784979842982878 -0.15058497865968479 -0.14606426654140661 0.14815568004060553 0.014480375280911527 -0.14116359997617836 0.011886329225407239 +leaf_weight=73999.696181516862 2.3305909201735657 2.2952116198139292 0.46471504308283051 0.72630568407476026 12.728781023120971 0.93365108361467974 9.0289986993884686 1.4820127631537636 2.6155243304092464 4.7717976495623571 0.83080838667228851 3.1312911808490753 13.72615570863127 1.0833030317444343 15.022679108427836 0.87566889380104784 2.3623432471067645 1.8689606399275362 1.733954906463623 3.4489918278995901 0.87330099381506432 6.529641579021698 2.7334155253192867 2.2870816294016549 5.078965794760733 2.4721919002186032 0.82219929841812689 3.9521327777765682 143.93407505087089 1.7893304058816308 19.159482966992073 +leaf_count=24202185 766 752 153 237 4157 305 2954 483 857 1561 273 1023 4485 355 4913 288 771 612 567 1131 284 2136 893 748 1665 809 265 1291 47085 586 6263 +internal_value=0 0.0116902 0.0497537 0.0812252 0.0956908 0.00926308 0.0065288 -0.0215624 -0.0496246 0.022692 -0.0275273 0.0610948 -0.00917537 0.0474225 0.059577 0.0107538 -0.0828506 0.0812566 0.0106815 -0.088022 2.81047e-05 0.0134026 0.0108423 0.0663219 0.0132699 0.0109649 0.0810791 0.0132987 0.0180527 -0.0169482 -0.00118634 +internal_weight=0 271.094 16.2504 13.9198 13.1935 254.843 237.804 46.7498 28.6087 18.1411 9.76641 3.9621 19.5797 17.0396 15.9563 16.9642 3.23801 8.37471 3.60292 5.80431 2.35531 191.054 182.237 8.81672 179.504 173.602 5.90117 171.13 147.886 23.244 20.9488 +internal_count=24290853 88668 5313 4547 4310 83355 77782 15289 9355 5934 3194 1296 6401 5573 5218 5544 1059 2740 1179 1898 767 62493 59609 2884 58716 56786 1930 55977 48376 7601 6849 +is_linear=0 +shrinkage=0.1 + + +Tree=155 +num_leaves=32 +num_cat=0 +split_feature=6 15 6 0 10 0 12 19 13 13 16 20 11 6 13 0 1 13 12 11 0 19 6 12 10 1 18 12 0 0 6 +split_gain=8.12328 11.3396 7.15492 17.9563 23.9413 21.0713 17.0606 22.6472 16.1561 15.6202 15.2163 11.8791 12.7428 11.5635 14.5947 16.2881 12.7887 10.2759 9.37339 9.68393 8.9072 10.1068 7.28741 14.6356 11.1677 7.16906 8.8984 6.75562 6.75102 10.7173 7.51455 +threshold=5949.0000000000009 65.500000000000014 3803.5000000000005 1.5000000000000002 9.5000000000000018 126.50000000000001 42.500000000000007 12.500000000000002 690.50000000000011 2.5000000000000004 95.500000000000014 1.5000000000000002 186.50000000000003 5007.5000000000009 374.50000000000006 9.5000000000000018 100.50000000000001 1164.5000000000002 30.500000000000004 41.500000000000007 2.5000000000000004 79.500000000000014 5007.5000000000009 179.50000000000003 16.500000000000004 24.500000000000004 319.00000000000006 55.500000000000007 104.50000000000001 98.500000000000014 4904.0000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=2 27 -1 4 -4 6 7 9 25 -5 11 12 -7 14 15 -9 -17 28 20 -20 21 -15 23 -8 -24 26 -6 -2 29 30 -11 +right_child=1 -3 3 5 8 10 22 13 -10 17 -12 -13 -14 18 -16 16 -18 -19 19 -21 -22 -23 24 -25 -26 -27 -28 -29 -30 -31 -32 +leaf_value=-8.3500389251367491e-05 0.0070941035279087064 0.10483334977445509 -0.1909421890369167 -0.1892932808619574 -0.17700386583137362 0.10732902866130596 -0.1977350682461162 -0.17966917639403276 -0.18648039161806484 0.021309348561626314 -0.19472305509175905 0.14924404895879129 -0.18147990303183867 -0.18961860466429559 -0.17923675538966594 0.097877364025187485 -0.1904878248842756 -0.18204709376682618 0.20071675289561972 -0.19734965976986329 -0.18686731161455239 0.14328913814141075 0.14152095047762683 0.16901222650046319 -0.10048649490956084 0.093431699857254691 0.12416430572055125 -0.17709343237060249 -0.19005824347071035 0.15884273591897477 -0.034221009528352213 +leaf_weight=72659.142739601462 1336.930903207598 11.90890413513989 4.074528186058159 5.5491091895382842 2.2876090004574499 3.4780315428506645 2.742532142845449 3.8575511339295252 2.4392246396746478 37.693538249994162 2.0209290413185945 12.728786063147707 2.7244157707318664 1.7757555756252323 6.3470408669672898 10.532056651703895 1.8009259433601972 3.6774323480203739 1.4934406280517571 1.0344563554972408 22.865659373899689 1.8746836166828869 10.078459231357554 1.8037888305261729 2.3517539262538785 34.792833302461077 1.7177041321992872 1.9943070878798597 2.093736467359121 3.6861639281269154 68.938882883288898 +leaf_count=23765146 437279 3894 1335 1813 749 1138 898 1263 800 12329 660 4163 892 580 2076 3443 589 1204 490 338 7477 614 3297 589 769 11376 561 653 685 1206 22547 +internal_value=0 0.00768384 -0.000142352 -0.0168158 0.040304 -0.0290736 -0.0395585 -0.0489345 0.0631526 -0.0253884 0.0661049 0.0939486 -0.0195299 -0.104461 -0.0507113 -0.000326583 0.0557688 -0.0175538 -0.146169 0.0378215 -0.16371 -0.0186538 0.0561105 -0.0522252 0.095734 0.0788469 -0.047846 0.00681976 -0.0121726 -0.00879646 -0.0145916 +internal_weight=0 1350.83 72915.6 256.461 45.3119 211.149 190.197 173.22 41.2374 121.639 20.9522 18.9312 6.20245 51.5816 22.5376 16.1905 12.333 116.09 29.044 2.5279 26.5161 3.65044 16.9765 4.54632 12.4302 38.7981 4.00531 1338.93 112.412 110.319 106.632 +internal_count=24290853 441826 23849027 83881 14821 69060 62207 56654 13486 39784 6853 6193 2030 16870 7371 5295 4032 37971 9499 828 8671 1194 5553 1487 4066 12686 1310 437932 36767 36082 34876 +is_linear=0 +shrinkage=0.1 + + +Tree=156 +num_leaves=32 +num_cat=0 +split_feature=6 6 15 8 2 14 20 21 15 15 21 21 6 8 20 11 21 11 15 20 16 12 15 6 16 1 21 21 1 6 15 +split_gain=5.23451 12.2967 12.6222 11.2567 6.16582 6.24944 10.5653 5.86567 5.32222 8.17632 8.42705 8.39874 6.72561 5.65328 16.3929 9.07416 7.63425 10.4118 11.672 10.5994 10.2596 9.93696 14.3684 21.7135 12.4516 10.7439 10.2437 13.6427 19.4661 10.8322 10.5901 +threshold=4558.0000000000009 4085.0000000000005 3.5000000000000004 1.5000000000000002 56.500000000000007 7.5000000000000009 1.5000000000000002 2615.0000000000005 129.50000000000003 57.500000000000007 44.500000000000007 112.50000000000001 6139.0000000000009 531.50000000000011 152.50000000000003 17.500000000000004 141.50000000000003 534.50000000000011 1.0000000180025095e-35 4.5000000000000009 18.500000000000004 3.5000000000000004 5.5000000000000009 5239.0000000000009 96.500000000000014 77.500000000000014 116.50000000000001 1.5000000000000002 36.500000000000007 32760.500000000004 12.500000000000002 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 -1 3 -3 5 6 -5 8 9 13 12 -12 -11 16 15 -15 21 18 20 -20 -18 24 -23 25 26 -24 27 28 -2 -29 -30 +right_child=7 2 -4 4 -6 -7 -8 -9 -10 10 11 -13 -14 14 -16 -17 17 -19 19 -21 -22 22 23 -25 -26 -27 -28 29 30 -31 -32 +leaf_value=-9.3444721980718952e-05 -0.18043862066812694 -0.18988445517967856 -0.17288028876277153 -0.14904300951085697 -0.17485535017179393 0.093474295145444142 0.029979092919449109 0.099221651127734123 -0.2044578564922174 -0.16888159061889388 -0.18446265033687215 0.053320916288254486 0.10944926443044499 -0.19482510755119667 -0.17437817709640757 0.010573883779028851 -0.19197630485332795 0.07978259561353182 0.065575861201378011 -0.18796295805975471 0.02136589795510992 -0.013750823669234994 0.10442904533905671 -0.14044831646383957 -0.17962449875692277 -0.18291313421342947 -0.19059034569348715 0.077385509447146367 -0.0066243230686968101 -0.033713866915394949 0.084228678229733178 +leaf_weight=72668.467263556799 5.9641223583930687 3.7202863912098136 7.6226973867742336 4.6578514901921215 1.9269899809733022 6.5841130225453517 11.28034586412832 6.6865823645457558 1.2080275328771666 0.91268135816790774 2.4115424575284115 3.867937043774873 17.804734580277 2.1556918926071367 4.8405268718488506 957.21801077254349 2.3192146038636556 22.89695513737388 2.3527630828320971 5.5116441491991282 80.317228619402158 217.73745867179241 6.7655284008942562 21.646498020621948 3.4716969556175163 1.6111423727124927 2.4722778785508117 44.582657269551419 102.2729860128602 10.926884460728614 14.670158440480007 +leaf_count=23774452 1954 1217 2493 1522 633 2155 3690 2186 393 301 790 1262 5824 707 1585 313171 767 7491 770 1802 26270 71236 2214 7080 1136 526 806 14586 33460 3575 4799 +internal_value=0 -0.000122307 -0.0587217 -0.0278303 -0.00317166 0.0115175 -0.0223391 0.00576432 0.00535747 0.00552262 0.062247 -0.0379964 0.0958776 0.00458342 0.00918618 0.0101124 -0.00355203 0.0195409 0.00429957 -0.112113 0.0153784 -0.0096121 -0.0226931 -0.0875456 0.00796737 0.0491626 0.0115677 0.014369 -0.00421455 0.0555159 0.00477291 +internal_weight=0 72704.3 35.7923 28.1696 24.4493 22.5223 15.9382 1542.62 1535.94 1534.73 24.9969 6.27948 18.7174 1509.73 964.214 959.374 545.519 113.398 90.5009 7.86441 82.6364 432.121 247.761 30.0232 184.361 8.37667 180.889 178.417 122.907 55.5095 116.943 +internal_count=24290853 23786162 11710 9217 8000 7367 5212 504691 502505 502112 8177 2052 6125 493935 315463 313878 178472 37100 29609 2572 27037 141372 81056 9820 60316 2740 59180 58374 40213 18161 38259 +is_linear=0 +shrinkage=0.1 + + +Tree=157 +num_leaves=32 +num_cat=0 +split_feature=6 4 4 12 21 0 6 4 0 5 3 17 4 8 4 0 0 5 4 16 12 0 4 0 21 16 8 7 8 4 5 +split_gain=5.39846 6.26104 7.13469 11.4998 6.36309 5.13658 12.0284 9.0344 19.5133 9.78632 7.43618 7.19574 18.6233 14.5953 17.033 11.3078 9.56833 14.0827 12.534 12.6352 10.7874 10.665 12.7771 12.4211 11.208 18.0226 10.2602 30.378 11.5124 10.7588 34.2683 +threshold=5092.5000000000009 11622.000000000002 21932.500000000004 21.500000000000004 66.500000000000014 80.500000000000014 5239.0000000000009 9965.5000000000018 73.500000000000014 19403.000000000004 1.5000000000000002 18.500000000000004 7260.5000000000009 1275.5000000000002 8500.0000000000018 54.500000000000007 70.500000000000014 61561.000000000007 9470.5000000000018 1.0000000180025095e-35 17.500000000000004 34.500000000000007 9716.0000000000018 9.5000000000000018 188.50000000000003 462.50000000000006 18816.500000000004 34017.500000000007 266.00000000000006 8884.5000000000018 19122.000000000004 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 5 3 -3 -5 7 -7 11 -9 -10 -8 16 -13 -14 -15 -16 21 18 19 -18 -20 23 26 -2 25 -25 28 -28 29 30 -23 +right_child=1 2 -4 4 -6 6 10 8 9 -11 -12 12 13 14 15 -17 17 -19 20 -21 -22 22 -24 24 -26 -27 27 -29 -30 -31 -32 +leaf_value=-0.00012099714132641949 0.01368651591246145 -0.19177966861715187 0.077997379323777372 -0.20279407768697333 0.16791753240655735 0.14636130061352418 0.015623669057090101 -0.18483525770183407 0.17618685989248378 -0.18992239644773898 -0.12447254041858868 0.0030446902316919257 -0.15405829705500845 -0.19481465279714011 0.12186009006879094 -0.15580369645099013 -0.0062357940151539244 0.13264077193498372 -0.18053963271938395 -0.17121101078052381 0.18226851946942554 0.019678428551199136 -0.18277194052490756 -0.014551153338153123 0.071266305404019389 -0.17399093872931343 -0.17813461348366244 0.036244095800987794 0.050168431725777479 0.067285987098488459 -0.17161317910589066 +leaf_weight=72778.736701101967 369.475131682746 5.7779181635705736 2.4824550142220678 0.6301789311692122 1.7454965184442697 7.0142502011731258 364.97280764166499 7.2003452706849185 3.0888637881726027 0.95613041706383217 3.8285011856933115 89.291695371764945 15.768532474205132 3.5427497250493607 7.0786458558868599 1.8500115764327345 7.302947601303468 3.6310533457435659 1.1359226322965685 12.743098049832041 2.9422034099698067 47.878353863532539 2.9224236400332293 257.81740835428354 14.530172225437125 7.2901077505666754 9.2835637747775746 22.951171711465577 158.72239472938236 19.845024541718885 11.641977832186965 +leaf_count=23811429 120881 1891 810 205 572 2298 119412 2356 1010 313 1250 29211 5160 1158 2316 605 2395 1188 371 4164 963 15665 956 84354 4754 2385 3036 7510 51931 6494 3810 +internal_value=0 0.00600948 -0.0704359 -0.115628 0.0695814 0.00656841 0.0166366 0.00306131 -0.0861024 0.0896482 0.0141693 0.00400046 -0.0193414 -0.0901237 -0.00928634 0.0643283 0.00688791 -0.0509627 -0.0785978 -0.111109 0.0812118 0.00862872 0.0252022 0.00165208 -0.0142486 -0.0189355 0.0274506 -0.0254967 0.0346192 0.00352227 -0.0177376 +internal_weight=0 1465.34 10.636 8.15359 2.37568 1454.71 375.816 1078.89 11.2453 4.04499 368.801 1067.64 117.532 28.2399 12.4714 8.92866 950.113 27.7552 24.1242 20.046 4.07813 922.358 273.245 649.113 279.638 265.108 270.322 32.2347 238.088 79.3654 59.5203 +internal_count=24290853 479424 3478 2668 777 475946 122960 352986 3679 1323 120662 349307 38450 9239 4079 2921 310857 9081 7893 6559 1334 301776 89402 212374 91493 86739 88446 10546 77900 25969 19475 +is_linear=0 +shrinkage=0.1 + + +Tree=158 +num_leaves=32 +num_cat=0 +split_feature=6 6 11 14 6 0 9 15 6 4 15 0 0 4 9 19 14 11 11 9 0 11 11 1 1 21 6 17 14 2 7 +split_gain=5.71046 8.98782 9.61873 11.7268 14.1543 11.9437 9.26848 9.48362 9.21951 18.8177 10.8178 8.82123 15.9845 13.4964 8.8105 15.766 10.3424 8.74881 11.2148 11.3003 8.67526 8.4116 8.57437 17.1741 8.06671 8.53778 7.33084 9.41336 7.65408 7.29111 7.14259 +threshold=6471.5000000000009 7781.5000000000009 63.500000000000007 9.5000000000000018 7347.0000000000009 155.50000000000003 184.50000000000003 19.500000000000004 8968.5000000000018 1.0000000180025095e-35 7.5000000000000009 41.500000000000007 31.500000000000004 2435.5000000000005 1463.5000000000002 14.500000000000002 14.500000000000002 462.00000000000006 680.50000000000011 9639.0000000000018 3.5000000000000004 712.00000000000011 3714.5000000000005 63.500000000000007 71.500000000000014 1.0000000180025095e-35 8144.0000000000009 1.0000000180025095e-35 10.500000000000002 29.500000000000004 9793.5000000000018 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 3 4 24 -5 -4 11 9 10 17 12 21 14 16 30 -13 -3 -19 -20 -21 -8 23 29 25 -2 -12 28 -28 -23 -16 +right_child=1 8 6 5 -6 -7 7 -9 -10 -11 26 13 -14 -15 15 -17 -18 18 19 20 -22 22 -24 -25 -26 -27 27 -29 -30 -31 -32 +leaf_value=-0.00011621501718938462 -0.19106239107894535 0.02317619228797399 0.11974170113856293 -0.17774753740111346 -0.17752825235012645 0.12050523909185472 0.035713527011202943 -0.16697396689797217 0.0065819888992403978 -0.12619832440502407 -0.17982685147068556 0.14838396222981357 -0.17638695714763464 -0.20677950193279765 -0.071346187579777995 -0.17834041630432296 -0.18320433454074403 -0.1696715859820509 -0.17574479183837 -0.16018763914129863 0.026758439849041513 -0.16879572240512219 0.062808460249918915 -0.17192909356290254 0.12614576779844527 0.11594543294987417 -0.16790810688105981 -0.1802365672588746 0.043257160733718693 0.057059718767329126 0.084760414734468922 +leaf_weight=72957.107481654952 2.0159341765102021 75.890530436765403 11.463558383751659 7.4617927213548665 2.88721753808204 1.637277577538043 98.682336561847478 2.4852009324240489 915.74494423426222 15.07320953029557 5.0746330091496956 17.170541647356007 4.1835681185475542 1.7596478525083501 3.3377652211929734 2.9296078050392671 0.99515422782860596 5.3720031277043736 3.7812878810800639 2.7523820584174272 25.294100721250288 1.7045869273133636 7.4539400607172857 8.2593483282253128 7.9875925171654671 1.6449697415810076 2.1071597535628817 3.6625613803043953 9.259017998701891 8.8514306598808599 24.049504285678267 +leaf_count=23871721 661 24831 3752 2441 945 537 32286 813 299627 4931 1661 5619 1370 575 1093 957 325 1757 1239 899 8276 559 2440 2702 2612 539 689 1200 3030 2896 7870 +internal_value=0 0.0066189 0.0251687 -0.0350508 0.0206798 -0.12408 0.0325308 0.0270335 0.00283644 -0.0202973 -0.00831274 0.0297214 0.0158892 0.0652738 0.0751478 0.0421495 0.130219 0.00370296 -0.036024 -0.0134665 0.00841222 0.0223268 -0.0279611 -0.0639206 0.069808 -0.0531132 -0.0759063 -0.0408163 0.00410953 0.0205886 0.0657353 +internal_weight=0 1280.97 216.961 23.6348 14.5357 9.09907 193.326 181.863 1064.01 148.267 133.194 179.377 129.135 50.2422 48.4826 30.3169 18.1657 113.09 37.1998 31.8278 28.0465 124.952 26.2693 18.8154 11.6485 3.6609 20.1034 15.0287 11.3662 10.556 27.3873 +internal_count=24290853 419132 70992 7735 4757 2978 63257 59505 348140 48513 43582 58692 42253 16439 15864 9920 5944 37002 12171 10414 9175 40883 8597 6157 3812 1200 6580 4919 3719 3455 8963 +is_linear=0 +shrinkage=0.1 + + +Tree=159 +num_leaves=32 +num_cat=0 +split_feature=6 6 1 1 19 11 21 11 20 19 11 1 1 16 9 10 4 1 10 9 10 20 20 14 13 11 5 20 17 21 13 +split_gain=6.00598 11.0423 8.86533 18.4104 13.6241 10.1674 10.8486 8.21382 9.41811 8.56946 8.5198 14.6082 8.84568 11.4357 15.2877 9.52162 6.80931 6.28135 6.23631 6.16836 5.84562 5.83944 27.4513 13.3949 18.7082 15.194 10.1913 8.76012 17.1672 11.5815 10.9816 +threshold=6471.5000000000009 7781.5000000000009 24.500000000000004 32.500000000000007 2.5000000000000004 216.00000000000003 100.50000000000001 216.00000000000003 3.5000000000000004 25.500000000000004 279.50000000000006 135.50000000000003 107.50000000000001 5.5000000000000009 32392.500000000004 148.50000000000003 1.0000000180025095e-35 190.00000000000003 1776.5000000000002 3352.5000000000005 49.500000000000007 79.500000000000014 63.500000000000007 1.0000000180025095e-35 9.5000000000000018 9.5000000000000018 51967.000000000007 48.500000000000007 12.500000000000002 132.50000000000003 1.5000000000000002 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 5 -4 20 6 -2 9 10 -6 11 -9 16 14 15 -14 18 -15 -12 -13 -5 22 27 26 25 -25 -23 30 -29 -30 -3 +right_child=1 21 3 4 7 -7 -8 8 -10 -11 12 19 13 17 -16 -17 -18 -19 -20 -21 -22 23 -24 24 -26 -27 -28 28 29 -31 -32 +leaf_value=-0.00011924558061227529 -0.17348852743391663 -0.040267296170621893 -0.17983221401264174 -0.028609420590362273 0.065285782607660645 0.12789637756110625 0.12302689451789278 -0.18186746855048605 -0.17269133720221039 -0.18334843412744375 0.05685865749901016 0.12979536965375654 0.092563092917290785 0.12754669058154652 0.035187133807385799 -0.17542592993010109 -0.16893579635627048 -0.17197213680117016 -0.1694144544950244 -0.074655113662467187 -0.1779343313984604 0.072361536649014013 -0.18494657388695002 0.13901603819654204 0.064693682645577219 -0.17434438225214163 -0.1812562979497124 0.095937493378821584 -0.18299800922633996 0.034006907221251922 0.0032120666390396679 +leaf_weight=72960.660529372748 2.6597944323439178 62.119652195251547 4.4713617454981422 7.7729769155266677 65.468165950616822 16.018118078354746 2.3016301032621413 5.3575643460499105 2.5908112915931261 1.4162042594980437 68.18363222113112 3.5444588046520948 1.5457508051768005 6.0954532119212663 9.7524030420172476 9.3171497428556886 1.412918944377451 0.79103674693033088 1.2401964990422119 2.528309321089182 3.9557584809372202 42.581163403388928 7.9015029930742449 1.853746152017268 10.179616529261692 9.3612070053932239 1.6456480589695264 24.874862808734179 4.302840671502052 5.7404396552592516 895.45770604515565 +leaf_count=23871264 871 20325 1464 2543 21416 5241 754 1753 845 463 22309 1160 509 1993 3192 3045 463 261 406 828 1294 13931 2584 607 3332 3061 539 8140 1411 1874 292975 +internal_value=0 0.00678406 0.0273781 0.020747 0.0254433 0.0891525 -0.0359335 0.0322757 0.0157596 0.0600212 0.0202075 -0.0615086 0.0297057 -0.0184283 -0.055698 -0.137292 0.0483934 0.0931416 0.0528165 0.0446754 -0.0789723 0.00260305 0.000707478 0.031501 -0.0334579 -0.122548 0.0629246 0.00218552 0.051384 -0.0589645 0.000391487 +internal_weight=0 1282.44 216.424 195.444 190.973 20.9795 4.96142 179.244 112.36 66.8844 109.769 11.4303 98.3385 27.5018 20.6153 10.8629 70.8367 6.88649 69.4238 6.07277 11.7287 1066.02 1000.4 65.6214 21.3946 11.215 44.2268 992.496 34.9181 10.0433 957.577 +internal_count=24290853 419589 70810 63944 62480 6866 1625 58643 36764 21879 35919 3741 32178 9000 6746 3554 23178 2254 22715 1988 3837 348779 327309 21470 7000 3668 14470 324725 11425 3285 313300 +is_linear=0 +shrinkage=0.1 + + +Tree=160 +num_leaves=32 +num_cat=0 +split_feature=7 15 21 3 21 9 0 0 20 8 8 12 11 20 9 7 21 21 21 21 14 21 20 9 11 20 11 17 9 9 21 +split_gain=4.51008 10.5349 5.92563 5.91866 6.50494 8.54204 5.20835 4.16717 3.97366 10.9887 10.3507 10.2562 12.7853 8.47324 7.27829 7.23609 4.93105 6.93165 12.2226 6.10827 5.35914 5.15392 7.08994 4.65571 8.70961 5.02023 4.0845 4.02912 3.6266 4.34095 3.50056 +threshold=1028.0000000000002 45.500000000000007 116.50000000000001 3161.5000000000005 3.5000000000000004 447.50000000000006 206.50000000000003 64.500000000000014 169.50000000000003 341.00000000000006 14240.000000000002 2.5000000000000004 4.5000000000000009 152.50000000000003 70.500000000000014 1255.5000000000002 547.00000000000011 492.50000000000006 406.50000000000006 392.50000000000006 1.0000000180025095e-35 351.50000000000006 147.50000000000003 95.500000000000014 2.5000000000000004 290.50000000000006 1007.0000000000001 11.500000000000002 201.00000000000003 562.00000000000011 327.50000000000006 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 8 3 6 5 -5 -3 -4 13 11 -11 12 14 16 -10 -13 17 18 19 21 -21 30 -23 25 -25 -16 -17 -19 -26 -30 -2 +right_child=1 2 7 4 -6 -7 -8 -9 9 10 -12 15 -14 -15 23 26 -18 27 -20 20 -22 22 -24 24 28 -27 -28 -29 29 -31 -32 +leaf_value=-0.00013108615934269394 0.0027701011191380727 0.093241669062826688 0.05135189037482199 -0.17549787274549042 0.1804896593629971 0.024486641966399499 -0.19597591350510427 -0.17520632277421799 -0.18073058950044699 -0.16770377431951713 0.085134317742632526 -0.18670684500211882 -0.18758767472693771 -0.14485030624255937 0.12859183247982214 0.096461071051413105 -0.16165442703265059 -0.19172171747776037 -0.18263502415432678 -0.20055955192964414 0.12946572054895678 -0.1789522254333748 0.15485714917633667 -0.17472840844476967 -0.16402204417005842 -0.19383816879519022 -0.1842501932898345 0.1202182403092341 0.10073769296197316 -0.14774984183537554 0.065867424240367164 +leaf_weight=72203.018288609223 1898.082969753712 28.506579445092942 1.4154921700246625 2.5997724810149547 2.0151502732187501 11.968810429563744 0.63656372553668816 1.9037815387127919 1.5720901495078639 4.9734279043041161 2.4007053229724988 0.96605822478886949 3.328945118119008 3.8829632778652003 6.6788132204674211 28.923253455854141 1.8166886436520133 0.4410858687479039 3.5476555094355708 0.53786262637004156 5.7756256461143494 3.1441866576205935 0.79770457744598378 2.2224994266871345 0.71655141632072616 0.52053121663629998 0.52780381520278652 6.759108133148402 6.1402456541545689 0.79393575899302948 8.8334832011605595 +leaf_count=23622631 621004 9328 462 850 659 3916 208 623 515 1626 784 317 1089 1270 2185 9463 591 144 1161 177 1889 1029 261 726 233 170 173 2211 2009 259 2890 +internal_value=0 0.00463406 0.0504202 0.0597851 0.0120923 -0.0112007 0.0869244 -0.0785914 0.00350751 0.0289033 -0.0853904 0.0449905 -0.007066 0.00272257 0.0251656 0.0825966 0.00301951 0.00317469 0.00280756 0.00315072 0.10135 0.00282626 -0.111401 0.044125 -0.000467296 0.105279 0.0914303 0.101109 0.0501547 0.0722869 0.00306239 +internal_weight=0 2042.43 49.0462 45.7269 16.5837 14.5686 29.1431 3.31927 1993.38 59.7649 7.37413 52.3907 21.9736 1933.62 18.6447 30.4171 1929.74 1927.92 1920.72 1917.17 6.31349 1910.86 3.94189 17.0726 9.87323 7.19934 29.4511 7.20019 7.65073 6.93418 1906.92 +internal_count=24290853 668222 16046 14961 5425 4766 9536 1085 652176 19549 2410 17139 7186 632627 6097 9953 631357 630766 628411 627250 2066 625184 1290 5582 3227 2355 9636 2355 2501 2268 623894 +is_linear=0 +shrinkage=0.1 + + +Tree=161 +num_leaves=32 +num_cat=0 +split_feature=6 6 1 2 13 13 10 9 0 9 0 2 19 0 1 13 11 11 21 13 21 18 3 11 4 4 19 21 9 0 11 +split_gain=6.55486 12.9879 7.90391 11.37 10.9564 10.8425 15.6079 9.9482 8.62614 14.2746 8.5609 17.815 15.5846 12.6183 16.7628 15.3751 7.98877 9.6827 7.16028 7.14753 15.7104 6.53672 6.12818 6.3086 9.64339 40.4956 16.2453 7.99434 22.4247 12.1357 9.42216 +threshold=6471.5000000000009 7781.5000000000009 43.500000000000007 42.500000000000007 1975.0000000000002 38.500000000000007 1057.5000000000002 33914.500000000007 2.5000000000000004 2641.5000000000005 6.5000000000000009 168.50000000000003 21.500000000000004 21.500000000000004 174.00000000000003 12.500000000000002 601.50000000000011 604.00000000000011 1.0000000180025095e-35 118.50000000000001 1.5000000000000002 645.00000000000011 3.5000000000000004 53.500000000000007 9470.5000000000018 9369.5000000000018 2.5000000000000004 32.500000000000007 223.50000000000003 1.5000000000000002 1.5000000000000002 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 7 5 8 -4 -7 -2 9 -5 21 12 13 14 15 -12 -17 -18 -11 20 -15 -10 23 27 25 -25 -27 28 29 -3 -31 +right_child=1 22 3 4 -6 6 -8 -9 10 18 11 -13 -14 19 -16 16 17 -19 -20 -21 -22 -23 -24 24 -26 26 -28 -29 -30 30 -32 +leaf_value=-0.00012451699033230656 0.075078160263608321 -0.17717067587287758 0.13198138550458627 -0.17797802225486481 -0.1755681952207534 -0.17569565589075509 0.074709520561463266 -0.14806787949726349 0.089514265372962032 0.035344812714818846 0.15430237032740368 -0.14184725063961215 -0.16495976345396532 -0.17056542457511106 0.099316479701399174 -0.17739061310029222 0.17347472443969641 -0.15369139971779563 -0.17331090674723679 0.087095208543774449 0.12352836490444528 -0.17842381046473316 -0.18008950925400136 -0.00088353837195647095 0.028636773527209919 -0.18742763405168539 0.12076319094761578 0.037478342805181267 -0.16314036884760086 -0.17319258433833501 0.02940399277059725 +leaf_weight=72960.202376407935 45.978794905822724 3.1920258635655037 2.3333212537690988 4.8112205569050301 2.6111530417110762 8.6235102720092964 3.4992412948049596 2.0886189092416307 35.237987276923377 16.944321528077129 2.3497560331597951 5.9814748328935812 3.647118921740911 3.5350592606700948 8.1071184068568964 8.6170111836981942 1.7002640962600701 1.9330815274734048 1.8214250615565095 46.951427179330494 3.736162923858501 0.93467510095797379 1.8326962887658726 763.9408097389387 99.984676261490677 15.758882031310351 1.9185955562279549 107.01549887662986 8.0027166956569982 2.3786085315514347 65.737572895595804 +leaf_count=23871658 15046 1042 763 1578 853 2820 1143 684 11532 5541 768 1958 1190 1157 2650 2820 557 633 596 15364 1225 304 599 249950 32714 5157 627 35014 2620 778 21512 +internal_value=0 0.00709075 0.0297375 0.0192504 0.0274697 -0.0654211 -0.103416 0.065382 0.0310933 -0.0243064 0.0417358 0.0246625 0.037023 0.0465987 -0.0159852 -0.0800096 -0.124953 -0.000590409 0.0150924 0.0728074 -0.0194516 0.0825909 0.00261452 0.00292807 -0.00060536 -0.00434602 -0.153979 0.0196465 -0.0044144 0.013399 0.0223293 +internal_weight=0 1281.2 211.443 163.375 148.919 14.4561 12.1228 48.0674 146.308 23.577 122.731 86.5585 80.577 76.9299 22.7072 14.6001 12.2504 3.63335 18.7657 54.2226 7.27122 36.1727 1069.76 1067.93 881.603 781.618 17.6775 186.326 79.3109 71.3082 68.1162 +internal_count=24290853 419195 69182 53452 48726 4726 3963 15730 47873 7715 40158 28322 26364 25174 7428 4778 4010 1190 6137 17746 2382 11836 350013 349414 288448 255734 5784 60966 25952 23332 22290 +is_linear=0 +shrinkage=0.1 + + +Tree=162 +num_leaves=32 +num_cat=0 +split_feature=6 6 6 11 9 2 12 0 10 12 1 14 12 2 0 14 17 2 6 11 0 17 10 0 11 9 16 15 4 4 4 +split_gain=7.10669 15.8109 7.0393 11.0275 8.4916 10.9439 7.77709 7.43523 13.896 10.9121 9.45503 11.2914 7.23978 16.2692 12.4548 10.482 7.10551 6.68079 15.5871 13.3477 6.57395 15.7526 15.384 14.2521 18.8862 21.0828 8.64593 9.35978 8.18673 7.91346 18.6884 +threshold=6471.5000000000009 7781.5000000000009 7422.0000000000009 4.5000000000000009 1463.5000000000002 29.500000000000004 3.5000000000000004 6.5000000000000009 148.50000000000003 1.5000000000000002 170.00000000000003 5.5000000000000009 1.0000000180025095e-35 49.500000000000007 6.5000000000000009 12.500000000000002 1.0000000180025095e-35 78.500000000000014 7029.5000000000009 340.00000000000006 192.50000000000003 18.500000000000004 12.500000000000002 245.50000000000003 604.00000000000011 1674.5000000000002 254.50000000000003 7.5000000000000009 21932.500000000004 8087.0000000000009 7916.5000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 3 -2 12 -6 7 8 -7 17 -9 -12 15 14 -14 -5 -15 19 -19 -10 29 23 -23 24 -22 -26 27 -25 -28 30 -3 +right_child=1 20 -4 4 5 6 -8 10 9 -11 11 -13 13 16 -16 -17 -18 18 -20 -21 21 22 -24 26 25 -27 28 -29 -30 -31 -32 +leaf_value=-0.00012894514377742317 -0.15947854995116709 0.0010117802567293282 0.06013146838463912 0.10610314622372569 -0.16153596260481196 -0.18911058734314071 0.061968391002402347 -0.13433888794626678 -0.19465584671382027 -0.17293426230191816 0.10448018921580715 -0.16174450433223408 -0.17311925302162035 -0.17796996113810548 0.082482478393704817 -0.18494916667816605 0.034385932159733866 0.13399940730555646 -0.18167980758076979 0.096787375602141679 -0.13952147832057404 -0.18701360219182278 0.10049563761876308 0.051978902416367302 0.13882525963602954 -0.18153069873877697 -0.17642219640621437 -0.16791522319137106 0.092361556551777982 0.020350166407307767 -0.18437101570367731 +leaf_weight=72961.9753509328 3.3324654058087608 729.17243197681091 64.951142126054037 25.336808625375856 3.8458376706112167 3.0151400443864977 21.562104898723192 8.9597973001655209 1.6924413028173209 2.3335972056374876 4.958314641728065 2.3473280793405138 2.0863536382094088 4.9438024609698905 22.100638367235661 1.3009041177574534 2.3128078384324908 2.8414687148760995 3.4794329296564683 21.980081676039845 14.796304850373419 9.5672039552591723 2.3105467726709312 39.326516230328707 6.6805966598913056 2.9664905123063363 3.7007513834396368 2.035911831422708 1.6333311251364646 246.03554158823681 5.4787867134436956 +leaf_count=23876209 1087 238618 21255 8294 1258 985 7055 2934 553 764 1622 768 682 1619 7234 425 758 930 1140 7191 4841 3132 756 12866 2185 973 1213 665 534 80514 1793 +internal_value=0 0.00742493 0.0329714 0.0202279 0.0246607 0.00288859 0.0115308 -0.00954242 0.0162075 0.0353576 -0.0654929 0.0189413 0.0535305 0.0210347 0.0604344 0.091889 -0.110288 0.0515634 -0.0397708 0.075951 0.00254044 -0.0244794 -0.131085 -0.00668015 -0.0685451 0.0403154 0.0257032 0.0411554 -0.0941188 0.00482774 -0.000370744 +internal_weight=0 1267.08 203.38 138.429 135.097 77.0155 73.1697 51.6076 35.3422 32.327 16.2654 7.30564 58.0813 31.4436 24.187 26.6377 7.25661 29.9934 6.3209 23.6725 1063.7 83.0177 11.8778 71.1399 24.4434 9.64709 46.6965 41.3624 5.33408 980.687 734.651 +internal_count=24290853 414644 66554 45299 44212 25200 23942 16887 11563 10578 5324 2390 19012 10293 7916 8719 2377 9814 2070 7744 348090 27165 3888 23277 7999 3158 15278 13531 1747 320925 240411 +is_linear=0 +shrinkage=0.1 + + +Tree=163 +num_leaves=32 +num_cat=0 +split_feature=6 6 11 13 11 6 4 0 15 9 0 0 1 0 12 14 1 6 14 1 0 15 18 6 4 13 6 6 18 13 0 +split_gain=8.03658 18.8532 6.47342 8.39211 7.81462 5.87978 5.57808 5.57662 5.42729 4.66014 5.05724 5.02738 4.60316 6.90676 9.50477 5.77461 11.6535 6.886 5.45953 18.3262 8.21049 6.4871 4.59251 4.56428 11.0057 8.76298 10.9561 10.9947 10.2152 14.0421 13.2627 +threshold=6471.5000000000009 7781.5000000000009 63.500000000000007 439.00000000000006 534.50000000000011 6627.0000000000009 8327.5000000000018 44.500000000000007 8.5000000000000018 20901.500000000004 1.0000000180025095e-35 57.500000000000007 70.500000000000014 2.5000000000000004 13.500000000000002 14.500000000000002 36.500000000000007 7029.5000000000009 9.5000000000000018 118.50000000000001 29.500000000000004 2.5000000000000004 9.5000000000000018 8968.5000000000018 1.0000000180025095e-35 113.50000000000001 8510.5000000000018 8727.0000000000018 9.5000000000000018 240.50000000000003 18.500000000000004 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 3 22 5 -4 9 -6 -9 12 -11 -12 15 14 -14 -7 17 -17 -15 -20 21 -21 -2 24 25 26 -3 -28 -27 -30 -31 +right_child=1 23 4 -5 7 6 -8 8 -10 10 11 -13 13 18 -16 16 -18 -19 19 20 -22 -23 -24 -25 -26 28 27 -29 29 30 -32 +leaf_value=-0.00013684826820384622 -0.15921232582626965 0.04886713088087024 -0.074279749224171923 -0.16389624687316073 -0.013309679001224178 0.10631037613739203 -0.20916214948909362 0.073106939527493628 -0.16601590816070788 -0.16265603368213677 0.044591586037802833 -0.1638047349967714 -0.17155904741532871 0.096393518085294602 0.12339292504992605 0.13671120497960798 -0.16493858248242677 -0.19021276988265112 -0.17327462104485822 -0.21142670712131106 -0.16030754213910484 0.10514115067603469 0.027409369546256848 0.0053095371567287407 -0.10135410881048115 0.073469126429467527 -0.16219710646413266 0.027861695138557203 -0.12279215813095884 -0.07639397485046473 0.024067640201537333 +leaf_weight=72966.433275771546 1.4438973719079573 43.439576519071124 3.3718539457186116 3.2751455843972499 34.447558271407615 42.87847474089358 0.76743186905514349 15.05499762753607 1.0130312642431807 1.4071787697903371 20.664933352905795 1.2263036684598763 3.0540431400877415 21.437358845490962 1.7010859949514268 4.929492609575389 2.506687807617709 0.74114470975473512 3.8223204130772528 0.67750115389935928 1.4211147917667393 14.529306801268829 15.20041091571329 917.97350229823496 13.033067744661819 9.0780333757866156 5.5564260415267182 6.7308320068113972 15.652326186333083 21.66392265673494 33.402829407714307 +leaf_count=23877840 474 14216 1104 1073 11276 14030 252 4922 332 460 6764 401 1001 7014 559 1613 820 242 1251 222 466 4754 4971 300397 4264 2973 1818 2203 5126 7090 10925 +internal_value=0 0.0079116 0.0364533 -0.0175726 0.04258 0.0559814 0.0595885 0.00938244 0.0580311 0.0612931 0.0211054 0.0329176 0.0708767 0.0481669 -0.0660438 0.0916237 0.0146126 0.0939827 0.0611322 0.0241689 0.0695559 0.0910373 0.0112199 0.00267787 -0.0135839 -0.00514322 0.0252852 -0.0580849 -0.0263931 -0.0392122 -0.0154552 +internal_weight=0 1262.1 195.571 19.9195 175.652 125.136 121.764 50.5156 16.068 120.997 23.2984 21.8912 97.6985 46.6427 4.75513 51.0558 8.17733 5.67064 41.8876 20.4502 16.6279 15.2068 16.6443 1066.53 148.557 135.524 55.7268 12.2873 79.7971 70.7191 55.0668 +internal_count=24290853 413013 64001 6518 57483 40953 39849 16530 5254 39597 7625 7165 31972 15267 1560 16705 2675 1855 13707 6693 5442 4976 5445 349012 48615 44351 18237 4021 26114 23141 18015 +is_linear=0 +shrinkage=0.1 + + +Tree=164 +num_leaves=32 +num_cat=0 +split_feature=6 6 20 20 18 18 6 14 21 21 21 14 12 20 18 10 6 7 2 12 18 0 7 10 10 19 10 17 21 6 5 +split_gain=7.84746 19.3523 7.0908 12.3404 10.3967 8.94743 14.0174 14.5155 8.70599 8.01256 14.6031 7.46109 6.41083 5.51396 11.2694 5.23355 9.99242 5.9388 6.59918 5.6133 5.54164 5.15402 5.117 6.6107 9.28382 6.07986 6.95417 5.19109 5.18113 8.36155 4.89091 +threshold=6471.5000000000009 7781.5000000000009 79.500000000000014 63.500000000000007 1.0000000180025095e-35 3.5000000000000004 9891.0000000000018 4.5000000000000009 235.50000000000003 76.500000000000014 66.500000000000014 2.5000000000000004 27.500000000000004 61.500000000000007 1.0000000180025095e-35 376.00000000000006 7162.0000000000009 47626.500000000007 31.500000000000004 25.500000000000004 187.50000000000003 149.50000000000003 19666.000000000004 42.500000000000007 9.5000000000000018 2.5000000000000004 49.500000000000007 7.5000000000000009 86.500000000000014 7029.5000000000009 48273.500000000007 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 15 3 13 9 8 12 -8 11 10 -4 30 -7 -3 -15 22 17 18 19 -17 -18 -9 23 24 27 26 -25 28 29 -2 -6 +right_child=1 2 4 -5 5 6 7 21 -10 -11 -12 -13 -14 14 -16 16 20 -19 -20 -21 -22 -23 -24 25 -26 -27 -28 -29 -30 -31 -32 +leaf_value=-0.00013490122396607839 -0.14807235939926286 0.00052514935635221156 -0.18555315032616795 -0.16184096639408996 0.10582758919099861 0.12027706521425097 -0.14392438829454365 -0.16678916228012688 -0.16585839806794314 -0.18103726144020202 0.14822926341034112 -0.15953178950151672 -0.16651197031497822 -0.17497131250643894 0.13109919158154815 -0.16422524382103978 -0.13334197235031459 0.045459104708129583 -0.15936418196275504 0.12279433884188007 0.060780535452646126 0.12611364797999766 0.0057964372145693846 0.084186842836553588 -0.17678109472377504 0.069674072546479213 -0.16355085773502687 -0.19794941098444821 0.10473944070130053 0.03908848370051822 -0.17310652187265804 +leaf_weight=72965.788630470459 2.935258896446622 984.45106362306979 2.22200628754217 4.6626327747653695 31.878940116323065 8.4586538680596277 8.4686314617065346 0.715743163600563 1.3678692289395247 3.6057439214782789 3.1961096080485731 1.1437697689980257 0.85856791981495995 1.4415651159360985 7.2683412479236722 1.0657827997347322 1.5961323620285863 3.5294017286505541 6.8883793905843049 1.8892453480511904 18.693979859352112 3.739478989969939 27.848318558972096 2.1158278118818989 2.45122252695728 98.948034853179706 2.4394976878538728 0.98079846156178874 7.591254591709002 12.780172849597873 0.64126263267826189 +leaf_count=23879840 961 322182 728 1526 10436 2770 2774 233 445 1178 1046 375 279 470 2381 348 522 1155 2256 618 6119 1225 9113 692 802 32385 797 321 2484 4183 209 +internal_value=0 0.00783765 0.00256815 0.00046403 0.034237 0.049956 0.000350647 -0.067056 0.0814495 -0.0655282 0.0113428 0.0914983 0.0938499 0.00122601 0.0804419 0.0370803 0.0012786 -0.0658319 -0.105736 0.0192757 0.0455098 0.079058 0.0447037 0.0530229 0.00869722 0.0644738 -0.0484833 0.0274167 0.0369006 0.00413141 0.100327 +internal_weight=0 1255.87 1064.12 997.824 66.2968 57.2729 22.2411 12.9239 35.0318 9.02386 5.41812 33.664 9.31722 993.161 8.70991 191.753 33.6629 13.3728 9.84341 2.95503 20.2901 4.45522 158.09 130.242 26.7387 103.503 4.55533 24.2875 23.3067 15.7154 32.5202 +internal_count=24290853 411013 348257 326559 21698 18746 7281 4232 11465 2952 1774 11020 3049 325033 2851 62756 11018 4377 3222 966 6641 1458 51738 42625 8751 33874 1489 7949 7628 5144 10645 +is_linear=0 +shrinkage=0.1 + + +Tree=165 +num_leaves=32 +num_cat=0 +split_feature=7 15 19 19 1 2 7 21 18 18 12 19 19 5 7 2 2 2 21 21 21 21 21 16 4 18 21 8 21 5 5 +split_gain=3.9339 10.4988 5.39321 9.62206 8.49793 11.6629 11.4044 7.72691 5.48897 10.8355 8.88234 6.92143 4.86252 5.52684 4.65227 4.50021 4.29374 5.14844 3.85975 5.24515 6.37108 5.24157 9.31262 22.2832 10.8618 7.3135 6.76442 16.4183 16.0343 8.73481 4.38146 +threshold=1028.0000000000002 67.500000000000014 163.00000000000003 138.50000000000003 37.500000000000007 4379.0000000000009 1442.0000000000002 1.0000000180025095e-35 50.500000000000007 26.500000000000004 1.0000000180025095e-35 219.50000000000003 249.50000000000003 135590.50000000003 2642.5000000000005 5780.0000000000009 8741.5000000000018 9892.0000000000018 547.00000000000011 492.50000000000006 406.50000000000006 327.50000000000006 258.50000000000006 340.00000000000006 9220.0000000000018 1.0000000180025095e-35 234.50000000000003 552.50000000000011 218.50000000000003 11913.500000000002 2083.5000000000005 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 3 18 5 -4 15 8 9 10 -8 -12 -13 -14 -10 -7 -3 -18 19 20 21 22 26 -24 -25 -26 28 29 -2 -28 -23 +right_child=1 16 4 -5 -6 6 7 -9 14 -11 11 12 13 -15 -16 -17 17 -19 -20 -21 -22 30 23 24 25 -27 27 -29 -30 -31 -32 +leaf_value=-0.00012171699635168411 0.0028250699784655433 0.075779058905121752 0.10383410761228662 -0.1574648966593066 -0.15304540947084377 0.040991366241299773 -0.15759279071885091 -0.16058927742465881 0.096148563545756946 -0.17595086404130708 -0.15077407769777765 0.14742341290603503 -0.16323996972388718 0.11316177295823589 -0.15938694187037938 -0.17559990970843342 -0.10387911217562384 0.083865992649289317 -0.14839816343633475 0.090286805388798921 -0.1584406699318596 0.14067849580265801 -0.1735446151378392 -0.18212269165596817 0.13584533317660483 -0.20744826441225112 -0.17603552878319692 -0.17873987829646837 -0.17707472323522408 0.11218479639041101 0.028690357326714568 +leaf_weight=72193.45448732638 1844.0177064833697 28.066132077656221 19.314368002160336 3.7536837049119631 2.3667196875903747 1.24572697514668 1.946182970219527 1.9912590477615584 15.113475552585438 2.8430768004618576 1.2196133132092644 6.9410768345696834 1.2702310107415542 1.6805329155176878 0.74771111278096292 4.1720211814390495 3.1989301905268794 2.6879207536694594 1.6869525298825454 6.8487000113818786 2.4529810089152297 4.4718767211306867 9.6170098850270751 1.6639011410297806 6.3151244213804603 0.68820239044725884 1.1220800599549026 2.5177494630916035 4.9677204955369225 16.713683249196038 15.970072652620727 +leaf_count=23630446 603589 9188 6318 1229 775 405 638 653 4951 931 399 2271 415 550 245 1367 1047 878 551 2241 803 1463 3147 544 2069 225 370 823 1626 5469 5227 +internal_value=0 0.00435519 0.00341144 0.00248385 0.0327218 0.0402392 0.00888187 0.0304995 0.0424795 0.000959992 0.0394793 0.0739967 0.10171 -0.00582235 0.0841024 -0.125798 0.0594925 -0.0181552 0.00279671 0.00292973 0.00261658 0.00282363 0.00227821 -0.0687423 0.0475446 0.102111 0.00297287 0.0603077 0.00234173 0.0940523 0.0531889 +internal_weight=0 2017.61 1983.66 1922.81 60.852 58.4853 39.1709 33.7532 31.7619 15.9007 13.0576 11.1115 9.89184 2.95076 15.8612 5.41775 33.953 5.88685 1919.05 1917.37 1910.52 1908.07 1887.62 18.2842 8.66723 7.00333 1869.34 20.3535 1848.99 17.8358 20.4419 +internal_count=24290853 660407 649294 629376 19918 19143 12825 11053 10400 5204 4273 3635 3236 965 5196 1772 11113 1925 628147 627596 625355 624552 617862 5985 2838 2294 611877 6662 605215 5839 6690 +is_linear=0 +shrinkage=0.1 + + +Tree=166 +num_leaves=32 +num_cat=0 +split_feature=6 6 20 5 11 8 4 4 7 19 0 11 16 0 11 16 16 20 0 21 21 17 0 0 0 11 19 21 20 16 0 +split_gain=8.08446 17.5836 10.0345 12.6636 10.3508 8.45637 8.47531 21.9547 12.5691 7.36231 7.2017 5.64021 5.32681 10.9513 10.0032 9.3235 10.8654 11.5321 9.35144 11.9793 9.37963 9.73826 8.18434 7.63466 15.3145 7.93843 8.46497 15.759 9.85061 9.13596 7.53013 +threshold=6471.5000000000009 7781.5000000000009 54.500000000000007 51967.000000000007 30.500000000000004 97.500000000000014 5205.5000000000009 8087.0000000000009 32291.500000000004 1.0000000180025095e-35 41.500000000000007 1.5000000000000002 2150.0000000000005 8.5000000000000018 92.500000000000014 650.50000000000011 409.00000000000006 27.500000000000004 11.500000000000002 56.500000000000007 327.50000000000006 8.5000000000000018 52.500000000000007 23.500000000000004 38.500000000000007 229.50000000000003 1.5000000000000002 71.500000000000014 16.500000000000004 3691.0000000000005 132.50000000000003 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 -2 11 4 9 -6 8 -8 -7 -4 -9 -3 15 -14 -15 16 -13 -18 19 -17 21 22 -20 -16 -25 -26 27 28 29 -27 -22 +right_child=1 2 3 -5 5 6 7 10 -10 -11 -12 12 13 14 23 18 17 -19 20 -21 30 -23 -24 24 25 26 -28 -29 -30 -31 -32 +leaf_value=-0.00013621802943525612 0.036079727889298543 -0.14841227376206753 -0.16192439276071358 -0.1643160800776175 -0.17177739550314788 0.10740760149524831 -0.15148887380198306 -0.16432688074794527 -0.16789562774065603 0.12194174869164363 0.048500565293306652 -0.0016287349973512961 -0.18598759697776104 -0.13052460184621015 0.12233248085599763 -0.13025641887246175 0.06806524548003004 -0.18728256136651839 -0.18918549735365464 0.16107104852764606 0.1197676065112699 -0.17488956460813326 -0.0081232467340796449 -0.18934011429224831 0.10698162838393106 0.10260065073077813 0.14763721152665182 -0.19077312112597133 -0.19688051786520522 -0.19058200814279463 -0.19105770619501097 +leaf_weight=72962.425832235778 189.05406086215226 2.5526740364439311 0.96224664594047027 3.058515135140623 2.1387642911286084 18.735461536445658 7.4130205190740517 1.6708501917892125 1.8194204674800847 18.096652426000219 32.831645044498146 839.15484791806375 2.22881138883531 3.2096235462231553 11.642146493308244 2.1621640850789836 33.819686734699644 1.8662543543614436 3.1888961521908703 4.0652938336133957 3.7158963610418132 10.13021513505373 11.497431505937127 2.9442434931406742 13.738870291272177 9.4430148266255909 3.574253858998417 4.1436638275627038 1.5951087297871698 1.1976683188695449 0.98629398364573706 +leaf_count=23884080 61887 831 313 1002 697 6130 2428 549 597 5932 10745 274694 727 1051 3813 708 11071 611 1044 1333 1216 3314 3764 965 4497 3093 1170 1356 520 392 323 +internal_value=0 0.00799808 0.00295914 0.0355442 0.0428502 0.0237469 0.0304409 0.00464739 0.0830391 0.10761 0.038194 3.62764e-05 0.000429244 0.0310298 0.040424 -0.00137595 0.000669456 0.0547114 -0.0514345 0.0599226 -0.0749271 -0.0994642 -0.047438 0.0517888 0.029372 0.0484843 0.00820663 -0.0222193 0.0348616 0.0696013 0.0545714 +internal_weight=0 1242.64 1053.58 86.7266 83.6681 64.6092 62.4704 41.9155 20.5549 19.0589 34.5025 966.857 964.304 53.7174 51.4886 910.587 874.841 35.6859 35.7462 6.22746 29.5187 24.8165 14.6863 48.279 36.6368 33.6926 19.9537 16.3795 12.2358 10.6407 4.70219 +internal_count=24290853 406773 344886 28393 27391 21146 20449 13722 6727 6245 11294 316493 315662 17584 16857 298078 286376 11682 11702 2041 9661 8122 4808 15806 11993 11028 6531 5361 4005 3485 1539 +is_linear=0 +shrinkage=0.1 + + +Tree=167 +num_leaves=32 +num_cat=0 +split_feature=6 6 20 11 4 4 4 5 0 14 1 0 8 3 1 10 10 0 14 10 11 1 20 10 15 0 1 0 14 15 6 +split_gain=7.91781 12.9903 9.01833 9.94155 6.10601 7.48447 13.246 7.94982 5.82607 5.01204 4.75056 8.46885 10.7548 10.9237 6.94644 13.202 7.28963 6.42978 9.63131 11.7453 11.9095 11.0253 11.5689 10.0432 10.3111 18.4959 24.7803 16.7446 6.97076 6.54529 16.3876 +threshold=6471.5000000000009 7781.5000000000009 54.500000000000007 30.500000000000004 1565.0000000000002 5205.5000000000009 8087.0000000000009 29959.500000000004 145.50000000000003 1.5000000000000002 208.50000000000003 4.5000000000000009 33372.000000000007 1.0000000180025095e-35 190.00000000000003 378.50000000000006 549.00000000000011 9.5000000000000018 5.5000000000000009 333.50000000000006 1920.0000000000002 493.50000000000006 4.5000000000000009 217.50000000000003 7.5000000000000009 11.500000000000002 484.50000000000006 22.500000000000004 3.5000000000000004 3.5000000000000004 10119.500000000002 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 -2 10 8 -5 7 -7 9 -4 -6 14 12 13 -12 -3 -16 -17 28 29 21 -21 23 -23 24 25 -20 -27 -26 -13 30 -19 +right_child=1 2 3 4 5 6 -8 -9 -10 -11 11 17 -14 -15 15 16 -18 18 19 20 -22 22 -24 -25 27 26 -28 -29 -30 -31 -32 +leaf_value=-0.00013440692773935997 0.031931892922610804 -0.00091447614686425741 0.11100838589242866 -0.16251419484522703 0.099087233486073928 -0.13275322809376347 0.021693975071340455 -0.1490987373568215 -0.17137031223823807 -0.17644558967095514 -0.17577902262496001 -0.18503962006440353 0.088523725031645698 0.083002816588599593 -0.17083455628323274 0.10827268321163304 -0.15637787370691736 -0.16117070265954114 0.13173875421243639 -0.17931205923623939 0.034947113746738506 -0.19409696781537938 0.10825904543845997 0.094208381236881314 -0.18506959481527566 -0.18126249649978252 0.1360570413956935 0.091811130693578588 0.080813473882607123 0.1093673640616467 0.09023214101177908 +leaf_weight=72964.133140011094 190.80782361650199 813.3819329644175 17.859059492126111 1.8491023003007274 18.289712765486918 6.555393863527569 36.313260799870477 1.5122709156712506 0.76182192575652052 0.684911546879447 7.096301806624977 1.0267134075693345 3.5519407657411639 2.1180356908589602 6.7129362627165374 3.9893304342695046 1.4081579580088144 3.8080059178755645 4.1234771610470515 6.9719222802377754 4.1316644159087446 4.632250018650665 1.741140531376004 15.852537233964538 2.5456427300232489 10.835657081741372 3.1842151344753793 15.38257259968668 25.039451277669286 14.917717527918283 8.1251524531398882 +leaf_count=23886480 62467 266272 5850 608 5991 2146 11882 494 251 224 2328 338 1162 694 2195 1307 463 1247 1348 2284 1352 1516 571 5186 831 3547 1042 5036 8200 4882 2659 +internal_value=0 0.00793938 0.00355606 0.0350123 0.0166088 0.0218367 -0.0019238 0.0715555 0.0994556 0.0891415 0.000811 0.0181954 -0.0593084 -0.116295 -0.0020338 -0.0772121 0.0392276 0.0262844 0.0143532 -0.0053227 -0.0995858 0.0126311 -0.111497 0.027867 -0.00128831 -0.0544355 -0.109192 0.0524966 0.0703419 0.0652091 0.0100066 +internal_weight=0 1235.21 1044.4 83.8255 65.2047 63.3555 42.8687 20.4869 18.6209 18.9746 960.577 135.084 12.7663 9.21434 825.492 12.1104 5.39749 122.318 96.252 69.4011 11.1036 58.2975 6.37339 51.9241 36.0716 18.1433 14.0199 17.9282 26.0662 26.8509 11.9332 +internal_count=24290853 404373 341906 27446 21345 20737 14028 6709 6101 6215 314460 44223 4184 3022 270237 3965 1770 40039 31501 22713 3636 19077 2087 16990 11804 5937 4589 5867 8538 8788 3906 +is_linear=0 +shrinkage=0.1 + + +Tree=168 +num_leaves=32 +num_cat=0 +split_feature=6 6 2 2 6 12 18 12 6 15 12 14 14 2 2 0 9 15 15 21 2 2 18 15 6 21 4 9 9 6 12 +split_gain=8.17539 9.25659 10.2922 8.57331 6.1667 8.52727 8.49307 6.64347 6.17152 13.3432 9.40484 7.20024 27.0783 11.1657 9.5803 17.3794 12.3984 9.55839 8.85404 8.74362 8.41058 8.23103 8.09028 7.27527 11.5417 9.68782 11.2319 7.96967 8.91921 7.93229 7.29602 +threshold=6471.5000000000009 7781.5000000000009 151.50000000000003 143.50000000000003 8049.0000000000009 3.5000000000000004 9.5000000000000018 5.5000000000000009 8144.0000000000009 6.5000000000000009 2.5000000000000004 18.500000000000004 13.500000000000002 50.500000000000007 85.500000000000014 21.500000000000004 20901.500000000004 45.500000000000007 11.500000000000002 1.0000000180025095e-35 129.50000000000003 50.500000000000007 24.500000000000004 12.500000000000002 9385.5000000000018 1.5000000000000002 8944.0000000000018 23937.000000000004 38636.000000000007 8320.5000000000018 4.5000000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 -2 3 4 5 6 -3 -7 9 10 -6 12 20 19 15 16 -13 -15 -17 -14 23 -18 -16 27 -25 -26 -27 29 30 -10 -29 +right_child=1 2 -4 -5 8 7 -8 -9 11 -11 -12 14 13 17 22 18 21 -19 -20 -21 -22 -23 -24 24 25 26 -28 28 -30 -31 -32 +leaf_value=-0.00013628997293785606 0.028231776708805986 0.074228609041794055 0.030667390907328562 -0.15439545572788704 0.1052110995702194 0.10658414378009842 -0.16573005736778068 -0.16194762636780291 0.090547933170648784 -0.16292858629988649 -0.16752949527905855 -0.16208042890739177 0.025891086226366745 -0.14342855689801978 -0.16703192775348871 0.086177021385879904 0.1228103856777487 0.074662801303312418 -0.15927480987421039 -0.15988758999713226 -0.11877808689499823 -0.17630845912569171 0.095799568207036806 -0.1621351079254105 0.11583826288267006 -0.10898907996247692 0.10817164056754683 -0.1595792083499212 -0.012978911666104393 0.0029419607398336119 0.031936250478631738 +leaf_weight=72968.328423062674 192.37939744154573 1.8279029130935649 129.98249519208912 3.5556024086836251 17.101428941707134 3.8144525550305834 7.6399384306860156 1.2146904971450565 10.493721698527223 2.4588180058053686 1.3652372599317448 8.3357893053907919 11.747204006067475 22.296175218420107 1.2332551277941117 15.921593175851742 3.3914309489773578 2.2086623595096162 1.6190820802003134 3.2299376260489217 5.6573884210665701 1.2623899637255815 23.252152213128284 2.305105229374024 17.250905944500122 5.1994595667347294 4.3948741550557315 8.765145250130443 32.190070860320702 685.4360660329985 2.5731662386097005 +leaf_count=23888141 62980 602 42554 1164 5599 1248 2496 397 3435 805 446 2730 3850 7298 406 5213 1110 723 528 1054 1853 414 7609 754 5643 1704 1443 2870 10538 224404 842 +internal_value=0 0.0080845 0.00434948 0.000580929 0.00119035 -0.0635057 -0.119403 0.0417256 0.00224454 0.0559094 0.0850474 0.000951941 -0.00141518 -0.0821965 0.0359645 -0.00140644 -0.0890816 -0.123772 0.0635207 -0.0141735 0.00270408 0.0416718 0.0825615 0.00359826 0.0525995 0.071038 -0.00951432 0.00166657 -0.0398441 0.00426295 -0.116116 +internal_weight=0 1230.1 1037.72 907.742 904.186 14.497 9.46784 5.02914 889.689 20.9255 18.4667 868.764 813.748 39.482 55.0157 30.5303 12.9896 24.5048 17.5407 14.9771 774.266 4.65382 24.4854 768.609 29.1503 26.8452 9.59433 739.458 43.5284 695.93 11.3383 +internal_count=24290853 402712 339732 297178 296014 4743 3098 1645 291271 6850 6045 284421 266411 12925 18010 9995 4254 8021 5741 4904 253486 1524 8015 251633 9544 8790 3147 242089 14250 227839 3712 +is_linear=0 +shrinkage=0.1 + + +Tree=169 +num_leaves=32 +num_cat=0 +split_feature=7 19 19 15 12 9 9 20 9 19 19 7 8 0 0 19 9 0 20 5 9 14 16 20 8 7 8 8 7 15 19 +split_gain=3.75535 9.92491 7.18363 6.92636 4.67626 6.72393 5.61355 6.75041 4.5222 9.24235 6.64835 4.43271 4.33713 4.13658 5.14603 4.09134 3.76534 3.74659 8.42908 6.56749 5.58173 10.7606 11.2963 18.0707 11.2824 7.12011 7.07067 6.19683 9.96254 9.46935 8.59657 +threshold=1028.0000000000002 163.00000000000003 138.50000000000003 57.500000000000007 4.5000000000000009 70.500000000000014 562.00000000000011 118.50000000000001 269.00000000000006 219.50000000000003 367.00000000000006 1255.5000000000002 2.5000000000000004 2.5000000000000004 51.500000000000007 106.50000000000001 209.50000000000003 34.500000000000007 108.50000000000001 135590.50000000003 7862.5000000000009 12.500000000000002 371.50000000000006 3.5000000000000004 5706.5000000000009 1255.5000000000002 5706.5000000000009 25908.000000000004 34017.500000000007 1.5000000000000002 20.500000000000004 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 3 17 5 -3 8 13 9 -7 16 -6 -10 -8 -15 -5 -11 18 -2 20 21 26 24 -24 25 -23 -19 30 29 -29 -22 +right_child=1 4 -4 15 11 6 7 -9 12 10 -12 -13 -14 14 -16 -17 -18 19 -20 -21 27 22 23 -25 -26 -27 -28 28 -30 -31 -32 +leaf_value=-0.00011829480220989976 -0.0019445696181954718 -0.15901931055284729 -0.14355959300969232 0.05484425992123481 -0.19009968867433555 -0.14957813912633397 0.10451888224490916 -0.10874525255617146 0.11012597262729912 0.088958380793153158 -0.15572515660355182 0.097227451458391456 -0.16760570949112785 -0.15596101396768222 0.087834167117889869 -0.16604055127491371 -0.15522391632742175 0.0060084703349383428 -0.15624427438971769 -0.17522936146085355 0.042397463517608001 -0.022758329416286634 0.13150262377282057 -0.19187507736617079 0.045266846053406445 -0.1832305235147659 -0.18098253162669653 0.065425137694475055 0.016921290610765752 -0.1523392092254979 -0.17830464091374298 +leaf_weight=72191.787117538348 817.87128218823636 1.8703627995564591 3.338429966417606 31.685026391380234 0.55492149933707224 2.636095084686537 4.2603679683525097 5.214634174364619 13.763259391533207 12.25652182649355 1.3644178162212472 16.55881423111714 0.58622835273854512 1.8594132424332204 1.6202578902011735 0.86135602719150384 0.66580950532807115 884.78237035381608 3.5557732954912344 1.9880528657231469 101.20059884130023 3.701199328992522 5.9732934825588 2.4314591098809615 4.0882535905111572 10.930414996284524 2.0268089119344941 2.7466533528640849 46.176653532806085 7.3148765433579683 1.7961918739601959 +leaf_count=23637416 267793 611 1094 10374 183 861 1389 1709 4509 4015 447 5422 192 612 532 281 218 289695 1166 651 33136 1213 1953 800 1339 3579 662 899 15120 2394 588 +internal_value=0 0.00427916 0.00300372 0.00325736 0.0432713 0.0266989 0.0345529 -0.0208002 0.0574831 0.0224663 0.0542108 0.0879107 0.0987796 0.0384503 -0.0424414 0.0489984 0.0763771 0.00247242 -0.0026125 0.00635733 0.00669372 0.0036834 -0.0583599 0.0379508 -0.101601 -0.142638 0.0055811 0.0239715 -0.00272544 -0.0928927 0.0385486 +internal_weight=0 1995.68 1932.47 1929.13 63.2111 46.0974 44.227 12.9547 31.2723 16.9228 14.2867 17.1137 14.3495 7.74004 3.47967 32.5464 12.9223 1896.58 821.427 1075.16 1073.17 913.934 27.1246 8.40475 18.7199 14.6316 886.809 159.235 56.2382 10.0615 102.997 +internal_count=24290853 653437 632737 631643 20700 15095 14484 4242 10242 5541 4680 5605 4701 2533 1144 10655 4233 620988 268959 352029 351378 299241 8884 2753 6131 4792 290357 52137 18413 3293 33724 +is_linear=0 +shrinkage=0.1 + + +Tree=170 +num_leaves=32 +num_cat=0 +split_feature=5 15 20 19 20 19 8 18 4 2 9 2 18 20 2 5 19 12 9 2 9 4 4 8 15 18 9 15 20 9 16 +split_gain=3.19617 12.5957 10.0728 14.3983 10.4247 11.0131 10.6718 9.5943 15.151 15.1688 14.8998 12.129 15.6709 23.335 19.3521 18.2251 15.6047 10.1526 10.0602 8.94961 8.91627 8.78113 8.11944 12.5987 13.1157 10.6646 7.95397 16.1262 7.91191 7.80485 8.8337 +threshold=1576.5000000000002 218.00000000000003 100.50000000000001 53.500000000000007 95.500000000000014 64.500000000000014 1.5000000000000002 24.500000000000004 2435.5000000000005 9603.5000000000018 34.500000000000007 2419.0000000000005 1.5000000000000002 44.500000000000007 8234.0000000000018 50629.000000000007 98.500000000000014 1.5000000000000002 554.00000000000011 5780.0000000000009 116.50000000000001 6875.5000000000009 8944.0000000000018 10.500000000000002 4.5000000000000009 14.500000000000002 600.50000000000011 5.5000000000000009 55.500000000000007 293.00000000000006 2.5000000000000004 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 3 -2 5 6 26 10 -9 18 -7 16 14 19 -13 22 -12 -17 21 -14 -16 -10 -15 24 25 -24 27 -5 -22 30 -23 +right_child=1 -3 -4 4 -6 7 -8 8 9 -11 11 12 13 15 20 17 -18 -19 -20 -21 28 29 23 -25 -26 -27 -28 -29 -30 -31 -32 +leaf_value=-0.00010105575875091423 0.0053522764828940386 -0.14320431546027343 0.020324256639434123 0.039385137616867245 -0.16737153059060972 0.081050769788950733 -0.15911767001516564 -0.18428398327326243 -0.18401112415655665 -0.16600072212808717 -0.18352266427291855 -0.11565152221302864 0.16516879429145337 -0.18670359756178928 -0.17815116211981522 -0.1517587670658648 0.13291902027060695 0.06112507873432458 0.096489906376782092 -0.18658155857276204 -0.17667181798835341 0.060376219359847189 -0.16789015462225795 0.062896687753977679 -0.15479524246558024 0.098806591567387386 0.054312343116943509 -0.173412086288524 0.11752027180504761 -0.089348041129538508 -0.15553672807302424 +leaf_weight=72460.830685748515 1164.1509100035619 5.7725179457338518 334.57750084169675 6.5435381293064037 4.882299853314179 11.330961559491696 10.051114001777021 3.4174046907573929 2.164956447784788 3.6394624806707716 1.9641659427434186 7.486287670471941 5.0139882736839345 14.011260828236116 1.2568856213474671 2.9082418504985972 7.5427577155642211 9.7530199226457608 22.649337906739675 0.84526778297731642 0.97607160999905218 25.987833338382192 2.3109843849670169 4.3394285736139855 23.220192629145458 4.2693014922551811 6.8849587347358456 7.8137456891126931 14.410798634751698 5.1460683658951885 2.0439277502009636 +leaf_count=23728472 381223 1892 109559 2143 1599 3717 3291 1122 711 1191 643 2454 1643 4588 414 952 2467 3192 7416 276 319 8508 757 1421 7603 1400 2254 2558 4716 1683 669 +internal_value=0 0.0042638 0.00476115 0.000979739 -0.0229336 -0.019543 -0.0742219 -0.00985877 0.0206687 0.0320331 -0.0276462 -0.0399247 -0.0511764 -0.0761688 0.0178781 -0.0945324 0.067541 0.0122265 0.0444613 0.114425 0.0779392 0.0111189 -0.122604 -0.0962977 -0.119479 0.00514318 -0.034052 -0.0764267 0.0988581 0.0238518 0.044633 +internal_weight=0 1717.37 1711.59 1377.02 212.864 207.982 31.2934 176.689 65.049 61.6316 111.64 100.309 90.8017 66.6717 24.13 60.8124 9.50692 12.6613 57.9921 5.85926 16.6438 35.3428 48.1512 34.1399 29.8005 6.58029 21.2422 14.3573 15.3869 33.1778 28.0318 +internal_count=24290853 562381 560489 450930 69707 68108 10246 57862 21300 20178 36562 32845 29735 21832 7903 19913 3110 4144 18987 1919 5449 11571 15769 11181 9760 2157 6955 4701 5035 10860 9177 +is_linear=0 +shrinkage=0.1 + + +Tree=171 +num_leaves=32 +num_cat=0 +split_feature=6 6 2 21 2 14 12 21 0 1 0 11 0 14 10 2 2 10 10 6 9 0 2 15 1 9 9 0 21 21 15 +split_gain=7.5987 5.88633 5.71447 6.06793 6.03293 5.19485 7.99301 5.85524 5.81263 5.25072 4.95372 9.15519 8.981 7.22941 6.12752 15.0007 11.7275 9.39548 6.78117 6.64581 5.77417 5.09383 4.92014 6.57195 5.70054 5.07844 14.9831 7.67809 6.50464 6.10587 8.37979 +threshold=6471.5000000000009 7781.5000000000009 151.50000000000003 3.5000000000000004 143.50000000000003 18.500000000000004 25.500000000000004 1571.0000000000002 175.50000000000003 190.00000000000003 40.500000000000007 5.5000000000000009 31.500000000000004 1.0000000180025095e-35 157.50000000000003 73.500000000000014 28.500000000000004 3000.5000000000005 176.50000000000003 6673.5000000000009 1286.5000000000002 5.5000000000000009 153.50000000000003 86.500000000000014 204.50000000000003 1035.0000000000002 1835.0000000000002 5.5000000000000009 1.5000000000000002 1.0000000180025095e-35 5.5000000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 10 4 22 5 6 7 9 -7 -3 11 -2 14 -12 19 16 -16 18 21 -13 -18 -17 -4 24 -24 27 -27 -26 29 30 -28 +right_child=1 2 3 -5 -6 8 -8 -9 -10 -11 13 12 -14 -15 15 17 20 -19 -20 -21 -22 -23 23 -25 25 26 28 -29 -30 -31 -32 +leaf_value=-0.00013059856945914312 -0.1408570789784481 0.00080786528708234385 0.13575426843310068 -0.15469357741289855 -0.14344101532766931 0.036847781085547281 -0.15524581243060442 0.11431734130557401 -0.15744145111584218 -0.14811846076007537 -0.21854158607588109 -0.15761716674106985 -0.14817554754273385 0.053674790114379381 -0.15466960388219339 0.069565122571883201 -0.16942028695198011 0.10377361664301893 -0.15434161278115577 0.049196208776424472 0.046898402542837718 -0.16694862195160212 -0.1500206976342725 -0.19374388487198121 -0.15194528874803043 -0.18055945115504637 0.032694222316475917 0.064053415458648802 -0.15881557820480605 0.076768096678743666 -0.060084646520294285 +leaf_weight=72959.227213865146 3.7327728927048147 818.6988451837824 4.0207358525367445 1.8673491911613371 2.8455816554487674 55.826495500688907 3.2870197180891401 4.5350670216139397 1.5835159989073861 2.3742872567090663 0.9931863701203828 1.5934020499698807 3.1787548224674529 55.115947387166671 3.2750573254888868 2.9203349808231023 1.2682320734020311 2.3780470462515941 10.325496339210074 62.491480255412171 45.663630833325442 1.3232041141018269 1.8172841150080774 1.378798210149397 1.7125039813108731 3.7997467748355112 40.935990963829681 42.187872983980924 1.9167129622073833 18.86083441489609 12.772371237253536 +leaf_count=23893065 1219 268112 1317 611 932 18282 1075 1486 519 777 325 521 1040 18053 1074 955 416 778 3383 20472 14951 432 597 452 556 1242 13405 13818 628 6177 4183 +internal_value=0 0.00784428 0.00480694 0.0242831 0.00193158 0.00239831 0.000383478 0.00100309 0.0314888 0.000377217 0.023799 0.0136221 0.017912 0.0488563 0.0219348 0.000826554 0.0282856 -0.0805229 -0.110605 0.044054 0.0410529 -0.00418369 0.0268658 0.023374 0.0257881 0.0284029 0.0131362 0.0556276 0.0230172 0.0278198 0.0106305 +internal_weight=0 1214.68 1020.42 131.27 889.151 886.305 828.895 825.608 57.41 821.073 194.26 138.15 134.418 56.1091 131.239 67.154 50.2069 16.9471 14.569 64.0849 46.9319 4.24354 129.403 125.382 124.003 122.186 78.2857 43.9004 74.4859 72.5692 53.7084 +internal_count=24290853 397788 334169 42986 291183 290251 271450 270375 18801 268889 63619 45241 44022 18378 42982 21989 16441 5548 4770 20993 15367 1387 42375 41058 40606 40009 25635 14374 24393 23765 17588 +is_linear=0 +shrinkage=0.1 + + +Tree=172 +num_leaves=32 +num_cat=0 +split_feature=11 7 19 14 4 12 20 12 2 20 1 10 19 2 15 15 4 1 15 19 4 4 4 20 1 19 1 19 4 7 2 +split_gain=3.14331 3.18275 11.1788 9.79039 14.8919 9.4591 9.13849 9.79935 8.89514 10.8807 9.03714 7.01405 6.907 6.75865 6.42981 8.71583 6.31412 13.6771 5.59142 5.30364 7.46401 5.13789 5.10946 4.88582 4.8553 4.58744 8.65312 5.05032 4.58571 12.3196 9.62561 +threshold=42558.500000000007 1028.0000000000002 72.500000000000014 521.50000000000011 21932.500000000004 1.0000000180025095e-35 36.500000000000007 3.5000000000000004 5262.5000000000009 88.500000000000014 46.500000000000007 5.5000000000000009 52.500000000000007 1693.0000000000002 32.500000000000007 22.500000000000004 8782.5000000000018 53.500000000000007 9.5000000000000018 282.50000000000006 88897.500000000015 88897.500000000015 27713.500000000004 57.500000000000007 1.0000000180025095e-35 66.500000000000014 100.50000000000001 64.500000000000014 27713.500000000004 1667.5000000000002 7591.5000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 -1 3 14 -5 8 7 13 10 -10 22 21 -8 -6 15 25 17 -16 -11 28 -21 -7 -4 -14 -18 27 -27 -3 -13 -30 -31 +right_child=-2 2 5 4 6 11 12 -9 9 18 -12 19 23 -15 16 -17 24 -19 -20 20 -22 -23 -24 -25 -26 26 -28 -29 29 30 -32 +leaf_value=-0.0001497314404447003 0.010420427958940838 0.0021979824416525676 0.079478051036153546 -0.17024698841754021 0.073900056003220013 0.098262445845900451 0.12102983363137887 0.1219759808069415 -0.16392692141063886 -0.15025481519188971 -0.15708043995579568 0.051213123749026972 -0.13465014914437989 -0.16563821423795513 -0.1126872577948666 -0.18515597423117464 0.10190989433678442 0.068179129331841337 0.021421645304634588 -0.15836721720742836 0.11057569882596328 -0.13789956879960621 -0.15067851367152196 0.127100987002218 -0.18530660281407829 -0.1675611019215496 0.072502994119057626 0.1124858530835782 -0.17519561543278106 -0.097053653478386195 0.088558425866028648 +leaf_weight=71891.184003347706 288.35324672596471 1764.4233283063077 14.960325227817522 8.1909682390978578 1.4238369589438677 26.189369008789075 6.3300247550941986 2.0102691522333762 6.5044783049961579 2.2727981471689409 2.077111864462494 56.945793131715618 2.3690912406600551 6.8195178886526264 6.4019418060779598 2.4878257226664564 15.550402749446221 12.051817640662192 11.478183825354789 3.2025044910988063 1.5225384426303206 0.95480779185891052 1.0310332230874326 1.0202064821496604 0.61172254750272337 4.1891391442622981 2.3402812571730465 4.1618486845400176 3.4985043181804967 3.9223221034044391 9.7118075488833693 +leaf_count=23546383 94440 577897 4899 2683 463 8579 2072 658 2131 743 682 18649 777 2238 2094 815 5093 3950 3759 1051 499 313 338 335 200 1375 763 1363 1146 1286 3179 +internal_value=0 -4.06742e-05 0.00390976 0.00180845 -0.0566981 0.030715 -0.0101314 -0.0759874 -0.0118591 -0.0573611 0.0391505 0.0461151 0.0593449 -0.124264 0.0027177 0.00188649 0.0454025 0.00543332 -0.00695349 0.0310141 -0.0717064 0.0899553 0.0646388 -0.0558609 0.091039 0.00214863 -0.0815171 0.00245751 0.0375661 -0.00779424 0.0351608 +internal_weight=0 73875.8 1984.65 1840.38 28.1639 144.272 19.9729 10.2536 38.3239 20.2555 18.0685 105.948 9.71932 8.24335 1812.22 1777.6 34.6159 18.4538 13.751 78.8035 4.72504 27.1442 15.9914 3.3893 16.1621 1775.11 6.52942 1768.59 74.0784 17.1326 13.6341 +internal_count=24290853 24196413 650030 602776 9226 47254 6543 3359 12552 6633 5919 34702 3184 2701 593550 582213 11337 6044 4502 25810 1550 8892 5237 1112 5293 581398 2138 579260 24260 5611 4465 +is_linear=0 +shrinkage=0.1 + + +Tree=173 +num_leaves=32 +num_cat=0 +split_feature=6 19 15 5 20 6 16 21 0 0 0 21 21 11 11 1 4 4 4 0 7 17 21 20 0 11 20 21 21 16 21 +split_gain=6.7311 6.30645 11.934 11.5427 10.1867 7.83726 14.5013 11.4348 9.59586 8.68325 18.809 18.63 16.5748 11.9308 10.9667 9.90294 9.4569 7.70865 7.60734 7.35104 15.1272 7.01167 6.94769 6.90497 21.0674 7.7736 12.542 10.9212 9.86179 6.90448 12.2772 +threshold=6471.5000000000009 1.5000000000000002 1.0000000180025095e-35 61561.000000000007 9.5000000000000018 9504.5000000000018 9.5000000000000018 123.50000000000001 336.00000000000006 67.500000000000014 59.500000000000007 19.500000000000004 34.500000000000007 16.500000000000004 11.500000000000002 1.5000000000000002 9965.5000000000018 9716.0000000000018 3790.0000000000005 8.5000000000000018 6.5000000000000009 29.500000000000004 126.50000000000001 1.0000000180025095e-35 19.500000000000004 928.50000000000011 79.500000000000014 392.50000000000006 281.50000000000006 267.50000000000006 141.50000000000003 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 5 3 -3 -4 19 7 -7 9 10 16 -12 17 -14 -13 18 23 21 -15 20 -2 -11 -9 24 -8 28 27 -27 29 30 -25 +right_child=1 2 4 -5 -6 6 8 22 -10 12 11 14 13 15 -16 -17 -18 -19 -20 -21 -22 -23 -24 25 -26 26 -28 -29 -30 -31 -32 +leaf_value=-0.00012269590543638562 0.11234919939482289 0.063571112563049301 0.011389276145804705 -0.18148111693667279 -0.087808423862713814 -0.18395894444109392 -0.17406792746474159 0.17773938873563538 -0.11571030189290117 0.084226580626279884 0.11620047315180193 0.16450494081166078 -0.17837124138338467 -0.18826960040857632 -0.15931652036868701 -0.13329399617172341 -0.16317050346332196 -0.16898632852400097 0.0097070940194201281 0.059950043531967992 -0.17568877627200385 -0.16358806832555592 -0.17639068989890469 -0.034074108827087494 0.07232680288123243 -0.17549980367554197 0.13401738240855648 0.13847974149023667 -0.17546869133456056 0.02974266521820583 0.046772670534395586 +leaf_weight=72951.946864812606 3.2407682314587865 60.032619310273731 589.72270337046939 1.9857351558748622 10.537151489610549 7.5807988878514134 3.9712246153503683 1.695075795054436 7.2595935701974659 40.165775010245852 3.335430669598284 1.1114196944981838 3.629187040613032 1.9593352797674004 17.724220875068568 5.0928277655038974 3.6982567557133725 1.3124603570904572 206.36404012003914 34.956425200914964 4.1686182714765891 1.1751450949814159 0.82298545725643624 79.796124019660056 27.502456649322994 8.5696702157147211 2.1050956568215033 1.2722812546417106 3.2806047493359065 51.365013819886371 24.566124043019954 +leaf_count=23894531 1063 19664 193163 650 3447 2483 1301 556 2379 13158 1095 362 1186 643 5805 1669 1211 429 67591 11448 1364 385 271 26137 9008 2805 690 417 1073 16822 8047 +internal_value=0 0.00739738 0.0139628 0.0557249 0.00964793 -0.000541156 -0.00400463 -0.122632 -0.00158568 0.000112072 -0.014115 -0.101635 0.0126189 0.00141964 -0.140209 0.00447702 -0.0047014 0.0696076 0.00784507 0.0407724 -0.0497048 0.0771823 0.061998 -0.00180626 0.0412377 -0.00973087 -0.0875255 -0.134911 -0.00388578 -0.000271155 -0.0150434 +internal_weight=0 1210 662.278 62.0184 600.26 547.721 505.355 10.0989 495.256 487.997 228.298 22.1711 259.699 217.045 18.8356 213.416 206.127 42.6534 208.323 42.3658 7.40939 41.3409 2.51806 202.429 31.4737 170.955 11.947 9.84195 159.008 155.727 104.362 +internal_count=24290853 396322 216924 20314 196610 179398 165523 3310 162213 159834 74773 7262 85061 71089 6167 69903 67511 13972 68234 13875 2427 13543 827 66300 10309 55991 3912 3222 52079 51006 34184 +is_linear=0 +shrinkage=0.1 + + +Tree=174 +num_leaves=32 +num_cat=0 +split_feature=6 12 20 21 0 12 4 4 2 18 0 1 7 6 6 2 0 13 2 7 6 7 18 18 0 19 2 0 19 6 6 +split_gain=8.04 4.76874 6.54578 8.66413 6.27067 9.69002 6.02904 6.32821 3.83965 6.27537 8.87535 5.97815 13.4304 11.5527 8.73129 13.0426 8.13337 7.93839 7.88585 9.98837 6.96127 6.51236 5.89001 6.04159 6.39369 5.19711 4.82405 12.3502 15.4826 8.45987 6.98625 +threshold=6471.5000000000009 21.500000000000004 6.5000000000000009 392.50000000000006 11.500000000000002 52.500000000000007 11622.000000000002 21932.500000000004 137.50000000000003 31.500000000000004 91.500000000000014 265.50000000000006 15822.000000000002 9653.0000000000018 9305.0000000000018 228.50000000000003 19.500000000000004 439.00000000000006 153.50000000000003 209.50000000000003 13236.000000000002 41915.000000000007 25.500000000000004 18.500000000000004 4.5000000000000009 4.5000000000000009 115.50000000000001 121.50000000000001 9.5000000000000018 8320.5000000000018 9622.5000000000018 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 6 4 -4 5 -3 8 -8 26 10 22 12 18 14 15 -13 -15 21 19 -11 -20 25 23 -10 -25 -14 -2 28 -28 -30 -31 +right_child=1 2 3 -5 -6 -7 7 -9 9 11 -12 13 17 16 -16 -17 -18 -19 20 -21 -22 -23 -24 24 -26 -27 27 -29 29 30 -32 +leaf_value=-0.00013366552785487221 0.006445475831788121 -0.12320104347342838 -0.15863554667619845 0.073089642706429425 0.075301235057911642 0.11736481596488456 -0.16276743520409345 0.049941678130747569 0.047370628809309284 0.10092247073893451 -0.14928467819161373 0.067840169643287263 -0.15288677225030162 -0.12285009464646218 0.099432288826856799 -0.14546841114705425 0.0092127784101268591 -0.16060446211446938 -0.14589460532464729 -0.14798266705791424 0.073313466951512821 -0.1280523362718497 0.094664146262757598 0.069116649922310189 -0.16509219678094783 0.074939669868310951 -0.14190124564963058 0.079982342116904789 -0.15976308729008479 0.076185261269215085 -0.094536341294460924 +leaf_weight=72958.326078422731 952.19827040078235 4.287395035440567 3.5248215684405286 2.9757041962875519 31.084851012245053 2.7473250491602812 4.1465877917944445 2.1105337548069656 25.521588958508804 4.3794155197683748 2.1740258975769384 8.6688059021544159 1.054730379604744 6.3737701028585461 23.674036497657653 4.2825803027517386 17.379330532450695 2.0031043097842476 12.623744780314153 2.551553783705458 1.6364930192939926 1.9427122379420314 23.288067305926234 1.9095176776172582 2.9918282886501402 19.756143617676571 12.965530153043803 7.9659331850125445 2.6141371950361671 10.384274710930184 3.1163359940983346 +leaf_count=23897038 311889 1402 1155 974 10179 901 1358 689 8365 1434 711 2838 344 2091 7754 1405 5690 655 4136 836 536 637 7629 623 981 6469 4245 2609 859 3400 1021 +internal_value=0 0.00811084 0.0401901 -0.0525602 0.0560068 -0.0292509 0.00687446 -0.0910204 0.00740643 0.0216669 0.048797 0.00740733 -0.019774 0.0280923 0.0633188 -0.00269368 -0.0262242 0.0302459 -0.07821 0.00929101 -0.120738 0.0470473 0.0568146 0.0278417 -0.0738467 0.063393 0.00506807 -0.0303354 -0.0605546 0.00489484 0.0367777 +internal_weight=0 1202.33 44.6201 6.50053 38.1196 7.03472 1157.71 6.25712 1151.46 162.211 55.885 106.326 45.9479 60.3785 36.6254 12.9514 23.7531 24.7567 21.1912 6.93097 14.2602 22.7536 53.711 30.4229 4.90135 20.8109 989.244 37.0462 29.0803 16.1147 13.5006 +internal_count=24290853 393815 14611 2129 12482 2303 379204 2047 377157 53134 18309 34825 15047 19778 11997 4243 7781 8105 6942 2270 4672 7450 17598 9969 1604 6813 324023 12134 9525 5280 4421 +is_linear=0 +shrinkage=0.1 + + +Tree=175 +num_leaves=32 +num_cat=0 +split_feature=6 1 0 17 15 1 1 21 1 8 13 0 13 18 6 20 0 0 6 9 6 21 6 9 6 9 13 10 10 18 0 +split_gain=7.75227 5.11279 9.79548 12.2678 9.99785 9.41489 8.83039 8.55084 7.41721 6.20189 5.94987 5.43152 13.0114 8.42614 6.09715 7.2426 5.65875 7.63124 5.50167 5.4066 5.18401 4.72998 5.32066 4.44098 4.41306 9.4931 13.7844 7.93354 7.65839 11.2003 8.43021 +threshold=6471.5000000000009 128.50000000000003 8.5000000000000018 1.5000000000000002 1.5000000000000002 374.50000000000006 389.50000000000006 2.5000000000000004 124.50000000000001 4.5000000000000009 1203.0000000000002 22.500000000000004 246.00000000000003 16.500000000000004 9385.5000000000018 1.0000000180025095e-35 12.500000000000002 11.500000000000002 8822.0000000000018 43707.000000000007 7029.5000000000009 4.5000000000000009 7029.5000000000009 43707.000000000007 8144.0000000000009 56.500000000000007 10.500000000000002 2236.5000000000005 65.500000000000014 278.50000000000006 7.5000000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 8 3 7 5 21 -7 9 24 -3 19 12 13 18 16 -16 17 -14 -6 -11 -13 22 -4 -22 25 26 -2 -27 29 -26 -30 +right_child=1 2 4 -5 11 6 -8 -9 -10 10 -12 20 14 -15 15 -17 -18 -19 -20 -21 23 -23 -24 -25 28 27 -28 -29 30 -31 -32 +leaf_value=-0.00013122141961455916 0.13694373158960491 -0.16090951179277024 0.050674289485740265 -0.16323600169984634 0.12825826866764245 0.14422910966130489 -0.16996454537205341 -0.16004512703262916 -0.14749656406234812 0.066028835240133116 -0.1443504005692175 -0.14695624056119061 -0.13755862204083688 -0.13945636325126781 0.066623469306457309 -0.15255480302356994 -0.15466855991878281 0.10723454657334056 -0.16264343893569361 -0.12515723069556828 0.031162340076224562 0.089230336496822688 -0.16316060775160646 -0.078062421439048749 0.004665984159616463 0.028199176859824673 -0.1577712059730916 -0.16144442234028536 -0.052858266587909701 0.095945042662660129 -0.0020743830411118847 +leaf_weight=72955.142930834685 2.1213445272296649 1.295086754253133 1.3308147594798381 2.7259191281627855 2.2901170282857488 3.1569919143803409 1.2481685981620101 1.8634164042014152 3.2017509621218769 86.57792866366799 1.4092449460295018 1.7293026080005791 2.0742359167779814 10.631793514068702 19.489711435278881 1.6340502069797356 3.2350585801759735 3.2988449964905158 0.90786254173144687 1.5048575500259165 110.61697799616377 1.0194819759344671 9.2614938704355136 3.852150476872338 600.61555131431669 153.15259519420215 6.3007992072380148 2.2381605620030305 48.391057030094089 13.750532816280609 100.73118801019154 +leaf_count=23897235 694 419 438 895 747 1035 409 610 1049 28365 460 564 680 3485 6382 538 1060 1081 297 493 36233 335 3032 1262 196744 50162 2068 733 15853 4502 32993 +internal_value=0 0.00796667 0.0200501 0.0458528 0.00604955 -0.0692715 0.0552047 0.0520045 0.0044455 0.0563569 0.0595011 0.013601 -0.0165134 -0.0966468 0.0207605 0.0496687 -0.0501782 0.0127341 0.0456753 0.0627625 0.0248906 -0.116494 -0.136294 0.0274867 0.00497012 0.0198633 -0.0835393 0.0254677 0.00177466 0.00670896 -0.0185541 +internal_weight=0 1201.66 271.154 95.3765 175.777 16.017 4.40516 92.6505 930.503 90.7871 89.492 159.76 43.5617 13.8298 29.7319 21.1238 8.60814 5.37308 3.19798 88.0828 116.198 11.6118 10.5923 114.469 927.301 163.813 8.42214 155.391 763.488 614.366 149.122 +internal_count=24290853 393618 88820 31242 57578 5249 1444 30347 304798 29737 29318 52329 14270 4529 9741 6920 2821 1761 1044 28858 38059 3805 3470 37495 303749 53657 2762 50895 250092 201246 48846 +is_linear=0 +shrinkage=0.1 + + +Tree=176 +num_leaves=32 +num_cat=0 +split_feature=6 2 1 12 0 2 12 20 5 20 8 21 8 8 0 14 20 4 20 12 20 21 16 16 5 4 20 21 20 4 7 +split_gain=8.07661 5.20504 4.31553 8.62162 6.91244 4.48188 4.17989 4.36054 13.5743 11.0969 16.6071 6.55651 5.78761 12.1927 8.08136 6.10283 5.59385 4.87125 4.6926 14.118 26.8022 22.6783 17.9777 16.0211 10.9976 9.25493 6.2906 12.4636 6.12486 5.85271 5.31019 +threshold=6471.5000000000009 137.50000000000003 185.50000000000003 1.0000000180025095e-35 1.5000000000000002 144.50000000000003 21.500000000000004 44.500000000000007 54241.500000000007 40.500000000000007 2734.5000000000005 26.500000000000004 208.50000000000003 169.50000000000003 114.00000000000001 8.5000000000000018 39.500000000000007 9470.5000000000018 12.500000000000002 9.5000000000000018 20.500000000000004 96.500000000000014 40.500000000000007 3195.5000000000005 10072.000000000002 10599.500000000002 36.500000000000007 403.00000000000006 18.500000000000004 11622.000000000002 29306.500000000004 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 6 3 -3 -4 -6 7 9 11 16 -11 -9 13 14 17 -14 18 -13 29 22 23 -22 28 -21 -24 -23 30 -28 -20 -2 -26 +right_child=1 2 4 -5 5 -7 -8 8 -10 10 -12 12 15 -15 -16 -17 -18 -19 19 20 21 25 24 -25 26 -27 27 -29 -30 -31 -32 +leaf_value=-0.0001332229623316168 0.0064896234516695385 0.10900183106735926 -0.13889703514906224 -0.15298231535625473 -0.14044434482142093 0.02493094365012756 0.038259594291331904 -0.17017448976480901 -0.16027242249891316 -0.18096698264198385 0.11964892347108216 0.059609891032854995 0.051724463109589926 -0.16660680122191462 -0.16204704472319631 -0.15591562385957342 0.10748698217053983 -0.16678506094162909 -0.023584588594524069 -0.17473470606744224 0.050148216601541756 -0.15862109550675496 -0.17698879237671961 0.10750519120653959 0.0063481548908522339 0.093042836451028746 -0.19062675393125186 0.13187806170542013 -0.17445384403379427 -0.15634927357708278 0.060841987995539887 +leaf_weight=72959.555653973803 718.13350332320988 11.498913310293572 2.6850579988386007 1.4101915317805822 1.6577779830549833 142.96235357303522 37.670444923147443 1.5963582239346568 3.8596358067006795 7.2166077789152023 2.4655086752027273 16.226102888351317 73.297382951073814 4.0691664334735824 2.0860320790670803 1.4433717678766687 5.1163457701914004 1.0095309054013331 4.7089985237689671 18.340259096061349 25.806242211838253 9.5217750740703178 2.5797898746095589 2.2589177088811985 28.923224727448542 1.7261840314604331 3.3078471836633971 1.8790134284645317 6.2788140564225614 2.2139988358831024 46.842850129585713 +leaf_count=23901408 235257 3770 882 462 543 46830 12340 521 1264 2363 808 5318 24016 1333 682 472 1676 331 1542 6005 8458 3119 848 740 9476 565 1083 616 2056 726 15343 +internal_value=0 0.00817621 0.0249421 0.0803827 0.0200836 0.0230352 0.0055647 0.00432177 0.0237369 0.00205521 -0.104416 0.0308583 0.0341287 -0.00928247 0.0238502 0.0477146 0.0032298 0.0463494 0.00261845 -0.0133374 -0.0523377 -0.00150073 0.0104511 -0.143784 0.0262683 -0.119999 0.0327457 -0.0737948 -0.109796 0.00598914 0.0400393 +internal_weight=0 1188.79 160.214 12.9091 147.305 144.62 1028.58 990.907 103.588 887.32 9.68212 99.7279 98.1316 23.3908 19.3217 74.7408 877.638 17.2356 872.521 152.174 57.6534 37.0542 94.5205 20.5992 83.5327 11.248 80.9529 5.18686 10.9878 720.348 75.7661 +internal_count=24290853 389445 52487 4232 48255 47373 336958 324618 33937 290681 3171 32673 32152 7664 6331 24488 287510 5649 285834 49851 18887 12142 30964 6745 27366 3684 26518 1699 3598 235983 24819 +is_linear=0 +shrinkage=0.1 + + +Tree=177 +num_leaves=32 +num_cat=0 +split_feature=6 6 17 10 19 10 0 7 15 11 13 0 1 10 19 20 0 13 19 10 0 13 1 1 15 0 1 0 0 0 17 +split_gain=8.22324 4.64698 4.23996 4.14307 7.86783 6.63888 6.33591 5.45444 7.61721 7.44234 6.38742 5.32537 6.47617 8.44538 5.25491 7.15664 5.2288 4.99983 4.79088 4.29374 4.1669 4.06798 9.69386 11.7449 13.0751 8.28679 11.136 6.88538 6.51226 10.9544 18.1766 +threshold=6471.5000000000009 7781.5000000000009 14.500000000000002 199.50000000000003 25.500000000000004 217.50000000000003 70.500000000000014 19666.000000000004 4.5000000000000009 106.50000000000001 572.00000000000011 44.500000000000007 104.50000000000001 3000.5000000000005 4.5000000000000009 1.5000000000000002 40.500000000000007 133.50000000000003 8.5000000000000018 3376.0000000000005 3.5000000000000004 485.00000000000006 71.500000000000014 30.500000000000004 2.5000000000000004 126.50000000000001 77.500000000000014 1.0000000180025095e-35 9.5000000000000018 12.500000000000002 29.500000000000004 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 3 4 7 -5 11 9 20 10 14 12 -7 -14 15 -2 -17 -13 -16 -8 -9 27 23 -23 -25 -24 -27 -3 -29 30 -30 +right_child=1 21 -4 5 -6 6 19 8 -10 -11 -12 17 13 -15 18 16 -18 -19 -20 -21 -22 22 25 24 -26 26 -28 28 29 -31 -32 +leaf_value=-0.0001340476130384981 -0.14839752002995274 -0.06255323893934471 -0.17730290775324964 -0.16820802971218168 -0.18300480942312453 0.020093084116188389 -0.15223713163384153 -0.14963244194015951 -0.17352981305835791 0.064170613494440421 -0.1526747049684708 -0.17426050616538238 -0.13573494619209034 0.087184905248705633 0.088680263360255709 -0.1403408085064746 0.09841327108718767 0.090617672199140895 -0.026985911773156148 0.099564420656114538 0.034151802236734641 0.10538538196892895 0.041848364659269568 0.078685145346642366 -0.13737229075796509 0.14220788789801819 -0.16439984576597333 0.024082216449460948 -0.12946647754860766 0.0025279033789882056 0.10920664572479921 +leaf_weight=72960.134042629536 3.7818893170988357 15.649172366247511 1.0527544006981759 2.157660918426699 1.625500038237077 40.037820519297384 3.620812278444645 1.3798576074186701 2.5756270314886924 71.562255594995804 2.4097390816896214 0.75665163213852693 6.2872159147227631 2.3290747217833996 11.017666503204966 1.2421362204477189 3.5073103765025735 12.248084973834919 5.3053333963034666 0.83299935981631268 11.643850611842934 4.1965129463933435 97.02517580386484 3.7237183826509854 11.303097192256244 1.6352960341610003 4.2978828234190587 140.34423386806156 14.196218179131394 700.14358858299966 4.1159735249821088 +leaf_count=23903598 1240 5130 344 708 534 13115 1187 454 845 23449 791 247 2059 763 3608 407 1148 4013 1737 273 3812 1376 31785 1221 3704 537 1405 45983 4653 229378 1349 +internal_value=0 0.00827412 0.0228126 0.0239555 0.0354546 0.00440845 0.010042 0.038558 -0.0163956 0.0472322 0.00277268 0.0183621 0.00316825 -0.0754773 0.017844 -0.0457583 0.0359712 0.0752063 0.0510862 -0.105143 0.0146799 0.00556996 0.0226617 -0.0425254 -0.0838322 0.0348328 -0.079893 0.00318183 0.00437966 0.000530934 -0.0758207 +internal_weight=0 1182.01 185.374 184.321 116.051 68.2703 66.1127 114.426 15.5993 98.8263 27.2641 61.6588 48.6541 8.61629 24.8543 8.53134 4.74945 13.0047 16.323 4.45381 13.0237 996.631 122.182 19.2233 15.0268 102.958 5.93318 874.449 858.8 718.456 18.3122 +internal_count=24290853 387255 60734 60390 38025 22365 21657 37491 5111 32380 8931 20197 15937 2822 8140 2795 1555 4260 5345 1460 4266 326521 40028 6301 4925 33727 1942 286493 281363 235380 6002 +is_linear=0 +shrinkage=0.1 + + +Tree=178 +num_leaves=32 +num_cat=0 +split_feature=6 6 10 19 10 19 13 18 18 13 1 10 13 18 18 21 6 18 1 6 13 1 10 6 6 19 1 15 11 1 1 +split_gain=8.30322 5.46338 4.03697 5.08372 4.46543 3.76502 3.86934 6.09603 7.50051 7.94278 11.7653 8.40274 6.14886 6.20639 8.82764 6.74954 6.39492 4.23867 9.52203 4.68851 4.13941 4.0802 7.5758 7.97576 8.79943 6.32471 4.34492 10.4958 4.2672 7.55471 7.46794 +threshold=6471.5000000000009 7781.5000000000009 253.00000000000003 24.500000000000004 290.00000000000006 100.50000000000001 485.00000000000006 55.500000000000007 61.500000000000007 177.50000000000003 330.50000000000006 1414.0000000000002 446.50000000000006 39.500000000000007 31.500000000000004 2.5000000000000004 8144.0000000000009 29.500000000000004 174.00000000000003 8049.0000000000009 454.50000000000006 71.500000000000014 176.50000000000003 9534.5000000000018 9305.0000000000018 25.500000000000004 159.50000000000003 2.5000000000000004 651.50000000000011 170.00000000000003 180.50000000000003 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 3 -2 -4 6 7 12 -9 -10 11 20 13 14 17 16 -15 19 -19 -3 -11 22 23 24 25 -8 27 -23 30 -30 -28 +right_child=1 5 4 -5 -6 -7 21 8 9 10 -12 -13 -14 15 -16 -17 -18 18 -20 -21 -22 26 -24 -25 -26 -27 28 -29 29 -31 -32 +leaf_value=-0.00013424638839071212 0.035345925581935354 -0.15997706436947304 -0.1870121123564131 -0.16273051341088046 0.0045621402031578518 0.057355267001481414 -0.15112575573325548 -0.16677834439983719 0.0025049043219487928 -0.11425172211343151 0.047266587770927897 0.039119922440032227 -0.16162947623607271 -0.15472672584789984 -0.13592442091206111 -0.15220092965631332 0.046539482934142985 -0.14152064539100617 0.12227482793977618 0.0042014071545312245 -0.0073627707705271536 -0.1670355786777655 -0.14402644042469537 -0.15110607800035189 0.11983775861916442 0.11515371704521182 -0.15410009062818239 0.053749341224356678 0.082393259361397839 -0.1605438802016633 0.028990320387318476 +leaf_weight=72960.687049000888 130.90995411176118 1.7442558823386196 1.2489977598015674 1.3086881388444442 47.073933103529271 13.772999937922576 2.7886075405986066 3.2278217509156084 102.75221880991012 24.12085600086721 9.9217807031527609 5.2862434649723573 2.1758374827913931 1.6197124617174257 4.4941053879447272 1.8521746394690115 62.311220541072544 1.6835107756778578 7.3091684896498919 626.46710463201453 4.2634126401972017 2.2337897960096589 4.0053670877823597 2.4234068575315169 6.7398909293115139 1.3115227837115524 2.3878569358203086 59.657783649046905 1.9543383505661029 3.7101323020178825 33.228812582136015 +leaf_count=23906186 42889 572 406 429 15432 4512 915 1059 33662 7905 3251 1731 713 532 1472 606 20418 553 2394 205271 1397 732 1313 794 2210 430 782 19546 640 1215 10886 +internal_value=0 0.00834306 0.0243454 0.0333854 -0.00038946 0.00543492 0.00470498 0.00235203 -0.0159951 -0.0126693 -0.0484369 -0.0766381 0.006219 0.00673521 0.00373637 0.0359882 0.0414403 0.00472137 0.0728899 0.00374556 -0.0981966 0.0214909 -0.0234979 0.0129 0.0495654 -0.06595 0.029021 0.0457808 0.00389355 -0.0767265 0.0167153 +internal_weight=0 1173.99 180.542 132.219 48.3229 993.444 979.671 859.229 149.572 146.345 43.5923 33.6705 709.657 707.481 641.698 65.7831 63.9309 637.204 8.99268 628.211 28.3843 120.442 17.2688 13.2634 10.84 4.10013 103.173 61.8916 41.2811 5.66447 35.6167 +internal_count=24290853 384667 59156 43318 15838 325511 320999 281536 49005 47946 14284 11033 232531 231818 210262 21556 20950 208790 2947 205843 9302 39463 5662 4349 3555 1345 33801 20278 13523 1855 11668 +is_linear=0 +shrinkage=0.1 + + +Tree=179 +num_leaves=32 +num_cat=0 +split_feature=6 6 13 6 13 2 6 19 13 2 10 6 12 6 6 7 21 21 11 6 17 10 11 14 6 3 2 10 19 11 19 +split_gain=8.23063 4.70364 4.34894 8.6072 5.09268 6.31651 4.40841 8.64316 12.7558 10.2684 8.1373 7.72162 8.00589 7.52428 5.73178 5.42618 6.10211 5.416 4.633 4.29367 3.9755 4.27377 5.83841 4.74188 4.22951 5.94995 4.93693 4.71519 4.73777 4.09042 9.46857 +threshold=6471.5000000000009 8144.0000000000009 961.50000000000011 10633.500000000002 1074.0000000000002 65.500000000000014 8968.5000000000018 4.5000000000000009 58.500000000000007 59.500000000000007 102.50000000000001 8510.5000000000018 1.0000000180025095e-35 8822.0000000000018 8510.5000000000018 10.500000000000002 129.50000000000003 132.50000000000003 511.00000000000006 8727.0000000000018 14.500000000000002 199.50000000000003 340.00000000000006 2.5000000000000004 7162.0000000000009 1.0000000180025095e-35 65.500000000000014 549.00000000000011 7.5000000000000009 109.50000000000001 4.5000000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 20 6 4 5 -4 7 10 14 11 15 -10 -13 -14 18 19 -17 -18 -9 -3 21 29 -23 -24 26 -26 27 28 -25 30 -2 +right_child=1 2 3 -5 -6 -7 -8 8 9 -11 -12 12 13 -15 -16 16 17 -19 -20 -21 -22 22 23 24 25 -27 -28 -29 -30 -31 -32 +leaf_value=-0.00013338083535460518 -0.072724553460050814 0.065650551362474899 -0.17381147950263615 -0.15411203186221545 0.021880365216089843 0.11160911661880304 0.0059326553582304898 0.095485976984422072 -0.13640303182914251 -0.13585298014250577 0.065922639513719655 -0.13746590717803561 0.096116554934374543 -0.16177270271522703 -0.069143263678398112 -0.13682952196486639 0.18213364401982748 -0.13714143756168568 -0.15006015947523926 -0.15102830243725596 -0.16332227780824921 -0.17143338083124879 -0.14026020775880782 -0.14951759035483606 0.037087390393745881 -0.14012660524808318 -0.10343769740739468 -0.078677400020320254 0.043899672594263472 0.044233196867274421 0.038128263231963173 +leaf_weight=72962.833603473395 10.969175441423429 5.9330069790594271 0.82399852469097723 2.2891309800324953 30.601838270711596 13.13714143130346 824.4753807130619 9.0786953864735516 4.4840428016614196 16.506700265788822 17.993651364697143 2.6168851401889688 8.1249861915130133 1.3143727561691783 3.8435593496542415 6.293443944596218 1.3439669869840147 0.87868046399671573 0.83946802956052202 1.081180030480027 1.1564509808376886 1.8429829711094488 2.0841319016180924 1.3781894999556339 47.810503278218675 1.972774531925096 5.5018443887238382 5.4498158781789234 15.618319671048082 97.76665542460978 25.896110791654792 +leaf_count=23907771 3592 1943 271 751 10024 4305 270157 2970 1470 5414 5896 857 2663 431 1258 2062 440 290 277 353 379 604 682 451 15668 647 1803 1786 5119 32031 8488 +internal_value=0 0.0083241 0.00529213 0.0349995 0.0447139 0.0947633 0.00375384 -0.0186079 -0.0463669 -0.0800538 0.0201514 -0.024368 0.0173009 0.0602071 0.0345277 -0.0328799 -0.0865245 0.0559144 0.0747031 0.0322513 0.0215936 0.0225823 0.00453301 0.00859617 0.0125873 0.0300649 -0.018545 0.0022631 0.0282161 0.0335298 0.00514428 +internal_weight=0 1169.11 951.66 46.8521 44.563 13.9611 904.808 80.3326 46.8087 33.047 33.5239 16.5403 12.0562 9.43936 13.7617 15.5303 8.51609 2.22265 9.91816 7.01419 217.447 216.291 81.6586 79.8156 77.7314 49.7833 27.9482 22.4463 16.9965 134.632 36.8653 +internal_count=24290853 383082 311832 15351 14600 4576 296481 26324 15340 10835 10984 5421 3951 3094 4505 5088 2792 730 3247 2296 71250 70871 26760 26156 25474 16315 9159 7356 5570 44111 12080 +is_linear=0 +shrinkage=0.1 + + +Tree=180 +num_leaves=32 +num_cat=0 +split_feature=5 15 17 18 5 9 19 9 12 10 0 12 0 18 12 12 18 5 14 16 17 9 18 9 12 18 10 14 15 19 21 +split_gain=4.25202 4.31395 3.73856 4.69486 7.65005 7.62534 4.71051 4.1661 16.1641 8.64482 7.41249 7.21604 6.4609 4.00689 20.3757 10.3714 8.4667 11.8164 7.95367 7.8326 8.40932 6.02056 5.37528 10.2273 5.2563 5.54162 4.48614 3.89498 5.02055 4.28121 13.3705 +threshold=1576.5000000000002 218.00000000000003 410.00000000000006 180.50000000000003 1717.0000000000002 715.50000000000011 627.50000000000011 261.50000000000006 218.50000000000003 1.0000000180025095e-35 11807.500000000002 1.0000000180025095e-35 10503.000000000002 95.500000000000014 1.5000000000000002 289.50000000000006 89.500000000000014 2849.5000000000005 1.0000000180025095e-35 62.500000000000007 194.00000000000003 4.5000000000000009 136.00000000000003 1.5000000000000002 515.50000000000011 103.50000000000001 12.500000000000002 873.50000000000011 112.50000000000001 96.500000000000014 1.5000000000000002 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 3 6 -5 7 13 8 11 12 -11 -6 -10 16 -15 19 27 -18 21 20 22 24 23 -16 -17 -26 -19 29 -29 -2 -31 +right_child=1 -3 -4 4 5 -7 -8 -9 9 10 -12 -13 -14 14 15 18 17 26 -20 -21 -22 -23 -24 -25 25 -27 -28 28 -30 30 -32 +leaf_value=-0.00011580434447609544 0.0033679631104305517 -0.10121320558951274 -0.1437730464098689 -0.17009409933114125 0.0063221688403809085 -0.16828587442106935 -0.16441307198889507 0.085194215567102716 0.13730599724620191 -0.1600578613428344 0.1359504519184882 -0.16920544188468298 -0.17507397770858502 -0.16024270766725723 0.094716587901044813 -0.13067665700253003 -0.1743711135058765 0.10650403051899211 -0.16776639326726273 0.097915890868040742 -0.16291976303430636 -0.14850398737677301 0.081198949570731385 -0.10896219744459007 -0.17862270276094777 0.065747928373082812 -0.15574532633158003 -0.017927960182724616 -0.17639272656387819 0.021590998572924649 -0.1705210010837396 +leaf_weight=72429.829228026909 1293.3589343800195 3.8187154006154733 1.6830361923130102 1.743278273323084 4.7199794173357095 1.646558659384026 1.6507808504393313 19.921786757477093 12.501391219906509 2.3364698832156132 1.3261267498019149 4.6490123206167482 0.6991329565644272 8.9103912468999606 4.1637351176468709 2.1584998469334122 1.7757899928838039 16.838546393206347 4.7268261520075603 12.854642688122111 2.7823702305322504 2.9205351966083972 8.6704042921774072 6.0436558224610053 1.0322449705563475 9.1872231459710729 0.67858238343614985 36.403061494289432 2.1155257909558705 218.23528726605582 3.6839195275679222 +leaf_count=23735962 423851 1253 550 571 1547 542 543 6530 4096 766 434 1523 228 2923 1366 707 578 5522 1550 4215 913 956 2839 1978 339 3008 222 11928 692 71514 1207 +internal_value=0 0.0049536 0.00519357 0.00534213 0.0356704 0.0431746 0.00442491 0.0507185 0.0245362 0.0830471 -0.0528813 -0.0807769 0.120762 0.00459522 -0.0200424 0.00286252 0.00558898 0.0714273 -0.0543872 0.0360785 -0.000620061 -0.019356 0.0233011 -0.0258788 0.011116 0.0410646 0.096345 0.00477149 -0.0266312 0.00556975 0.0184019 +internal_weight=0 1693.24 1689.42 1687.73 49.5437 47.8005 1638.19 46.1539 26.2321 16.8631 3.6626 9.36899 13.2005 1636.54 63.4505 54.5401 1573.09 19.2929 20.0253 34.5148 21.6602 15.2985 18.8778 10.2074 12.378 10.2195 17.5171 1553.8 38.5186 1515.28 221.919 +internal_count=24290853 554891 553638 553088 16237 15666 536851 15124 8594 5524 1200 3070 4324 536308 20794 17871 515514 6322 6560 11311 7096 5010 6183 3344 4054 3347 5744 509192 12620 496572 72721 +is_linear=0 +shrinkage=0.1 + + +Tree=181 +num_leaves=32 +num_cat=0 +split_feature=6 18 17 13 2 8 9 13 9 11 6 5 13 10 12 11 6 17 17 17 6 1 11 12 1 11 5 11 18 9 14 +split_gain=7.95134 4.91726 8.57201 6.76799 6.7126 6.08304 12.5906 8.04548 6.45542 8.09297 6.44756 6.21943 8.48924 6.59136 11.8729 6.45706 6.01884 6.02503 8.22064 5.6855 12.6616 5.49707 7.88275 8.85065 5.41623 5.39559 8.33335 6.41579 5.56303 9.51179 8.26048 +threshold=6471.5000000000009 1.5000000000000002 4.5000000000000009 13.500000000000002 2.5000000000000004 126.50000000000001 293.00000000000006 10.500000000000002 801.50000000000011 37.500000000000007 37301.000000000007 23284.500000000004 2.5000000000000004 2.5000000000000004 6.5000000000000009 234.50000000000003 9891.0000000000018 2.5000000000000004 3.5000000000000004 22.500000000000004 10462.500000000002 1.0000000180025095e-35 90.500000000000014 7.5000000000000009 81.500000000000014 65.500000000000014 54241.500000000007 84.500000000000014 2.5000000000000004 759.00000000000011 1.0000000180025095e-35 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 3 -3 4 5 -2 10 8 9 -8 -7 12 -11 14 -13 21 -10 -18 -19 25 -21 22 -16 -24 -5 -4 27 -27 29 -29 -31 +right_child=1 2 19 24 -6 6 7 -9 16 11 -12 13 -14 -15 15 -17 17 18 -20 20 -22 -23 23 -25 -26 26 -28 28 -30 30 -32 +leaf_value=-0.00013073169908980905 0.033362512415954969 0.0090647125327030838 0.092051083153960281 0.043281286592042355 -0.16843772003170987 -0.14776498371110619 0.047092840845798754 -0.069659937922017787 0.079225913247985932 -0.11555321087239345 -0.037354068441571388 -0.18281063366048095 -0.026588073052627503 0.042833601397349014 -0.1947408076477771 -0.060810793612055429 0.087278695482410962 -0.19634617569038404 0.0062213782784545681 0.051181188646716759 -0.17696560354525587 0.11700650176294669 -0.16164644073060686 0.055565360288246157 -0.1737391075550184 -0.17368440654164302 -0.15990335095828928 -0.16783506865304784 0.059524783148813147 0.0926221951509167 -0.16942036619554429 +leaf_weight=72960.553656205942 43.382323516889301 571.68619271348871 22.101784948026761 43.793236500452622 2.4426027917070305 8.435298766242342 17.661684513092041 16.663632094743662 12.466963117709382 14.463055372121742 14.179722272790967 4.3545206668786696 41.508247159596067 20.469347202219069 1.8752768445992902 14.014252358232623 9.122795849805696 2.0232919943518928 203.68563954444835 7.0303454160530219 3.7194914011633955 5.105977746017742 2.2071671537123612 12.49851555470377 1.1810073823435221 1.356951419264077 2.1234675885643801 3.0831258581601997 52.204609908571001 6.0570263953413814 1.5011287969537077 +leaf_count=23909924 14213 187346 7234 14352 801 2765 5783 5461 4086 4741 4648 1428 13604 6711 614 4595 2991 664 66749 2304 1217 1673 723 4095 387 440 697 1013 17115 1988 491 +internal_value=0 0.0082056 0.0137729 0.000607161 -0.00311671 -0.00220746 -0.00605815 -0.00172327 0.0014087 -0.0159863 -0.0785368 -0.0255495 -0.0495768 -0.00333004 -0.0269207 -0.00790658 0.0116757 0.00775568 0.00422898 0.040912 -0.0277586 0.0262805 -0.00165787 0.0229642 0.0375824 0.04926 0.0350008 0.0414471 0.0460922 -0.0198062 0.0405778 +internal_weight=0 1162.4 670.864 491.535 446.56 444.118 400.735 378.12 361.457 134.158 22.615 116.496 55.9713 60.5251 40.0557 35.7012 227.299 214.832 205.709 99.1779 10.7498 21.6869 16.581 14.7057 44.9742 88.4281 66.3263 64.2028 62.8459 10.6413 7.55816 +internal_count=24290853 380929 219845 161084 146345 145544 131331 123918 118457 43967 7413 38184 18345 19839 13128 11700 74490 70404 67413 32499 3521 7105 5432 4818 14739 28978 21744 21047 20607 3492 2479 +is_linear=0 +shrinkage=0.1 + + +Tree=182 +num_leaves=32 +num_cat=0 +split_feature=5 18 18 4 7 7 9 16 18 3 17 12 19 17 3 12 2 19 10 12 18 16 17 4 18 3 12 17 7 18 17 +split_gain=5.24804 7.56682 18.8596 7.8326 6.08007 10.0445 8.58751 14.0345 10.0681 8.49585 6.84132 6.974 7.04214 6.40191 6.58917 7.75827 5.30843 5.28182 4.78181 4.18679 10.4505 6.63671 6.63018 6.01161 4.66436 4.21466 4.04554 7.93757 4.7599 9.01951 11.9726 +threshold=1576.5000000000002 74.500000000000014 67.500000000000014 696.00000000000011 1839.0000000000002 686.50000000000011 434.50000000000006 1.0000000180025095e-35 224.50000000000003 20112.500000000004 121.50000000000001 4.5000000000000009 272.00000000000006 53.500000000000007 14983.000000000002 1.0000000180025095e-35 8312.5000000000018 257.50000000000006 21.500000000000004 737.50000000000011 176.00000000000003 33.500000000000007 324.00000000000006 928.00000000000011 213.00000000000003 7395.5000000000009 3277.5000000000005 12.500000000000002 341.50000000000006 21.500000000000004 1.0000000180025095e-35 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 26 -3 5 6 7 -5 18 -7 11 12 -11 14 -9 -16 17 -6 -8 22 21 -21 24 -23 25 -15 28 -28 29 -2 -31 +right_child=1 3 -4 4 16 9 8 13 -10 10 -12 -13 -14 19 15 -17 -18 -19 -20 20 -22 23 -24 -25 -26 -27 27 -29 -30 30 -32 +leaf_value=-0.00012837671996569227 0.0026439491968902047 -0.15817892401052192 -0.14421810871241983 -0.15843668799138277 -0.15352513418869768 -0.14401552424394298 0.12429407808148132 -0.16571456738258231 -0.17319561431157338 -0.15006811052539276 -0.14064553732619764 0.076568102065622243 0.072519196632314251 -0.18163775863800702 0.068149199531363946 -0.16092432504401499 0.10358078943600078 0.084734124810862912 -0.15211610055052749 -0.13238596262034905 0.078769735726584689 0.093548879764981829 -0.14430142936403373 -0.1417406659963259 0.13526459197517873 0.049849974877120264 -0.17642027266977214 0.05612961290264417 0.0063615513577625253 -0.11345710234960814 -0.017164917857895211 +leaf_weight=72437.303261380555 229.03297594469768 2.282883360516279 8.6173456110700482 4.1652770739674443 1.7714054639218404 5.7379176806425702 17.117068933439448 3.1382925059297104 1.3088674816535775 3.5907396404945784 3.0782252894714466 9.3539444977650401 2.3526221768115647 0.79776676092296739 5.5151518156635575 2.0199891241500154 16.659096348565072 1.9598315233306491 0.64962363452650507 6.3960153160151112 9.1255591087392514 3.5078445316758007 1.6989570676523715 1.572748720878735 6.6260467456886536 55.764179888530634 1.5582556995213988 25.273745636863168 1173.3237777386676 16.049243938585278 66.064127720543183 +leaf_count=23738303 75056 747 2824 1365 580 1880 5607 1027 431 1181 1008 3064 769 261 1809 662 5459 643 213 2097 2992 1148 558 514 2171 18274 510 8284 384507 5262 21647 +internal_value=0 0.00551517 0.00330001 0.0257743 0.0283364 0.0210768 0.0329655 0.0212717 0.0944686 -0.037793 -0.00462409 0.022747 -0.0619591 0.0290558 -0.0439665 0.00674013 0.0794333 -0.0283795 0.114187 0.0381727 -0.00110145 -0.0646106 0.0506426 0.0207126 0.0558842 0.046585 0.00414115 0.0426244 0.00344556 -0.00755058 -0.0359854 +internal_weight=0 1686.11 1519.92 166.19 163.907 143.517 119.403 100.328 19.0756 24.1134 18.3755 15.2973 5.94336 96.1626 10.6734 7.53514 20.3903 3.73124 17.7667 85.4891 20.6022 11.4766 64.887 5.08059 63.188 56.5619 1511.3 26.832 1484.47 311.146 82.1134 +internal_count=24290853 552550 498090 54460 53713 47031 39129 32878 6251 7902 6022 5014 1950 31513 3498 2471 6682 1223 5820 28015 6751 3759 21264 1662 20706 18535 495266 8794 486472 101965 26909 +is_linear=0 +shrinkage=0.1 + + +Tree=183 +num_leaves=32 +num_cat=0 +split_feature=7 19 4 14 18 10 18 2 10 10 7 20 2 9 19 9 18 20 19 2 2 1 20 19 14 4 18 18 9 14 19 +split_gain=3.72071 9.64892 20.3928 4.81174 4.24898 19.1123 11.0132 16.8162 10.141 10.0599 8.64512 13.6619 10.4123 7.54226 7.07673 11.8075 7.18701 6.71591 6.66559 6.10271 5.31583 4.99231 5.5879 4.34701 4.17676 4.65072 5.35329 3.91455 6.90395 4.21151 3.87055 +threshold=1028.0000000000002 41.500000000000007 42797.000000000007 2377.0000000000005 50.500000000000007 2.5000000000000004 27.500000000000004 2100.5000000000005 5.5000000000000009 11.500000000000002 1646.5000000000002 57.500000000000007 10579.500000000002 209.50000000000003 77.500000000000014 536.00000000000011 1.5000000000000002 2.5000000000000004 98.500000000000014 11581.500000000002 2617.0000000000005 104.50000000000001 1.0000000180025095e-35 295.00000000000006 413.50000000000006 21932.500000000004 10.500000000000002 59.500000000000007 79.500000000000014 267.50000000000006 14.500000000000002 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 24 4 5 -3 10 -8 27 -10 12 -12 14 19 20 16 -16 21 -18 -13 -7 22 -11 -20 -2 -26 30 -6 -29 -30 -27 +right_child=1 3 -4 -5 8 6 7 -9 9 17 11 13 -14 -15 15 -17 18 -19 23 -21 -22 -23 -24 -25 25 26 -28 28 29 -31 -32 +leaf_value=-0.00011610087261549793 0.0029995816108187301 -0.17741764737804178 -0.16450114570799962 0.072957653599341352 0.14229416374772785 -0.17636254681268179 0.10829269784961207 -0.12721538954675501 -0.15253315463967351 0.027172161843452749 -0.13256334486507082 -0.15537619755091703 -0.15193500588978395 0.055598094334325589 -0.16106488586877776 -0.15829671157537095 -0.16400058742276188 0.064140264230169108 0.091754597657304135 0.13177459231312955 0.089702922595170376 -0.11118561595676626 -0.16420426348126449 -0.15180933955660236 -0.15515261275970191 -0.14783832213258583 -0.1594934373819652 -0.15852177902959383 0.098670502366724144 -0.16123002042996043 0.081038342725202397 +leaf_weight=72181.811547431542 1717.7975429116923 5.3019104420673093 7.3326558491971801 18.94042749714572 9.7978778232354689 0.76602352084591885 4.1939070158405221 10.942833404813426 3.3910808781511141 16.751160786254339 7.4013867034809691 3.3779705505585325 2.4403437111759549 16.813907073461451 1.558763871435082 3.3833115912857457 1.2022252166643763 22.928148653110838 15.318646467814688 0.94778175617102522 38.088307158672251 4.1901078522787421 1.6785972123034287 0.76957748632412482 3.4682468033861351 0.89956182427704323 1.8315017138374967 1.3185082479030814 10.578870110679414 0.66252918529789973 4.1363514937693253 +leaf_count=23655656 562963 1737 2403 6210 3214 249 1376 3584 1111 5489 2425 1108 801 5509 510 1110 398 7517 5022 311 12481 1370 550 251 1136 295 603 431 3461 219 1353 +internal_value=0 0.00432373 0.00191213 0.0249667 0.0200214 0.00791791 0.0170838 -0.0619636 0.0391208 0.0136296 0.0300799 -0.0156369 0.0506192 0.0253012 0.0587109 0.0137156 0.0445907 0.0260005 0.0631309 -0.0924608 0.0844574 -0.0126591 0.00974144 0.0801038 0.00261824 -0.0607611 -0.0130907 0.0949187 0.0579615 0.0833529 0.0401543 +internal_weight=0 1938.21 1735.47 202.744 183.804 112.507 107.205 15.1367 71.2969 48.9391 92.0682 28.541 63.5272 21.1397 61.0869 22.2325 18.8492 45.548 17.2904 4.32575 38.8543 22.6199 18.4298 16.0882 1728.13 10.3357 6.86742 22.3578 12.5599 11.2414 5.03591 +internal_count=24290853 635197 568753 66444 60234 36872 35135 4960 23362 16037 30175 9353 20822 6928 20021 7291 6181 14926 5671 1419 12730 7409 6039 5273 566350 3387 2251 7325 4111 3680 1648 +is_linear=0 +shrinkage=0.1 + + +Tree=184 +num_leaves=32 +num_cat=0 +split_feature=6 18 17 17 4 4 13 5 4 12 4 4 14 10 4 9 12 4 17 10 7 5 16 11 12 4 4 16 11 4 4 +split_gain=7.90111 3.76912 7.26051 6.44721 9.12213 8.06549 7.67459 6.21879 13.9341 5.8225 7.46275 5.80862 8.32458 5.5363 14.6991 9.81747 5.26673 5.06925 10.6476 8.54185 7.91211 12.3084 9.71299 7.65647 12.069 8.18841 12.6153 11.5795 6.69155 6.57505 6.96633 +threshold=6471.5000000000009 1.0000000180025095e-35 3.5000000000000004 24.500000000000004 1745.0000000000002 8884.5000000000018 21.500000000000004 41328.500000000007 9541.0000000000018 5.5000000000000009 3790.0000000000005 1131.5000000000002 1.0000000180025095e-35 2.5000000000000004 8087.0000000000009 693.00000000000011 8.5000000000000018 9057.5000000000018 16.500000000000004 9.5000000000000018 24820.000000000004 19122.000000000004 4931.0000000000009 197.50000000000003 18.500000000000004 6053.0000000000009 5915.5000000000009 559.00000000000011 21.500000000000004 2014.5000000000002 8782.5000000000018 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 -2 11 -5 9 -7 -3 13 10 -6 12 -4 15 -15 -9 -11 18 20 -19 21 -13 28 25 -25 26 27 -20 -22 -30 -31 +right_child=1 7 3 4 5 6 -8 8 -10 16 -12 17 -14 14 -16 -17 -18 19 23 -21 22 -23 -24 24 -26 -27 -28 -29 29 30 -32 +leaf_value=-0.00013014191959117056 0.031604742279570178 0.01009972609401766 -0.14147611015748227 -0.16948260655425773 0.060464539695696887 -0.16866364255994479 0.064360631172979174 -0.16180503574373059 -0.16375239487145843 0.10236429316661844 -0.14909306283244314 -0.013087171255024866 0.13253449033556158 -0.1754979999511474 0.074840760555371144 0.094216584680615389 0.0073878343473028412 0.010371825089492066 -0.16852834731381486 -0.16438035120938535 -0.1617183058179073 -0.13456889619408491 -0.15374065669916537 -0.0041383846422857551 -0.16737567108004484 -0.16510675386465629 0.12507346402575695 0.060599227022324759 -0.16226458287774403 0.042505982835366503 -0.083812738556485494 +leaf_weight=72959.508086133952 64.264241492262954 689.68712738377144 1.3751714109675948 2.1703304262628071 4.2266059215180567 3.3116990509442976 2.4656409078743309 1.5723484090995032 2.9275517763890084 21.966022814274769 2.8421120520215482 53.834413873730227 5.7225425032665953 3.3276402877527271 7.9468192047497723 31.579348223283887 7.9523452466819426 103.02408970400575 7.4939798695850204 2.8751517870696253 1.8937380874995131 9.8692390040378069 3.2225124852266154 31.844393202161882 5.280334217939525 10.687270842026917 3.2123575340956441 3.1255795522592962 1.7555399606935669 63.027927082264796 4.6907699739094815 +leaf_count=23910955 21062 226038 450 713 1384 1085 808 514 960 7198 933 17642 1874 1092 2602 10350 2607 33760 2454 944 618 3235 1056 10436 1730 3504 1053 1025 574 20659 1538 +internal_value=0 0.00819118 0.000656477 -0.00490104 0.0305198 0.0406701 -0.069214 0.0125066 0.0475621 0.0578339 -0.0237921 -0.00998717 0.0794453 0.0614871 0.00095364 0.0820738 0.0771194 -0.0120627 -0.0214324 0.00562732 -0.00602528 -0.0319076 0.0160794 -0.0559972 -0.027356 -0.099363 -0.0485659 -0.101091 0.0237474 0.0288028 0.0337561 +internal_weight=0 1159.17 422.134 357.87 44.9348 42.7644 5.77734 737.041 47.3537 36.9871 7.06872 312.935 7.09771 44.4262 11.2745 33.1517 29.9184 305.837 199.938 105.899 138.294 63.7037 74.5905 61.6439 37.1247 24.5192 13.8319 10.6196 71.368 69.4742 67.7187 +internal_count=24290853 379898 138342 117280 14728 14015 1893 241556 15518 12122 2317 102552 2324 14558 3694 10864 9805 100228 65524 34704 45322 20877 24445 20202 12166 8036 4532 3479 23389 22771 22197 +is_linear=0 +shrinkage=0.1 + + +Tree=185 +num_leaves=32 +num_cat=0 +split_feature=6 9 7 2 18 12 6 1 2 13 4 12 1 12 8 19 2 12 6 4 6 6 6 1 19 18 17 2 1 8 2 +split_gain=7.92411 3.70169 5.00291 11.6537 23.0518 7.94396 17.8713 10.5732 9.73189 11.4583 8.94763 15.1462 13.3666 11.5893 8.06597 7.93492 7.87775 14.0268 9.38182 7.63793 7.01258 6.55782 6.16567 6.12022 6.03216 7.65447 6.17356 5.78941 9.09934 17.3096 11.1785 +threshold=6471.5000000000009 48668.000000000007 5436.0000000000009 134.50000000000003 34.500000000000007 1.0000000180025095e-35 3878.0000000000005 104.50000000000001 1.5000000000000002 1074.0000000000002 5915.5000000000009 6.5000000000000009 36.500000000000007 13.500000000000002 243.50000000000003 3.5000000000000004 29.500000000000004 1.5000000000000002 5364.0000000000009 5044.0000000000009 2947.5000000000005 4558.0000000000009 6311.0000000000009 255.50000000000003 3.5000000000000004 55.500000000000007 71.500000000000014 3.5000000000000004 9.5000000000000018 10053.000000000002 11.500000000000002 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 2 -1 5 -5 15 8 10 -7 -10 11 12 19 -13 -12 -4 -14 -18 21 -8 -11 -19 -15 -6 25 -25 -27 -17 -29 -30 -31 +right_child=-2 -3 3 4 23 6 7 -9 9 20 14 13 16 22 -16 27 17 18 -20 -21 -22 -23 -24 24 -26 26 -28 28 29 30 -32 +leaf_value=-0.000112056198586508 0.0082163238244737195 0.019408217078920015 -0.16131917709515273 -0.084042985097345715 0.11023986973921884 0.15175498644386456 -0.15946468716554252 -0.14715547282615862 -0.17320839436780672 -0.14770389184703445 -0.018256501427238142 0.11626374795302419 0.11247501756172283 -0.15984361177194023 -0.19417796755338432 -0.18797331836124123 -0.16244369371691797 -0.17087331619988563 -0.13262460030288689 0.043021804888871584 0.06420220459047736 0.10494433206090825 0.14629065741517738 -0.18267274023535837 -0.16120488747466533 0.18306743526813976 -0.16593746239942378 0.10833807146815355 -0.17127568427013937 -0.18330313132691484 0.025189981210511959 +leaf_weight=72657.111721640118 1155.5026831030409 96.8383939958876 2.9731245755683622 11.675919580622574 20.814012985472804 1.2762533733621264 9.2710025631822628 5.9907900773105203 17.780517023755241 2.5628746813163152 6.2259298119461146 15.706323529651856 5.890996920643377 2.6607528789900239 4.482856357935816 1.5091114891692985 5.025053184945139 0.95193468686193128 2.6480218925280488 2.3313181120902291 3.9975875737145543 9.1259141764603537 0.87399926874786604 1.0359438669402141 1.7272312252316622 3.0563526507467036 0.60760184901300807 8.2422495367936772 5.5844832435250273 2.7155932160094371 48.490162216679892 +leaf_count=23813141 378712 31739 974 3827 6825 417 3039 1962 5827 839 2040 5148 1932 872 1468 495 1645 312 868 764 1310 2991 287 341 568 1003 195 2700 1830 889 15893 +internal_value=0 -0.000130128 -0.000156096 -0.0157471 0.0335142 -0.0272739 -0.0457942 -0.0200186 -0.117419 -0.131532 -0.00833579 0.00808826 -0.0308685 0.0794462 -0.0918998 -0.00148374 0.0122732 -0.0209808 0.0348785 -0.118778 -0.0185799 0.0788911 -0.0841492 0.0839007 -0.00139753 0.0573323 0.125191 0.00565782 0.0101511 -0.00409924 0.014133 +internal_weight=0 72959.2 72862.3 205.234 38.9171 166.317 96.8021 71.1849 25.6172 24.341 65.1941 54.4853 35.2442 19.2411 10.7088 69.5147 23.6419 17.7509 12.7259 11.6023 6.56046 10.0778 3.53475 27.2411 6.42713 4.6999 3.66395 66.5416 65.0325 56.7902 51.2058 +internal_count=24290853 23912141 23880402 67261 12759 54502 31721 23328 8393 7976 21366 17858 11551 6307 3508 22781 7748 5816 4171 3803 2149 3303 1159 8932 2107 1539 1198 21807 21312 18612 16782 +is_linear=0 +shrinkage=0.1 + + +Tree=186 +num_leaves=32 +num_cat=0 +split_feature=6 5 20 3 20 20 16 14 11 14 1 14 5 20 20 4 16 7 17 16 7 20 16 4 12 19 20 9 20 11 20 +split_gain=6.23953 3.93936 4.18105 11.0743 10.2734 12.7938 10.1541 5.90339 9.47808 5.9191 8.96856 5.20661 4.85364 10.0393 13.4333 11.6582 9.46209 12.6752 8.56545 8.2464 21.8641 9.63826 7.94707 7.38818 6.50202 6.35853 13.3809 15.8613 14.7404 9.21619 6.63691 +threshold=6471.5000000000009 1576.5000000000002 9852.0000000000018 4211.5000000000009 9415.0000000000018 7325.5000000000009 60.500000000000007 29.500000000000004 41.500000000000007 38.500000000000007 31531.500000000004 8.5000000000000018 3357.0000000000005 3780.0000000000005 3602.0000000000005 1131.5000000000002 103.50000000000001 1839.0000000000002 12.500000000000002 68.500000000000014 24.500000000000004 5647.5000000000009 60.500000000000007 5285.5000000000009 501.00000000000006 168.50000000000003 138.50000000000003 658.00000000000011 373.50000000000006 17.500000000000004 129.50000000000003 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 -1 4 -4 5 12 -7 8 -8 -9 -11 -10 16 14 15 -14 19 -18 -19 21 -21 25 -23 24 -22 -3 27 30 29 -28 -27 +right_child=-2 2 3 -5 -6 6 7 9 11 10 -12 -13 13 -15 -16 -17 17 18 -20 20 23 22 -24 -25 -26 26 28 -29 -30 -31 -32 +leaf_value=-0.00020708353826996923 0.0072818160606532003 0.0082435493004469739 -0.16246558385003779 0.076908350419444285 0.10630015194874337 -0.18386974988122554 -0.16420295149646921 0.16027600098489159 0.15939192677330632 -0.17457622630415282 0.084580323563638926 -0.17565162429799538 -0.18141800471548997 -0.19447847007975416 0.17149909015355486 0.0024412469020535926 -0.16209520676605643 -0.18876477513671208 0.0092876582364029674 -0.1901189469922849 0.11759270759453577 0.12386704264795199 -0.19519160416424219 -0.19349765682920661 -0.15240307360778832 -0.19169642409360313 -0.17500329941823395 -0.20067803034042053 0.14884016632906663 0.070168686067904301 0.16128440421712606 +leaf_weight=71840.629267937518 1158.3255984920543 273.99565223057289 4.8000847673974913 3.2353829692583531 10.180898289545437 5.8094352820189696 5.4231170087587071 2.4657648229040205 1.8073856167029592 2.3359823190839961 3.1174387895734981 0.62394428648985911 3.4662782098166636 2.5928089298540717 4.6806244754698119 681.62768788445101 6.2248228594689854 2.3633132596733075 28.730126210022718 2.8959933691658106 25.49674914605567 11.644805846735832 0.83676435612142164 0.83772301487624545 0.92427082956419226 0.55356659786776319 5.3385444064042513 1.4323150375857938 3.3090917346416973 2.1510113971307874 14.115763562731443 +leaf_count=23546402 379652 89805 1574 1060 3337 1903 1778 809 594 764 1022 204 1136 852 1534 223404 2038 774 9417 950 8354 3820 273 275 304 181 1751 470 1086 705 4625 +internal_value=0 -0.000115618 0.00578807 -0.0660846 0.00631073 0.0053809 -0.0708479 -0.0292219 -0.09065 0.031704 -0.0264298 0.0734108 0.00691391 0.00192622 0.00266449 0.001511 0.0159813 -0.031842 -0.00576568 0.0211764 0.0711228 0.0163703 0.102477 0.0988774 0.108148 0.0127984 0.0591926 0.116951 -0.0269296 -0.10459 0.147964 +internal_weight=0 72953.6 1113.02 8.03547 1104.98 1094.8 21.5831 15.7736 7.85445 7.91919 5.45342 2.43133 1073.22 692.367 689.775 685.094 380.851 37.3183 31.0934 343.532 30.1547 313.378 12.4816 27.2587 26.421 300.896 26.9003 16.1016 10.7986 7.48956 14.6693 +internal_count=24290853 23911201 364799 2634 362165 358828 7074 5171 2576 2595 1786 798 351754 226926 226074 224540 124828 12229 10191 112599 9883 102716 4093 8933 8658 98623 8818 5276 3542 2456 4806 +is_linear=0 +shrinkage=0.1 + + +Tree=187 +num_leaves=32 +num_cat=0 +split_feature=5 18 18 6 19 6 19 19 11 19 11 5 8 9 16 5 6 11 13 6 10 15 0 20 18 16 0 0 13 5 10 +split_gain=6.49984 7.63842 9.8135 6.79814 6.21072 6.36887 5.21178 11.1108 8.2349 7.8033 8.5459 7.55498 7.17687 6.42407 12.5334 8.85766 5.18971 5.35031 4.90675 4.8045 7.28134 6.85273 7.94916 4.66695 7.91728 6.43093 8.56662 8.46007 12.0605 10.2299 18.5652 +threshold=1576.5000000000002 74.500000000000014 67.500000000000014 14532.000000000002 272.00000000000006 187.50000000000003 110.50000000000001 160.50000000000003 9.5000000000000018 83.500000000000014 6.5000000000000009 25719.500000000004 1.0000000180025095e-35 562.00000000000011 3.5000000000000004 1717.0000000000002 6627.0000000000009 680.50000000000011 1.0000000180025095e-35 7347.0000000000009 1057.5000000000002 9.5000000000000018 80.500000000000014 1.0000000180025095e-35 63.500000000000007 1.0000000180025095e-35 11.500000000000002 155.50000000000003 2.5000000000000004 13948.500000000002 1.0000000180025095e-35 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=3 2 23 16 6 -6 9 -8 11 10 12 -9 -3 14 15 -14 -1 -18 -11 20 -19 -21 -23 24 25 -2 -27 29 -29 -28 -31 +right_child=1 4 -4 -5 5 -7 7 8 -10 18 -12 -13 13 -15 -16 -17 17 19 -20 21 -22 22 -24 -25 -26 26 27 28 -30 30 -32 +leaf_value=-0.00021351633801582854 0.064523876212787976 -0.14684363513651347 -0.1184784755915228 -0.1714048891642817 -0.16063070161922777 0.091218307881879554 -0.1555203035363294 -0.16033261895703019 0.11430779750482388 0.10784248538304118 -0.15325129786056713 0.030987682398940081 0.070335980045119498 0.12630470803340635 0.031701890388781739 -0.14982833573759619 0.014405524339465734 -0.14438695448536096 -0.1484742001747118 0.0038745625139840418 0.0086098512210109231 -0.15509762273330555 0.083648172853263023 0.0020577880763008641 -0.1569365292046786 -0.17190728549786793 0.085898719532274417 -0.15202591071792182 0.0062600691281610166 -0.091461127623090846 0.053040408652824937 +leaf_weight=71851.006492911096 27.601304529591289 2.4547308149922165 6.519864495683577 2.3176792692975132 1.0587894654599974 19.443924865627196 5.1057399252604254 3.3763454367290233 5.3528750381083219 14.241228938219134 2.8541288343258193 5.3102254093100782 2.5010406538494845 6.0307351477676994 86.966136016882956 6.7841315860496252 452.66368984905421 6.2021551636862551 0.7881973360199489 111.22605564461264 6.2404391427116934 5.5696513440925619 1.8604420425253918 1272.9058726466319 2.5813656541868104 2.4712013546377412 31.318523857160471 5.0011956933012689 128.41642547777155 12.570801692781968 30.374146893984289 +leaf_count=23547533 9050 802 2137 760 347 6374 1675 1105 1754 4668 939 1738 818 1975 28499 2227 148351 2032 258 36454 2044 1829 607 417159 846 813 10260 1642 42080 4121 9956 +internal_value=0 0.0061454 0.00394341 -0.000142701 0.0267686 0.0782125 0.0193286 -0.0291957 0.0167449 0.0269048 0.0174764 -0.0433758 0.0221288 0.0261841 0.0199109 -0.0905252 -0.000137221 0.00925344 0.0944003 -0.0085359 -0.0676532 -0.00233671 -0.0953174 0.00447087 0.0172515 0.0191427 0.0131824 0.0153848 0.000326676 0.0424373 0.0107421 +internal_weight=0 1682.03 1519.76 72437.1 162.268 20.5027 141.766 19.1452 14.0394 122.62 107.591 8.68657 104.737 102.282 96.2513 9.28517 72434.8 583.762 15.0294 131.099 12.4426 118.656 7.43009 1513.24 240.335 237.754 210.152 207.681 133.418 74.2635 42.9449 +internal_count=24290853 551243 498064 23739610 53179 6721 46458 6272 4597 40186 35260 2843 34321 33519 31544 3045 23738850 191317 4926 42966 4076 38890 2436 495927 78768 77922 68872 68059 43722 24337 14077 +is_linear=0 +shrinkage=0.1 + + +Tree=188 +num_leaves=32 +num_cat=0 +split_feature=7 19 4 12 1 13 0 16 13 0 1 0 13 19 0 11 13 19 1 19 1 9 19 0 0 12 18 10 8 19 18 +split_gain=4.97178 9.14691 12.4726 3.9673 5.02275 4.49059 3.91763 14.5744 7.28848 4.17552 3.90128 7.09915 12.3719 10.5168 10.5213 9.88787 11.5373 8.51274 8.16197 7.51021 7.24602 11.8445 7.18054 6.84122 6.5118 7.87991 6.50278 6.31582 10.4018 11.2557 7.88743 +threshold=1028.0000000000002 41.500000000000007 42797.000000000007 16.500000000000004 81.500000000000014 14762.500000000002 11494.500000000002 2.5000000000000004 2.5000000000000004 11807.500000000002 143.50000000000003 8.5000000000000018 246.00000000000003 20.500000000000004 5.5000000000000009 272.50000000000006 329.50000000000006 25.500000000000004 49.500000000000007 32.500000000000007 124.50000000000001 43231.000000000007 2.5000000000000004 3.5000000000000004 9.5000000000000018 11.500000000000002 213.00000000000003 5.5000000000000009 275.00000000000006 17.500000000000004 12.500000000000002 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 6 -3 5 -5 10 8 9 -8 11 12 19 14 15 -14 23 20 -19 27 24 -22 -16 -17 25 26 -13 28 29 -2 -29 +right_child=1 3 -4 4 -6 -7 7 -9 -10 -11 -12 17 13 -15 22 16 -18 18 -20 -21 21 -23 -24 -25 -26 -27 -28 30 -30 -31 -32 +leaf_value=-0.00013340588159990553 0.028978329655102032 0.02203980882425198 -0.14714136267928141 0.11360121920640343 -0.16172656536647884 -0.20070616110288209 -0.19779987321976855 -0.1653493149795166 -0.14961810463823219 0.15929717326406412 0.012074445675192854 0.084856469243897101 -0.12233132484952898 -0.16332764039018335 0.047626681954059828 -0.16518597603428831 0.020147416507606132 -0.076776152501619024 0.086679514973322672 -0.09771476574616865 -0.14082346945982513 0.051409296212695181 -0.15705022721855913 0.0043932834037672822 0.0030095229408512426 -0.16904767575833213 -0.1569592700647115 0.047005371002508692 -0.1346555577710562 -0.17665271989089251 -0.0024032881193627657 +leaf_weight=72190.047205920549 18.014202171005316 181.26349103531538 5.536074147654289 10.400639803847293 0.7871220593806364 0.47533760976512063 0.35896623600274136 5.8149887043982744 1.2309778823982922 3.72892311506439 410.89963432331569 20.526674810156699 7.9503120947629204 7.0479245121823615 2.0765784415416411 6.1153908942360413 42.398912251286674 3.5757018353906487 20.973597092262935 7.5161338836187488 8.575804791034896 5.1182112185051656 9.8176018138474301 3.8936457254458219 962.7198838754266 1.4497015560045827 1.175763386680045 46.062855700612999 8.1048000213340838 3.1234610582469022 108.21057598733751 +leaf_count=23663160 5908 59413 1814 3409 258 156 117 1905 406 1223 134688 6728 2605 2310 681 2005 13898 1171 6876 2464 2811 1678 3218 1276 315571 475 383 15100 2656 1024 35466 +internal_value=0 0.00502913 0.00271581 0.0256773 0.08221 0.0998643 0.00319913 -0.0559265 0.0637028 0.12794 0.00358515 0.000890375 -0.0135237 -0.0467273 -0.0353534 -0.0184136 -0.00264926 0.00469521 0.0628716 0.000259644 0.0032664 -0.0689755 -0.121316 -0.0992174 0.00426986 0.0566775 0.0717557 0.00427231 -0.0383382 -0.00140727 0.0123491 +internal_weight=0 1914.94 1722.02 192.927 11.6631 10.876 1716.48 11.1339 5.31887 4.08789 1705.35 1294.45 270.332 79.3004 72.2524 60.3583 52.4079 1024.12 24.5493 191.032 999.566 13.694 11.8942 10.009 985.872 23.1521 21.7024 183.516 29.2425 21.1377 154.273 +internal_count=24290853 627693 564457 63236 3823 3565 562643 3651 1746 1340 558992 424304 88611 25993 23683 19784 17179 335693 8047 62618 327646 4489 3899 3281 323157 7586 7111 60154 9588 6932 50566 +is_linear=0 +shrinkage=0.1 + + +Tree=189 +num_leaves=32 +num_cat=0 +split_feature=11 10 10 17 21 21 19 2 0 16 0 8 16 12 10 19 21 17 10 16 8 19 17 11 17 2 16 2 21 8 20 +split_gain=3.24182 14.2 19.3659 12.1158 10.0931 18.7124 12.3216 19.7048 21.7851 13.9451 12.4056 11.2645 13.4956 10.1098 8.92997 12.7774 10.241 8.59581 9.25498 30.3165 11.7511 8.5228 8.38891 8.06299 18.1094 7.74388 10.3138 14.773 22.9817 12.5465 10.9585 +threshold=295.00000000000006 10857.500000000002 11117.500000000002 339.50000000000006 1669.0000000000002 711.50000000000011 96.500000000000014 5.5000000000000009 1.0000000180025095e-35 9140.0000000000018 1.5000000000000002 8.5000000000000018 11872.000000000002 191.50000000000003 10458.000000000002 15.500000000000002 2.5000000000000004 46822.500000000007 15.500000000000002 9558.5000000000018 1.0000000180025095e-35 133519.50000000003 349.50000000000006 84.500000000000014 1117.0000000000002 65.500000000000014 6.5000000000000009 238.00000000000003 25.500000000000004 10053.000000000002 1787.5000000000002 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 14 -3 -4 5 6 9 8 -8 25 -10 12 13 -7 -1 16 23 18 -9 20 -20 -6 -5 -16 -25 -2 -27 30 -29 -30 -28 +right_child=4 2 3 22 21 11 7 17 10 -11 -12 -13 -14 -15 15 -17 -18 -19 19 -21 -22 -23 -24 24 -26 26 27 28 29 -31 -32 +leaf_value=-0.00012458935186150809 0.00057053612679741888 -0.19670926370502312 -0.10852511278959712 0.1445063120811266 0.077471906644738547 0.12256227120518476 -0.19548908470969473 0.11183063894047068 0.13738952992915118 -0.19347323664672003 -0.10550451892611677 0.065343794003882391 0.11574112013138212 -0.19828270956282193 0.13306977373556625 -0.1985643266579202 -0.19001898394769076 -0.19703672504887282 -0.19625919119833801 -0.19513384678418433 0.079976872621037781 -0.20484307425115791 -0.01407249972441094 -0.19422785643891441 0.13618527665778971 0.0036262979524950782 0.053717425535843744 -0.18304345518572229 -0.20069525396025509 0.15170535213960845 -0.18170535509654334 +leaf_weight=70591.090880667529 2906.9055818123234 7.6514621276874086 13.711584560340269 3.6567501453682771 24.727848645008638 1.0700152548961366 6.8362495035398743 27.809587681200355 5.3715806422987935 3.6420040606753892 3.4553227273281664 3.4207845186465411 1.8644094457849849 11.952356910333036 15.821813983609909 1.7972972374409426 1.3474357584491361 1.255501745501532 1.6085560366045673 4.94466867626761 36.126151416159701 1.117669220548122 38.021708868327551 2.7712320529390118 4.1322124414145947 242.2733109440087 116.04445391599438 7.1756341760337801 1.2698618470458312 4.942641974426806 2.0114820811431846 +leaf_count=23141892 952975 2508 4497 1199 8104 353 2241 9116 1762 1195 1134 1124 610 3916 5186 589 443 413 523 1620 11848 366 12464 908 1354 79423 38040 2354 417 1619 660 +internal_value=0 -0.000145412 -0.0475846 -0.0269848 0.00300883 0.0025342 0.00308166 0.0401373 -0.0614797 0.00209546 0.0423078 -0.0982919 -0.135893 -0.17192 -0.000103062 0.0586387 0.0778418 0.0623222 0.0669417 0.0376925 0.0682015 0.0652634 -0.000159243 0.093724 0.00354838 0.00231257 0.0158627 0.0384166 -0.0611349 0.0796732 0.0497062 +internal_weight=0 70680 63.0415 55.39 3415.83 3389.98 3371.67 87.4076 15.6632 3284.26 8.8269 18.3076 14.8868 13.0224 70617 25.87 24.0727 71.7445 70.489 42.6794 37.7347 25.8455 41.6785 22.7253 6.90344 3280.62 373.717 131.444 13.3881 6.2125 118.056 +internal_count=24290853 23171040 20668 18160 1119813 1111343 1105340 28657 5137 1076683 2896 6003 4879 4269 23150372 8480 7891 23520 23107 13991 12371 8470 13663 7448 2262 1075488 122513 43090 4390 2036 38700 +is_linear=0 +shrinkage=0.1 + + +Tree=190 +num_leaves=32 +num_cat=0 +split_feature=5 13 8 17 10 17 6 11 11 1 1 1 8 8 13 8 1 12 8 12 20 1 12 19 1 1 6 6 11 1 20 +split_gain=5.60086 5.03875 6.15626 4.20431 11.4966 5.72743 4.15946 4.33943 4.34774 7.32952 5.95056 7.76554 7.16036 6.83199 5.86543 5.76676 4.84924 9.64329 4.53951 9.57371 10.0481 4.75164 4.58547 9.22953 5.24118 5.22721 12.2426 5.86876 4.63291 9.10527 4.53721 +threshold=1576.5000000000002 10.500000000000002 1.5000000000000002 257.50000000000006 1.0000000180025095e-35 208.50000000000003 6627.0000000000009 680.50000000000011 307.50000000000006 484.50000000000006 39.500000000000007 59.500000000000007 21463.500000000004 10178.500000000002 22.500000000000004 1132.0000000000002 33.500000000000007 23.500000000000004 21463.500000000004 1.5000000000000002 5.5000000000000009 68.500000000000014 4.5000000000000009 5.5000000000000009 484.50000000000006 50.500000000000007 8727.0000000000018 11196.500000000002 225.50000000000003 389.50000000000006 1.5000000000000002 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=6 -2 -3 5 -5 -4 -1 8 16 10 12 13 15 -12 -13 -10 17 -8 22 20 -20 -21 -18 24 -24 -25 -27 28 30 -30 -28 +right_child=1 2 3 4 -6 -7 7 -9 9 -11 11 14 -14 -15 -16 -17 18 -19 19 21 -22 -23 23 25 -26 26 27 -29 29 -31 -32 +leaf_value=-0.00020047056043830791 0.003480994092903008 0.069387543931966014 0.012317613446343171 0.11238256514113978 -0.1559655844394402 -0.15497506437052877 -0.15989550476352543 -0.0079038784234825767 -0.15264801836913505 -0.16928020504514829 -0.15520110891715647 -0.15880767841930915 -0.15246620949854672 -0.0015588408443849297 0.029062866340657356 0.10201440619195397 0.018934731359288654 0.072092105530975864 -0.11880618662332849 -0.13861628578741239 0.093930054756272946 0.039314375484439196 -0.1461196154056246 0.082360307359092361 0.058502214146775824 -0.16520341328124061 0.070126546244230953 -0.16578871076802418 -0.14903052359475591 0.13101430342453921 -0.16192703651955384 +leaf_weight=71829.838772624702 1436.7816463757845 22.148142759499024 202.20689621879865 10.346638265327782 1.8878079493879329 2.0673943670117287 4.8105771559603445 128.85168356059876 0.93160035146866604 1.9225121597992245 4.4069288243772453 1.6814529472030688 1.2901849619229313 8.4313024295261112 142.28007311161491 19.539999202825129 188.10026021671365 2.8553893577518465 13.304950579244176 1.6522457193350444 2.664946810778928 16.381434322524001 7.0793472784280294 7.0962202833034089 1.5206545040127819 4.4734291589411468 14.903004058869559 1.6485044373548579 3.3622319700662038 1.7733823512680826 0.89307539910078126 +leaf_count=23550758 471078 7262 66296 3392 619 679 1574 42250 307 630 1446 554 424 2763 46643 6404 61670 941 4363 542 873 5371 2319 2327 498 1466 4888 540 1101 582 293 +internal_value=0 0.00571605 0.0191717 0.0140348 0.0709757 0.0106245 -0.000132257 0.00828866 0.0128944 0.0249326 0.0270236 0.0202228 0.0760252 -0.054299 0.0268686 0.0904255 0.00492181 -0.0734857 0.00719125 -0.0269205 -0.0833062 0.0230124 0.0122158 -0.0173476 -0.109938 0.00596969 -0.0140677 0.0158742 0.0301813 -0.0523281 0.0570067 +internal_weight=0 1675.44 238.657 216.509 12.2344 204.274 72411.7 581.855 453.004 180.484 178.562 156.8 21.7618 12.8382 143.962 20.4716 272.52 7.66597 264.854 34.0036 15.9699 18.0337 230.85 42.7498 8.6 34.1498 27.0536 22.5802 20.9317 5.13561 15.7961 +internal_count=24290853 549326 78248 70986 4011 66975 23741527 190769 148519 59171 58541 51406 7135 4209 47197 6711 89348 2515 86833 11149 5236 5913 75684 14014 2817 11197 8870 7404 6864 1683 5181 +is_linear=0 +shrinkage=0.1 + + +Tree=191 +num_leaves=32 +num_cat=0 +split_feature=5 18 18 9 9 4 4 2 2 11 18 18 14 16 11 17 4 17 2 9 11 2 9 9 5 11 16 16 16 16 4 +split_gain=5.19627 7.57514 6.69484 5.97193 6.53848 5.90531 5.37915 4.62076 6.87538 6.59699 4.99477 4.48731 4.40489 4.08714 3.97763 22.2892 13.0408 12.5894 12.2031 11.6289 19.8204 10.1775 9.72158 15.9177 9.5648 8.98755 19.5127 20.8688 22.3028 15.7555 25.8398 +threshold=1576.5000000000002 74.500000000000014 67.500000000000014 434.50000000000006 1206.0000000000002 35060.000000000007 696.00000000000011 6868.0000000000009 8580.0000000000018 3.5000000000000004 91.500000000000014 265.50000000000006 53.500000000000007 1.0000000180025095e-35 295.00000000000006 5487.5000000000009 1.0000000180025095e-35 46822.500000000007 153.50000000000003 1.0000000180025095e-35 534.50000000000011 137.50000000000003 474.00000000000006 991.00000000000011 69.500000000000014 216.00000000000003 11384.000000000002 6450.5000000000009 8684.5000000000018 9423.5000000000018 1.5000000000000002 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=14 2 -2 5 -5 6 -3 12 10 11 -9 13 -8 -10 25 18 -17 19 21 20 -18 -16 -20 -24 -22 -1 27 -27 -29 30 -30 +right_child=1 3 -4 4 -6 -7 7 8 9 -11 -12 -13 -14 -15 15 16 17 -19 22 -21 24 -23 23 -25 -26 26 -28 28 29 -31 -32 +leaf_value=-0.00021157045334403203 0.003749409020629499 -0.14629891262665076 -0.10418196288559918 0.072988935338600694 -0.15894008892579578 -0.10154570704833686 0.015120992092614456 -0.16439499644247207 -0.13305843083940075 -0.14663490173015883 0.09370718490368507 -0.13088541902959885 -0.15098972571860989 0.027176041239215284 0.0011487127345480477 0.093885152670340616 -0.19678031723489348 -0.19768459063949564 -0.17841655310835267 0.086262603519541778 0.11994766765329977 -0.087843497497660794 0.11151799742309354 0.01913572311102299 -0.20082012270040336 -0.00967149229874123 0.095382827781763199 -0.1920187865782407 0.0850110374869536 -0.096410392904812972 -0.1931997123839363 +leaf_weight=68975.196750118193 1513.0501870574299 1.9059327420836769 5.7689638851006739 31.831290697969962 1.2637933458900068 4.1092544190178151 3.5674735865322864 0.78234351758146914 1.6214670514746092 2.4100584482657714 18.01174786730553 1.9044487063001714 2.8893244260980282 87.209629183926154 2929.1457978911058 42.42860820578062 5.0201634934637749 2.7200291568879029 2.1476200048346064 15.046639584790681 5.554578666749876 12.907608147288554 22.870126864174381 101.10178658665973 1.116439417004585 216.77407300617779 14.445024109503718 13.117255275486967 15.175970023730772 24.79269373085117 4.2799154505773904 +leaf_count=22616932 496132 624 1892 10438 415 1346 1169 256 533 789 5909 625 947 28592 960470 13911 1649 892 702 4930 1823 4232 7498 33156 364 71076 4737 4302 4977 8131 1404 +internal_value=0 0.00550423 0.00333945 0.0263789 0.0641323 0.016336 0.0203626 0.0230455 0.0277901 0.0166578 0.0829631 0.0209951 -0.0592112 0.0242512 -0.000127437 0.00335365 0.0583957 0.00727957 0.00206403 0.0281305 -0.046686 0.000758279 0.032524 0.0361782 0.066265 -0.000285252 -0.017896 -0.0238649 -0.0774989 -0.04355 0.0238101 +internal_weight=0 1676.33 1518.82 157.507 33.0951 124.412 120.302 118.396 111.94 93.1456 18.7941 90.7355 6.4568 88.8311 72403.8 3140.06 71.8865 29.4579 3068.17 26.7378 11.6912 2942.05 126.12 123.972 6.67102 69263.8 288.585 274.14 57.3658 44.2486 19.4559 +internal_count=24290853 549667 498024 51643 10853 40790 39444 38820 36704 30539 6165 29750 2116 29125 23741186 1029627 23569 9658 1006058 8766 3836 964702 41356 40654 2187 22711559 94627 89890 18814 14512 6381 +is_linear=0 +shrinkage=0.1 + + +Tree=192 +num_leaves=32 +num_cat=0 +split_feature=7 15 6 6 7 1 2 1 19 1 8 2 2 14 8 5 2 12 14 14 8 3 12 4 10 8 14 10 2 4 11 +split_gain=4.60789 7.74719 3.99281 9.90425 5.80569 5.47196 4.14294 3.95348 3.86528 3.77499 3.77423 16.4339 16.7828 13.8832 11.0945 10.1051 9.57266 7.51985 10.781 15.6844 5.80355 12.1075 12.5148 11.5341 19.4407 17.3967 13.2676 13.5147 22.813 18.8998 13.4077 +threshold=1028.0000000000002 45.500000000000007 9305.0000000000018 9069.5000000000018 887.00000000000011 145.50000000000003 33.500000000000007 100.50000000000001 33.500000000000007 428.00000000000006 11816.000000000002 44.500000000000007 13.500000000000002 1.0000000180025095e-35 11119.000000000002 1.0000000180025095e-35 491.50000000000006 222.50000000000003 45.500000000000007 36.500000000000007 9737.5000000000018 47.500000000000007 8.5000000000000018 59.500000000000007 11117.500000000002 10833.500000000002 17.500000000000004 11117.500000000002 4.5000000000000009 277.50000000000006 47802.500000000007 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=2 -2 3 10 6 -6 -4 -3 -8 -10 14 12 13 -12 20 -15 -13 18 19 -17 -1 -22 -23 24 25 -24 27 28 -25 30 -29 +right_child=1 7 4 -5 5 -7 8 -9 9 -11 11 16 -14 15 -16 17 -18 -19 -20 -21 21 22 23 26 -26 -27 -28 29 -30 -31 -32 +leaf_value=-0.00011362532043873396 0.0038694534835478664 0.050168787088314661 -0.15143072926529066 -0.15848019392144183 -0.16497954437181525 0.13199487777971836 0.021611415804009059 -0.17439846776336848 -0.15485612616520478 0.067021440843751404 0.079428694667981692 0.11952444374868582 -0.18248159636279745 -0.14189303556085384 -0.15892031316904509 -0.18952170946226921 -0.17145123679392527 0.12759820977886566 -0.19544933293417158 0.16832970124960397 -0.16025761536644317 -0.16766971139976031 -0.11622446906104077 -0.18982608370861273 0.086182501427364028 0.12433247759572358 0.077751050905838934 -0.17516091234755587 0.011315508694843407 0.048942962999339548 -0.027159615487081935 +leaf_weight=71837.946344549986 1860.3581113737528 43.611416814084805 1.4960131253756106 3.9508528779260805 0.68199692363850672 6.8748694952810183 72.428336076671258 0.79829765425529231 2.4404023573442823 1.1181525937281547 16.735614134580828 17.985592683777217 5.0061591357225543 6.9148970862152082 4.4018025457335161 1.8952885335311274 1.20646918111015 3.7363196779042473 2.9837873975047842 3.4621290594805032 5.8549998601665711 5.1109694729093453 11.562993757892398 6.285138566279783 27.303307728725486 4.0625078431330612 11.055251263780518 18.442955859354701 54.823438583291136 7.9502019970677784 9.1616300763562304 +leaf_count=23562788 610203 14306 491 1296 224 2254 23756 260 800 367 5492 5900 1638 2268 1443 621 396 1225 982 1135 1922 1672 3793 2063 8956 1332 3627 6048 17984 2608 3003 +internal_value=0 0.00485481 -0.000128163 -0.000153717 0.0215275 0.105193 0.0133677 0.046132 0.0166122 -0.0851388 -0.000145037 0.0249406 -0.0110048 0.0130223 -0.000165915 -0.0454932 0.101233 0.00969978 -0.0431111 0.0417329 -0.000156209 -0.0190849 -0.0137782 -0.00855713 0.0352739 -0.0536815 -0.026025 -0.0378937 -0.0093723 -0.0869139 -0.126041 +internal_weight=0 1904.77 72152.9 72067.8 85.0398 7.55687 77.4829 44.4097 75.9869 3.55855 72063.9 59.9263 40.7342 35.728 72004 18.9924 19.1921 12.0775 8.3412 5.35742 71999.6 161.613 155.758 150.647 42.9288 15.6255 107.719 96.6634 61.1086 35.5548 27.6046 +internal_count=24290853 624769 23666084 23638192 27892 2478 25414 14566 24923 1167 23636896 19657 13361 11723 23617239 6231 6296 3963 2738 1756 23615796 53008 51086 49414 14081 5125 35333 31706 20047 11659 9051 +is_linear=0 +shrinkage=0.1 + + +Tree=193 +num_leaves=32 +num_cat=0 +split_feature=6 6 10 6 10 16 7 7 11 12 16 4 18 11 0 14 6 11 11 14 8 0 11 0 13 0 13 21 0 0 8 +split_gain=4.42997 3.78785 4.64226 4.32645 3.93457 3.81314 3.51482 6.81521 4.4839 7.44566 8.35243 7.18008 3.97238 5.34876 5.06066 3.90492 3.38696 8.59356 9.32697 7.12283 5.9234 5.19628 4.69934 4.26624 3.78992 5.94369 4.76973 4.51092 5.17323 12.0421 7.1474 +threshold=6471.5000000000009 8234.0000000000018 253.00000000000003 8144.0000000000009 290.00000000000006 77.500000000000014 19894.000000000004 24820.000000000004 279.50000000000006 4.5000000000000009 41.500000000000007 1745.0000000000002 224.50000000000003 220.50000000000003 2.5000000000000004 20.500000000000004 8968.5000000000018 417.00000000000006 272.50000000000006 12.500000000000002 18816.500000000004 4.5000000000000009 207.50000000000003 175.50000000000003 485.00000000000006 126.50000000000001 439.00000000000006 4.5000000000000009 1.0000000180025095e-35 6.5000000000000009 17921.000000000004 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 5 4 -4 6 8 -8 9 12 -11 -12 13 -2 15 -15 17 18 19 22 -19 -20 -3 -22 26 27 -18 28 -26 30 -30 +right_child=1 16 3 -5 -6 -7 7 -9 -10 10 11 -13 -14 14 -16 -17 24 20 21 -21 23 -23 -24 -25 25 -27 -28 -29 29 -31 -32 +leaf_value=-9.7299229510539349e-05 0.045628781241782601 0.0048863983409354233 -0.16401134494106687 -0.14178266377556539 0.0033756410444697169 -0.19339005417107968 -0.13812403211908864 0.013359427851080971 0.057378850459972035 -0.12466796549501379 0.095691624024581812 -0.15736678409268373 -0.059200022781030814 -0.16845363940807978 -0.18226126169665119 0.1479889579560604 0.003489276109548284 -0.14617857320712233 -0.143612747405201 -0.12498683501739889 -0.060569764344104049 0.070619242255157352 -0.12646593751895413 0.10209084995475881 0.081855707547225737 -0.13958759622843805 -0.091990630755380831 0.1280183007240375 -0.14386555908187712 0.032534193803812686 0.004529157255516318 +leaf_weight=72899.26459725693 63.439042961348605 27.853557176771577 1.4407285772613239 2.2651715169340596 55.511280321807135 0.78584551973472017 3.3634966646204729 25.382770432639518 51.527247480349615 6.931955804655443 4.6973315984942019 1.4727437896654008 4.4193262943299478 0.57221908593783077 2.0669991776230745 1.2243302389979362 759.29118206811836 7.4063252164050963 1.2061349985888252 6.2543868611828648 8.2726032260834454 18.470712363254279 3.0189211173565118 2.0027953496610271 12.81513052023365 2.1523383994353926 5.2683252255083053 4.3393216594122341 6.8941455833846685 57.171300907852128 6.1331856545293704 +leaf_count=23912433 20810 9139 474 743 18205 258 1103 8326 16904 2273 1540 484 1448 188 677 403 249061 2429 397 2051 2716 6056 990 656 4205 705 1728 1424 2263 18753 2011 +internal_value=0 0.00614828 0.0177862 -0.0062494 -0.000858788 0.0263664 0.0274124 -0.0043651 0.0341119 0.0199781 -0.0493403 0.0352888 0.0326411 0.0386717 -0.0755624 0.0471988 0.00332701 -0.0171239 0.00182679 -0.0276728 -0.0780042 0.0574874 -0.00795813 -0.0288653 0.00511059 0.0245799 0.00283136 0.0286249 0.0234294 0.0127634 -0.0740023 +internal_weight=0 1153.65 225.1 59.2172 56.952 165.883 165.097 28.7463 136.351 84.8239 13.102 6.17008 71.7219 67.3026 3.86355 1.79655 928.55 74.4854 56.8037 37.1269 17.6817 19.6768 30.8725 10.2754 854.065 89.5054 764.56 87.3531 83.0138 70.1986 13.0273 +internal_count=24290853 378420 73836 19422 18679 54414 54156 9429 44727 27823 4297 2024 23526 22078 1268 591 304584 24434 18633 12180 5801 6453 10129 3372 280150 29361 250789 28656 27232 23027 4274 +is_linear=0 +shrinkage=0.1 + + +Tree=194 +num_leaves=32 +num_cat=0 +split_feature=5 18 20 19 11 21 19 11 20 1 5 18 20 15 8 19 0 16 9 10 19 8 8 13 16 18 18 13 12 16 1 +split_gain=4.28474 8.04278 6.23899 6.8339 7.09562 9.57626 6.2092 6.43996 5.26197 5.07831 4.98945 4.70904 4.5641 4.19296 4.00229 6.666 7.49779 6.87493 5.67363 11.9683 5.84158 5.34199 6.80265 4.65163 5.73042 6.04274 8.87366 9.43698 8.94833 5.28645 4.97038 +threshold=1576.5000000000002 52.500000000000007 189.50000000000003 186.50000000000003 8.5000000000000018 1.0000000180025095e-35 295.00000000000006 2.5000000000000004 249.00000000000003 134128.00000000003 41328.500000000007 61.500000000000007 118.50000000000001 15.500000000000002 4.5000000000000009 41.500000000000007 1755.5000000000002 1.5000000000000002 135.50000000000003 11.500000000000002 62.500000000000007 14.500000000000002 21.500000000000004 15.500000000000002 69.500000000000014 136.00000000000003 107.50000000000001 9.5000000000000018 701.00000000000011 7.5000000000000009 50065.500000000007 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 -2 3 4 5 9 13 -8 -9 12 -5 -6 14 -4 18 16 -16 -18 -3 -20 -21 23 -23 24 25 26 27 28 -19 -27 -31 +right_child=1 2 6 10 11 -7 7 8 -10 -11 -12 -13 -14 -15 15 -17 17 21 19 20 -22 22 -24 -25 -26 29 -28 -29 -30 30 -32 +leaf_value=-0.00011578367592919761 0.002281708530100427 0.05434291919761032 0.0006166367059376504 -0.15194499535277853 -0.15436528706394945 -0.15031283387428165 -0.16310724006156266 -0.15461183027432501 0.06666983051078175 -0.17636486816076744 0.024371527578508165 0.086789932876610479 -0.15293049732322284 0.13951977827297704 0.10048791145834131 -0.16061501962939367 -0.15309708386184562 0.070551799904753187 -0.15392931488723008 -0.14935549697089498 0.057201010676724201 -0.1448261896866207 0.013842238213893979 0.049693371127305881 -0.15272261152255071 -0.16562043392121795 -0.17963626659934548 -0.13408308752078973 -0.14734507131274818 0.082563985668600459 -0.15116540933295175 +leaf_weight=72372.338964742579 1453.2326941949141 52.868521907177637 2.6792249869904508 4.0855436688289037 0.84583818854298698 3.5057573567319187 1.7022316908696655 1.1930069639929559 10.829742025118319 1.3512549375591323 2.6434052801632788 18.966489454149269 1.5421645203023207 11.505827395943923 8.3708220482803863 2.3388541026506564 2.7930788948397085 12.111419003165798 4.4022698079061255 1.5354672112734977 12.640654817572793 4.956680334016708 5.9404174897354096 26.526416831562528 2.4416855261370065 1.0423449295340095 3.3107325717573977 4.1814772277139118 2.2320202873088411 13.94753242388833 0.97332797216950062 +leaf_count=23740838 476714 17340 881 1340 280 1151 560 392 3548 443 866 6220 506 3774 2749 769 914 3971 1444 504 4147 1626 1948 8702 802 339 1085 1372 732 4577 319 +internal_value=0 0.0049976 0.0226596 0.0163471 0.019876 0.013239 0.0668885 0.0189376 0.0447122 0.0167034 -0.0826807 0.0764944 0.0182926 0.113284 0.0199164 0.00602814 0.0104159 0.00104471 0.037638 -0.00989917 0.0348281 0.00658821 -0.0583301 0.0171836 -0.00424675 0.00534431 -0.0288422 -0.00189259 0.0366443 0.0521071 0.0673172 +internal_weight=0 1676.7 223.464 195.554 188.825 169.013 27.91 13.725 12.0227 165.507 6.72895 19.8123 164.156 14.1851 162.614 91.1668 88.828 80.4571 71.4469 18.5784 14.1761 77.6641 10.8971 66.767 40.2405 37.7989 21.8356 18.5249 14.3434 15.9632 14.9209 +internal_count=24290853 550015 73301 64146 61940 55440 9155 4500 3940 54289 2206 6500 53846 4655 53340 29905 29136 26387 23435 6095 4651 25473 3574 21899 13197 12395 7160 6075 4703 5235 4896 +is_linear=0 +shrinkage=0.1 + + +Tree=195 +num_leaves=32 +num_cat=0 +split_feature=6 0 10 8 1 12 0 3 10 20 0 9 11 20 8 8 8 11 3 8 9 5 5 17 17 17 0 0 0 2 20 +split_gain=4.42276 3.50644 7.33018 7.71879 8.74009 6.25983 8.20599 11.5292 6.86592 6.05926 12.8667 5.84898 5.57389 6.12261 5.03932 4.75093 5.27613 5.31586 3.90899 4.28285 3.58743 3.49517 4.71013 4.43345 5.47233 7.41168 5.23101 10.8893 9.65724 6.44934 5.61651 +threshold=6471.5000000000009 11.500000000000002 639.50000000000011 24739.000000000004 81.500000000000014 21.500000000000004 14.500000000000002 1.0000000180025095e-35 148.50000000000003 1.0000000180025095e-35 145.50000000000003 411.00000000000006 3.5000000000000004 57.500000000000007 33372.000000000007 552.50000000000011 208.50000000000003 165.50000000000003 1.5000000000000002 8.5000000000000018 201.00000000000003 1576.5000000000002 3357.0000000000005 303.50000000000006 144.50000000000003 130.50000000000003 5892.5000000000009 6814.0000000000009 5683.5000000000009 269.50000000000006 9852.0000000000018 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=21 2 12 -4 -5 6 7 14 -9 -7 11 20 13 -2 15 16 -3 -18 -8 -20 -11 -1 23 24 25 26 28 -28 30 -29 -23 +right_child=1 5 3 4 -6 9 18 8 -10 10 -12 -13 -14 -15 -16 -17 17 -19 19 -21 -22 22 -24 -25 -26 -27 27 29 -30 -31 -32 +leaf_value=-0.00018270077112254502 -0.14160002511252515 -0.1555511706413979 -0.11117600545765152 -0.13212726794132959 0.039113849816053842 0.079097598651633808 0.0037602022078429714 -0.16527145994444867 0.10205087651808981 -0.16202624380897215 0.10609406938118474 -0.15971373594389474 0.022541955096323594 0.10512692961062181 0.007562535605060712 -0.15488899821843594 0.068194931826718153 -0.16865125935702235 0.085005142619375307 -0.11362667816101778 0.085118140298728259 0.016987790184925557 0.00047020783312373156 -0.1183758559930892 0.053743155849143492 -0.1563970354533171 -0.14807813865503969 -0.1645063879528767 0.11804810862359005 0.0048898585914207964 -0.15138546957944235 +leaf_weight=71792.033524908707 4.0445732045627656 3.0831424279313158 8.7902258879621531 3.7723260246857562 14.201003922586095 20.554840924291057 781.77755942201475 1.1404364929767314 6.0992640363401733 0.80334800059790579 5.7658089222386479 4.7785556278540753 264.03869965024933 1.3386733803199602 4.6931740930303922 10.007302684301974 3.8603629702702165 1.2559396013384683 1.364069141622166 5.3156952454301063 2.1842183582484722 269.68442726496141 690.96838639389171 2.5135819476854513 33.201649531547446 2.6147341596370088 5.2691519351210436 2.3119363035075358 9.5528617136878875 80.691646936225879 1.9958236329257477 +leaf_count=23553440 1331 1010 2882 1234 4662 6742 256484 375 2000 264 1891 1569 86622 440 1541 3285 1264 412 451 1741 717 88476 226695 828 10892 857 1731 763 3135 26464 655 +internal_value=0 0.00615624 0.0155299 -0.0343838 0.00317297 0.00290023 0.00115181 -0.0500577 0.0599408 0.0448886 -0.00707436 -0.0910941 0.0204882 -0.0802455 -0.0848332 -0.10865 -0.0522163 0.0100544 0.00310935 -0.0730642 0.0186617 -9.70326e-05 0.00550023 0.0140222 0.0148433 0.0113726 0.0125597 -0.00867769 0.0192257 0.000171588 0.0157509 +internal_weight=0 1148.87 296.186 26.7636 17.9733 852.684 818.597 30.1396 7.2397 34.0868 13.5319 7.76612 269.422 5.38325 22.8999 18.2067 8.19944 5.1163 788.457 6.67976 2.98757 72890.8 1098.8 407.836 405.322 372.121 369.506 88.2727 281.233 83.0036 271.68 +internal_count=24290853 376917 97171 8778 5896 279746 268563 9887 2375 11183 4441 2550 88393 1771 7512 5971 2686 1676 258676 2192 981 23913936 360496 133801 132973 122081 121224 28958 92266 27227 89131 +is_linear=0 +shrinkage=0.1 + + +Tree=196 +num_leaves=32 +num_cat=0 +split_feature=6 6 10 10 4 18 11 1 17 7 1 18 13 4 6 6 1 6 4 4 11 6 14 21 6 6 16 6 6 14 6 +split_gain=4.53352 3.27357 3.08564 17.7688 28.9554 15.0171 14.5088 11.7019 12.5721 11.2047 11.3303 23.5063 16.3797 12.5898 10.6486 14.9554 11.2895 9.88685 9.37039 15.3557 9.31488 9.60617 9.01081 11.1022 8.65895 8.47556 15.7815 21.7195 14.6487 12.2964 12.6552 +threshold=6471.5000000000009 7781.5000000000009 8193.5000000000018 7568.5000000000009 50.500000000000007 21.500000000000004 103.50000000000001 9.5000000000000018 568.50000000000011 3.5000000000000004 3.5000000000000004 15.500000000000002 1022.0000000000001 42.500000000000007 371.00000000000006 59.500000000000007 2.5000000000000004 202.00000000000003 436.50000000000006 290.50000000000006 30900.000000000004 1.0000000180025095e-35 64.500000000000014 1.0000000180025095e-35 454.00000000000006 111.50000000000001 1.0000000180025095e-35 58.500000000000007 55.500000000000007 313.50000000000006 130.50000000000003 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=2 -2 3 -1 7 6 -6 -5 -9 10 11 18 13 14 15 16 -13 20 19 24 21 -8 -23 -24 25 26 -4 28 -28 30 -27 +right_child=1 -3 9 4 5 -7 17 8 -10 -11 -12 12 -14 -15 -16 -17 -18 -19 -20 -21 -22 22 23 -25 -26 29 27 -29 -30 -31 -32 +leaf_value=-0.00012698020418221842 0.018913549420271181 0.0039874571455512367 0.073932913808670689 -0.15911457311357957 -0.19691338241642978 0.15996839701317966 -0.17319272461683535 -0.010878079389695421 -0.18969417137574407 -0.17683627080709674 0.015030809175644914 -0.18899665984140404 -0.1941601369575906 0.056889141060521266 0.15581439269156017 -0.19962762649179203 0.1597854100608731 -0.19305949774007278 0.090488396379279162 -0.16133381850265122 -0.16952167068863797 0.13385588152663824 0.036438820942795352 -0.19603566938435013 0.1353695273910431 -0.19169530656758108 -0.00090591550373248354 -0.15121039621215207 0.1647517274607099 -0.19815745879428787 0.0040262619440755666 +leaf_weight=71576.84718445169 173.15620383149508 970.37831949714018 28.965856697534036 20.541272533242591 3.8683244751300654 5.7455881261266759 2.1337527066934845 16.425013572123138 5.169221324438694 3.3839010811643666 609.63289559208351 1.4561219492461557 9.9011468882672471 8.428957026684655 1.4902009712532152 8.7948960261419398 2.5589474034495652 2.3428954792179857 11.325150786258744 5.9386817561171474 2.4508864332165095 8.5520998969441262 21.325800174090549 2.2732567798811933 4.7075316014233968 3.8698577925097259 482.04503058706177 9.5677881060983037 5.3977483420167109 4.8279732741066246 22.578573860169854 +leaf_count=23483996 56813 318372 9503 6739 1269 1885 702 5393 1695 1109 200022 477 3246 2768 489 2886 839 769 3715 1948 806 2804 6995 744 1545 1269 158159 3137 1771 1583 7405 +internal_value=0 0.00624759 -9.8013e-05 -0.000183071 -0.0443852 0.00813752 -0.0121749 -0.105081 -0.053683 0.00487876 0.00538217 -0.00423144 -0.0868127 -0.0400505 -0.0971897 -0.126622 0.0332946 0.00611201 0.000420723 -0.00137542 0.0188146 0.0322779 0.0459143 0.014045 0.000314984 -0.000825923 0.00218142 -0.00200026 0.000928512 -0.0514006 -0.0246112 +internal_weight=0 1143.53 72892.5 71667.7 90.8281 48.6926 42.947 42.1355 21.5942 1224.87 1221.49 611.854 32.6303 22.7291 14.3002 12.81 4.01507 39.0787 579.224 567.899 36.7358 34.2849 32.1512 23.5991 561.96 557.253 525.976 497.011 487.443 31.2764 26.4484 +internal_count=24290853 375185 23915668 23513797 29801 15974 14089 13827 7088 401871 400762 200740 10705 7459 4691 4202 1316 12820 190035 186320 12051 11245 10543 7739 184372 182827 172570 163067 159930 10257 8674 +is_linear=0 +shrinkage=0.1 + + +Tree=197 +num_leaves=32 +num_cat=0 +split_feature=6 9 1 1 1 1 21 13 21 7 6 20 0 10 0 21 20 0 20 17 16 16 20 21 0 0 0 16 20 9 20 +split_gain=4.03861 3.06112 2.73192 6.68239 3.15931 8.68088 2.68014 7.20258 10.1772 11.5356 8.53653 7.21201 6.81336 5.3235 5.02456 5.01518 4.84363 8.48706 8.15923 5.047 4.74942 12.105 7.34422 5.67859 10.3581 5.24179 4.58099 13.9371 7.02822 12.7576 9.49254 +threshold=6471.5000000000009 48010.000000000007 153.50000000000003 85.500000000000014 77.500000000000014 49.500000000000007 89.500000000000014 12.500000000000002 96.500000000000014 12615.000000000002 11196.500000000002 51.500000000000007 309.50000000000006 10.500000000000002 32.500000000000007 392.50000000000006 55.500000000000007 145.50000000000003 47.500000000000007 24.500000000000004 2150.0000000000005 650.50000000000011 18.500000000000004 51.500000000000007 98.500000000000014 20.500000000000004 268.50000000000006 58.500000000000007 34.500000000000007 1534.5000000000002 44.500000000000007 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=1 -1 3 4 5 -3 16 8 9 10 -8 13 -10 14 -9 -15 18 19 20 -18 21 26 -23 24 -22 -25 28 -28 -2 -30 -31 +right_child=6 2 -4 -5 -6 -7 7 11 12 -11 -12 -13 -14 15 -16 -17 17 -19 -20 -21 23 22 -24 25 -26 -27 27 -29 29 30 -32 +leaf_value=-0.00011641969192025569 0.0069954023370509578 0.020401722185404474 0.096539850987846385 -0.141377666551498 0.095894105155769932 -0.14456315764082556 -0.17949598357988247 -0.16250549271616985 -0.0022960748987664241 -0.17160307323080079 0.16131233201993747 -0.14431870700431304 -0.15458190223426638 -0.15431960612113865 0.083079243582462811 0.11883141990655231 0.062736102498314308 -0.1628797341177213 -0.16758896209240068 -0.15292947823280009 0.03371097534970826 -0.14412420242469057 0.015937358278681032 -0.16801129603853582 -0.17299075566814329 0.094453723241987975 0.065504775251690198 -0.17017813480562702 -0.17159779432261812 -0.17109238413633207 0.14398406357178528 +leaf_weight=72794.700368615071 762.37195517588771 83.556933595704322 4.187770466967776 2.6891413031262337 4.9941861712140954 3.3165417354321098 1.0866743698716148 0.86513261729852331 207.36896383744897 7.5915433331392705 2.2707230448722839 1.8935252769733768 2.980156665085814 2.0795190851204093 22.497609795769677 0.99321525124832977 33.115194277357659 1.8741539760958392 2.6702859749784684 1.1218657734571014 18.32874589346466 8.5422115147193818 4.3145113247446707 0.79616760602221148 2.7938792372588059 17.185294435359538 28.629977847129343 2.7501128867734215 5.9640844387467933 1.3605591292143797 3.2174000933300704 +leaf_count=23882906 250131 27416 1374 881 1639 1087 356 281 68030 2493 743 623 978 683 7384 326 10865 613 876 368 6010 2801 1417 261 920 5638 9392 903 1956 446 1056 +internal_value=0 -9.25524e-05 0.0175024 0.014002 0.0185503 0.0141039 0.00589378 -0.00326873 -0.00934623 -0.103342 0.0510044 0.044207 -0.00445361 0.0577107 0.0739851 -0.0660276 0.00844922 0.0443267 0.00694084 0.0556693 0.00748513 0.00585591 -0.0904101 0.0415306 0.00637064 0.0828325 0.00739473 0.0448498 0.00587407 -0.0752179 0.0503441 +internal_weight=0 72893.4 98.7446 94.5568 91.8677 86.8735 1144.66 249.627 221.298 10.9489 3.3574 28.329 210.349 26.4355 23.3627 3.07273 895.036 36.1112 858.925 34.2371 856.255 817.151 12.8567 39.1041 21.1226 17.9815 804.294 31.3801 772.914 10.542 4.57796 +internal_count=24290853 23915303 32397 31023 30142 28503 375550 81897 72600 3592 1099 9297 69008 8674 7665 1009 293653 11846 281807 11233 280931 268102 4218 12829 6930 5899 263884 10295 253589 3458 1502 +is_linear=0 +shrinkage=0.1 + + +Tree=198 +num_leaves=32 +num_cat=0 +split_feature=7 15 20 9 18 18 0 18 7 16 16 7 16 18 18 18 1 16 18 13 18 1 0 18 7 7 14 16 1 9 18 +split_gain=3.77816 7.13208 3.2906 3.28761 9.78403 18.1529 9.3659 6.85414 6.60099 5.7087 8.19499 5.53938 14.9471 11.0619 9.2381 7.74343 16.6958 5.85033 5.10097 9.9227 6.16988 4.90024 4.71942 4.38892 14.0462 8.54518 4.02972 3.98397 4.03732 3.91106 3.90403 +threshold=1028.0000000000002 45.500000000000007 210.50000000000003 6.5000000000000009 53.500000000000007 37.500000000000007 8881.5000000000018 224.50000000000003 1839.0000000000002 32.500000000000007 157.00000000000003 1255.5000000000002 16.500000000000004 10.500000000000002 1.0000000180025095e-35 18.500000000000004 44075.000000000007 6.5000000000000009 14.500000000000002 19.500000000000004 29.500000000000004 134128.00000000003 2371.0000000000005 97.500000000000014 1512.0000000000002 2642.5000000000005 1.0000000180025095e-35 20.500000000000004 10467.000000000002 5.5000000000000009 26.500000000000004 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 3 -3 4 5 6 8 9 11 22 29 15 14 -14 -13 17 -17 26 -15 -20 -21 -7 -6 27 -25 -26 -2 28 -24 -11 -10 +right_child=1 2 -4 -5 7 21 -8 -9 30 10 -12 12 13 18 -16 16 -18 -19 19 20 -22 -23 23 24 25 -27 -28 -29 -30 -31 -32 +leaf_value=-0.00011581299058640767 0.15589550172400529 0.048010767428169947 -0.14597235811542841 0.0025410966701097412 -0.18200999755158109 -0.18842615727703177 -0.18726377171973929 -0.17221391069241096 -0.16341494849995269 -0.18727026295711713 0.10675205682314232 0.18060973815299541 -0.19422093952737776 0.2014627907563368 -0.19231104234341687 -0.19880578213278985 0.11731133736042253 -0.18360909179059012 -0.19866347150563834 -0.18459283822214378 0.17415369947604384 0.084141133895139331 0.13757598233699309 -0.1839419355412647 0.11997795563683111 -0.19847484561076437 -0.19587459046020797 -0.18253992110207362 -0.15834736666245117 0.057865813630815811 0.16854771079455846 +leaf_weight=72137.917845727963 7.7246616668926436 43.327969143305381 0.89248736714944144 1767.8629915063066 0.65139611251652496 6.1023183288052669 2.1011667489074162 1.2360645060834929 0.3863108700606962 2.5394917842932041 2.7824830799363554 0.75465101003646751 1.5477018142119039 2.9820276498794538 5.5469515642616898 3.0231546529103062 3.7347774859517813 0.59600244369357902 1.3239368620561434 0.56476250197738442 3.1719031296670437 0.73951514996588219 16.945077733951631 2.2090454659191883 9.8774008140899205 0.92120498418808061 0.33998726308345784 0.41893675422761489 0.47393002838361908 0.87513788754586119 4.2713916996726766 +leaf_count=23668792 2533 14216 293 580043 216 2003 691 406 129 834 912 248 508 979 1816 991 1226 197 435 186 1040 243 5560 725 3239 302 111 138 155 288 1398 +internal_value=0 0.00440652 0.0440957 0.00345871 0.0228073 -0.0089977 0.0179545 0.0594987 0.0299428 0.067097 -0.0206378 0.0134199 -0.0280105 0.0506032 -0.147652 0.0561222 -0.0241034 0.118722 0.0977165 0.0365825 0.119932 -0.158965 0.084359 0.0899842 0.0458114 0.0928115 0.141066 0.122196 0.129525 -0.124444 0.141015 +internal_weight=0 1895.92 44.2205 1851.7 83.8414 44.9112 38.0694 38.9302 35.9682 37.6941 6.19711 31.3105 15.8919 9.59033 6.3016 15.4186 6.75793 8.66065 8.04263 5.0606 3.73667 6.84183 31.497 30.8456 13.0077 10.7986 8.06465 17.8379 17.419 3.41463 4.6577 +internal_count=24290853 622061 14509 607552 27509 14734 12488 12775 11797 12369 2034 10270 5212 3148 2064 5058 2217 2841 2640 1661 1226 2246 10335 10119 4266 3541 2644 5853 5715 1122 1527 +is_linear=0 +shrinkage=0.1 + + +Tree=199 +num_leaves=32 +num_cat=0 +split_feature=5 18 2 11 9 9 9 2 18 5 18 9 7 2 3 19 2 19 2 15 19 11 3 19 7 7 2 3 2 7 18 +split_gain=4.22582 9.3671 4.63013 4.81411 15.0891 8.03549 5.25615 4.59447 11.2232 9.41615 7.87476 12.2985 9.60272 10.3753 9.7826 9.27653 7.61156 14.0466 18.257 11.7979 9.81346 11.2517 14.0335 13.1222 20.7526 10.5668 10.4966 13.3408 13.0444 10.2834 9.5211 +threshold=1576.5000000000002 51.500000000000007 2240.5000000000005 2.5000000000000004 44.500000000000007 34.500000000000007 12.500000000000002 6568.5000000000009 39.500000000000007 46860.000000000007 19.500000000000004 991.00000000000011 320.50000000000006 1910.5000000000002 33841.000000000007 52.500000000000007 9226.0000000000018 20.500000000000004 8943.0000000000018 2.5000000000000004 33.500000000000007 1.5000000000000002 2511.0000000000005 66.500000000000014 1512.0000000000002 1156.5000000000002 8741.5000000000018 25426.500000000004 8486.0000000000018 299.50000000000006 8.5000000000000018 +decision_type=2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 7 -3 4 5 6 -4 8 10 -10 -2 12 -12 -14 -15 -13 17 -9 19 -19 25 30 -23 24 -24 29 27 28 -25 -21 -22 +right_child=1 2 3 -5 -6 -7 -8 16 9 -11 11 15 13 14 -16 -17 -18 18 -20 20 21 22 23 26 -26 -27 -28 -29 -30 -31 -32 +leaf_value=-0.0001146497110726553 0.0043174480940603045 -0.097287385650645555 0.017855499013137457 0.045305016585368424 -0.11877631850066717 0.10212615214667485 -0.13698692118661801 -0.13751012551241645 -0.16555931841103966 0.044243696226314647 -0.11016013602980719 -0.16535302530203558 -0.12726087167793732 0.059658896646657279 -0.14453552791639954 0.15003374615443218 0.0047633164348568693 -0.18111875854673554 -0.1643970505936152 -0.17666624174053286 0.08642840517042176 -0.18035237025089013 -0.14739295026182522 0.029504863007784682 0.038609052393130699 -0.16474315928608352 0.10251293467026726 -0.1018069531875407 -0.16533604643885463 0.094055424313202088 -0.054463920270224113 +leaf_weight=72366.936369995557 976.50400109715702 3.0909965337486929 113.64486702126669 80.73213745780231 7.9985675066272952 11.611214312142691 2.2353590149323272 10.113172099401707 6.8557523576309887 3.1094211284071207 5.2366373213590114 1.0091231137048442 3.7104106609476721 38.796795016387478 2.4972270478610872 12.29958503320813 228.95385780920333 3.8431232674047342 7.8717668212484559 1.5073464354500234 17.054382012167483 5.8323041724506757 16.712007996859032 40.991649771342054 9.3568961449782346 2.0017142628785214 9.1505726709438004 13.029938828258308 3.7504693275550371 20.289079139591195 6.6731059214507686 +leaf_count=23744076 320396 1014 37287 26490 2625 3809 734 3317 2251 1019 1720 331 1217 12728 818 4036 75126 1262 2583 494 5598 1913 5481 13450 3070 656 3003 4275 1230 6656 2188 +internal_value=0 0.00497867 0.0242375 0.0259747 0.0144567 0.0228155 0.0148685 0.00206004 0.00552525 -0.100095 0.00653723 0.0406464 0.0180047 0.0329178 0.0473104 0.12612 -0.00710201 -0.0232553 -0.0159451 -0.00816453 -0.00362277 -0.0150339 -0.0298811 -0.0204437 -0.0806314 0.0551401 0.00300166 -0.01276 0.0131725 0.0753335 0.0468039 +internal_weight=0 1666.46 219.313 216.222 135.49 127.491 115.88 1447.15 1050.02 9.96517 1040.05 63.5498 50.2411 45.0044 41.294 13.3087 397.131 168.178 158.064 150.193 146.349 122.551 98.8238 92.9915 26.0689 23.7981 66.9226 57.7721 44.7421 21.7964 23.7275 +internal_count=24290853 546777 71959 70945 44455 41830 38021 474818 344516 3270 341246 20850 16483 14763 13546 4367 130302 55176 51859 49276 48014 40208 32422 30509 8551 7806 21958 18955 14680 7150 7786 +is_linear=0 +shrinkage=0.1 + + +end of trees + +feature_importances: +catalog_ordered_quantity=422 +catalog_anonymous_total_clicks=418 +user_clicks_unique=344 +catalog_unique_clicks=340 +user_num_add_to_carts=332 +catalog_anonymous_views=326 +user_num_of_cancelled_orders=326 +catalog_anonymous_unique_clicks=294 +supplier_num_catalog_offerings=287 +supplier_num_catalog_orders=280 +catalog_signedup_views=276 +catalog_avg_rating=270 +supplier_num_product_orders=256 +user_num_of_orders=255 +user_clicks_total=251 +catalog_num_of_orders=249 +supplier_num_of_orders=241 +user_num_of_shares=228 +supplier_ordered_quantity=213 +supplier_num_of_customers=207 +supplier_num_sscat_offerings=198 +catalog_total_clicks=187 + +parameters: +[boosting: gbdt] +[objective: lambdarank] +[metric: ndcg] +[tree_learner: data] +[device_type: cpu] +[linear_tree: 0] +[data: ] +[valid: ] +[num_iterations: 200] +[learning_rate: 0.1] +[num_leaves: 32] +[num_threads: 3] +[deterministic: 0] +[force_col_wise: 0] +[force_row_wise: 0] +[histogram_pool_size: -1] +[max_depth: -1] +[min_data_in_leaf: 20] +[min_sum_hessian_in_leaf: 0.001] +[bagging_fraction: 1] +[pos_bagging_fraction: 1] +[neg_bagging_fraction: 1] +[bagging_freq: 0] +[bagging_seed: 42] +[feature_fraction: 0.7] +[feature_fraction_bynode: 1] +[feature_fraction_seed: 2] +[extra_trees: 0] +[extra_seed: 6] +[early_stopping_round: 0] +[first_metric_only: 0] +[max_delta_step: 0] +[lambda_l1: 0] +[lambda_l2: 0] +[linear_lambda: 0] +[min_gain_to_split: 0] +[drop_rate: 0.1] +[max_drop: 50] +[skip_drop: 0.5] +[xgboost_dart_mode: 0] +[uniform_drop: 0] +[drop_seed: 4] +[top_rate: 0.2] +[other_rate: 0.1] +[min_data_per_group: 100] +[max_cat_threshold: 32] +[cat_l2: 10] +[cat_smooth: 10] +[max_cat_to_onehot: 4] +[top_k: 20] +[monotone_constraints: ] +[monotone_constraints_method: basic] +[monotone_penalty: 0] +[feature_contri: ] +[forcedsplits_filename: ] +[refit_decay_rate: 0.9] +[cegb_tradeoff: 1] +[cegb_penalty_split: 0] +[cegb_penalty_feature_lazy: ] +[cegb_penalty_feature_coupled: ] +[path_smooth: 0] +[interaction_constraints: ] +[verbosity: -1] +[saved_feature_importance_type: 0] +[max_bin: 255] +[max_bin_by_feature: ] +[min_data_in_bin: 3] +[bin_construct_sample_cnt: 200000] +[data_random_seed: 1] +[is_enable_sparse: 1] +[enable_bundle: 1] +[use_missing: 1] +[zero_as_missing: 0] +[feature_pre_filter: 1] +[pre_partition: 1] +[two_round: 0] +[header: 0] +[label_column: ] +[weight_column: ] +[group_column: ] +[ignore_column: ] +[categorical_feature: ] +[forcedbins_filename: ] +[objective_seed: 5] +[num_class: 1] +[is_unbalance: 0] +[scale_pos_weight: 1] +[sigmoid: 1] +[boost_from_average: 1] +[reg_sqrt: 0] +[alpha: 0.9] +[fair_c: 1] +[poisson_max_delta_step: 0.7] +[tweedie_variance_power: 1.5] +[lambdarank_truncation_level: 30] +[lambdarank_norm: 1] +[label_gain: ] +[eval_at: 1,3,5] +[multi_error_top_k: 1] +[auc_mu_weights: ] +[num_machines: 56] +[local_listen_port: 12400] +[time_out: 120] +[machine_list_filename: ] +[machines: ] +[gpu_platform_id: -1] +[gpu_device_id: -1] +[gpu_use_dp: 0] +[num_gpu: 1] + +end of parameters + diff --git a/horizon/configs/models/dummy_model/config.pbtxt b/horizon/configs/models/dummy_model/config.pbtxt new file mode 100644 index 00000000..c5563f47 --- /dev/null +++ b/horizon/configs/models/dummy_model/config.pbtxt @@ -0,0 +1,53 @@ +name: "dummy_model" +backend: "fil" +max_batch_size: 200 + +dynamic_batching { + max_queue_delay_microseconds: 100 +} + +input [ + { + name: "input__0" + data_type: TYPE_FP32 + dims: [ 22 ] + } +] +output [ + { + name: "output__0" + data_type: TYPE_FP32 + dims: [ 1 ] + } +] +response_cache { + enable: false +} +instance_group [ + { + count: 1 + kind : KIND_CPU + } +] +parameters [ + { + key: "model_type" + value: { string_value: "lightgbm" } + }, + { + key: "predict_proba" + value: { string_value: "false" } + }, + { + key: "output_class" + value: { string_value: "false" } + }, + { + key: "storage_type" + value: { string_value: "AUTO" } + }, + { + key: "use_experimental_optimizations" + value: { string_value: "true" } + } +] diff --git a/horizon/configs/services/predator/int/config.yaml b/horizon/configs/services/predator/int/config.yaml new file mode 100644 index 00000000..9361aaa8 --- /dev/null +++ b/horizon/configs/services/predator/int/config.yaml @@ -0,0 +1,106 @@ +# Service Configuration for Predator (Staging Environment) +# This is an example config file. Copy this to services/predator/stg/config.yaml in your config repository + +# Mandatory fields (must be provided) +repo_name: "predator" # GitHub repository name +app_port: 8001 # Application port +ingress_class: "internal" # Ingress class +domain: "int.meesho.int" # Domain name for host generation (format: .) + +# Optional fields (with defaults) +health_check: "/self/health" # Health check endpoint (default: /health) +module: "controlplane" # Module name +app_type: "python" # Application type (python, java, go, etc.) +build_no: "latest" # Build number/tag (default: latest) # Container registry URL (required) +triton_repository: "asia-southeast1-docker.pkg.dev/meesho-devops-admin-0622/prd/ml/triton-inference-server/triton-inference-server" # Triton inference server image repository path without tag (required, tag comes from payload triton_image_tag) +telegraf_image: "asia-southeast1-docker.pkg.dev/meesho-devops-admin-0622/telegraf:1.24.4-arm64" # Complete Telegraf image path with tag (optional, used for ARM64 nodes) +init_container_image: "asia-southeast1-docker.pkg.dev/meesho-devops-admin-0622/admin/devops/build-tools:lunar-v1.0.3-slim" # Complete init container image path with tag for GCS operations (required if gcs_triton_path is enabled) + +# Labels section (optional, vendor-agnostic) +# If provided, these values drive directory structure, file names, and ArgoCD labels +# If absent, defaults are used (bu: "default", team: "default", priority_v2: "p2") +# Note: ArgoCD has filename length constraints (Kubernetes resource names: 63 chars max) +# Keep bu, team, and other label values concise to avoid exceeding limits +# Recommended max lengths: bu (20 chars), team (20 chars), priority_v2 (10 chars) +labels: + bu: "datascience" # Business unit (used as-is, no normalization) + team: "ml-platform" # Team name (used as-is, no normalization) + primary_owner: "jigar.dave@meesho.com" # Primary owner email (username extracted for labels) + secondary_owner: "tushar.rohilla@meesho.com" # Secondary owner email (username extracted for labels) + priority_v2: "up0" # Priority level (e.g., "cp1", "p0", "p1") + # custom_labels: # Optional: Custom labels for vendor extensibility + # example_key: "example_value" + +# Legacy fields (deprecated, kept for backward compatibility) +# These are now read from labels section if present +# primary_owner: "jigar.dave@meesho.com" # Deprecated: Use labels.primary_owner +# secondary_owner: "tushar.rohilla@meesho.com" # Deprecated: Use labels.secondary_owner +# team: "ml-platform" # Deprecated: Use labels.team +# bu: "datascience" # Deprecated: Use labels.bu +# priority_v2: "cp1" # Deprecated: Use labels.priority_v2 + +# Service type flags (Meesho-specific, optional) +service_type: + web: false + grpc: true + cache: false + worker: false + consumer: true + database: false + producer: true + scheduler: false + websocket: false + httpstateless: true + +# Custom labels (for vendor extensibility) +custom_labels: + # Add your custom key-value pairs here + # example_key: "example_value" + +# Probe configuration (optional, with defaults) +# These values control Kubernetes liveness and readiness probes +liveness_failure_threshold: "5" # Number of consecutive failures before restart (default: "5") +liveness_period_seconds: "10" # How often to perform the probe (default: "10") +liveness_success_threshold: "1" # Minimum consecutive successes (default: "1") +liveness_timeout_seconds: "2" # Probe timeout in seconds (default: "2") +readiness_failure_threshold: "5" # Number of consecutive failures before marking not ready (default: "5") +readiness_period_seconds: "10" # How often to perform the probe (default: "10") +readiness_success_threshold: "1" # Minimum consecutive successes (default: "1") +readiness_timeout_seconds: "2" # Probe timeout in seconds (default: "2") + +# Node selector configuration (optional, with environment-based defaults) +# nodeSelector is the Kubernetes node selector key +# Default: "dedicated" for most environments, "cloud.google.com/compute-class" for int environment +# nodeSelectorValue (the actual value like "gpu-node") is provided in the API request +node_selector: "dedicated" # Override default if needed (default: "dedicated" for stg/prd, "cloud.google.com/compute-class" for int) + +# Autoscaling configuration (optional, with defaults) +as_enabled: "true" # Enable autoscaling (default: "true") +as_poll: "30" # Autoscaling polling interval in seconds (default: "30") +as_down_period: "300" # Autoscaling down period in seconds (default: "300") +as_up_period: "60" # Autoscaling up period in seconds (default: "60") +as_up_stable_window: "300" # Autoscaling up stable window in seconds (default: "300") +as_down_stable_window: "1800" # Autoscaling down stable window in seconds (default: "1800") +as_trigger_type: "AverageValue" # Autoscaling trigger type (default: "AverageValue") +as_trigger_metric: "cpu" # Autoscaling trigger metric (default: "cpu") +as_trigger_value: "50" # Autoscaling trigger value (default: "50") +as_down_pod_count: "2" # Autoscaling down pod count (default: "2") +as_up_pod_count: "2" # Autoscaling up pod count (default: "2") +as_up_pod_percentage: "10" # Autoscaling up pod percentage (default: "10") +cpu_threshold: "50" # CPU threshold for autoscaling (default: "50") + +# Deployment configuration (optional, with defaults) +max_surge: "50" # Max surge percentage for rolling update (default: "50") +termination_grace_period_seconds: "300" # Termination grace period in seconds (default: "300") +contour_response_timeout: "false" # Contour response timeout (default: "false") +pod_distribution_skew: "false" # Pod distribution skew (default: "false") +enable_websocket: "false" # Enable websocket (default: "false") +add_headless: "false" # Add headless service (default: "false") +create_contour_gateway: "true" # Create contour gateway (default: "false") +pdb_min_available: "" # PDB min available (default: "") +pdb_max_unavailable: "10%" # PDB max unavailable (default: "10%") +pod_annotations: + telegraf.influxdata.com/limits-cpu: 500m + telegraf.influxdata.com/limits-memory: 500Mi + telegraf.influxdata.com/requests-cpu: 200m + telegraf.influxdata.com/requests-memory: 200Mi diff --git a/horizon/configs/services/predator/prd/config.yaml b/horizon/configs/services/predator/prd/config.yaml new file mode 100644 index 00000000..8739da7e --- /dev/null +++ b/horizon/configs/services/predator/prd/config.yaml @@ -0,0 +1,106 @@ +# Service Configuration for Predator (Staging Environment) +# This is an example config file. Copy this to services/predator/stg/config.yaml in your config repository + +# Mandatory fields (must be provided) +repo_name: "predator" # GitHub repository name +app_port: 8001 # Application port +ingress_class: "contour-internal" # Ingress class +domain: "prd.meesho.int" # Domain name for host generation (format: .) + +# Optional fields (with defaults) +health_check: "/self/health" # Health check endpoint (default: /health) +module: "controlplane" # Module name +app_type: "python" # Application type (python, java, go, etc.) +build_no: "latest" # Build number/tag (default: latest) # Container registry URL (required) +triton_repository: "nvcr.io/nvidia/tritonserver" # Triton inference server image repository path without tag (required, tag comes from payload triton_image_tag) - Public NVIDIA image +telegraf_image: "docker.io/telegraf:1.24.4" # Complete Telegraf image path with tag (optional, used for ARM64 nodes) - Public InfluxData image +init_container_image: "gcr.io/google.com/cloudsdktool/cloud-sdk:latest" # Complete init container image path with tag for GCS operations (required if gcs_triton_path is enabled) - Public BusyBox image (lightweight alternative) + +# Labels section (optional, vendor-agnostic) +# If provided, these values drive directory structure, file names, and ArgoCD labels +# If absent, defaults are used (bu: "default", team: "default", priority_v2: "p2") +# Note: ArgoCD has filename length constraints (Kubernetes resource names: 63 chars max) +# Keep bu, team, and other label values concise to avoid exceeding limits +# Recommended max lengths: bu (20 chars), team (20 chars), priority_v2 (10 chars) +labels: + bu: "datascience" # Business unit (used as-is, no normalization) + team: "ml-platform" # Team name (used as-is, no normalization) + primary_owner: "aditya.garg@meesho.com" # Primary owner email (username extracted for labels) + secondary_owner: "ansh.agarwal@meesho.com" # Secondary owner email (username extracted for labels) + priority_v2: "up0" # Priority level (e.g., "cp1", "p0", "p1") + # custom_labels: # Optional: Custom labels for vendor extensibility + # example_key: "example_value" + +# Legacy fields (deprecated, kept for backward compatibility) +# These are now read from labels section if present +# primary_owner: "jigar.dave@meesho.com" # Deprecated: Use labels.primary_owner +# secondary_owner: "tushar.rohilla@meesho.com" # Deprecated: Use labels.secondary_owner +# team: "ml-platform" # Deprecated: Use labels.team +# bu: "datascience" # Deprecated: Use labels.bu +# priority_v2: "cp1" # Deprecated: Use labels.priority_v2 + +# Service type flags (Meesho-specific, optional) +service_type: + web: false + grpc: true + cache: false + worker: false + consumer: true + database: false + producer: true + scheduler: false + websocket: false + httpstateless: true + +# Custom labels (for vendor extensibility) +custom_labels: + # Add your custom key-value pairs here + # example_key: "example_value" + +# Probe configuration (optional, with defaults) +# These values control Kubernetes liveness and readiness probes +liveness_failure_threshold: "5" # Number of consecutive failures before restart (default: "5") +liveness_period_seconds: "10" # How often to perform the probe (default: "10") +liveness_success_threshold: "1" # Minimum consecutive successes (default: "1") +liveness_timeout_seconds: "2" # Probe timeout in seconds (default: "2") +readiness_failure_threshold: "5" # Number of consecutive failures before marking not ready (default: "5") +readiness_period_seconds: "10" # How often to perform the probe (default: "10") +readiness_success_threshold: "1" # Minimum consecutive successes (default: "1") +readiness_timeout_seconds: "2" # Probe timeout in seconds (default: "2") + +# Node selector configuration (optional, with environment-based defaults) +# nodeSelector is the Kubernetes node selector key +# Default: "dedicated" for most environments, "cloud.google.com/compute-class" for int environment +# nodeSelectorValue (the actual value like "gpu-node") is provided in the API request +node_selector: "dedicated" # Override default if needed (default: "dedicated" for stg/prd, "cloud.google.com/compute-class" for int) + +# Autoscaling configuration (optional, with defaults) +as_enabled: "true" # Enable autoscaling (default: "true") +as_poll: "30" # Autoscaling polling interval in seconds (default: "30") +as_down_period: "300" # Autoscaling down period in seconds (default: "300") +as_up_period: "60" # Autoscaling up period in seconds (default: "60") +as_up_stable_window: "300" # Autoscaling up stable window in seconds (default: "300") +as_down_stable_window: "1800" # Autoscaling down stable window in seconds (default: "1800") +as_trigger_type: "AverageValue" # Autoscaling trigger type (default: "AverageValue") +as_trigger_metric: "cpu" # Autoscaling trigger metric (default: "cpu") +as_trigger_value: "50" # Autoscaling trigger value (default: "50") +as_down_pod_count: "2" # Autoscaling down pod count (default: "2") +as_up_pod_count: "2" # Autoscaling up pod count (default: "2") +as_up_pod_percentage: "10" # Autoscaling up pod percentage (default: "10") +cpu_threshold: "50" # CPU threshold for autoscaling (default: "50") + +# Deployment configuration (optional, with defaults) +max_surge: "50" # Max surge percentage for rolling update (default: "50") +termination_grace_period_seconds: "300" # Termination grace period in seconds (default: "300") +contour_response_timeout: "false" # Contour response timeout (default: "false") +pod_distribution_skew: "false" # Pod distribution skew (default: "false") +enable_websocket: "false" # Enable websocket (default: "false") +add_headless: "false" # Add headless service (default: "false") +create_contour_gateway: "true" # Create contour gateway (default: "false") +pdb_min_available: "" # PDB min available (default: "") +pdb_max_unavailable: "10%" # PDB max unavailable (default: "10%") +pod_annotations: + telegraf.influxdata.com/limits-cpu: 500m + telegraf.influxdata.com/limits-memory: 500Mi + telegraf.influxdata.com/requests-cpu: 200m + telegraf.influxdata.com/requests-memory: 200Mi diff --git a/horizon/configs/services/predator/stg/config.yaml b/horizon/configs/services/predator/stg/config.yaml new file mode 100644 index 00000000..d89e9c17 --- /dev/null +++ b/horizon/configs/services/predator/stg/config.yaml @@ -0,0 +1,106 @@ +# Service Configuration for Predator (Staging Environment) +# This is an example config file. Copy this to services/predator/stg/config.yaml in your config repository + +# Mandatory fields (must be provided) +repo_name: "predator" # GitHub repository name +app_port: 8001 # Application port +ingress_class: "contour-internal" # Ingress class +domain: "stg.meesho.int" # Domain name for host generation (format: .) + +# Optional fields (with defaults) +health_check: "/self/health" # Health check endpoint (default: /health) +module: "controlplane" # Module name +app_type: "python" # Application type (python, java, go, etc.) +build_no: "latest" # Build number/tag (default: latest) +triton_repository: "asia-southeast1-docker.pkg.dev/meesho-devops-admin-0622/prd/ml/triton-inference-server/triton-inference-server" # Triton inference server image repository path without tag (required, tag comes from payload triton_image_tag) +telegraf_image: "asia-southeast1-docker.pkg.dev/meesho-devops-admin-0622/telegraf:1.24.4-arm64" # Complete Telegraf image path with tag (optional, used for ARM64 nodes) +init_container_image: "asia-southeast1-docker.pkg.dev/meesho-devops-admin-0622/admin/devops/build-tools:lunar-v1.0.3-slim" # Complete init container image path with tag for GCS operations (required if gcs_triton_path is enabled) + +# Labels section (optional, vendor-agnostic) +# If provided, these values drive directory structure, file names, and ArgoCD labels +# If absent, defaults are used (bu: "default", team: "default", priority_v2: "p2") +# Note: ArgoCD has filename length constraints (Kubernetes resource names: 63 chars max) +# Keep bu, team, and other label values concise to avoid exceeding limits +# Recommended max lengths: bu (20 chars), team (20 chars), priority_v2 (10 chars) +labels: + bu: "datascience" # Business unit (used as-is, no normalization) + team: "ml-platform" # Team name (used as-is, no normalization) + primary_owner: "jigar.dave@meesho.com" # Primary owner email (username extracted for labels) + secondary_owner: "tushar.rohilla@meesho.com" # Secondary owner email (username extracted for labels) + priority_v2: "up0" # Priority level (e.g., "cp1", "p0", "p1") + # custom_labels: # Optional: Custom labels for vendor extensibility + # example_key: "example_value" + +# Legacy fields (deprecated, kept for backward compatibility) +# These are now read from labels section if present +# primary_owner: "jigar.dave@meesho.com" # Deprecated: Use labels.primary_owner +# secondary_owner: "tushar.rohilla@meesho.com" # Deprecated: Use labels.secondary_owner +# team: "ml-platform" # Deprecated: Use labels.team +# bu: "datascience" # Deprecated: Use labels.bu +# priority_v2: "cp1" # Deprecated: Use labels.priority_v2 + +# Service type flags (Meesho-specific, optional) +service_type: + web: false + grpc: true + cache: false + worker: false + consumer: true + database: false + producer: true + scheduler: false + websocket: false + httpstateless: true + +# Custom labels (for vendor extensibility) +custom_labels: + # Add your custom key-value pairs here + # example_key: "example_value" + +# Probe configuration (optional, with defaults) +# These values control Kubernetes liveness and readiness probes +liveness_failure_threshold: "5" # Number of consecutive failures before restart (default: "5") +liveness_period_seconds: "10" # How often to perform the probe (default: "10") +liveness_success_threshold: "1" # Minimum consecutive successes (default: "1") +liveness_timeout_seconds: "2" # Probe timeout in seconds (default: "2") +readiness_failure_threshold: "5" # Number of consecutive failures before marking not ready (default: "5") +readiness_period_seconds: "10" # How often to perform the probe (default: "10") +readiness_success_threshold: "1" # Minimum consecutive successes (default: "1") +readiness_timeout_seconds: "2" # Probe timeout in seconds (default: "2") + +# Node selector configuration (optional, with environment-based defaults) +# nodeSelector is the Kubernetes node selector key +# Default: "dedicated" for most environments, "cloud.google.com/compute-class" for int environment +# nodeSelectorValue (the actual value like "gpu-node") is provided in the API request +node_selector: "dedicated" # Override default if needed (default: "dedicated" for stg/prd, "cloud.google.com/compute-class" for int) + +# Autoscaling configuration (optional, with defaults) +as_enabled: "true" # Enable autoscaling (default: "true") +as_poll: "30" # Autoscaling polling interval in seconds (default: "30") +as_down_period: "300" # Autoscaling down period in seconds (default: "300") +as_up_period: "60" # Autoscaling up period in seconds (default: "60") +as_up_stable_window: "300" # Autoscaling up stable window in seconds (default: "300") +as_down_stable_window: "1800" # Autoscaling down stable window in seconds (default: "1800") +as_trigger_type: "AverageValue" # Autoscaling trigger type (default: "AverageValue") +as_trigger_metric: "cpu" # Autoscaling trigger metric (default: "cpu") +as_trigger_value: "50" # Autoscaling trigger value (default: "50") +as_down_pod_count: "2" # Autoscaling down pod count (default: "2") +as_up_pod_count: "2" # Autoscaling up pod count (default: "2") +as_up_pod_percentage: "10" # Autoscaling up pod percentage (default: "10") +cpu_threshold: "50" # CPU threshold for autoscaling (default: "50") + +# Deployment configuration (optional, with defaults) +max_surge: "50" # Max surge percentage for rolling update (default: "50") +termination_grace_period_seconds: "300" # Termination grace period in seconds (default: "300") +contour_response_timeout: "false" # Contour response timeout (default: "false") +pod_distribution_skew: "false" # Pod distribution skew (default: "false") +enable_websocket: "false" # Enable websocket (default: "false") +add_headless: "false" # Add headless service (default: "false") +create_contour_gateway: "true" # Create contour gateway (default: "false") +pdb_min_available: "" # PDB min available (default: "") +pdb_max_unavailable: "10%" # PDB max unavailable (default: "10%") +pod_annotations: + telegraf.influxdata.com/limits-cpu: 500m + telegraf.influxdata.com/limits-memory: 500Mi + telegraf.influxdata.com/requests-cpu: 200m + telegraf.influxdata.com/requests-memory: 200Mi diff --git a/horizon/env.example b/horizon/env.example index 27f06d64..8756ccf7 100644 --- a/horizon/env.example +++ b/horizon/env.example @@ -1,37 +1,206 @@ -APP_NAME=horizon -APP_PORT=8082 -APP_LOG_LEVEL=INFO -MYSQL_MASTER_MAX_POOL_SIZE=5 -MYSQL_MASTER_MIN_POOL_SIZE=2 -MYSQL_MASTER_PASSWORD=root -MYSQL_MASTER_HOST=127.0.0.1 -MYSQL_MASTER_PORT=3306 -MYSQL_MASTER_USERNAME=root -MYSQL_SLAVE_MAX_POOL_SIZE=5 -MYSQL_SLAVE_MIN_POOL_SIZE=2 -MYSQL_SLAVE_PASSWORD=root -MYSQL_SLAVE_HOST=127.0.0.1 -MYSQL_SLAVE_USERNAME=root -MYSQL_SLAVE_PORT=3306 -MYSQL_DB_NAME=testdb - -ETCD_WATCHER_ENABLED=true -ETCD_SERVER=127.0.0.1:2379 - -CORS_ORIGINS=http://localhost:3000,http://localhost:8080 - -ONLINE_FEATURE_STORE_APP_NAME=onfs - -SCYLLA_1_CONTACT_POINTS=127.0.0.1 -SCYLLA_1_KEYSPACE=onfs -SCYLLA_1_NUM_CONNS=1 -SCYLLA_1_PASSWORD= -SCYLLA_1_PORT=9042 -SCYLLA_1_TIMEOUT_IN_MS=10000 -SCYLLA_1_USERNAME= - -SCYLLA_ACTIVE_CONFIG_IDS=1 -REDIS_FAILOVER_ACTIVE_CONFIG_IDS=4 - -NUMERIX_APP_NAME=numerix -NUMERIX_MONITORING_URL=http://localhost:8125/numerix_dashboard \ No newline at end of file +# GitHub Configuration for Testing +# Copy this file to .env and update with your actual values + +# GitHub App Authentication +# Get these from your GitHub App settings: https://github.com/settings/apps +GITHUB_APP_ID= +GITHUB_INSTALLATION_ID= +GITHUB_PRIVATE_KEY_PATH=/path/to/your/github-app-private-key.pem + +# GitHub Organization/Owner +# Your GitHub organization or username +GITHUB_OWNER=your-org-name + +# GitHub Repository Names +# Update these with your actual repository names +GITHUB_HELM_CHART_REPO=your-helm-charts-repo +GITHUB_INFRA_HELM_CHART_REPO=your-infra-helm-charts-repo +GITHUB_ARGO_REPO=your-argo-config-repo + +# GitHub Commit Information +# These will be used as commit author for GitHub operations +GITHUB_COMMIT_AUTHOR=horizon-bot +GITHUB_COMMIT_EMAIL=devops@your-org.com + +# VictoriaMetrics Server Address +# For GPU metrics queries (used in GPU threshold updates) +# Format: http://hostname:port/select/100/prometheus/ +VICTORIAMETRICS_SERVER_ADDRESS=http://vmselect-datascience-prd-proxy.victoriametrics.svc.cluster.local:8481/select/100/prometheus/ + + + +SUPPORTED_ENVIRONMENTS=gcp_stg,gcp_int,gcp_prd,stg,int,prd + +# GCP Configuration for Workload Identity Binding +# These are used to bind Kubernetes Service Accounts (KSA) to GCP Service Accounts (GSA) +# Service account is provided in the onboarding request payload + +# GCP Project ID (environment-specific configuration) +# Format: {WORKING_ENV}_GCP_PROJECT_ID +# Example: GCP_STG_GCP_PROJECT_ID= +# Example: GCP_INT_GCP_PROJECT_ID= +# Example: GCP_PRD_GCP_PROJECT_ID= +# Generic fallback: GCP_PROJECT_ID (if environment-specific not set) +GCP_STG_GCP_PROJECT_ID=your-gcp-project-id-for-stg +GCP_INT_GCP_PROJECT_ID=your-gcp-project-id-for-int +GCP_PROJECT_ID=your-gcp-project-id-fallback + +# Note: GCP credentials are automatically fetched from: +# - GCE metadata service (when running on GCE/GKE) - primary method, no configuration needed +# - gcloud auth application-default login (for local development) +# JSON-based credentials (GOOGLE_APPLICATION_CREDENTIALS) are not used + +# ============================================================================= +# Repository and Branch Configuration (MANDATORY) +# ============================================================================= +# All files will be pushed to the specified repository and branch +# These are required - the application will fail to start if not set +REPOSITORY_NAME= +BRANCH_NAME= + + +# ============================================================================= +# Default ArgoCD Configuration (Fallback for all environments) +# ============================================================================= +# These are used if environment-specific values are not set +ARGOCD_API=https://argocd.example.com +ARGOCD_TOKEN=your-default-argocd-token +ARGOCD_NAMESPACE=argocd-dev +ARGOCD_DESTINATION_NAME=k8s-{bu_norm}-{env}-ase1 +ARGOCD_PROJECT=default +ARGOCD_HELMCHART_PATH=values_v2 +ARGOCD_SYNC_POLICY_OPTIONS=CreateNamespace=true +# Note: ARGOCD_DESTINATION_NAMESPACE is hardcoded to {env}-{appName} format + +# ============================================================================= +# Environment-Specific ArgoCD Configuration +# ============================================================================= +# Group all ArgoCD config for each environment together for easier management +# Uncomment and configure the sections for environments you use + +# ----------------------------------------------------------------------------- +# GCP Staging Environment (gcp_stg) +# ----------------------------------------------------------------------------- +# GCP_STG_ARGOCD_API=https://argocd-stg.example.com +# GCP_STG_ARGOCD_TOKEN=your-argocd-token-for-stg +# GCP_STG_ARGOCD_NAMESPACE=argocd-dev +# GCP_STG_ARGOCD_DESTINATION_NAME=k8s-{bu_norm}-stg-ase1 +# GCP_STG_ARGOCD_PROJECT=default +# GCP_STG_ARGOCD_SYNC_POLICY_OPTIONS=CreateNamespace=true +# Note: DESTINATION_NAMESPACE is hardcoded to {env}-{appName} format +# Note: SOURCE_REPO_URL and SOURCE_TARGET_REVISION are auto-derived +# HELMCHART_PATH can be configured via {WORKING_ENV}_ARGOCD_HELMCHART_PATH or ARGOCD_HELMCHART_PATH (defaults to "values_v2") + +# ----------------------------------------------------------------------------- +# GCP Integration Environment (gcp_int) +# ----------------------------------------------------------------------------- +# GCP_INT_ARGOCD_API=https://argocd-int.example.com +# GCP_INT_ARGOCD_TOKEN=your-argocd-token-for-int +# GCP_INT_ARGOCD_NAMESPACE=argocd-shared-int +# GCP_INT_ARGOCD_DESTINATION_NAME=k8s-{bu_norm}-int-ase1 +# GCP_INT_ARGOCD_PROJECT=default +# GCP_INT_ARGOCD_SYNC_POLICY_OPTIONS=CreateNamespace=true +# Note: DESTINATION_NAMESPACE is hardcoded to {env}-{appName} format +# Note: SOURCE_REPO_URL and SOURCE_TARGET_REVISION are auto-derived +# HELMCHART_PATH can be configured via {WORKING_ENV}_ARGOCD_HELMCHART_PATH or ARGOCD_HELMCHART_PATH (defaults to "values_v2") + +# ----------------------------------------------------------------------------- +# GCP Production Environment (gcp_prd) +# ----------------------------------------------------------------------------- +# GCP_PRD_ARGOCD_API=https://argocd-prod.example.com +# GCP_PRD_ARGOCD_TOKEN=your-argocd-token-for-prod +# GCP_PRD_ARGOCD_NAMESPACE=argocd-{bu_norm}-prd +# GCP_PRD_ARGOCD_DESTINATION_NAME=k8s-{bu_norm}-prd-ase1 +# GCP_PRD_ARGOCD_PROJECT=default +# GCP_PRD_ARGOCD_SYNC_POLICY_OPTIONS=CreateNamespace=true,Prune=true +# Note: DESTINATION_NAMESPACE is hardcoded to {env}-{appName} format +# Note: SOURCE_REPO_URL and SOURCE_TARGET_REVISION are auto-derived +# HELMCHART_PATH can be configured via {WORKING_ENV}_ARGOCD_HELMCHART_PATH or ARGOCD_HELMCHART_PATH (defaults to "values_v2") + +# ----------------------------------------------------------------------------- +# Staging Environment (stg) +# ----------------------------------------------------------------------------- +# STG_ARGOCD_API=https://argocd-stg.example.com +# STG_ARGOCD_TOKEN=your-argocd-token-for-stg +# STG_ARGOCD_NAMESPACE=argocd-dev +# STG_ARGOCD_DESTINATION_NAME=k8s-{bu_norm}-stg-ase1 +# STG_ARGOCD_PROJECT=default +# STG_ARGOCD_SYNC_POLICY_OPTIONS=CreateNamespace=true +# Note: DESTINATION_NAMESPACE is hardcoded to {env}-{appName} format +# Note: SOURCE_REPO_URL and SOURCE_TARGET_REVISION are auto-derived +# HELMCHART_PATH can be configured via {WORKING_ENV}_ARGOCD_HELMCHART_PATH or ARGOCD_HELMCHART_PATH (defaults to "values_v2") + +# ----------------------------------------------------------------------------- +# Development Environment (dev) +# ----------------------------------------------------------------------------- +# DEV_ARGOCD_API=https://argocd-dev.example.com +# DEV_ARGOCD_TOKEN=your-argocd-token-for-dev +# DEV_ARGOCD_NAMESPACE=argocd-dev +# DEV_ARGOCD_DESTINATION_NAME=k8s-{bu_norm}-dev-ase1 +# DEV_ARGOCD_PROJECT=default +# DEV_ARGOCD_SYNC_POLICY_OPTIONS=CreateNamespace=true +# Note: DESTINATION_NAMESPACE is hardcoded to {env}-{appName} format +# Note: SOURCE_REPO_URL and SOURCE_TARGET_REVISION are auto-derived +# HELMCHART_PATH can be configured via {WORKING_ENV}_ARGOCD_HELMCHART_PATH or ARGOCD_HELMCHART_PATH (defaults to "values_v2") + +# ----------------------------------------------------------------------------- +# Production Environment (prd) +# ----------------------------------------------------------------------------- +# PRD_ARGOCD_API=https://argocd-prod.example.com +# PRD_ARGOCD_TOKEN=your-argocd-token-for-prd +# PRD_ARGOCD_NAMESPACE=argocd-{bu_norm}-prd +# PRD_ARGOCD_DESTINATION_NAME=k8s-{bu_norm}-prd-ase1 +# PRD_ARGOCD_PROJECT=default +# PRD_ARGOCD_SYNC_POLICY_OPTIONS=CreateNamespace=true,Prune=true +# Note: DESTINATION_NAMESPACE is hardcoded to {env}-{appName} format +# Note: SOURCE_REPO_URL and SOURCE_TARGET_REVISION are auto-derived +# HELMCHART_PATH can be configured via {WORKING_ENV}_ARGOCD_HELMCHART_PATH or ARGOCD_HELMCHART_PATH (defaults to "values_v2") + +# ----------------------------------------------------------------------------- +# Integration Environment (int) +# ----------------------------------------------------------------------------- +# INT_ARGOCD_API=https://argocd-int.example.com +# INT_ARGOCD_TOKEN=your-argocd-token-for-int +# INT_ARGOCD_NAMESPACE=argocd-shared-int +# INT_ARGOCD_DESTINATION_NAME=k8s-{bu_norm}-int-ase1 +# INT_ARGOCD_PROJECT=default +# INT_ARGOCD_SYNC_POLICY_OPTIONS=CreateNamespace=true +# Note: DESTINATION_NAMESPACE is hardcoded to {env}-{appName} format +# Note: SOURCE_REPO_URL and SOURCE_TARGET_REVISION are auto-derived +# HELMCHART_PATH can be configured via {WORKING_ENV}_ARGOCD_HELMCHART_PATH or ARGOCD_HELMCHART_PATH (defaults to "values_v2") + +# ----------------------------------------------------------------------------- +# Custom Environment Example (aws_stg, custom_env, etc.) +# ----------------------------------------------------------------------------- +# For any custom environment, follow the same pattern: +# {ENV}_ARGOCD_API=... +# {ENV}_ARGOCD_TOKEN=... +# {ENV}_ARGOCD_NAMESPACE=... +# {ENV}_ARGOCD_DESTINATION_NAME=... +# {ENV}_ARGOCD_PROJECT=... (hardcoded value, not a pattern) +# {ENV}_ARGOCD_SYNC_POLICY_OPTIONS=... +# +# Note: SOURCE_REPO_URL and SOURCE_TARGET_REVISION are auto-derived +# HELMCHART_PATH can be configured via {WORKING_ENV}_ARGOCD_HELMCHART_PATH or ARGOCD_HELMCHART_PATH (defaults to "values_v2") +# from REPOSITORY_NAME, BRANCH_NAME, and GITHUB_OWNER during deployable onboarding +# +# Example for AWS staging: +# AWS_STG_ARGOCD_API=https://argocd-aws-stg.example.com +# AWS_STG_ARGOCD_TOKEN=your-argocd-token-for-aws-stg +# AWS_STG_ARGOCD_NAMESPACE=argocd-aws-stg +# AWS_STG_ARGOCD_DESTINATION_NAME=k8s-{bu_norm}-aws-stg-ase1 +# AWS_STG_ARGOCD_PROJECT=default +# AWS_STG_ARGOCD_SYNC_POLICY_OPTIONS=CreateNamespace=true +# Note: DESTINATION_NAMESPACE is hardcoded to {env}-{appName} format + +# ============================================================================= +# Service Config Source Configuration +# ============================================================================= +# Options: "local" (from codebase) or "github" (from config repo) +# If "local": configs are read from SERVICE_CONFIG_PATH (default: ./configs) +# Standard path format: configs/services/{serviceName}/{env}/config.yaml +# If "github": configs are read from SERVICE_CONFIG_REPO +# Path format: services/{serviceName}/{env}/config.yaml +SERVICE_CONFIG_SOURCE=local +SERVICE_CONFIG_REPO=BharatMLStack-internal-configs +SERVICE_CONFIG_PATH=./configs diff --git a/horizon/go.mod b/horizon/go.mod index 179ae0ae..ddb290ee 100644 --- a/horizon/go.mod +++ b/horizon/go.mod @@ -1,80 +1,140 @@ module github.com/Meesho/BharatMLStack/horizon -go 1.22.0 +go 1.24.4 + +toolchain go1.24.10 require ( - github.com/DataDog/datadog-go/v5 v5.5.0 + cloud.google.com/go/storage v1.57.2 + github.com/DataDog/datadog-go/v5 v5.6.0 + github.com/Meesho/BharatMLStack/helix-client v1.0.0-alpha-7f5f39 + github.com/bradleyfalzon/ghinstallation/v2 v2.17.0 + github.com/deckarep/golang-set/v2 v2.8.0 github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/gin-contrib/cors v1.7.3 - github.com/gin-gonic/gin v1.10.0 + github.com/gin-gonic/gin v1.11.0 github.com/gocql/gocql v1.7.0 - github.com/rs/zerolog v1.33.0 - github.com/spf13/viper v1.16.0 - github.com/stretchr/testify v1.9.0 + github.com/google/go-github/v53 v53.2.0 + github.com/google/uuid v1.6.0 + github.com/maolinc/copier v0.0.0-20230308122822-96b2f568544f + github.com/robfig/cron/v3 v3.0.1 + github.com/rs/zerolog v1.34.0 + github.com/spf13/viper v1.20.1 + github.com/stretchr/testify v1.11.1 + github.com/tidwall/gjson v1.18.0 github.com/x448/float16 v0.8.4 - go.etcd.io/etcd/client/v3 v3.5.9 - golang.org/x/crypto v0.33.0 + go.etcd.io/etcd/client/v3 v3.5.12 + golang.org/x/crypto v0.44.0 + golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 + golang.org/x/oauth2 v0.33.0 + google.golang.org/api v0.256.0 + google.golang.org/grpc v1.76.0 + google.golang.org/protobuf v1.36.10 + gopkg.in/yaml.v3 v3.0.1 gorm.io/driver/mysql v1.5.6 gorm.io/gorm v1.25.10 ) require ( + cel.dev/expr v0.24.0 // indirect + cloud.google.com/go v0.121.6 // indirect + cloud.google.com/go/auth v0.17.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect + cloud.google.com/go/compute/metadata v0.9.0 // indirect + cloud.google.com/go/iam v1.5.2 // indirect + cloud.google.com/go/monitoring v1.24.2 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/bytedance/sonic v1.12.6 // indirect - github.com/bytedance/sonic/loader v0.2.1 // indirect - github.com/cloudwego/base64x v0.1.4 // indirect - github.com/cloudwego/iasm v0.2.0 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect + github.com/bytedance/sonic v1.14.0 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cloudflare/circl v1.3.3 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect github.com/coreos/go-semver v0.3.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.7 // indirect - github.com/gin-contrib/sse v0.1.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect + github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.9 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/go-jose/go-jose/v4 v4.1.2 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.23.0 // indirect + github.com/go-playground/validator/v10 v10.27.0 // indirect github.com/go-sql-driver/mysql v1.7.0 // indirect - github.com/goccy/go-json v0.10.4 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/goccy/go-yaml v1.18.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/golang/snappy v0.0.4 // indirect + github.com/golang/snappy v1.0.0 // indirect + github.com/google/go-github/v75 v75.0.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/google/s2a-go v0.1.9 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect + github.com/googleapis/gax-go/v2 v2.15.0 // indirect github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect - github.com/hashicorp/hcl v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.2.9 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect - github.com/magiconair/properties v1.8.7 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.2.3 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/spf13/afero v1.9.5 // indirect - github.com/spf13/cast v1.5.1 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/quic-go/qpack v0.5.1 // indirect + github.com/quic-go/quic-go v0.54.0 // indirect + github.com/sagikazarmark/locafero v0.9.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.14.0 // indirect + github.com/spf13/cast v1.9.2 // indirect + github.com/spf13/pflag v1.0.6 // indirect + github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect github.com/stretchr/objx v0.5.2 // indirect - github.com/subosito/gotenv v1.4.2 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.2.12 // indirect - go.etcd.io/etcd/api/v3 v3.5.9 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.5.9 // indirect + github.com/ugorji/go/codec v1.3.0 // indirect + github.com/zeebo/errs v1.4.0 // indirect + go.etcd.io/etcd/api/v3 v3.5.12 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.5.12 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect + go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.38.0 // indirect go.uber.org/atomic v1.9.0 // indirect - go.uber.org/multierr v1.8.0 // indirect + go.uber.org/mock v0.5.0 // indirect + go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.21.0 // indirect - golang.org/x/arch v0.12.0 // indirect - golang.org/x/net v0.33.0 // indirect - golang.org/x/sys v0.30.0 // indirect - golang.org/x/text v0.22.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect - google.golang.org/grpc v1.67.1 // indirect - google.golang.org/protobuf v1.36.1 // indirect + golang.org/x/arch v0.20.0 // indirect + golang.org/x/mod v0.30.0 // indirect + golang.org/x/net v0.47.0 // indirect + golang.org/x/sync v0.18.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/text v0.31.0 // indirect + golang.org/x/time v0.14.0 // indirect + golang.org/x/tools v0.39.0 // indirect + google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/horizon/go.sum b/horizon/go.sum index 07af712e..2066e511 100644 --- a/horizon/go.sum +++ b/horizon/go.sum @@ -1,204 +1,166 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DataDog/datadog-go/v5 v5.5.0 h1:G5KHeB8pWBNXT4Jtw0zAkhdxEAWSpWH00geHI6LDrKU= -github.com/DataDog/datadog-go/v5 v5.5.0/go.mod h1:K9kcYBlxkcPP8tvvjZZKs/m1edNAUFzBbdpTUKfCsuw= +cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= +cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c= +cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI= +cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4= +cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ= +cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= +cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= +cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= +cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= +cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8= +cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE= +cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc= +cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA= +cloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E= +cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY= +cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM= +cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U= +cloud.google.com/go/storage v1.57.2 h1:sVlym3cHGYhrp6XZKkKb+92I1V42ks2qKKpB0CF5Mb4= +cloud.google.com/go/storage v1.57.2/go.mod h1:n5ijg4yiRXXpCu0sJTD6k+eMf7GRrJmPyr9YxLXGHOk= +cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4= +cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI= +github.com/DataDog/datadog-go/v5 v5.6.0 h1:2oCLxjF/4htd55piM75baflj/KoE6VYS7alEUqFvRDw= +github.com/DataDog/datadog-go/v5 v5.6.0/go.mod h1:K9kcYBlxkcPP8tvvjZZKs/m1edNAUFzBbdpTUKfCsuw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 h1:UQUsRi8WTzhZntp5313l+CHIAT95ojUI2lpP/ExlZa4= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0/go.mod h1:Cz6ft6Dkn3Et6l2v2a9/RpN7epQ1GtDlO6lj8bEcOvw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 h1:owcC2UnmsZycprQ5RfRgjydWhuoxg71LUfyiQdijZuM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0/go.mod h1:ZPpqegjbE99EPKsu3iUWV22A04wzGPcAY/ziSIQEEgs= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0 h1:4LP6hvB4I5ouTbGgWtixJhgED6xdf67twf9PoY96Tbg= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0/go.mod h1:jUZ5LYlw40WMd07qxcQJD5M40aUxrfwqQX1g7zxYnrQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 h1:Ron4zCA/yk6U7WOBXhTJcDpsUBG9npumK6xw2auFltQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo= +github.com/Meesho/BharatMLStack/helix-client v1.0.0-alpha-7f5f39 h1:kWQqArO0PP/o3lQmtScI044fs4nZSS4AZJHw3JBlDlc= +github.com/Meesho/BharatMLStack/helix-client v1.0.0-alpha-7f5f39/go.mod h1:Pw9RyIH12tZRFFkX3lodoMrywH3RLjlKzPnVyZwzyyU= github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA= +github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY= github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= -github.com/bytedance/sonic v1.12.6 h1:/isNmCUF2x3Sh8RAp/4mh4ZGkcFAX/hLrzrK3AvpRzk= -github.com/bytedance/sonic v1.12.6/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= -github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E= -github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= -github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= -github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= -github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/bradleyfalzon/ghinstallation/v2 v2.17.0 h1:SmbUK/GxpAspRjSQbB6ARvH+ArzlNzTtHydNyXUQ6zg= +github.com/bradleyfalzon/ghinstallation/v2 v2.17.0/go.mod h1:vuD/xvJT9Y+ZVZRv4HQ42cMyPFIYqpc7AbB4Gvt/DlY= +github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= +github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= +github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls= +github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set/v2 v2.8.0 h1:swm0rlPCmdWn9mESxKOjWk8hXSqoxOp+ZlfuyaAdFlQ= +github.com/deckarep/golang-set/v2 v2.8.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= -github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA= -github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU= +github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= +github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= +github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A= +github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= +github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= +github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= +github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= github.com/gin-contrib/cors v1.7.3 h1:hV+a5xp8hwJoTw7OY+a70FsL8JkVVFTXw9EcfrYUdns= github.com/gin-contrib/cors v1.7.3/go.mod h1:M3bcKZhxzsvI+rlRSkkxHyljJt1ESd93COUvemZ79j4= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= -github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= +github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= +github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI= +github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o= -github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= +github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= -github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= -github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/gocql/gocql v1.7.0 h1:O+7U7/1gSN7QTEAaMEsJc1Oq2QHXvCWoF3DFK9HDHus= github.com/gocql/gocql v1.7.0/go.mod h1:vnlvXyFZeLBF0Wy+RS8hrOdbn0UWsWtdg07XJnFxZ+4= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= +github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= +github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-github/v53 v53.2.0 h1:wvz3FyF53v4BK+AsnvCmeNhf8AkTaeh2SoYu/XUvTtI= +github.com/google/go-github/v53 v53.2.0/go.mod h1:XhFRObz+m/l+UCm9b7KSIC3lT3NWSXGt7mOsAWEloao= +github.com/google/go-github/v75 v75.0.0 h1:k7q8Bvg+W5KxRl9Tjq16a9XEgVY1pwuiG5sIL7435Ic= +github.com/google/go-github/v75 v75.0.0/go.mod h1:H3LUJEA1TCrzuUqtdAQniBNwuKiQIqdGKgBo1/M/uqI= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= +github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ= +github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= +github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= +github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= -github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= -github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -208,47 +170,56 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/maolinc/copier v0.0.0-20230308122822-96b2f568544f h1:sRTOY+RyQBvYIXUD64+jD+rrdJ3DmrKu/QD3s+CwQeI= +github.com/maolinc/copier v0.0.0-20230308122822-96b2f568544f/go.mod h1:kZ+zAWCoPv9+nPxdwQjK7cdfhlKZH2+q3PjvdpsXR7Q= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= -github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= +github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= +github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= +github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k= +github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= -github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= -github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= -github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= -github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= +github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= +github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE= +github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= +github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= +github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= +github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -256,369 +227,158 @@ github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= -github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= -github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= +github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.etcd.io/etcd/api/v3 v3.5.9 h1:4wSsluwyTbGGmyjJktOf3wFQoTBIURXHnq9n/G/JQHs= -go.etcd.io/etcd/api/v3 v3.5.9/go.mod h1:uyAal843mC8uUVSLWz6eHa/d971iDGnCRpmKd2Z+X8k= -go.etcd.io/etcd/client/pkg/v3 v3.5.9 h1:oidDC4+YEuSIQbsR94rY9gur91UPL6DnxDCIYd2IGsE= -go.etcd.io/etcd/client/pkg/v3 v3.5.9/go.mod h1:y+CzeSmkMpWN2Jyu1npecjB9BBnABxGM4pN8cGuJeL4= -go.etcd.io/etcd/client/v3 v3.5.9 h1:r5xghnU7CwbUxD/fbUtRyJGaYNfDun8sp/gTr1hew6E= -go.etcd.io/etcd/client/v3 v3.5.9/go.mod h1:i/Eo5LrZ5IKqpbtpPDuaUnDOUv471oDg8cjQaUr2MbA= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= +github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= +go.etcd.io/etcd/api/v3 v3.5.12 h1:W4sw5ZoU2Juc9gBWuLk5U6fHfNVyY1WC5g9uiXZio/c= +go.etcd.io/etcd/api/v3 v3.5.12/go.mod h1:Ot+o0SWSyT6uHhA56al1oCED0JImsRiU9Dc26+C2a+4= +go.etcd.io/etcd/client/pkg/v3 v3.5.12 h1:EYDL6pWwyOsylrQyLp2w+HkQ46ATiOvoEdMarindU2A= +go.etcd.io/etcd/client/pkg/v3 v3.5.12/go.mod h1:seTzl2d9APP8R5Y2hFL3NVlD6qC/dOT+3kvrqPyTas4= +go.etcd.io/etcd/client/v3 v3.5.12 h1:v5lCPXn1pf1Uu3M4laUE2hp/geOTc5uPcYYsNe1lDxg= +go.etcd.io/etcd/client/v3 v3.5.12/go.mod h1:tSbBCakoWmmddL+BKVAJHa9km+O/E+bumDe9mSbPiqw= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw= +go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0 h1:rbRJ8BBoVMsQShESYZ0FkvcITu8X8QNwJogcLUmDNNw= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0/go.mod h1:ru6KHrNtNHxM4nD/vd6QrLVWgKhxPYgblq4VAtNawTQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= -go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= -golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg= -golang.org/x/arch v0.12.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= +golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= -golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= +golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= +golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 h1:DHNhtq3sNNzrvduZZIiFyXWOL9IWaDPHqTnLJp+rCBY= +golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= +golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= +golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= +golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8= -google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= -google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= -google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI= +google.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964= +google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79 h1:Nt6z9UHqSlIdIGJdz6KhTIs2VRx/iOsA5iE8bmQNcxs= +google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79/go.mod h1:kTmlBHMPqR5uCZPBvwa2B18mvubkjyY3CRLI0c6fj0s= +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:AtEkQdl5b6zsybXcbz00j1LwNodDuH6hVifIaNqk7NQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 h1:tRPGkdGHuewF4UisLzzHHr1spKw92qLM98nIzxbC0wY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= +google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= @@ -631,14 +391,3 @@ gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkD gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s= gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/horizon/horizon b/horizon/horizon new file mode 100755 index 00000000..a80bc40a Binary files /dev/null and b/horizon/horizon differ diff --git a/horizon/internal/application/controller/controller.go b/horizon/internal/application/controller/controller.go new file mode 100644 index 00000000..61c0fd39 --- /dev/null +++ b/horizon/internal/application/controller/controller.go @@ -0,0 +1,90 @@ +package controller + +import ( + "sync" + + "github.com/Meesho/BharatMLStack/horizon/internal/application/handler" + "github.com/Meesho/BharatMLStack/horizon/pkg/api" + "github.com/gin-gonic/gin" +) + +type Config interface { + Onboard(ctx *gin.Context) + GetAll(ctx *gin.Context) + Edit(ctx *gin.Context) +} + +var ( + configController Config + once sync.Once + emptyResponse = "" +) + +type V1 struct { + Config handler.Config +} + +func NewConfigController() Config { + if configController == nil { + once.Do(func() { + configController = &V1{ + Config: handler.NewConfigHandler(1), + } + }) + } + return configController +} + +func (c *V1) Onboard(ctx *gin.Context) { + var request handler.OnboardRequest + if err := ctx.ShouldBindJSON(&request); err != nil { + ctx.JSON(api.NewBadRequestError(err.Error()).StatusCode, handler.Response{ + Error: err.Error(), + Data: handler.Message{Message: emptyResponse}, + }) + return + } + response, err := c.Config.Onboard(request) + if err != nil { + ctx.JSON(api.NewInternalServerError(err.Error()).StatusCode, handler.Response{ + Error: err.Error(), + Data: handler.Message{Message: emptyResponse}, + }) + return + } + ctx.JSON(200, response) +} + +func (c *V1) GetAll(ctx *gin.Context) { + response, err := c.Config.GetAll() + if err != nil { + ctx.JSON(api.NewInternalServerError(err.Error()).StatusCode, handler.Response{ + Error: err.Error(), + Data: handler.Message{Message: emptyResponse}, + }) + return + } + ctx.JSON(200, response) +} + +func (c *V1) Edit(ctx *gin.Context) { + var request handler.EditRequest + token := ctx.Param("token") + if err := ctx.ShouldBindJSON(&request); err != nil { + ctx.JSON(api.NewBadRequestError(err.Error()).StatusCode, handler.Response{ + Error: err.Error(), + Data: handler.Message{Message: emptyResponse}, + }) + return + } + request.Payload.AppToken = token + response, err := c.Config.Edit(request) + if err != nil { + ctx.JSON(api.NewInternalServerError(err.Error()).StatusCode, handler.Response{ + Error: err.Error(), + Data: handler.Message{Message: emptyResponse}, + }) + return + } + ctx.JSON(200, response) +} diff --git a/horizon/internal/application/handler/application.go b/horizon/internal/application/handler/application.go new file mode 100644 index 00000000..954d84ca --- /dev/null +++ b/horizon/internal/application/handler/application.go @@ -0,0 +1,114 @@ +package handler + +import ( + "fmt" + "time" + + "github.com/Meesho/BharatMLStack/horizon/internal/repositories/sql/application" + "github.com/Meesho/BharatMLStack/horizon/pkg/infra" + "github.com/google/uuid" + "github.com/rs/zerolog/log" +) + +var ( + emptyResponse = "" + activeTrue = true +) + +type applicationConfig struct { + ApplicationRepo application.ApplicationRepository +} + +func InitV1ConfigHandler() Config { + if config == nil { + conn, err := infra.SQL.GetConnection() + if err != nil { + log.Fatal().Err(err).Msg("Failed to get SQL connection") + } + sqlConn := conn.(*infra.SQLConnection) + + ApplicationRepo, err := application.Repository(sqlConn) + if err != nil { + log.Fatal().Err(err).Msg("Failed to create config repository") + } + + config = &applicationConfig{ + ApplicationRepo: ApplicationRepo, + } + } + return config +} + +func (c *applicationConfig) Onboard(request OnboardRequest) (Response, error) { + appToken := uuid.New().String() + + application := application.Application{ + AppToken: appToken, + Bu: request.Payload.Bu, + Team: request.Payload.Team, + Service: request.Payload.Service, + Active: activeTrue, + CreatedBy: request.CreatedBy, + } + + err := c.ApplicationRepo.Create(&application) + if err != nil { + return Response{ + Error: err.Error(), + Data: Message{Message: emptyResponse}, + }, err + } + return Response{ + Error: emptyResponse, + Data: Message{Message: fmt.Sprintf("Application onboarded successfully with token: %s", appToken)}, + }, nil +} + +func (c *applicationConfig) GetAll() (GetAllResponse, error) { + applications, err := c.ApplicationRepo.GetAll() + if err != nil { + return GetAllResponse{}, err + } + + response := []ApplicationConfig{} + + for _, application := range applications { + response = append(response, ApplicationConfig{ + AppToken: application.AppToken, + Active: application.Active, + Bu: application.Bu, + Team: application.Team, + Service: application.Service, + CreatedBy: application.CreatedBy, + UpdatedBy: application.UpdatedBy, + CreatedAt: application.CreatedAt.Format(time.RFC3339), + UpdatedAt: application.UpdatedAt.Format(time.RFC3339), + }) + } + return GetAllResponse{ + Data: response, + }, nil +} + +func (c *applicationConfig) Edit(request EditRequest) (Response, error) { + application := application.Application{ + AppToken: request.Payload.AppToken, + Bu: request.Payload.Bu, + Team: request.Payload.Team, + Service: request.Payload.Service, + UpdatedBy: request.CreatedBy, + } + + err := c.ApplicationRepo.Update(&application) + if err != nil { + return Response{ + Error: err.Error(), + Data: Message{Message: emptyResponse}, + }, err + } + + return Response{ + Error: emptyResponse, + Data: Message{Message: fmt.Sprintf("Application updated successfully with token: %s", request.Payload.AppToken)}, + }, nil +} diff --git a/horizon/internal/application/handler/config.go b/horizon/internal/application/handler/config.go new file mode 100644 index 00000000..59b594e6 --- /dev/null +++ b/horizon/internal/application/handler/config.go @@ -0,0 +1,7 @@ +package handler + +type Config interface { + Onboard(OnboardRequest) (Response, error) + GetAll() (GetAllResponse, error) + Edit(EditRequest) (Response, error) +} diff --git a/horizon/internal/application/handler/init.go b/horizon/internal/application/handler/init.go new file mode 100644 index 00000000..05cd5ff6 --- /dev/null +++ b/horizon/internal/application/handler/init.go @@ -0,0 +1,14 @@ +package handler + +var ( + config Config +) + +func NewConfigHandler(version int) Config { + switch version { + case 1: + return InitV1ConfigHandler() + default: + return nil + } +} diff --git a/horizon/internal/application/handler/models.go b/horizon/internal/application/handler/models.go new file mode 100644 index 00000000..20ddc04e --- /dev/null +++ b/horizon/internal/application/handler/models.go @@ -0,0 +1,50 @@ +package handler + +type OnboardRequestPayload struct { + Bu string `json:"bu"` + Team string `json:"team"` + Service string `json:"service"` +} + +type OnboardRequest struct { + Payload OnboardRequestPayload `json:"payload"` + CreatedBy string `json:"created_by"` +} + +type Message struct { + Message string `json:"message"` +} + +type Response struct { + Error string `json:"error"` + Data Message `json:"data"` +} + +type ApplicationConfig struct { + AppToken string `json:"app_token"` + Active bool `json:"active"` + Bu string `json:"bu"` + Team string `json:"team"` + Service string `json:"service"` + CreatedBy string `json:"created_by"` + UpdatedBy string `json:"updated_by"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +type GetAllResponse struct { + Data []ApplicationConfig `json:"data"` +} + +type EditRequestPayload struct { + AppToken string `json:"app_token"` + Bu string `json:"bu"` + Team string `json:"team"` + Service string `json:"service"` + UpdatedBy string `json:"updated_by"` +} + +type EditRequest struct { + Payload EditRequestPayload `json:"payload"` + CreatedBy string `json:"created_by"` +} diff --git a/horizon/internal/application/route/router.go b/horizon/internal/application/route/router.go new file mode 100644 index 00000000..12d65c66 --- /dev/null +++ b/horizon/internal/application/route/router.go @@ -0,0 +1,34 @@ +package route + +import ( + "sync" + + "github.com/Meesho/BharatMLStack/horizon/internal/application/controller" + "github.com/Meesho/BharatMLStack/horizon/pkg/httpframework" +) + +var initApplicationRouterOnce sync.Once + +func Init() { + initApplicationRouterOnce.Do(func() { + api := httpframework.Instance().Group("/api") + { + v1 := api.Group("/v1/horizon") + { + + // Application Config Registry/Discovery routes + registry := v1.Group("/application-registry/applications") + { + registry.POST("", controller.NewConfigController().Onboard) + registry.PUT("/:token", controller.NewConfigController().Edit) + } + + discovery := v1.Group("/application-discovery/applications") + { + discovery.GET("", controller.NewConfigController().GetAll) + } + + } + } + }) +} diff --git a/horizon/internal/auth/controller/controller.go b/horizon/internal/auth/controller/controller.go index 8a432250..5628f8a5 100644 --- a/horizon/internal/auth/controller/controller.go +++ b/horizon/internal/auth/controller/controller.go @@ -7,6 +7,7 @@ import ( "sync" "github.com/Meesho/BharatMLStack/horizon/internal/auth/handler" + "github.com/Meesho/BharatMLStack/horizon/internal/constant" "github.com/Meesho/BharatMLStack/horizon/internal/online-feature-store/controller" "github.com/Meesho/BharatMLStack/horizon/pkg/api" "github.com/gin-gonic/gin" @@ -19,6 +20,7 @@ type Auth interface { Logout(ctx *gin.Context) GetAllUsers(ctx *gin.Context) UpdateUserAccessAndRole(ctx *gin.Context) + GetPermissionByRole(ctx *gin.Context) } var ( @@ -138,3 +140,13 @@ func (a *AuthController) UpdateUserAccessAndRole(ctx *gin.Context) { } ctx.JSON(http.StatusOK, gin.H{"message": "User info updated successfully"}) } + +func (a *AuthController) GetPermissionByRole(ctx *gin.Context) { + role := ctx.GetString("role") + if role == "" { + ctx.JSON(http.StatusBadRequest, gin.H{constant.Error: "Role is required"}) + return + } + rolePermission := a.Authenticator.GetPermissionByRole(role) + ctx.JSON(http.StatusOK, rolePermission) +} diff --git a/horizon/internal/auth/handler/auth.go b/horizon/internal/auth/handler/auth.go index 4f51e79f..12190018 100644 --- a/horizon/internal/auth/handler/auth.go +++ b/horizon/internal/auth/handler/auth.go @@ -7,6 +7,7 @@ import ( "time" "github.com/Meesho/BharatMLStack/horizon/internal/repositories/sql/auth" + "github.com/Meesho/BharatMLStack/horizon/internal/repositories/sql/rolepermission" "github.com/Meesho/BharatMLStack/horizon/internal/repositories/sql/token" "github.com/Meesho/BharatMLStack/horizon/pkg/infra" "github.com/dgrijalva/jwt-go" @@ -15,8 +16,9 @@ import ( ) type AuthHandler struct { - authRepo auth.Repository - tokenRepo token.Repository + authRepo auth.Repository + tokenRepo token.Repository + rolePermission rolepermission.Repository } func InitAuthHandler() Authenticator { @@ -32,9 +34,14 @@ func InitAuthHandler() Authenticator { if err != nil { log.Error().Msgf("Error in creating token repository") } + rolePermission, err := rolepermission.NewRepository(sqlConn) + if err != nil { + log.Error().Msgf("Error in creating role permission repository") + } authenticator = &AuthHandler{ - authRepo: authRepo, - tokenRepo: tokenRepo, + authRepo: authRepo, + tokenRepo: tokenRepo, + rolePermission: rolePermission, } }) } @@ -216,3 +223,55 @@ func (a *AuthHandler) UpdateUserAccessAndRole(email string, isActive bool, role } return nil } + +func (a *AuthHandler) GetPermissionByRole(role string) PermissionResponse { + permissions, err := a.rolePermission.GetPermissionsByRole(role) + if err != nil { + log.Warn().Msgf("Error fetching permissions for role %s: %v", role, err) + return PermissionResponse{ + Role: role, + Permissions: []ServiceSet{}, + } + } + + serviceMap := make(map[string]map[string][]string) + + for _, perm := range permissions { + if _, ok := serviceMap[perm.Service]; !ok { + serviceMap[perm.Service] = make(map[string][]string) + } + serviceMap[perm.Service][perm.ScreenType] = append(serviceMap[perm.Service][perm.ScreenType], perm.Module) + } + + var serviceSets []ServiceSet + for service, screenMap := range serviceMap { + var screens []ScreenInfo + for screenType, actions := range screenMap { + screens = append(screens, ScreenInfo{ + ScreenType: screenType, + AllowedActions: unique(actions), + }) + } + serviceSets = append(serviceSets, ServiceSet{ + Service: service, + Screens: screens, + }) + } + + return PermissionResponse{ + Role: role, + Permissions: serviceSets, + } +} + +func unique(input []string) []string { + seen := make(map[string]struct{}) + var result []string + for _, v := range input { + if _, ok := seen[v]; !ok { + seen[v] = struct{}{} + result = append(result, v) + } + } + return result +} diff --git a/horizon/internal/auth/handler/handler.go b/horizon/internal/auth/handler/handler.go index 36f7b358..31713b02 100644 --- a/horizon/internal/auth/handler/handler.go +++ b/horizon/internal/auth/handler/handler.go @@ -16,4 +16,5 @@ type Authenticator interface { Logout(token string) error GetAllUsers() ([]UserListingResponse, error) UpdateUserAccessAndRole(email string, isActive bool, role string) error + GetPermissionByRole(role string) PermissionResponse } diff --git a/horizon/internal/auth/handler/models.go b/horizon/internal/auth/handler/models.go index dab597f8..0444af2c 100644 --- a/horizon/internal/auth/handler/models.go +++ b/horizon/internal/auth/handler/models.go @@ -39,3 +39,17 @@ type UserListingResponse struct { IsActive bool `json:"is_active"` Role string `json:"role"` } +type PermissionResponse struct { + Role string `json:"role"` + Permissions []ServiceSet `json:"permissions"` +} + +type ServiceSet struct { + Service string `json:"service"` + Screens []ScreenInfo `json:"screens"` +} + +type ScreenInfo struct { + ScreenType string `json:"screenType"` + AllowedActions []string `json:"allowedActions"` +} diff --git a/horizon/internal/auth/router/router.go b/horizon/internal/auth/router/router.go index 68d3ee6a..24e8f0d8 100644 --- a/horizon/internal/auth/router/router.go +++ b/horizon/internal/auth/router/router.go @@ -16,6 +16,7 @@ func Init() { api.GET("/users", controller.NewController().GetAllUsers) api.PUT("/update-user", controller.NewController().UpdateUserAccessAndRole) api.GET("/health", Health) + api.GET("/api/v1/horizon/permission-by-role", controller.NewController().GetPermissionByRole) } } diff --git a/horizon/internal/configs/app_config.go b/horizon/internal/configs/app_config.go new file mode 100644 index 00000000..927c7252 --- /dev/null +++ b/horizon/internal/configs/app_config.go @@ -0,0 +1,192 @@ +package configs + +type Configs struct { + // App configuration + AppName string `mapstructure:"app_name"` + AppEnv string `mapstructure:"app_env"` + AppLogLevel string `mapstructure:"app_log_level"` + AppMetricSamplingRate float64 `mapstructure:"app_metric_sampling_rate"` + AppPort int `mapstructure:"app_port"` + + // MySQL configuration + MysqlDbName string `mapstructure:"mysql_db_name"` + MysqlMasterHost string `mapstructure:"mysql_master_host"` + MysqlMasterMaxPoolSize string `mapstructure:"mysql_master_max_pool_size"` + MysqlMasterMinPoolSize string `mapstructure:"mysql_master_min_pool_size"` + MysqlMasterPassword string `mapstructure:"mysql_master_password"` + MysqlMasterPort int `mapstructure:"mysql_master_port"` + MysqlMasterUsername string `mapstructure:"mysql_master_username"` + MysqlSlaveHost string `mapstructure:"mysql_slave_host"` + MysqlSlaveMaxPoolSize string `mapstructure:"mysql_slave_max_pool_size"` + MysqlSlaveMinPoolSize string `mapstructure:"mysql_slave_min_pool_size"` + MysqlSlavePassword string `mapstructure:"mysql_slave_password"` + MysqlSlavePort int `mapstructure:"mysql_slave_port"` + MysqlSlaveUsername string `mapstructure:"mysql_slave_username"` + + // Etcd configuration + EtcdPassword string `mapstructure:"etcd_password"` + EtcdServer string `mapstructure:"etcd_server"` + EtcdUsername string `mapstructure:"etcd_username"` + EtcdWatcherEnabled bool `mapstructure:"etcd_watcher_enabled"` + + // Ringmaster configuration + RingmasterApiKey string `mapstructure:"ringmaster_api_key"` + RingmasterAuthorization string `mapstructure:"ringmaster_authorization"` + RingmasterBaseUrl string `mapstructure:"ringmaster_base_url"` + RingmasterEnvironment string `mapstructure:"ringmaster_environment"` + RingmasterMiscSession string `mapstructure:"ringmaster_misc_session"` + + // Slack configuration + SlackCcTags string `mapstructure:"slack_cc_tags"` + SlackChannel string `mapstructure:"slack_channel"` + SlackInactiveDays int `mapstructure:"slack_inactive_days"` + SlackWebhookUrl string `mapstructure:"slack_webhook_url"` + + // Vmselect configuration + VmselectApiKey string `mapstructure:"vmselect_api_key"` + VmselectBaseUrl string `mapstructure:"vmselect_base_url"` + VmselectStartDaysAgo int `mapstructure:"vmselect_start_days_ago"` + + // Horizon configuration + HorizonAppName string `mapstructure:"horizon_app_name"` + + // Other configurations + DefaultCpuThreshold string `mapstructure:"default_cpu_threshold"` + DefaultGpuThreshold string `mapstructure:"default_gpu_threshold"` + DefaultModelPath string `mapstructure:"default_model_path"` + + GcsModelBucket string `mapstructure:"gcs_model_bucket"` + GcsModelBasePath string `mapstructure:"gcs_model_base_path"` + GcsConfigBasePath string `mapstructure:"gcs_config_base_path"` + GcsConfigBucket string `mapstructure:"gcs_config_bucket"` + + GrafanaBaseUrl string `mapstructure:"grafana_base_url"` + + HostUrlSuffix string `mapstructure:"host_url_suffix"` + + NumerixAppName string `mapstructure:"numerix_app_name"` + NumerixMonitoringUrl string `mapstructure:"numerix_monitoring_url"` + + BulkDeletePredatorEnabled bool `mapstructure:"bulk_delete_predator_enabled"` + BulkDeleteInferflowEnabled bool `mapstructure:"bulk_delete_inferflow_enabled"` + BulkDeleteNumerixEnabled bool `mapstructure:"bulk_delete_numerix_enabled"` + BulkDeletePredatorMaxInactiveDays int `mapstructure:"bulk_delete_predator_max_inactive_days"` + BulkDeleteInferflowMaxInactiveDays int `mapstructure:"bulk_delete_inferflow_max_inactive_days"` + BulkDeleteNumerixMaxInactiveDays int `mapstructure:"bulk_delete_numerix_max_inactive_days"` + BulkDeletePredatorRequestSubmissionEnabled bool `mapstructure:"bulk_delete_predator_request_submission_enabled"` + + InferflowAppName string `mapstructure:"inferflow_app_name"` + + SkyeAppName string `mapstructure:"skye_app_name"` + + PhoenixServerBaseUrl string `mapstructure:"phoenix_server_base_url"` + + PredatorMonitoringUrl string `mapstructure:"predator_monitoring_url"` + ScheduledCronExpression string `mapstructure:"scheduled_cron_expression"` + + TestDeployableID int `mapstructure:"test_deployable_id"` + TestGpuDeployableID int `mapstructure:"test_gpu_deployable_id"` + + // Pricing Feature Retrieval Service configuration + PricingFeatureRetrievalBatchSize string `mapstructure:"pricing_feature_retrieval_batch_size"` + PricingFeatureRetrievalDialTimeout string `mapstructure:"pricing_feature_retrieval_dial_timeout_ms"` + PricingFeatureRetrievalHost string `mapstructure:"pricing_feature_retrieval_host"` + PricingFeatureRetrievalIdleConnTimeout string `mapstructure:"pricing_feature_retrieval_idle_conn_timeout_ms"` + PricingFeatureRetrievalMaxIdleConns string `mapstructure:"pricing_feature_retrieval_max_idle_conns"` + PricingFeatureRetrievalMaxIdleConnsPerHost string `mapstructure:"pricing_feature_retrieval_max_idle_conns_per_host"` + PricingFeatureRetrievalPort string `mapstructure:"pricing_feature_retrieval_port"` + PricingFeatureRetrievalGrpcPlainText bool `mapstructure:"pricing_feature_retrieval_grpc_plain_text"` + PricingFeatureRetrievalTimeoutMs string `mapstructure:"pricing_feature_retrieval_timeout_in_ms"` + + OnlineFeatureStoreAppName string `mapstructure:"online_feature_store_app_name"` + ScyllaActiveConfIds string `mapstructure:"scylla_active_conf_ids"` + RedisFailoverActiveConfIds string `mapstructure:"redis_failover_active_conf_ids"` + DistributedCacheActiveConfIds string `mapstructure:"distributed_cache_active_conf_ids"` + InMemoryCacheActiveConfIds string `mapstructure:"in_memory_cache_active_conf_ids"` + + ArgoCDAPI string `mapstructure:"argocd_api"` + ArgoCDToken string `mapstructure:"argocd_token"` + + WorkingEnv string `mapstructure:"working_env"` + + GitHubAppID int64 `mapstructure:"github_app_id"` + GitHubInstallationID int64 `mapstructure:"github_installation_id"` + GitHubPrivateKey string `mapstructure:"github_private_key"` + GitHubOwner string `mapstructure:"github_owner"` + LocalModelPath string `mapstructure:"local_model_path"` + + ServiceConfigSource string `mapstructure:"service_config_source"` + ServiceConfigRepo string `mapstructure:"service_config_repo"` + ServiceConfigPath string `mapstructure:"service_config_path"` + GitHubCommitAuthor string `mapstructure:"github_commit_author"` + GitHubCommitEmail string `mapstructure:"github_commit_email"` + + GitHubHelmChartRepo string `mapstructure:"github_helm_chart_repo"` + GitHubInfraHelmChartRepo string `mapstructure:"github_infra_helm_chart_repo"` + GitHubArgoRepo string `mapstructure:"github_argo_repo"` + + VictoriaMetricsServerAddress string `mapstructure:"victoriametrics_server_address"` + + GitHubBranchPrd string `mapstructure:"github_branch_prd"` + GitHubBranchGcpPrd string `mapstructure:"github_branch_gcp_prd"` + GitHubBranchInt string `mapstructure:"github_branch_int"` + GitHubBranchGcpInt string `mapstructure:"github_branch_gcp_int"` + GitHubBranchDev string `mapstructure:"github_branch_dev"` + GitHubBranchGcpDev string `mapstructure:"github_branch_gcp_dev"` + GitHubBranchGcpStg string `mapstructure:"github_branch_gcp_stg"` + GitHubBranchFtr string `mapstructure:"github_branch_ftr"` + GitHubBranchGcpFtr string `mapstructure:"github_branch_gcp_ftr"` + + IsMeeshoEnabled bool `mapstructure:"is_meesho_enabled"` + + RepositoryName string `mapstructure:"repository_name"` + BranchName string `mapstructure:"branch_name"` + + // DNS API configuration (for Meesho builds only) + DNSAPIBaseURL string `mapstructure:"dns_api_base_url"` + DNSAPIKey string `mapstructure:"dns_api_key"` + + ScyllaActiveConfigIds string `mapstructure:"scylla_active_config_ids"` + Scylla1ContactPoints string `mapstructure:"scylla_1_contact_points"` + Scylla1Port int `mapstructure:"scylla_1_port"` + Scylla1Keyspace string `mapstructure:"scylla_1_keyspace"` + Scylla1Username string `mapstructure:"scylla_1_username"` + Scylla1Password string `mapstructure:"scylla_1_password"` + + PrismBaseUrl string `mapstructure:"prism_base_url"` + PrismAppUserID string `mapstructure:"prism_app_user_id"` + AirflowBaseUrl string `mapstructure:"airflow_base_url"` + AirflowUsername string `mapstructure:"airflow_username"` + AirflowPassword string `mapstructure:"airflow_password"` + InitialIngestionPrismJobID int `mapstructure:"initial_ingestion_prism_job_id"` + InitialIngestionPrismStepID int `mapstructure:"initial_ingestion_prism_step_id"` + InitialIngestionAirflowDAGID string `mapstructure:"initial_ingestion_airflow_dag_id"` + VariantScaleUpPrismJobID int `mapstructure:"variant_scaleup_prism_job_id"` + VariantScaleUpPrismStepID int `mapstructure:"variant_scaleup_prism_step_id"` + VariantScaleUpAirflowDAGID string `mapstructure:"variant_scaleup_airflow_dag_id"` + VariantOnboardingCronExpression string `mapstructure:"variant_onboarding_cron_expression"` + VariantScaleUpCronExpression string `mapstructure:"variant_scaleup_cron_expression"` + MQIdTopicsMapping string `mapstructure:"mq_id_topics_mapping"` + VariantsList string `mapstructure:"variants_list"` + + SkyeScyllaActiveConfigIds string `mapstructure:"skye_scylla_active_config_ids"` + Scylla2ContactPoints string `mapstructure:"scylla_2_contact_points"` + Scylla2Port int `mapstructure:"scylla_2_port"` + Scylla2Keyspace string `mapstructure:"scylla_2_keyspace"` + Scylla2Username string `mapstructure:"scylla_2_username"` + Scylla2Password string `mapstructure:"scylla_2_password"` + SkyeNumberOfPartitions int `mapstructure:"skye_number_of_partitions"` + SkyeFailureProducerMqId int `mapstructure:"skye_failure_producer_mq_id"` + HorizonToSkyeScyllaConfIdMap string `mapstructure:"horizon_to_skye_scylla_conf_id_map"` + + SkyeHost string `mapstructure:"skye_host"` + SkyePort string `mapstructure:"skye_port"` + SkyeAuthToken string `mapstructure:"skye_auth_token"` + SkyeDeadlineExceedMS int `mapstructure:"skye_deadline_exceed_ms"` + + // When true, initial ingestion and variant processor call skye-trigger (OSS) instead of Airflow + UseSkyeTriggerInsteadOfAirflow bool `mapstructure:"use_skye_trigger_instead_of_airflow"` + SkyeTriggerURL string `mapstructure:"skye_trigger_url"` +} + +type DynamicConfigs struct{} diff --git a/horizon/internal/configs/config_init_stub.go b/horizon/internal/configs/config_init_stub.go new file mode 100644 index 00000000..b16a0d37 --- /dev/null +++ b/horizon/internal/configs/config_init_stub.go @@ -0,0 +1,205 @@ +//go:build !meesho + +package configs + +import ( + "log" + + "github.com/Meesho/BharatMLStack/horizon/pkg/config" + "github.com/spf13/viper" +) + +// ConfigHolder interface for app config +type ConfigHolder interface { + GetStaticConfig() interface{} + GetDynamicConfig() interface{} +} + +// InitConfig initializes configuration for open-source builds +func InitConfig(configHolder ConfigHolder) { + config.InitEnv() + + staticConfig := configHolder.GetStaticConfig() + cfg, ok := staticConfig.(*Configs) + if !ok { + log.Fatal("Failed to cast static config to *Configs") + } + + // Bind environment variables explicitly + // Viper needs explicit binding to map MYSQL_MASTER_HOST -> mysql_master_host + bindEnvVars() + + // Bind environment variables to config keys + // This maps APP_NAME (env) -> app_name (config key) + if err := viper.Unmarshal(cfg); err != nil { + log.Fatalf("Failed to unmarshal config from environment: %v", err) + } +} + +// bindEnvVars explicitly binds environment variables to Viper keys +// This is necessary because Viper's AutomaticEnv() doesn't automatically +// map uppercase env vars (MYSQL_MASTER_HOST) to lowercase keys (mysql_master_host) +func bindEnvVars() { + // App configuration + viper.BindEnv("app_name", "APP_NAME") + viper.BindEnv("app_env", "APP_ENV") + viper.BindEnv("app_log_level", "APP_LOG_LEVEL") + viper.BindEnv("app_metric_sampling_rate", "APP_METRIC_SAMPLING_RATE") + viper.BindEnv("app_port", "APP_PORT") + + // MySQL configuration + viper.BindEnv("mysql_db_name", "MYSQL_DB_NAME") + viper.BindEnv("mysql_master_host", "MYSQL_MASTER_HOST") + viper.BindEnv("mysql_master_max_pool_size", "MYSQL_MASTER_MAX_POOL_SIZE") + viper.BindEnv("mysql_master_min_pool_size", "MYSQL_MASTER_MIN_POOL_SIZE") + viper.BindEnv("mysql_master_password", "MYSQL_MASTER_PASSWORD") + viper.BindEnv("mysql_master_port", "MYSQL_MASTER_PORT") + viper.BindEnv("mysql_master_username", "MYSQL_MASTER_USERNAME") + viper.BindEnv("mysql_slave_host", "MYSQL_SLAVE_HOST") + viper.BindEnv("mysql_slave_max_pool_size", "MYSQL_SLAVE_MAX_POOL_SIZE") + viper.BindEnv("mysql_slave_min_pool_size", "MYSQL_SLAVE_MIN_POOL_SIZE") + viper.BindEnv("mysql_slave_password", "MYSQL_SLAVE_PASSWORD") + viper.BindEnv("mysql_slave_port", "MYSQL_SLAVE_PORT") + viper.BindEnv("mysql_slave_username", "MYSQL_SLAVE_USERNAME") + + // Etcd configuration + viper.BindEnv("etcd_password", "ETCD_PASSWORD") + viper.BindEnv("etcd_server", "ETCD_SERVER") + viper.BindEnv("etcd_username", "ETCD_USERNAME") + viper.BindEnv("etcd_watcher_enabled", "ETCD_WATCHER_ENABLED") + + // Slack configuration + viper.BindEnv("slack_cc_tags", "SLACK_CC_TAGS") + viper.BindEnv("slack_channel", "SLACK_CHANNEL") + viper.BindEnv("slack_inactive_days", "SLACK_INACTIVE_DAYS") + viper.BindEnv("slack_webhook_url", "SLACK_WEBHOOK_URL") + + // Vmselect configuration + viper.BindEnv("vmselect_api_key", "VMSELECT_API_KEY") + viper.BindEnv("vmselect_base_url", "VMSELECT_BASE_URL") + viper.BindEnv("vmselect_start_days_ago", "VMSELECT_START_DAYS_AGO") + + // Horizon configuration + viper.BindEnv("horizon_app_name", "HORIZON_APP_NAME") + + // Other configurations + viper.BindEnv("default_cpu_threshold", "DEFAULT_CPU_THRESHOLD") + viper.BindEnv("default_gpu_threshold", "DEFAULT_GPU_THRESHOLD") + viper.BindEnv("default_model_path", "DEFAULT_MODEL_PATH") + + viper.BindEnv("gcs_model_bucket", "GCS_MODEL_BUCKET") + viper.BindEnv("gcs_model_base_path", "GCS_MODEL_BASE_PATH") + viper.BindEnv("gcs_enabled", "GCS_ENABLED") + + viper.BindEnv("grafana_base_url", "GRAFANA_BASE_URL") + + viper.BindEnv("host_url_suffix", "HOST_URL_SUFFIX") + + viper.BindEnv("numerix_app_name", "NUMERIX_APP_NAME") + viper.BindEnv("numerix_monitoring_url", "NUMERIX_MONITORING_URL") + + viper.BindEnv("max_numerix_inactive_age", "MAX_NUMERIX_INACTIVE_AGE") + viper.BindEnv("max_inferflow_inactive_age", "MAX_INFERFLOW_INACTIVE_AGE") + viper.BindEnv("max_predator_inactive_age", "MAX_PREDATOR_INACTIVE_AGE") + + viper.BindEnv("inferflow_app_name", "INFERFLOW_APP_NAME") + + viper.BindEnv("phoenix_server_base_url", "PHOENIX_SERVER_BASE_URL") + + viper.BindEnv("scheduled_cron_expression", "SCHEDULED_CRON_EXPRESSION") + + viper.BindEnv("test_deployable_id", "TEST_DEPLOYABLE_ID") + viper.BindEnv("test_gpu_deployable_id", "TEST_GPU_DEPLOYABLE_ID") + + // Online Feature Store configuration + viper.BindEnv("online_feature_store_app_name", "ONLINE_FEATURE_STORE_APP_NAME") + viper.BindEnv("scylla_active_conf_ids", "SCYLLA_ACTIVE_CONFIG_IDS") + viper.BindEnv("redis_failover_active_conf_ids", "REDIS_FAILOVER_ACTIVE_CONFIG_IDS") + viper.BindEnv("distributed_cache_active_conf_ids", "DISTRIBUTED_CACHE_ACTIVE_CONFIG_IDS") + viper.BindEnv("in_memory_cache_active_conf_ids", "IN_MEMORY_CACHE_ACTIVE_CONFIG_IDS") + + viper.BindEnv("argocd_api", "ARGOCD_API") + viper.BindEnv("argocd_token", "ARGOCD_TOKEN") + + viper.BindEnv("working_env", "WORKING_ENV") + + viper.BindEnv("github_app_id", "GITHUB_APP_ID") + viper.BindEnv("github_installation_id", "GITHUB_INSTALLATION_ID") + viper.BindEnv("github_private_key", "GITHUB_PRIVATE_KEY") + viper.BindEnv("github_owner", "GITHUB_OWNER") + viper.BindEnv("github_commit_author", "GITHUB_COMMIT_AUTHOR") + viper.BindEnv("github_commit_email", "GITHUB_COMMIT_EMAIL") + + viper.BindEnv("github_helm_chart_repo", "GITHUB_HELM_CHART_REPO") + viper.BindEnv("github_infra_helm_chart_repo", "GITHUB_INFRA_HELM_CHART_REPO") + viper.BindEnv("github_argo_repo", "GITHUB_ARGO_REPO") + + viper.BindEnv("victoriametrics_server_address", "VICTORIAMETRICS_SERVER_ADDRESS") + + viper.BindEnv("github_branch_prd", "GITHUB_BRANCH_PRD") + viper.BindEnv("github_branch_gcp_prd", "GITHUB_BRANCH_GCP_PRD") + viper.BindEnv("github_branch_int", "GITHUB_BRANCH_INT") + viper.BindEnv("github_branch_gcp_int", "GITHUB_BRANCH_GCP_INT") + viper.BindEnv("github_branch_dev", "GITHUB_BRANCH_DEV") + viper.BindEnv("github_branch_gcp_dev", "GITHUB_BRANCH_GCP_DEV") + viper.BindEnv("github_branch_gcp_stg", "GITHUB_BRANCH_GCP_STG") + viper.BindEnv("github_branch_ftr", "GITHUB_BRANCH_FTR") + viper.BindEnv("github_branch_gcp_ftr", "GITHUB_BRANCH_GCP_FTR") + + viper.BindEnv("repository_name", "REPOSITORY_NAME") + viper.BindEnv("branch_name", "BRANCH_NAME") + + viper.BindEnv("service_config_source", "SERVICE_CONFIG_SOURCE") + viper.BindEnv("service_config_repo", "SERVICE_CONFIG_REPO") + viper.BindEnv("service_config_path", "SERVICE_CONFIG_PATH") + + // Meesho flags + viper.BindEnv("is_meesho_enabled", "MEESHO_ENABLED") + viper.BindEnv("is_dummy_model_enabled", "IS_DUMMY_MODEL_ENABLED") + + // DNS API configuration + viper.BindEnv("dns_api_base_url", "DNS_API_BASE_URL") + viper.BindEnv("dns_api_key", "DNS_API_KEY") + + viper.BindEnv("scylla_active_config_ids", "SCYLLA_ACTIVE_CONFIG_IDS") + viper.BindEnv("scylla_1_contact_points", "SCYLLA_1_CONTACT_POINTS") + viper.BindEnv("scylla_1_port", "SCYLLA_1_PORT") + viper.BindEnv("scylla_1_keyspace", "SCYLLA_1_KEYSPACE") + viper.BindEnv("scylla_1_username", "SCYLLA_1_USERNAME") + viper.BindEnv("scylla_1_password", "SCYLLA_1_PASSWORD") + + viper.BindEnv("prism_base_url", "PRISM_BASE_URL") + viper.BindEnv("prism_app_user_id", "PRISM_APP_USER_ID") + viper.BindEnv("airflow_base_url", "AIRFLOW_BASE_URL") + viper.BindEnv("airflow_username", "AIRFLOW_USERNAME") + viper.BindEnv("airflow_password", "AIRFLOW_PASSWORD") + viper.BindEnv("initial_ingestion_prism_job_id", "INITIAL_INGESTION_PRISM_JOB_ID") + viper.BindEnv("initial_ingestion_prism_step_id", "INITIAL_INGESTION_PRISM_STEP_ID") + viper.BindEnv("initial_ingestion_airflow_dag_id", "INITIAL_INGESTION_AIRFLOW_DAG_ID") + viper.BindEnv("variant_scaleup_prism_job_id", "VARIANT_SCALEUP_PRISM_JOB_ID") + viper.BindEnv("variant_scaleup_prism_step_id", "VARIANT_SCALEUP_PRISM_STEP_ID") + viper.BindEnv("variant_scaleup_airflow_dag_id", "VARIANT_SCALEUP_AIRFLOW_DAG_ID") + viper.BindEnv("variant_onboarding_cron_expression", "VARIANT_ONBOARDING_CRON_EXPRESSION") + viper.BindEnv("variant_scaleup_cron_expression", "VARIANT_SCALEUP_CRON_EXPRESSION") + viper.BindEnv("mq_id_topics_mapping", "MQ_ID_TOPICS_MAPPING") + viper.BindEnv("variants_list", "VARIANTS_LIST") + viper.BindEnv("skye_scylla_active_config_ids", "SKYE_SCYLLA_ACTIVE_CONFIG_IDS") + viper.BindEnv("scylla_2_contact_points", "SCYLLA_2_CONTACT_POINTS") + viper.BindEnv("scylla_2_port", "SCYLLA_2_PORT") + viper.BindEnv("scylla_2_keyspace", "SCYLLA_2_KEYSPACE") + viper.BindEnv("scylla_2_username", "SCYLLA_2_USERNAME") + viper.BindEnv("scylla_2_password", "SCYLLA_2_PASSWORD") + viper.BindEnv("skye_app_name", "SKYE_APP_NAME") + viper.BindEnv("horizon_to_skye_scylla_conf_id_map", "HORIZON_TO_SKYE_SCYLLA_CONF_ID_MAP") + viper.BindEnv("skye_number_of_partitions", "SKYE_NUMBER_OF_PARTITIONS") + viper.BindEnv("skye_failure_producer_mq_id", "SKYE_FAILURE_PRODUCER_MQ_ID") + + viper.BindEnv("skye_host", "SKYE_HOST") + viper.BindEnv("skye_port", "SKYE_PORT") + viper.BindEnv("skye_auth_token", "SKYE_AUTH_TOKEN") + viper.BindEnv("skye_deadline_exceed_ms", "SKYE_DEADLINE_EXCEED_MS") + + // OSS: use skye-trigger instead of Airflow for initial ingestion / variant processor + viper.BindEnv("use_skye_trigger_instead_of_airflow", "USE_SKYE_TRIGGER_INSTEAD_OF_AIRFLOW") + viper.BindEnv("skye_trigger_url", "SKYE_TRIGGER_URL") +} diff --git a/horizon/internal/configs/service_config.go b/horizon/internal/configs/service_config.go new file mode 100644 index 00000000..172118ac --- /dev/null +++ b/horizon/internal/configs/service_config.go @@ -0,0 +1,721 @@ +package configs + +import ( + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/Meesho/BharatMLStack/horizon/pkg/github" + "github.com/rs/zerolog/log" + "gopkg.in/yaml.v3" +) + +// LabelsConfig represents optional labels configuration +// If provided, these values drive directory structure, file names, and ArgoCD labels +// If absent, defaults are used (see ApplyDefaults method) +// Note: ArgoCD has filename length constraints (Kubernetes resource names: 63 chars max) +// Keep bu, team, and other label values concise to avoid exceeding limits +type LabelsConfig struct { + // Optional: Business unit (used as-is, no normalization) + // Max recommended length: 20 chars to avoid ArgoCD filename issues + BU string `yaml:"bu" json:"bu"` + + // Optional: Team name (used as-is, no normalization) + // Max recommended length: 20 chars to avoid ArgoCD filename issues + Team string `yaml:"team" json:"team"` + + // Optional: Primary owner email (username extracted for labels) + // Max recommended length: 50 chars (email format) + PrimaryOwner string `yaml:"primary_owner" json:"primary_owner"` + + // Optional: Secondary owner email (username extracted for labels) + // Max recommended length: 50 chars (email format) + SecondaryOwner string `yaml:"secondary_owner" json:"secondary_owner"` + + // Optional: Priority level (e.g., "cp1", "p0", "p1") + // Max recommended length: 10 chars + PriorityV2 string `yaml:"priority_v2" json:"priority_v2"` + + // Optional: Custom labels for vendor extensibility + CustomLabels map[string]string `yaml:"custom_labels" json:"custom_labels"` +} + +// ServiceConfig represents the service configuration loaded from config-as-code +// This replaces the database-based service_config table for open-source compatibility +type ServiceConfig struct { + // Mandatory fields (must be provided) + RepoName string `yaml:"repo_name" json:"repo_name"` // Mandatory: GitHub repository name + AppPort int `yaml:"app_port" json:"app_port"` // Mandatory: Application port + IngressClass string `yaml:"ingress_class" json:"ingress_class"` // Mandatory: Ingress class + Domain string `yaml:"domain" json:"domain"` // Mandatory: Domain name for host generation (e.g., "meesho.int", "example.com") + + // Optional fields (with defaults) + HealthCheck string `yaml:"health_check" json:"health_check"` // Optional: Health check endpoint (default: /health) + AppType string `yaml:"app_type" json:"app_type"` // Optional: Application type (python, java, go, etc.) + TritonRepository string `yaml:"triton_repository" json:"triton_repository"` // Optional: Triton inference server image repository path without tag (e.g., "asia-southeast1-docker.pkg.dev/meesho-devops-admin-0622/prd/ml/triton-inference-server/triton-inference-server") + TelegrafImage string `yaml:"telegraf_image" json:"telegraf_image"` // Optional: Complete Telegraf image path with tag (e.g., "asia-southeast1-docker.pkg.dev/meesho-devops-admin-0622/telegraf:1.24.4-arm64") + InitContainerImage string `yaml:"init_container_image" json:"init_container_image"` // Optional: Complete init container image path with tag for GCS operations (e.g., "asia-southeast1-docker.pkg.dev/meesho-devops-admin-0622/admin/devops/build-tools:lunar-v1.0.3-slim") + + // Labels section (optional, vendor-agnostic) + // If provided, these values drive directory structure, file names, and ArgoCD labels + // If absent, defaults are used (see ApplyDefaults method) + Labels *LabelsConfig `yaml:"labels" json:"labels"` // Optional: Labels configuration section + + // Legacy fields (deprecated, kept for backward compatibility) + // These are now read from Labels section if present + PrimaryOwner string `yaml:"primary_owner" json:"primary_owner"` // Deprecated: Use labels.primary_owner + SecondaryOwner string `yaml:"secondary_owner" json:"secondary_owner"` // Deprecated: Use labels.secondary_owner + Team string `yaml:"team" json:"team"` // Deprecated: Use labels.team + BU string `yaml:"bu" json:"bu"` // Deprecated: Use labels.bu + PriorityV2 string `yaml:"priority_v2" json:"priority_v2"` // Deprecated: Use labels.priority_v2 + ServiceType map[string]bool `yaml:"service_type" json:"service_type"` // Optional: Service type flags (Meesho-specific) + + // Custom labels (for vendor extensibility) + CustomLabels map[string]string `yaml:"custom_labels" json:"custom_labels"` // Optional: Custom key-value pairs for labels + + // Probe configuration (optional, with defaults) + LivenessFailureThreshold string `yaml:"liveness_failure_threshold" json:"liveness_failure_threshold"` // Optional: Liveness probe failure threshold (default: "5") + LivenessPeriodSeconds string `yaml:"liveness_period_seconds" json:"liveness_period_seconds"` // Optional: Liveness probe period in seconds (default: "10") + LivenessSuccessThreshold string `yaml:"liveness_success_threshold" json:"liveness_success_threshold"` // Optional: Liveness probe success threshold (default: "1") + LivenessTimeoutSeconds string `yaml:"liveness_timeout_seconds" json:"liveness_timeout_seconds"` // Optional: Liveness probe timeout in seconds (default: "2") + ReadinessFailureThreshold string `yaml:"readiness_failure_threshold" json:"readiness_failure_threshold"` // Optional: Readiness probe failure threshold (default: "5") + ReadinessPeriodSeconds string `yaml:"readiness_period_seconds" json:"readiness_period_seconds"` // Optional: Readiness probe period in seconds (default: "10") + ReadinessSuccessThreshold string `yaml:"readiness_success_threshold" json:"readiness_success_threshold"` // Optional: Readiness probe success threshold (default: "1") + ReadinessTimeoutSeconds string `yaml:"readiness_timeout_seconds" json:"readiness_timeout_seconds"` // Optional: Readiness probe timeout in seconds (default: "2") + + // Node selector configuration (optional, with environment-based defaults) + NodeSelector string `yaml:"node_selector" json:"node_selector"` // Optional: Kubernetes node selector key (default: "dedicated" for most envs, "cloud.google.com/compute-class" for int) + + // Autoscaling configuration (optional, with defaults) + ASEnabled string `yaml:"as_enabled" json:"as_enabled"` // Optional: Enable autoscaling (default: "true") + ASPoll string `yaml:"as_poll" json:"as_poll"` // Optional: Autoscaling polling interval in seconds (default: "30") + ASDownPeriod string `yaml:"as_down_period" json:"as_down_period"` // Optional: Autoscaling down period in seconds (default: "300") + ASUpPeriod string `yaml:"as_up_period" json:"as_up_period"` // Optional: Autoscaling up period in seconds (default: "60") + ASUpStableWindow string `yaml:"as_up_stable_window" json:"as_up_stable_window"` // Optional: Autoscaling up stable window in seconds (default: "300") + ASDownStableWindow string `yaml:"as_down_stable_window" json:"as_down_stable_window"` // Optional: Autoscaling down stable window in seconds (default: "1800") + ASTriggerType string `yaml:"as_trigger_type" json:"as_trigger_type"` // Optional: Autoscaling trigger type (default: "AverageValue") + ASTriggerMetric string `yaml:"as_trigger_metric" json:"as_trigger_metric"` // Optional: Autoscaling trigger metric (default: "cpu") + ASTriggerValue string `yaml:"as_trigger_value" json:"as_trigger_value"` // Optional: Autoscaling trigger value (default: "50") + ASDownPodCount string `yaml:"as_down_pod_count" json:"as_down_pod_count"` // Optional: Autoscaling down pod count (default: "2") + ASUpPodCount string `yaml:"as_up_pod_count" json:"as_up_pod_count"` // Optional: Autoscaling up pod count (default: "2") + ASUpPodPercentage string `yaml:"as_up_pod_percentage" json:"as_up_pod_percentage"` // Optional: Autoscaling up pod percentage (default: "10") + CPUThreshold string `yaml:"cpu_threshold" json:"cpu_threshold"` // Optional: CPU threshold for autoscaling (default: "50") + + // Deployment configuration (optional, with defaults) + MaxSurge string `yaml:"max_surge" json:"max_surge"` // Optional: Max surge percentage for rolling update (default: "50") + TerminationGracePeriodSeconds string `yaml:"termination_grace_period_seconds" json:"termination_grace_period_seconds"` // Optional: Termination grace period in seconds (default: "300") + ContourResponseTimeout string `yaml:"contour_response_timeout" json:"contour_response_timeout"` // Optional: Contour response timeout (default: "false") + PodDistributionSkew string `yaml:"pod_distribution_skew" json:"pod_distribution_skew"` // Optional: Pod distribution skew (default: "false") + EnableWebsocket string `yaml:"enable_websocket" json:"enable_websocket"` // Optional: Enable websocket (default: "false") + AddHeadless string `yaml:"add_headless" json:"add_headless"` // Optional: Add headless service (default: "false") + CreateContourGateway string `yaml:"create_contour_gateway" json:"create_contour_gateway"` // Optional: Create contour gateway (default: "false") + PDBMinAvailable string `yaml:"pdb_min_available" json:"pdb_min_available"` // Optional: PDB min available (default: "") + PDBMaxUnavailable string `yaml:"pdb_max_unavailable" json:"pdb_max_unavailable"` // Optional: PDB max unavailable (default: "10%") + PodAnnotations map[string]string `yaml:"pod_annotations" json:"pod_annotations"` // Optional: Pod annotations (default: {}) +} + +// GetBranchName returns the branch name based on working environment +// This replaces the database branch_name field +func GetBranchName(workingEnv string) string { + switch workingEnv { + case "gcp_stg", "stg": + return "develop" + case "gcp_int", "int": + return "develop" + case "gcp_prd", "prd": + return "main" + case "gcp_ftr", "ftr": + return "develop" + case "gcp_dev", "dev": + return "develop" + default: + return "main" // Default to main + } +} + +// ServiceConfigLoader loads service configuration from config-as-code +type ServiceConfigLoader interface { + LoadServiceConfig(serviceName, workingEnv string) (*ServiceConfig, error) +} + +// GitHubServiceConfigLoader loads service config from GitHub repository +type GitHubServiceConfigLoader struct { + repoOwner string + repoName string +} + +// NewGitHubServiceConfigLoader creates a new GitHub-based service config loader +func NewGitHubServiceConfigLoader(repoOwner, repoName string) *GitHubServiceConfigLoader { + return &GitHubServiceConfigLoader{ + repoOwner: repoOwner, + repoName: repoName, + } +} + +// LoadServiceConfig loads service configuration from GitHub +// Path format: services/{serviceName}/{env}/config.yaml +func (l *GitHubServiceConfigLoader) LoadServiceConfig(serviceName, workingEnv string) (*ServiceConfig, error) { + // Map workingEnv to config env name (with defaults for unknown environments) + envConfig := github.GetEnvConfig(workingEnv) + envName := envConfig["config_env"] + if envName == "" { + envName = workingEnv + } + + // Construct file path + configPath := fmt.Sprintf("horizon/configs/services/%s/%s/config.yaml", serviceName, envName) + branchName := GetBranchName(workingEnv) + + log.Info(). + Str("serviceName", serviceName). + Str("workingEnv", workingEnv). + Str("configEnv", envName). + Str("configPath", configPath). + Str("branch", branchName). + Str("repo", l.repoName). + Msg("Loading service config from GitHub") + + // Get file content from GitHub + client, err := github.GetGitHubClient() + if err != nil { + return nil, fmt.Errorf("failed to get GitHub client: %w", err) + } + + ctx := context.Background() + fileContent, err := github.GetFile(ctx, client, l.repoName, configPath, branchName) + if err != nil { + log.Error(). + Err(err). + Str("serviceName", serviceName). + Str("configPath", configPath). + Str("branch", branchName). + Msg("Failed to load service config from GitHub") + return nil, fmt.Errorf("failed to load service config from GitHub: %w", err) + } + + // Decode base64 content + content, err := fileContent.GetContent() + if err != nil { + log.Error(). + Err(err). + Str("serviceName", serviceName). + Str("configPath", configPath). + Msg("Failed to decode service config content from GitHub") + return nil, fmt.Errorf("failed to decode service config content: %w", err) + } + fileContentBytes := []byte(content) + + // Parse YAML + var config ServiceConfig + if err := yaml.Unmarshal(fileContentBytes, &config); err != nil { + log.Error(). + Err(err). + Str("serviceName", serviceName). + Str("configPath", configPath). + Msg("Failed to parse service config YAML") + return nil, fmt.Errorf("failed to parse service config YAML: %w", err) + } + + // Log podAnnotations immediately after YAML parsing (before ApplyDefaults) + if len(config.PodAnnotations) > 0 { + log.Info(). + Str("serviceName", serviceName). + Str("configPath", configPath). + Int("podAnnotationsCount", len(config.PodAnnotations)). + Interface("podAnnotations", config.PodAnnotations). + Msg("Service config: podAnnotations found in YAML from GitHub (after parsing, before ApplyDefaults)") + } else { + log.Warn(). + Str("serviceName", serviceName). + Str("configPath", configPath). + Interface("podAnnotationsIsNil", config.PodAnnotations == nil). + Interface("podAnnotations", config.PodAnnotations). + Msg("Service config: podAnnotations is empty or nil after YAML parsing from GitHub") + } + + // Validate mandatory fields + if err := config.Validate(); err != nil { + return nil, fmt.Errorf("invalid service config: %w", err) + } + + // Apply defaults + config.ApplyDefaults() + + logFields := log.Info(). + Str("serviceName", serviceName). + Str("repoName", config.RepoName). + Int("appPort", config.AppPort). + Str("ingressClass", config.IngressClass) + + // Log podAnnotations if present + if len(config.PodAnnotations) > 0 { + logFields = logFields. + Int("podAnnotationsCount", len(config.PodAnnotations)). + Interface("podAnnotations", config.PodAnnotations) + } + + logFields.Msg("Service config loaded successfully from GitHub") + + return &config, nil +} + +// LocalFileServiceConfigLoader loads service config from local filesystem +// Useful for development and testing +// Standard path format: configs/services/{serviceName}/{env}/config.yaml +type LocalFileServiceConfigLoader struct { + basePath string +} + +// NewLocalFileServiceConfigLoader creates a new local file-based service config loader +func NewLocalFileServiceConfigLoader(basePath string) *LocalFileServiceConfigLoader { + return &LocalFileServiceConfigLoader{ + basePath: basePath, + } +} + +// LoadServiceConfig loads service configuration from local filesystem +// Standard path format: configs/services/{serviceName}/{env}/config.yaml +func (l *LocalFileServiceConfigLoader) LoadServiceConfig(serviceName, workingEnv string) (*ServiceConfig, error) { + // Map workingEnv to config env name using GetEnvConfig which handles unknown environments gracefully + // This allows any environment name to be used without blocking onboarding + configEnv := github.GetEnvConfig(workingEnv) + envName := configEnv["config_env"] + if envName == "" { + envName = workingEnv + } + + // Standard path format: configs/services/{serviceName}/{env}/config.yaml + // basePath is the root directory (e.g., "." or "./configs") + // If basePath already ends with "configs", use services/... directly + // Otherwise, use configs/services/... + var configPath string + if filepath.Base(l.basePath) == "configs" || strings.HasSuffix(l.basePath, "/configs") || strings.HasSuffix(l.basePath, "\\configs") { + // basePath already includes "configs", so use services/... directly + configPath = filepath.Join(l.basePath, "services", serviceName, envName, "config.yaml") + } else { + // basePath is root (e.g., "."), use configs/services/... + configPath = filepath.Join(l.basePath, "configs", "services", serviceName, envName, "config.yaml") + } + + log.Info(). + Str("serviceName", serviceName). + Str("workingEnv", workingEnv). + Str("configEnv", envName). + Str("configPath", configPath). + Msg("Loading service config from standard path (configs/services/{service}/{env}/config.yaml)") + + fileContent, err := os.ReadFile(configPath) + if err != nil { + log.Error(). + Err(err). + Str("serviceName", serviceName). + Str("configPath", configPath). + Msg("Failed to read service config file") + return nil, fmt.Errorf("failed to read service config file at %s: %w", configPath, err) + } + + log.Info(). + Str("serviceName", serviceName). + Str("configPath", configPath). + Int("fileSize", len(fileContent)). + Msg("Service config file found") + + // Parse YAML + var config ServiceConfig + if err := yaml.Unmarshal(fileContent, &config); err != nil { + log.Error(). + Err(err). + Str("serviceName", serviceName). + Str("configPath", configPath). + Msg("Failed to parse service config YAML") + return nil, fmt.Errorf("failed to parse service config YAML: %w", err) + } + + // Log podAnnotations immediately after YAML parsing (before ApplyDefaults) + if len(config.PodAnnotations) > 0 { + log.Info(). + Str("serviceName", serviceName). + Str("configPath", configPath). + Int("podAnnotationsCount", len(config.PodAnnotations)). + Interface("podAnnotations", config.PodAnnotations). + Msg("Service config: podAnnotations found in YAML (after parsing, before ApplyDefaults)") + } else { + log.Warn(). + Str("serviceName", serviceName). + Str("configPath", configPath). + Interface("podAnnotationsIsNil", config.PodAnnotations == nil). + Interface("podAnnotations", config.PodAnnotations). + Msg("Service config: podAnnotations is empty or nil after YAML parsing") + } + + // Validate mandatory fields + if err := config.Validate(); err != nil { + return nil, fmt.Errorf("invalid service config: %w", err) + } + + // Apply defaults + config.ApplyDefaults() + + logFields := log.Info(). + Str("serviceName", serviceName). + Str("repoName", config.RepoName). + Int("appPort", config.AppPort). + Str("ingressClass", config.IngressClass). + Str("bu", config.BU). + Str("team", config.Team). + Str("priorityV2", config.PriorityV2). + Str("primaryOwner", config.PrimaryOwner). + Str("secondaryOwner", config.SecondaryOwner) + + // Log podAnnotations if present + if len(config.PodAnnotations) > 0 { + logFields = logFields. + Int("podAnnotationsCount", len(config.PodAnnotations)). + Interface("podAnnotations", config.PodAnnotations) + } + + logFields.Msg("Service config loaded successfully from local file") + + return &config, nil +} + +// NewServiceConfigLoader creates a service config loader based on the source type +// source: "local" or "github" +// basePath: Base path for local files (ignored for GitHub) +// repoOwner: GitHub repository owner (required for GitHub source) +// repoName: GitHub repository name (required for GitHub source) +func NewServiceConfigLoader(source, basePath, repoOwner, repoName string) (ServiceConfigLoader, error) { + switch source { + case "local": + if basePath == "" { + basePath = "./configs" // Default path + } + return NewLocalFileServiceConfigLoader(basePath), nil + case "github": + if repoOwner == "" || repoName == "" { + return nil, fmt.Errorf("repoOwner and repoName are required for GitHub source") + } + return NewGitHubServiceConfigLoader(repoOwner, repoName), nil + default: + return nil, fmt.Errorf("unknown service config source: %s (must be 'local' or 'github')", source) + } +} + +// Validate validates that mandatory fields are present +func (c *ServiceConfig) Validate() error { + if c.RepoName == "" { + return fmt.Errorf("repo_name is mandatory") + } + if c.AppPort == 0 { + return fmt.Errorf("app_port is mandatory and must be > 0") + } + if c.IngressClass == "" { + return fmt.Errorf("ingress_class is mandatory") + } + if c.Domain == "" { + return fmt.Errorf("domain is mandatory (used for host generation: .)") + } + return nil +} + +// ApplyDefaults applies default values for optional fields +func (c *ServiceConfig) ApplyDefaults() { + if c.HealthCheck == "" { + c.HealthCheck = "/health" + } + if c.ServiceType == nil { + c.ServiceType = make(map[string]bool) + } + if c.CustomLabels == nil { + c.CustomLabels = make(map[string]string) + } + // Initialize PodAnnotations if nil (but don't override if it has values from YAML) + if c.PodAnnotations == nil { + c.PodAnnotations = make(map[string]string) + } + + // Handle labels section: migrate legacy fields to Labels if Labels is nil + // This provides backward compatibility while supporting the new structure + if c.Labels == nil { + c.Labels = &LabelsConfig{} + // Migrate legacy fields to Labels section if present + if c.BU != "" { + c.Labels.BU = c.BU + } + if c.Team != "" { + c.Labels.Team = c.Team + } + if c.PrimaryOwner != "" { + c.Labels.PrimaryOwner = c.PrimaryOwner + } + if c.SecondaryOwner != "" { + c.Labels.SecondaryOwner = c.SecondaryOwner + } + if c.PriorityV2 != "" { + c.Labels.PriorityV2 = c.PriorityV2 + } + if len(c.CustomLabels) > 0 { + c.Labels.CustomLabels = c.CustomLabels + } + } + + // Apply defaults for labels if not provided + // Use "ml" as fallback when bu/team are absent (maintains backward compatibility) + if c.Labels.BU == "" { + c.Labels.BU = "ml" // Fallback BU maintains backward compatibility + } + if c.Labels.Team == "" { + c.Labels.Team = "ml" // Fallback team maintains backward compatibility + } + if c.Labels.PriorityV2 == "" { + c.Labels.PriorityV2 = "p2" // Default priority (lower than p0/p1) + } + if c.Labels.CustomLabels == nil { + c.Labels.CustomLabels = make(map[string]string) + } + + // Probe configuration defaults (matches RingMaster defaults) + if c.LivenessFailureThreshold == "" { + c.LivenessFailureThreshold = "5" + } + if c.LivenessPeriodSeconds == "" { + c.LivenessPeriodSeconds = "10" + } + if c.LivenessSuccessThreshold == "" { + c.LivenessSuccessThreshold = "1" + } + if c.LivenessTimeoutSeconds == "" { + c.LivenessTimeoutSeconds = "2" + } + if c.ReadinessFailureThreshold == "" { + c.ReadinessFailureThreshold = "5" + } + if c.ReadinessPeriodSeconds == "" { + c.ReadinessPeriodSeconds = "10" + } + if c.ReadinessSuccessThreshold == "" { + c.ReadinessSuccessThreshold = "1" + } + if c.ReadinessTimeoutSeconds == "" { + c.ReadinessTimeoutSeconds = "2" + } + + // NodeSelector default is set in ToWorkflowPayload based on environment + // We don't set it here because it depends on workingEnv + + // Autoscaling defaults + if c.ASEnabled == "" { + c.ASEnabled = "true" + } + if c.ASPoll == "" { + c.ASPoll = "30" + } + if c.ASDownPeriod == "" { + c.ASDownPeriod = "300" + } + if c.ASUpPeriod == "" { + c.ASUpPeriod = "60" + } + if c.ASUpStableWindow == "" { + c.ASUpStableWindow = "300" + } + if c.ASDownStableWindow == "" { + c.ASDownStableWindow = "1800" + } + if c.ASTriggerType == "" { + c.ASTriggerType = "AverageValue" + } + if c.ASTriggerMetric == "" { + c.ASTriggerMetric = "cpu" + } + if c.ASTriggerValue == "" { + c.ASTriggerValue = "50" + } + if c.ASDownPodCount == "" { + c.ASDownPodCount = "2" + } + if c.ASUpPodCount == "" { + c.ASUpPodCount = "2" + } + if c.ASUpPodPercentage == "" { + c.ASUpPodPercentage = "10" + } + if c.CPUThreshold == "" { + c.CPUThreshold = "50" + } + + // Deployment defaults + if c.MaxSurge == "" { + c.MaxSurge = "50" + } + if c.TerminationGracePeriodSeconds == "" { + c.TerminationGracePeriodSeconds = "300" + } + if c.ContourResponseTimeout == "" { + c.ContourResponseTimeout = "false" + } + if c.PodDistributionSkew == "" { + c.PodDistributionSkew = "false" + } + if c.EnableWebsocket == "" { + c.EnableWebsocket = "false" + } + if c.AddHeadless == "" { + c.AddHeadless = "false" + } + if c.CreateContourGateway == "" { + c.CreateContourGateway = "false" + } + if c.PDBMinAvailable == "" { + c.PDBMinAvailable = "" + } + if c.PDBMaxUnavailable == "" { + c.PDBMaxUnavailable = "10%" + } +} + +// ToWorkflowPayload converts ServiceConfig to workflow payload format +// This maintains backward compatibility with existing workflow activities +func (c *ServiceConfig) ToWorkflowPayload(appName, workingEnv string) map[string]interface{} { + payload := map[string]interface{}{ + "appName": appName, + "repoName": c.RepoName, + "branchName": GetBranchName(workingEnv), + "healthCheck": c.HealthCheck, + "appPort": c.AppPort, + "ingress_class": c.IngressClass, + "appType": c.AppType, + } + + // Add labels (from Labels section if present, otherwise from legacy fields) + // Labels section takes precedence for vendor-agnostic support + var bu, team, primaryOwner, secondaryOwner, priorityV2 string + if c.Labels != nil { + bu = c.Labels.BU + team = c.Labels.Team + primaryOwner = c.Labels.PrimaryOwner + secondaryOwner = c.Labels.SecondaryOwner + priorityV2 = c.Labels.PriorityV2 + } else { + // Fallback to legacy fields for backward compatibility + bu = c.BU + team = c.Team + primaryOwner = c.PrimaryOwner + secondaryOwner = c.SecondaryOwner + priorityV2 = c.PriorityV2 + } + + // Add to payload (use defaults from ApplyDefaults if empty) + if bu != "" { + payload["bu"] = bu + } + if team != "" { + payload["team"] = team + } + if primaryOwner != "" { + payload["primaryOwner"] = primaryOwner + } + if secondaryOwner != "" { + payload["secondaryOwner"] = secondaryOwner + } + if priorityV2 != "" { + payload["priorityV2"] = priorityV2 + } + + // Add service type flags (convert map[string]bool to map[string]string for compatibility) + if len(c.ServiceType) > 0 { + for k, v := range c.ServiceType { + payload[fmt.Sprintf("service_type_%s", k)] = v + } + } + + // Add custom labels + for k, v := range c.CustomLabels { + payload[k] = v + } + + // Add probe configuration + payload["liveness_failure_threshold"] = c.LivenessFailureThreshold + payload["liveness_period_seconds"] = c.LivenessPeriodSeconds + payload["liveness_success_threshold"] = c.LivenessSuccessThreshold + payload["liveness_timeout_seconds"] = c.LivenessTimeoutSeconds + payload["readiness_failure_threshold"] = c.ReadinessFailureThreshold + payload["readiness_period_seconds"] = c.ReadinessPeriodSeconds + payload["readiness_success_threshold"] = c.ReadinessSuccessThreshold + payload["readiness_timeout_seconds"] = c.ReadinessTimeoutSeconds + + // Add autoscaling configuration + payload["as_enabled"] = c.ASEnabled + payload["as_poll"] = c.ASPoll + payload["as_down_period"] = c.ASDownPeriod + payload["as_up_period"] = c.ASUpPeriod + payload["as_up_stable_window"] = c.ASUpStableWindow + payload["as_down_stable_window"] = c.ASDownStableWindow + payload["as_trigger_type"] = c.ASTriggerType + payload["as_trigger_metric"] = c.ASTriggerMetric + payload["as_trigger_value"] = c.ASTriggerValue + payload["as_down_pod_count"] = c.ASDownPodCount + payload["as_up_pod_count"] = c.ASUpPodCount + payload["as_up_pod_percentage"] = c.ASUpPodPercentage + payload["cpuThreshold"] = c.CPUThreshold + + // Add deployment configuration + payload["maxSurge"] = c.MaxSurge + payload["terminationGracePeriodSeconds"] = c.TerminationGracePeriodSeconds + payload["contourResponseTimeout"] = c.ContourResponseTimeout + payload["podDistributionSkew"] = c.PodDistributionSkew + payload["enableWebsocket"] = c.EnableWebsocket + payload["addHeadless"] = c.AddHeadless + payload["createContourGateway"] = c.CreateContourGateway + payload["pdbMinAvailable"] = c.PDBMinAvailable + payload["pdbMaxUnavailable"] = c.PDBMaxUnavailable + + // Add nodeSelector (use from config if specified, otherwise set based on environment) + if c.NodeSelector != "" { + payload["nodeSelector"] = c.NodeSelector + } else { + // Set default based on environment (matches RingMaster logic) + // Use GetEnvConfig to handle unknown environments gracefully + envConfig := github.GetEnvConfig(workingEnv) + configEnv := envConfig["config_env"] + if configEnv == "int" { + payload["nodeSelector"] = "cloud.google.com/compute-class" + } else { + payload["nodeSelector"] = "dedicated" + } + } + + return payload +} + +// ToServiceConfigJSON converts ServiceConfig to JSON format for database storage +// This is used for backward compatibility when service_config table is still used +func (c *ServiceConfig) ToServiceConfigJSON() (json.RawMessage, error) { + // Convert service_type map[string]bool to map[string]string for JSON compatibility + serviceTypeStr := make(map[string]string) + for k, v := range c.ServiceType { + if v { + serviceTypeStr[k] = "true" + } else { + serviceTypeStr[k] = "false" + } + } + + configMap := map[string]interface{}{ + "service_type_web": c.ServiceType["web"], + "service_type_grpc": c.ServiceType["grpc"], + "service_type_cache": c.ServiceType["cache"], + "service_type_worker": c.ServiceType["worker"], + "service_type_consumer": c.ServiceType["consumer"], + "service_type_database": c.ServiceType["database"], + "service_type_producer": c.ServiceType["producer"], + "service_type_scheduler": c.ServiceType["scheduler"], + "service_type_websocket": c.ServiceType["websocket"], + "service_type_httpstateless": c.ServiceType["httpstateless"], + } + + return json.Marshal(configMap) +} diff --git a/horizon/internal/connectionconfig/controller/controller.go b/horizon/internal/connectionconfig/controller/controller.go new file mode 100644 index 00000000..5df396ef --- /dev/null +++ b/horizon/internal/connectionconfig/controller/controller.go @@ -0,0 +1,98 @@ +package controller + +import ( + "strconv" + "sync" + + "github.com/Meesho/BharatMLStack/horizon/internal/connectionconfig/handler" + "github.com/Meesho/BharatMLStack/horizon/pkg/api" + "github.com/gin-gonic/gin" +) + +type Config interface { + Onboard(ctx *gin.Context) + GetAll(ctx *gin.Context) + Edit(ctx *gin.Context) +} + +var ( + configController Config + once sync.Once + emptyResponse = "" +) + +type V1 struct { + Config handler.Config +} + +func NewConfigController() Config { + if configController == nil { + once.Do(func() { + configController = &V1{ + Config: handler.NewConfigHandler(1), + } + }) + } + return configController +} + +func (c *V1) Onboard(ctx *gin.Context) { + var request handler.OnboardRequest + if err := ctx.ShouldBindJSON(&request); err != nil { + ctx.JSON(api.NewBadRequestError(err.Error()).StatusCode, handler.Response{ + Error: err.Error(), + Data: handler.Message{Message: emptyResponse}, + }) + return + } + response, err := c.Config.Onboard(request) + if err != nil { + ctx.JSON(api.NewInternalServerError(err.Error()).StatusCode, handler.Response{ + Error: err.Error(), + Data: handler.Message{Message: emptyResponse}, + }) + return + } + ctx.JSON(200, response) +} + +func (c *V1) GetAll(ctx *gin.Context) { + response, err := c.Config.GetAll() + if err != nil { + ctx.JSON(api.NewInternalServerError(err.Error()).StatusCode, handler.Response{ + Error: err.Error(), + Data: handler.Message{Message: emptyResponse}, + }) + return + } + ctx.JSON(200, response) +} + +func (c *V1) Edit(ctx *gin.Context) { + var request handler.EditRequest + id, err := strconv.Atoi(ctx.Param("id")) + if err != nil { + ctx.JSON(api.NewBadRequestError(err.Error()).StatusCode, handler.Response{ + Error: err.Error(), + Data: handler.Message{Message: emptyResponse}, + }) + return + } + if err := ctx.ShouldBindJSON(&request); err != nil { + ctx.JSON(api.NewBadRequestError(err.Error()).StatusCode, handler.Response{ + Error: err.Error(), + Data: handler.Message{Message: emptyResponse}, + }) + return + } + request.Payload.Id = uint(id) + response, err := c.Config.Edit(request) + if err != nil { + ctx.JSON(api.NewInternalServerError(err.Error()).StatusCode, handler.Response{ + Error: err.Error(), + Data: handler.Message{Message: emptyResponse}, + }) + return + } + ctx.JSON(200, response) +} diff --git a/horizon/internal/connectionconfig/handler/config.go b/horizon/internal/connectionconfig/handler/config.go new file mode 100644 index 00000000..59b594e6 --- /dev/null +++ b/horizon/internal/connectionconfig/handler/config.go @@ -0,0 +1,7 @@ +package handler + +type Config interface { + Onboard(OnboardRequest) (Response, error) + GetAll() (GetAllResponse, error) + Edit(EditRequest) (Response, error) +} diff --git a/horizon/internal/connectionconfig/handler/connection_config.go b/horizon/internal/connectionconfig/handler/connection_config.go new file mode 100644 index 00000000..f2ca2979 --- /dev/null +++ b/horizon/internal/connectionconfig/handler/connection_config.go @@ -0,0 +1,233 @@ +package handler + +import ( + "fmt" + "time" + + connection_config "github.com/Meesho/BharatMLStack/horizon/internal/repositories/sql/connectionconfig" + "github.com/Meesho/BharatMLStack/horizon/pkg/infra" + "github.com/rs/zerolog/log" +) + +var ( + emptyResponse = "" + activeTrue = true + grpcConfig = "grpc" + httpConfig = "http" +) + +type ConnectionConfig struct { + connectionConfigRepo connection_config.ConnectionConfigRepository +} + +func InitV1ConfigHandler() Config { + if config == nil { + conn, err := infra.SQL.GetConnection() + if err != nil { + log.Fatal().Err(err).Msg("Failed to get SQL connection") + } + sqlConn := conn.(*infra.SQLConnection) + + connectionConfigRepo, err := connection_config.Repository(sqlConn) + if err != nil { + log.Fatal().Err(err).Msg("Failed to create config repository") + } + + config = &ConnectionConfig{ + connectionConfigRepo: connectionConfigRepo, + } + } + return config +} + +func (c *ConnectionConfig) Onboard(request OnboardRequest) (Response, error) { + var application connection_config.ConnectionConfig + + if request.Payload.ConnProtocol == grpcConfig { + grpcCfg := connection_config.GrpcConfig{ + Deadline: request.Payload.Deadline, + PlainText: request.Payload.PlainText, + GrpcChannelAlgorithm: request.Payload.GrpcChannelAlgorithm, + ChannelThreadPoolSize: request.Payload.ChannelThreadPoolSize, + BoundedQueueSize: request.Payload.BoundedQueueSize, + } + + application = connection_config.ConnectionConfig{ + Service: request.Payload.Service, + ConnProtocol: request.Payload.ConnProtocol, + Default: request.Payload.Default, + GrpcConfig: grpcCfg, + Active: activeTrue, + CreatedBy: request.CreatedBy, + } + } else if request.Payload.ConnProtocol == httpConfig { + httpCfg := connection_config.HttpConfig{ + Timeout: request.Payload.Timeout, + MaxIdleConnection: request.Payload.MaxIdleConnection, + MaxConnectionPerHost: request.Payload.MaxConnectionPerHost, + IdleConnectionTimeout: request.Payload.IdleConnectionTimeout, + KeepAliveTime: request.Payload.KeepAliveTime, + } + + application = connection_config.ConnectionConfig{ + Service: request.Payload.Service, + ConnProtocol: request.Payload.ConnProtocol, + Default: request.Payload.Default, + HttpConfig: httpCfg, + Active: activeTrue, + CreatedBy: request.CreatedBy, + } + } + + err := c.connectionConfigRepo.Create(&application) + if err != nil { + return Response{ + Error: err.Error(), + Data: Message{Message: emptyResponse}, + }, err + } + return Response{ + Error: emptyResponse, + Data: Message{Message: fmt.Sprintf("Application onboarded successfully with id: %d", application.Id)}, + }, nil +} + +func (c *ConnectionConfig) GetAll() (GetAllResponse, error) { + connectionConfigs, err := c.connectionConfigRepo.GetAll() + if err != nil { + return GetAllResponse{}, err + } + + response := []Configs{} + + for _, connectionConfig := range connectionConfigs { + + if connectionConfig.ConnProtocol == grpcConfig { + response = append(response, Configs{ + Id: connectionConfig.Id, + Default: connectionConfig.Default, + Service: connectionConfig.Service, + ConnProtocol: connectionConfig.ConnProtocol, + Config: ConnConfig{ + GrpcConfig: GrpcConfig{ + Deadline: connectionConfig.GrpcConfig.Deadline, + PlainText: connectionConfig.GrpcConfig.PlainText, + GrpcChannelAlgorithm: connectionConfig.GrpcConfig.GrpcChannelAlgorithm, + ChannelThreadPoolSize: connectionConfig.GrpcConfig.ChannelThreadPoolSize, + BoundedQueueSize: connectionConfig.GrpcConfig.BoundedQueueSize, + }, + }, + CreatedBy: connectionConfig.CreatedBy, + UpdatedBy: connectionConfig.UpdatedBy, + CreatedAt: connectionConfig.CreatedAt.Format(time.RFC3339), + UpdatedAt: connectionConfig.UpdatedAt.Format(time.RFC3339), + }) + } else if connectionConfig.ConnProtocol == httpConfig { + response = append(response, Configs{ + Id: connectionConfig.Id, + Default: connectionConfig.Default, + Service: connectionConfig.Service, + ConnProtocol: connectionConfig.ConnProtocol, + Config: ConnConfig{ + HttpConfig: HttpConfig{ + Timeout: connectionConfig.HttpConfig.Timeout, + MaxIdleConnection: connectionConfig.HttpConfig.MaxIdleConnection, + MaxConnectionPerHost: connectionConfig.HttpConfig.MaxConnectionPerHost, + IdleConnectionTimeout: connectionConfig.HttpConfig.IdleConnectionTimeout, + KeepAliveTime: connectionConfig.HttpConfig.KeepAliveTime, + }, + }, + UpdatedBy: connectionConfig.UpdatedBy, + CreatedAt: connectionConfig.CreatedAt.Format(time.RFC3339), + UpdatedAt: connectionConfig.UpdatedAt.Format(time.RFC3339), + }) + } + } + return GetAllResponse{ + Data: response, + }, nil +} + +func (c *ConnectionConfig) Edit(request EditRequest) (Response, error) { + var connectionConfig connection_config.ConnectionConfig + + // First check if the connection exists + configs, err := c.connectionConfigRepo.GetAll() + if err != nil { + return Response{ + Error: err.Error(), + Data: Message{Message: emptyResponse}, + }, err + } + + found := false + for _, config := range configs { + if config.Id == request.Payload.Id { + found = true + break + } + } + + if !found { + return Response{ + Error: "Connection config not found", + Data: Message{Message: emptyResponse}, + }, fmt.Errorf("connection config with id %d not found", request.Payload.Id) + } + + if request.Payload.ConnProtocol == grpcConfig { + grpcCfg := connection_config.GrpcConfig{ + Deadline: request.Payload.Deadline, + PlainText: request.Payload.PlainText, + GrpcChannelAlgorithm: request.Payload.GrpcChannelAlgorithm, + ChannelThreadPoolSize: request.Payload.ChannelThreadPoolSize, + BoundedQueueSize: request.Payload.BoundedQueueSize, + } + + connectionConfig = connection_config.ConnectionConfig{ + Id: request.Payload.Id, + Default: request.Payload.Default, + Service: request.Payload.Service, + ConnProtocol: request.Payload.ConnProtocol, + GrpcConfig: grpcCfg, + Active: activeTrue, + UpdatedBy: request.UpdatedBy, + } + } else if request.Payload.ConnProtocol == httpConfig { + httpCfg := connection_config.HttpConfig{ + Timeout: request.Payload.Timeout, + MaxIdleConnection: request.Payload.MaxIdleConnection, + MaxConnectionPerHost: request.Payload.MaxConnectionPerHost, + IdleConnectionTimeout: request.Payload.IdleConnectionTimeout, + KeepAliveTime: request.Payload.KeepAliveTime, + } + + connectionConfig = connection_config.ConnectionConfig{ + Id: request.Payload.Id, + Default: request.Payload.Default, + Service: request.Payload.Service, + ConnProtocol: request.Payload.ConnProtocol, + HttpConfig: httpCfg, + Active: activeTrue, + UpdatedBy: request.UpdatedBy, + } + } else { + return Response{ + Error: "Invalid connection protocol", + Data: Message{Message: emptyResponse}, + }, fmt.Errorf("invalid connection protocol: %s", request.Payload.ConnProtocol) + } + + err = c.connectionConfigRepo.Update(&connectionConfig) + if err != nil { + return Response{ + Error: err.Error(), + Data: Message{Message: emptyResponse}, + }, err + } + + return Response{ + Error: emptyResponse, + Data: Message{Message: fmt.Sprintf("Connection config updated successfully with id: %d", connectionConfig.Id)}, + }, nil +} diff --git a/horizon/internal/connectionconfig/handler/init.go b/horizon/internal/connectionconfig/handler/init.go new file mode 100644 index 00000000..05cd5ff6 --- /dev/null +++ b/horizon/internal/connectionconfig/handler/init.go @@ -0,0 +1,14 @@ +package handler + +var ( + config Config +) + +func NewConfigHandler(version int) Config { + switch version { + case 1: + return InitV1ConfigHandler() + default: + return nil + } +} diff --git a/horizon/internal/connectionconfig/handler/models.go b/horizon/internal/connectionconfig/handler/models.go new file mode 100644 index 00000000..2d80499d --- /dev/null +++ b/horizon/internal/connectionconfig/handler/models.go @@ -0,0 +1,90 @@ +package handler + +type OnboardRequestPayload struct { + Service string `json:"service" binding:"required"` + Default bool `json:"default"` + ConnProtocol string `json:"conn_protocol" binding:"required"` + GrpcChannelAlgorithm string `json:"grpc_channel_algorithm,omitempty"` + PlainText bool `json:"plain_text,omitempty"` + Deadline int `json:"deadline,omitempty"` + Timeout int `json:"timeout,omitempty"` + ChannelThreadPoolSize int `json:"channel_thread_pool_size,omitempty"` + BoundedQueueSize int `json:"bounded_queue_size,omitempty"` + MaxConnectionPerHost int `json:"max_connection_per_host,omitempty"` + IdleConnectionTimeout int `json:"idle_connection_timeout,omitempty"` + MaxIdleConnection int `json:"max_idle_connection,omitempty"` + KeepAliveTime int `json:"keep_alive_time,omitempty"` +} + +type OnboardRequest struct { + Payload OnboardRequestPayload `json:"payload" binding:"required"` + CreatedBy string `json:"created_by" binding:"required"` +} + +type Message struct { + Message string `json:"message"` +} + +type Response struct { + Error string `json:"error"` + Data Message `json:"data"` +} + +type GrpcConfig struct { + Deadline int `json:"deadline"` + PlainText bool `json:"plain_text"` + GrpcChannelAlgorithm string `json:"grpc_channel_algorithm"` + ChannelThreadPoolSize int `json:"channel_thread_pool_size"` + BoundedQueueSize int `json:"bounded_queue_size"` +} + +type HttpConfig struct { + Timeout int `json:"timeout"` + MaxIdleConnection int `json:"max_idle_connection"` + MaxConnectionPerHost int `json:"max_connection_per_host"` + IdleConnectionTimeout int `json:"idle_connection_timeout"` + KeepAliveTime int `json:"keep_alive_time"` +} + +type ConnConfig struct { + GrpcConfig GrpcConfig `json:"grpc_config,omitempty"` + HttpConfig HttpConfig `json:"http_config,omitempty"` +} + +type Configs struct { + Id uint `json:"id"` + Default bool `json:"default"` + Service string `json:"service"` + ConnProtocol string `json:"conn_protocol"` + Config ConnConfig `json:"config"` + CreatedBy string `json:"created_by"` + UpdatedBy string `json:"updated_by"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +type GetAllResponse struct { + Data []Configs `json:"data"` +} + +type EditRequestPayload struct { + Id uint `json:"id" binding:"required"` + Service string `json:"service" binding:"required"` + Default bool `json:"default"` + ConnProtocol string `json:"conn_protocol" binding:"required"` + GrpcChannelAlgorithm string `json:"grpc_channel_algorithm,omitempty"` + PlainText bool `json:"plain_text,omitempty"` + Deadline int `json:"deadline,omitempty"` + Timeout int `json:"timeout,omitempty"` + ChannelThreadPoolSize int `json:"channel_thread_pool_size,omitempty"` + BoundedQueueSize int `json:"bounded_queue_size,omitempty"` + MaxConnectionPerHost int `json:"max_connection_per_host,omitempty"` + IdleConnectionTimeout int `json:"idle_connection_timeout,omitempty"` + MaxIdleConnection int `json:"max_idle_connection,omitempty"` + KeepAliveTime int `json:"keep_alive_time,omitempty"` +} + +type EditRequest struct { + Payload EditRequestPayload `json:"payload" binding:"required"` + UpdatedBy string `json:"updated_by" binding:"required"` +} diff --git a/horizon/internal/connectionconfig/route/router.go b/horizon/internal/connectionconfig/route/router.go new file mode 100644 index 00000000..8628a61b --- /dev/null +++ b/horizon/internal/connectionconfig/route/router.go @@ -0,0 +1,34 @@ +package route + +import ( + "sync" + + "github.com/Meesho/BharatMLStack/horizon/internal/connectionconfig/controller" + "github.com/Meesho/BharatMLStack/horizon/pkg/httpframework" +) + +var initConnectionConfigRouterOnce sync.Once + +func Init() { + initConnectionConfigRouterOnce.Do(func() { + api := httpframework.Instance().Group("/api") + { + v1 := api.Group("/v1/horizon") + { + + // Application Config Registry/Discovery routes + registry := v1.Group("/service-connection-registry/connections") + { + registry.POST("", controller.NewConfigController().Onboard) + registry.PUT("/:id", controller.NewConfigController().Edit) + } + + discovery := v1.Group("/service-connection-discovery/connections") + { + discovery.GET("", controller.NewConfigController().GetAll) + } + + } + } + }) +} diff --git a/horizon/internal/constant/api.go b/horizon/internal/constant/api.go new file mode 100644 index 00000000..9ccb3665 --- /dev/null +++ b/horizon/internal/constant/api.go @@ -0,0 +1,7 @@ +package constant + +const ( + Error = "error" + Data = "data" + EmptyString = "" +) diff --git a/horizon/internal/constant/repository.go b/horizon/internal/constant/repository.go new file mode 100644 index 00000000..949fae40 --- /dev/null +++ b/horizon/internal/constant/repository.go @@ -0,0 +1,6 @@ +package constant + +const ( + CreatedAt = "CreatedAt" + UpdatedAt = "UpdatedAt" +) diff --git a/horizon/internal/deployable/controller/controller.go b/horizon/internal/deployable/controller/controller.go new file mode 100644 index 00000000..ef171aef --- /dev/null +++ b/horizon/internal/deployable/controller/controller.go @@ -0,0 +1,406 @@ +package controller + +import ( + "encoding/json" + "fmt" + "net/http" + "sync" + + "github.com/Meesho/BharatMLStack/horizon/internal/configs" + "github.com/Meesho/BharatMLStack/horizon/internal/constant" + "github.com/Meesho/BharatMLStack/horizon/internal/deployable/handler" + "github.com/Meesho/BharatMLStack/horizon/internal/repositories/sql/servicedeployableconfig" + "github.com/gin-gonic/gin" + "github.com/rs/zerolog/log" + "github.com/spf13/viper" +) + +var ( + globalAppConfig configs.Configs +) + +// SetAppConfig sets the global app config (called from router.Init) +func SetAppConfig(cfg configs.Configs) { + globalAppConfig = cfg +} + +type Config interface { + GetMetaData(ctx *gin.Context) + CreateDeployable(ctx *gin.Context) + UpdateDeployable(ctx *gin.Context) + GetDeployablesByService(ctx *gin.Context) + RefreshDeployable(ctx *gin.Context) + TuneThresholds(ctx *gin.Context) +} + +var ( + deployable Config + deployableInitOnce sync.Once +) + +type V1 struct { + config handler.Config +} + +func NewConfigController() Config { + if deployable == nil { + deployableInitOnce.Do(func() { + // Use global config if set, otherwise create empty config (will fallback to database) + cfg := globalAppConfig + if cfg.ServiceConfigSource == "" { + // Config not set, will use database fallback + } + config, err := handler.NewDeployable(1, cfg) + if err != nil { + panic(fmt.Sprintf("Failed to initialize deployable config: %v", err)) + } + if config == nil { + panic("Deployable config is nil after initialization") + } + deployable = &V1{ + config: config, + } + }) + } + return deployable +} + +func (d *V1) GetMetaData(ctx *gin.Context) { + metaData, err := d.config.GetMetaData() + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{constant.Error: "Failed to fetch metadata"}) + return + } + ctx.JSON(http.StatusOK, metaData) +} + +func (d *V1) CreateDeployable(ctx *gin.Context) { + var request handler.DeployableRequest + if err := ctx.ShouldBindJSON(&request); err != nil { + log.Error(). + Err(err). + Msg("CreateDeployable: Failed to bind JSON request") + ctx.JSON(http.StatusBadRequest, gin.H{ + "error": "Invalid request format", + "data": nil, + }) + return + } + + // Log request details for debugging + log.Info(). + Str("appName", request.AppName). + Str("serviceName", request.ServiceName). + Int("environmentsCount", len(request.Environments)). + Str("queryWorkingEnv", ctx.Query("workingEnv")). + Msg("CreateDeployable: Request received") + + // Log each environment in the request for debugging + if len(request.Environments) > 0 { + for i, env := range request.Environments { + log.Info(). + Str("appName", request.AppName). + Int("index", i). + Str("workingEnv", env.WorkingEnv). + Str("machineType", env.MachineType). + Str("serviceType", env.ServiceType). + Msg("CreateDeployable: Environment in parsed request") + } + } + + // Support both new multi-environment format and legacy single-environment format + // If environments array is provided, use new multi-environment flow + // Otherwise, fall back to legacy flow with workingEnv query parameter + if len(request.Environments) > 0 { + log.Info(). + Str("appName", request.AppName). + Int("environmentsCount", len(request.Environments)). + Msg("CreateDeployable: Detected multi-environment request, routing to CreateDeployableMultiEnvironment") + // New multi-environment flow + workflowIDs, err := d.config.CreateDeployableMultiEnvironment(&request) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{ + "error": fmt.Sprintf("Error registering deployable: %v", err), + "data": nil, + }) + return + } + + ctx.JSON(http.StatusOK, gin.H{ + "error": nil, + "data": gin.H{ + "message": "Deployable registered is in progress for multiple environments.", + "workflowIds": workflowIDs, + }, + }) + return + } + + // Legacy single-environment flow (backward compatibility) + log.Info(). + Str("appName", request.AppName). + Int("environmentsCount", len(request.Environments)). + Msg("CreateDeployable: No environments array detected, using legacy single-environment flow") + + workingEnv := viper.GetString("WORKING_ENV") + if workingEnv == "" { + ctx.JSON(http.StatusBadRequest, gin.H{ + "error": "workingEnv query parameter is required when environments array is not provided", + "data": nil, + }) + return + } + + log.Info(). + Str("appName", request.AppName). + Str("workingEnv", workingEnv). + Msg("CreateDeployable: Using single-environment flow") + + workflowID, err := d.config.CreateDeployable(&request, workingEnv) + if err != nil { + log.Error(). + Err(err). + Str("appName", request.AppName). + Str("workingEnv", workingEnv). + Msg("CreateDeployable: Error registering deployable") + ctx.JSON(http.StatusInternalServerError, gin.H{ + "error": fmt.Sprintf("Error registering deployable: %v", err), + "data": nil, + }) + return + } + + ctx.JSON(http.StatusOK, gin.H{ + "error": nil, + "data": gin.H{ + "message": "Deployable registered is in progress.", + "workflowId": workflowID, + }, + }) +} + +func (d *V1) UpdateDeployable(ctx *gin.Context) { + var request handler.DeployableRequest // Using same request structure as create + if err := ctx.ShouldBindJSON(&request); err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{ + "error": "Invalid request format", + "data": nil, + }) + return + } + + workingEnv := viper.GetString("WORKING_ENV") + if workingEnv == "" { + ctx.JSON(http.StatusBadRequest, gin.H{ + "error": "workingEnv query parameter is required", + "data": nil, + }) + return + } + + if err := d.config.UpdateDeployable(&request, workingEnv); err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{ + "error": fmt.Sprintf("Error updating deployable: %v", err), + "data": nil, + }) + return + } + + ctx.JSON(http.StatusOK, gin.H{ + "error": nil, + "data": gin.H{ + "message": "Deployable update is in progress.", + }, + }) +} + +func (d *V1) GetDeployablesByService(ctx *gin.Context) { + serviceName := ctx.Query("service_name") + if serviceName == "" { + ctx.JSON(http.StatusBadRequest, gin.H{ + "error": "service_name query parameter is required", + "data": nil, + }) + return + } + + deployables, err := d.config.GetDeployablesByService(serviceName) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{ + "error": fmt.Sprintf("Error fetching deployables: %v", err), + "data": nil, + }) + return + } + + if len(deployables) == 0 { + ctx.JSON(http.StatusOK, gin.H{ + "error": nil, + "data": []gin.H{}, + }) + return + } + + // Use channels for concurrent processing + type deployableResult struct { + index int + data gin.H + err error + } + + results := make(chan deployableResult, len(deployables)) + var wg sync.WaitGroup + + // Process each deployable concurrently + for i, deployable := range deployables { + wg.Add(1) + go func(idx int, dep servicedeployableconfig.ServiceDeployableConfig) { + defer wg.Done() + + // Parse config JSON + var configMap map[string]interface{} + if err := json.Unmarshal(dep.Config, &configMap); err != nil { + results <- deployableResult{index: idx, err: fmt.Errorf("error parsing deployable config: %w", err)} + return + } + + // Create base response + deployableResponse := gin.H{ + "id": dep.ID, + "name": dep.Name, + "host": dep.Host, + "service": dep.Service, + "active": dep.Active, + "created_by": dep.CreatedBy, + "updated_by": dep.UpdatedBy, + "created_at": dep.CreatedAt, + "updated_at": dep.UpdatedAt, + "monitoring_url": dep.MonitoringUrl, + "deployable_workflow_id": dep.DeployableWorkFlowId, + "deployment_run_id": dep.DeploymentRunID, + "deployable_health": dep.DeployableHealth, + "workflow_status": dep.WorkFlowStatus, + } + + // Add config fields (excluding replica fields) + for key, value := range configMap { + if key != "min_replica" && key != "max_replica" { + deployableResponse[key] = value + } + } + + // TODO get directly from Argo, must be missed + // Get Ring Master config if needed + if dep.DeployableWorkFlowId != "" && dep.DeploymentRunID != "" { + ringMasterConfig := d.config.GetRingMasterConfig(dep.Name, dep.DeployableWorkFlowId, dep.DeploymentRunID) + deployableResponse["min_replica"] = ringMasterConfig.MinReplica + deployableResponse["max_replica"] = ringMasterConfig.MaxReplica + deployableResponse["deployable_running_status"] = ringMasterConfig.RunningStatus == "true" + } + + results <- deployableResult{index: idx, data: deployableResponse} + }(i, deployable) + } + + // Close results channel when all goroutines are done + go func() { + wg.Wait() + close(results) + }() + + // Collect results and handle errors + response := make([]gin.H, len(deployables)) + for result := range results { + if result.err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{ + "error": result.err.Error(), + "data": nil, + }) + return + } + response[result.index] = result.data + } + + ctx.JSON(http.StatusOK, gin.H{ + "error": nil, + "data": response, + }) +} + +func (d *V1) RefreshDeployable(ctx *gin.Context) { + appName := ctx.Query("app_name") + serviceType := ctx.Query("service_type") + workingEnv := viper.GetString("WORKING_ENV") + + if appName == "" || serviceType == "" { + ctx.JSON(http.StatusBadRequest, gin.H{ + "error": "app_name and service_type query parameters are required", + "data": nil, + }) + return + } + + if workingEnv == "" { + ctx.JSON(http.StatusBadRequest, gin.H{ + "error": "workingEnv query parameter is required", + "data": nil, + }) + return + } + + deployable, err := d.config.RefreshDeployable(appName, serviceType, workingEnv) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{ + "error": fmt.Sprintf("Error refreshing deployable: %v", err), + "data": nil, + }) + return + } + + ctx.JSON(http.StatusOK, gin.H{ + "error": nil, + "data": gin.H{ + "deployable_running_status": deployable.DeployableRunningStatus, + "deployable_health": deployable.DeployableHealth, + "workflow_status": deployable.WorkFlowStatus, + }, + }) +} + +func (d *V1) TuneThresholds(ctx *gin.Context) { + var request handler.TuneThresholdsRequest + if err := ctx.ShouldBindJSON(&request); err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{ + "error": "Invalid request format", + "data": nil, + }) + return + } + + // Get workingEnv from query parameter (required for multi-environment support) + workingEnv := viper.GetString("WORKING_ENV") + if workingEnv == "" { + ctx.JSON(http.StatusBadRequest, gin.H{ + "error": "workingEnv query parameter is required", + "data": nil, + }) + return + } + + workflowID, err := d.config.TuneThresholds(&request, workingEnv) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{ + "error": fmt.Sprintf("Error updating deployable thresholds: %v", err), + "data": nil, + }) + return + } + + ctx.JSON(http.StatusOK, gin.H{ + "error": nil, + "data": gin.H{ + "message": "Deployable Thresholds Update Started.", + "workflowId": workflowID, + }, + }) +} diff --git a/horizon/internal/deployable/handler/config.go b/horizon/internal/deployable/handler/config.go new file mode 100644 index 00000000..a24988ff --- /dev/null +++ b/horizon/internal/deployable/handler/config.go @@ -0,0 +1,17 @@ +package handler + +import ( + "github.com/Meesho/BharatMLStack/horizon/internal/externalcall" + "github.com/Meesho/BharatMLStack/horizon/internal/repositories/sql/servicedeployableconfig" +) + +type Config interface { + GetMetaData() (map[string][]string, error) + CreateDeployable(request *DeployableRequest, workingEnv string) (string, error) + CreateDeployableMultiEnvironment(request *DeployableRequest) (map[string]string, error) + UpdateDeployable(request *DeployableRequest, workingEnv string) error + GetDeployablesByService(serviceName string) ([]servicedeployableconfig.ServiceDeployableConfig, error) + RefreshDeployable(appName, serviceType, workingEnv string) (*servicedeployableconfig.ServiceDeployableConfig, error) + GetRingMasterConfig(appName, workflowID, runID string) externalcall.Config + TuneThresholds(request *TuneThresholdsRequest, workingEnv string) (string, error) +} diff --git a/horizon/internal/deployable/handler/deployable.go b/horizon/internal/deployable/handler/deployable.go new file mode 100644 index 00000000..0c9eb8df --- /dev/null +++ b/horizon/internal/deployable/handler/deployable.go @@ -0,0 +1,580 @@ +package handler + +import ( + "fmt" + "strings" + "sync" + + "github.com/Meesho/BharatMLStack/horizon/internal/configs" + mainHandler "github.com/Meesho/BharatMLStack/horizon/internal/externalcall" + infrastructurehandler "github.com/Meesho/BharatMLStack/horizon/internal/infrastructure/handler" + "github.com/Meesho/BharatMLStack/horizon/internal/repositories/sql/deployablemetadata" + "github.com/Meesho/BharatMLStack/horizon/internal/repositories/sql/serviceconfig" + "github.com/Meesho/BharatMLStack/horizon/internal/repositories/sql/servicedeployableconfig" + workflowHandler "github.com/Meesho/BharatMLStack/horizon/internal/workflow/handler" + "github.com/Meesho/BharatMLStack/horizon/pkg/argocd" + "github.com/Meesho/BharatMLStack/horizon/pkg/infra" + "github.com/rs/zerolog/log" +) + +const ( + ErrFailedToFetchDeployable = "failed to fetch service deployable" +) + +var ( + deployableOnce sync.Once +) + +type Deployable struct { + repo servicedeployableconfig.ServiceDeployableRepository + deployableMetaDataRepo deployablemetadata.DeployableMetadataRepository + serviceConfigRepo serviceconfig.ServiceConfigRepository + serviceConfigLoader configs.ServiceConfigLoader // Config-as-code loader + infrastructureHandler infrastructurehandler.InfrastructureHandler + workflowHandler workflowHandler.Handler // Workflow handler for async operations + workingEnv string +} + +func InitV1ConfigHandler(appConfig configs.Configs) Config { + var handler Config + deployableOnce.Do(func() { + infrastructureHandler := infrastructurehandler.InitInfrastructureHandler() + workflowHandler := workflowHandler.GetWorkflowHandler() + workingEnv := mainHandler.GetWorkingEnvironment() + connection, err := infra.SQL.GetConnection() + if err != nil { + log.Panic().Err(err).Msg("Failed to get SQL connection") + } + sqlConn, ok := connection.(*infra.SQLConnection) + if !ok { + log.Panic().Msg("Failed to cast connection to SQLConnection") + } + repo, err := servicedeployableconfig.NewRepository(sqlConn) + if err != nil { + log.Panic().Err(err).Msg("Failed to create service deployable repository") + } + deployableMetaDataRepo, err := deployablemetadata.NewRepository(sqlConn) + if err != nil { + log.Panic().Err(err).Msg("Failed to create deployable metadata repository") + } + serviceConfigRepo, err := serviceconfig.NewRepository(sqlConn) + if err != nil { + log.Panic().Err(err).Msg("Failed to create service config repository") + } + + // Initialize service config loader (config-as-code) + // This will be nil if SERVICE_CONFIG_SOURCE is not set or invalid + var serviceConfigLoader configs.ServiceConfigLoader + configSource := appConfig.ServiceConfigSource + if configSource != "" { + basePath := appConfig.ServiceConfigPath + if basePath == "" { + basePath = "./configs" // Default path + } + loader, err := configs.NewServiceConfigLoader( + configSource, + basePath, + appConfig.GitHubOwner, + appConfig.ServiceConfigRepo, + ) + if err != nil { + log.Warn(). + Err(err). + Str("source", configSource). + Str("basePath", basePath). + Str("repo", appConfig.ServiceConfigRepo). + Msg("Failed to create service config loader, will fallback to database") + } else { + serviceConfigLoader = loader + log.Info(). + Str("source", configSource). + Str("basePath", basePath). + Str("repo", appConfig.ServiceConfigRepo). + Msg("Service config loader initialized successfully") + } + } else { + log.Info(). + Msg("SERVICE_CONFIG_SOURCE not set, will use database for service config") + } + + handler = &Deployable{ + repo: repo, + deployableMetaDataRepo: deployableMetaDataRepo, + serviceConfigRepo: serviceConfigRepo, + serviceConfigLoader: serviceConfigLoader, + infrastructureHandler: infrastructureHandler, + workflowHandler: workflowHandler, + workingEnv: workingEnv, + } + }) + + return handler +} + +// SetServiceConfigLoader sets the service config loader on the deployable handler +// This allows updating the loader after initialization if needed +func (d *Deployable) SetServiceConfigLoader(loader configs.ServiceConfigLoader) { + d.serviceConfigLoader = loader +} + +func (d *Deployable) GetMetaData() (map[string][]string, error) { + deployablesMetaDataReponse, err := d.deployableMetaDataRepo.GetGroupedActiveMetadata() + if err != nil { + log.Error().Err(err).Msg("Failed to get deployable metadata") + return nil, fmt.Errorf("%s: %w", ErrFailedToFetchDeployable, err) + } + + return deployablesMetaDataReponse, nil +} + +func (d *Deployable) CreateDeployable(request *DeployableRequest, workingEnv string) (string, error) { + switch request.ServiceName { + case "predator": + handler := NewHandler(d.repo, d.serviceConfigRepo) + // Set service config loader if available + if d.serviceConfigLoader != nil { + handler.SetServiceConfigLoader(d.serviceConfigLoader) + } + return handler.CreateDeployable(request, workingEnv) + case "inferflow": + handler := NewInferflowHandler(d.repo) + // Inferflow handler doesn't return workflow ID yet, return empty string for now + err := handler.CreateDeployable(request) + return "", err + default: + return "", fmt.Errorf("unsupported service type: %s", request.ServiceName) + } +} + +// CreateDeployableMultiEnvironment creates deployables for multiple environments in a single request +// Each environment gets its own workflow ID, allowing independent tracking and execution +func (d *Deployable) CreateDeployableMultiEnvironment(request *DeployableRequest) (map[string]string, error) { + if len(request.Environments) == 0 { + return nil, fmt.Errorf("environments array is required for multi-environment onboarding") + } + + log.Info(). + Str("appName", request.AppName). + Str("serviceName", request.ServiceName). + Int("environmentCount", len(request.Environments)). + Msg("CreateDeployableMultiEnvironment: Starting multi-environment onboarding") + + // Log all environments being processed + for i, envConfig := range request.Environments { + log.Info(). + Str("appName", request.AppName). + Int("index", i). + Str("workingEnv", envConfig.WorkingEnv). + Str("machineType", envConfig.MachineType). + Str("serviceType", envConfig.ServiceType). + Msg("CreateDeployableMultiEnvironment: Environment in request") + } + + // Atomic validation: Validate all environments before proceeding + // This ensures strict atomicity - all environments must be whitelisted and have configs + // If any environment fails validation, the entire request is rejected + if err := ValidateMultiEnvironmentOnboarding(request.ServiceName, request.Environments, d.serviceConfigLoader); err != nil { + log.Error(). + Err(err). + Str("appName", request.AppName). + Str("serviceName", request.ServiceName). + Int("environmentCount", len(request.Environments)). + Msg("CreateDeployableMultiEnvironment: Multi-environment validation failed - blocking onboarding (atomic behavior)") + return nil, fmt.Errorf("onboarding blocked: %w", err) + } + + log.Info(). + Str("appName", request.AppName). + Str("serviceName", request.ServiceName). + Int("validatedEnvironments", len(request.Environments)). + Msg("CreateDeployableMultiEnvironment: All environments validated successfully - proceeding with onboarding") + + // Check ArgoCD configuration for all environments (optional - logs warnings if not found) + // ArgoCD configuration is optional - system will use defaults if not configured + for _, envConfig := range request.Environments { + if err := argocd.ValidateArgoCDConfiguration(envConfig.WorkingEnv); err != nil { + log.Warn(). + Err(err). + Str("appName", request.AppName). + Str("workingEnv", envConfig.WorkingEnv). + Msg("CreateDeployableMultiEnvironment: ArgoCD configuration not found for environment (optional - will use defaults if available)") + } + } + + log.Info(). + Str("appName", request.AppName). + Int("checkedEnvironments", len(request.Environments)). + Msg("CreateDeployableMultiEnvironment: ArgoCD configuration check completed (optional)") + + workflowIDs := make(map[string]string) + var errors []string + + log.Info(). + Str("appName", request.AppName). + Int("totalEnvironments", len(request.Environments)). + Msg("CreateDeployableMultiEnvironment: Starting to process environments") + + for i, envConfig := range request.Environments { + log.Info(). + Str("appName", request.AppName). + Int("index", i). + Int("totalEnvironments", len(request.Environments)). + Str("workingEnv", envConfig.WorkingEnv). + Str("machineType", envConfig.MachineType). + Str("serviceType", envConfig.ServiceType). + Msg("CreateDeployableMultiEnvironment: Processing environment") + + // Create a single-environment request from the environment config + singleEnvRequest := &DeployableRequest{ + AppName: request.AppName, + ServiceName: request.ServiceName, + CreatedBy: request.CreatedBy, + MachineType: envConfig.MachineType, + CPURequest: envConfig.CPURequest, + CPURequestUnit: envConfig.CPURequestUnit, + CPULimit: envConfig.CPULimit, + CPULimitUnit: envConfig.CPULimitUnit, + MemoryRequest: envConfig.MemoryRequest, + MemoryRequestUnit: envConfig.MemoryRequestUnit, + MemoryLimit: envConfig.MemoryLimit, + MemoryLimitUnit: envConfig.MemoryLimitUnit, + GPURequest: envConfig.GPURequest, + GPULimit: envConfig.GPULimit, + MinReplica: envConfig.MinReplica, + MaxReplica: envConfig.MaxReplica, + GCSBucketPath: envConfig.GCSBucketPath, + GCSTritonPath: envConfig.GCSTritonPath, + TritonImageTag: envConfig.TritonImageTag, + ServiceAccount: envConfig.ServiceAccount, + NodeSelectorValue: envConfig.NodeSelectorValue, + DeploymentStrategy: envConfig.DeploymentStrategy, + ServiceType: envConfig.ServiceType, // Use environment-specific service_type if provided + PodAnnotations: envConfig.PodAnnotations, // Use environment-specific podAnnotations if provided + } + // If podAnnotations not provided in environment config, use from parent request + if len(singleEnvRequest.PodAnnotations) == 0 { + if len(request.PodAnnotations) > 0 { + singleEnvRequest.PodAnnotations = request.PodAnnotations + } + } + // If service_type not provided in environment config, use from parent request or default + if singleEnvRequest.ServiceType == "" { + if request.ServiceType != "" { + singleEnvRequest.ServiceType = request.ServiceType + } else { + singleEnvRequest.ServiceType = "httpstateless" // Default + } + } + + log.Info(). + Str("appName", request.AppName). + Str("workingEnv", envConfig.WorkingEnv). + Str("finalServiceType", singleEnvRequest.ServiceType). + Int("podAnnotationsCount", len(singleEnvRequest.PodAnnotations)). + Msg("CreateDeployableMultiEnvironment: Calling CreateDeployable for environment") + + // Create deployable for this environment (creates separate workflow per environment) + workflowID, err := d.CreateDeployable(singleEnvRequest, envConfig.WorkingEnv) + if err != nil { + log.Error(). + Err(err). + Str("appName", request.AppName). + Str("workingEnv", envConfig.WorkingEnv). + Int("index", i). + Int("totalEnvironments", len(request.Environments)). + Msg("CreateDeployableMultiEnvironment: Failed to create deployable for environment") + errors = append(errors, fmt.Sprintf("%s: %v", envConfig.WorkingEnv, err)) + log.Info(). + Str("appName", request.AppName). + Str("workingEnv", envConfig.WorkingEnv). + Int("errorCount", len(errors)). + Int("successCount", len(workflowIDs)). + Msg("CreateDeployableMultiEnvironment: Continuing to next environment after error") + continue + } + + workflowIDs[envConfig.WorkingEnv] = workflowID + log.Info(). + Str("appName", request.AppName). + Str("workingEnv", envConfig.WorkingEnv). + Str("workflowID", workflowID). + Int("index", i). + Int("totalEnvironments", len(request.Environments)). + Int("successCount", len(workflowIDs)). + Int("errorCount", len(errors)). + Msg("CreateDeployableMultiEnvironment: Successfully created deployable for environment") + } + + log.Info(). + Str("appName", request.AppName). + Int("totalEnvironments", len(request.Environments)). + Int("successCount", len(workflowIDs)). + Int("errorCount", len(errors)). + Interface("workflowIDs", workflowIDs). + Strs("errors", errors). + Msg("CreateDeployableMultiEnvironment: Finished processing all environments") + + if len(errors) > 0 { + if len(workflowIDs) == 0 { + // All environments failed + log.Error(). + Strs("errors", errors). + Str("appName", request.AppName). + Msg("CreateDeployableMultiEnvironment: All environments failed") + return nil, fmt.Errorf("failed to create deployables for all environments: %v", errors) + } + // Some environments succeeded, some failed + log.Warn(). + Strs("errors", errors). + Int("successCount", len(workflowIDs)). + Int("failureCount", len(errors)). + Str("appName", request.AppName). + Msg("CreateDeployableMultiEnvironment: Partial success - some environments failed") + } + + log.Info(). + Str("appName", request.AppName). + Int("totalEnvironments", len(request.Environments)). + Int("successfulEnvironments", len(workflowIDs)). + Int("failedEnvironments", len(errors)). + Interface("workflowIDs", workflowIDs). + Strs("errors", errors). + Msg("CreateDeployableMultiEnvironment: Multi-environment onboarding completed") + + // Log each workflow ID for debugging + for env, workflowID := range workflowIDs { + log.Info(). + Str("appName", request.AppName). + Str("environment", env). + Str("workflowID", workflowID). + Msg("CreateDeployableMultiEnvironment: Workflow ID for environment") + } + + return workflowIDs, nil +} + +func (d *Deployable) UpdateDeployable(request *DeployableRequest, workingEnv string) error { + switch request.ServiceName { + case "predator": + handler := NewHandler(d.repo, d.serviceConfigRepo) + // Set service config loader if available + if d.serviceConfigLoader != nil { + handler.SetServiceConfigLoader(d.serviceConfigLoader) + } + return handler.UpdateDeployable(request, workingEnv) + case "inferflow": + handler := NewInferflowHandler(d.repo) + // Inferflow handler doesn't use workingEnv yet, but we pass it for consistency + return handler.UpdateDeployable(request) + default: + return fmt.Errorf("unsupported service type: %s", request.ServiceName) + } +} + +func (d *Deployable) GetDeployablesByService(serviceName string) ([]servicedeployableconfig.ServiceDeployableConfig, error) { + deployables, err := d.repo.GetByService(serviceName) + if err != nil { + log.Error().Err(err).Msg("Failed to get deployables by service") + return nil, fmt.Errorf("%s: %w", ErrFailedToFetchDeployable, err) + } + return deployables, nil +} + +func (d *Deployable) RefreshDeployable(appName, serviceType, workingEnv string) (*servicedeployableconfig.ServiceDeployableConfig, error) { + log.Info(). + Str("appName", appName). + Str("serviceType", serviceType). + Str("workingEnv", workingEnv). + Msg("RefreshDeployable: Starting refresh - received parameters") + + // Check if appName already starts with workingEnv prefix (backward compatibility) + // Standard case: appName is just the app name (without env prefix) + // Construct database entry name using standard format: {workingEnv}-{appName} + log.Info(). + Str("appName", appName). + Str("workingEnv", workingEnv). + Str("dbEntryName", appName). + Msg("RefreshDeployable: Using standard format - constructed database entry name") + + log.Info(). + Str("appName", appName). + Str("workingEnv", workingEnv). + Str("dbEntryName", appName). + Str("serviceType", serviceType). + Msg("RefreshDeployable: Looking up deployable in database") + + // Get deployable config using the database entry name + deployable, err := d.repo.GetByNameAndService(appName, serviceType) + if err != nil { + log.Error(). + Err(err). + Str("dbEntryName", appName). + Str("serviceType", serviceType). + Str("appName", appName). + Str("workingEnv", workingEnv). + Msg("RefreshDeployable: Failed to get deployable by name and service from database") + return nil, fmt.Errorf("%s: %w", ErrFailedToFetchDeployable, err) + } + + if deployable == nil { + log.Error(). + Str("dbEntryName", appName). + Str("serviceType", serviceType). + Str("appName", appName). + Str("workingEnv", workingEnv). + Msg("RefreshDeployable: Deployable not found in database") + return nil, fmt.Errorf("deployable not found with name: %s (searched as: %s) and service: %s in environment: %s", appName, appName, serviceType, workingEnv) + } + + log.Info(). + Str("appName", appName). + Str("workingEnv", workingEnv). + Str("dbEntryName", appName). + Int("deployableID", deployable.ID). + Str("deployableName", deployable.Name). + Msg("RefreshDeployable: Found deployable in database, now fetching resource detail from ArgoCD") + + // Use the actual app name (without env prefix) and workingEnv for ArgoCD lookup + // ArgoCD functions will construct the application name as {workingEnv}-{actualAppName} + log.Info(). + Str("workingEnv", workingEnv). + Str("expectedArgoCDAppName", fmt.Sprintf("%s-%s", workingEnv, appName)). + Msg("RefreshDeployable: Calling GetResourceDetail to connect to ArgoCD") + + result, err := d.infrastructureHandler.GetResourceDetail(appName, workingEnv) + if err != nil { + log.Error(). + Err(err). + Str("workingEnv", workingEnv). + Str("expectedArgoCDAppName", fmt.Sprintf("%s-%s", workingEnv, appName)). + Msg("RefreshDeployable: Failed to get resource detail from ArgoCD - ArgoCD connection or lookup failed") + return nil, fmt.Errorf("failed to get resource detail from ArgoCD: %w", err) + } + + log.Info(). + Str("workingEnv", workingEnv). + Int("nodeCount", len(result.Nodes)). + Msg("RefreshDeployable: Successfully connected to ArgoCD and retrieved resource detail") + + // Check if there are healthy pods + if len(result.Nodes) > 0 { + healthyPodCount := 0 + for _, node := range result.Nodes { + if node.Kind == "Pod" && node.Health.Status == "Healthy" { + for _, info := range node.Info { + if info.Value == "Running" { + healthyPodCount += 1 + } + } + } + } + + if healthyPodCount > 0 { + deployable.DeployableHealth = "DEPLOYMENT_REASON_ARGO_APP_HEALTHY" + deployable.WorkFlowStatus = "WORKFLOW_COMPLETED" + deployable.DeployableRunningStatus = true + } + } + + // Update in database + if err := d.repo.Update(deployable); err != nil { + log.Error().Err(err).Msg("Failed to update deployable status") + return nil, fmt.Errorf("failed to update deployable status: %w", err) + } + + return deployable, nil +} + +func (d *Deployable) GetRingMasterConfig(appName, workflowID, runID string) mainHandler.Config { + infraConfig := d.infrastructureHandler.GetConfig(appName, d.workingEnv) + // Convert to mainHandler.Config for compatibility + return mainHandler.Config{ + MinReplica: infraConfig.MinReplica, + MaxReplica: infraConfig.MaxReplica, + RunningStatus: infraConfig.RunningStatus, + } +} + +func (d *Deployable) TuneThresholds(request *TuneThresholdsRequest, workingEnv string) (string, error) { + // Construct database entry name using standard format: {env}-{appName} + // This matches the naming pattern used in CreateDeployable and UpdateDeployable + if workingEnv == "" { + return "", fmt.Errorf("workingEnv is required for TuneThresholds") + } + + log.Info(). + Str("appName", request.AppName). + Str("dbEntryName", request.AppName). + Str("workingEnv", workingEnv). + Msg("TuneThresholds: Looking up deployable with environment-prefixed name") + + deployable, err := d.repo.GetByNameAndService(request.AppName, request.ServiceName) + if err != nil { + log.Error().Err(err).Str("dbEntryName", request.AppName).Msg("Failed to get deployable by name and service") + return "", fmt.Errorf("failed to get deployable: %w", err) + } + + if deployable == nil { + return "", fmt.Errorf("deployable config not found for app: %s (searched as: %s) in environment: %s", request.AppName, request.AppName, workingEnv) + } + + // Use deployable.CreatedBy as email for commit author, fallback to empty string if not available + commitEmail := deployable.CreatedBy + if commitEmail == "" { + commitEmail = "horizon-system" + } + + // Prepare workflow payload for threshold update + workflowPayload := map[string]interface{}{ + "appName": request.AppName, + "machine_type": request.MachineType, + "created_by": commitEmail, + } + + // Add thresholds to payload if provided + if request.CPUThreshold != "" { + workflowPayload["cpu_threshold"] = request.CPUThreshold + } + if request.GPUThreshold != "" { + workflowPayload["gpu_threshold"] = request.GPUThreshold + } + + // Validate that at least one threshold is provided + if request.CPUThreshold == "" && request.GPUThreshold == "" { + return "", fmt.Errorf("at least one of cpu_threshold or gpu_threshold must be provided") + } + + // Validate machine type for GPU threshold + // Check if machine type is GPU (case-insensitive for consistency with other parts of the codebase) + machineTypeUpper := strings.ToUpper(strings.TrimSpace(request.MachineType)) + if request.GPUThreshold != "" && machineTypeUpper != "GPU" { + return "", fmt.Errorf("gpu_threshold can only be updated for GPU machine type, got: %s", request.MachineType) + } + + log.Info(). + Str("appName", request.AppName). + Str("workingEnv", workingEnv). + Str("cpuThreshold", request.CPUThreshold). + Str("gpuThreshold", request.GPUThreshold). + Str("machineType", request.MachineType). + Msg("TuneThresholds: Starting threshold update workflow") + + // Start threshold update workflow (non-blocking, returns workflow ID) + workflowID, err := d.workflowHandler.StartThresholdUpdateWorkflow(workflowPayload, commitEmail, workingEnv) + if err != nil { + log.Error(). + Err(err). + Str("appName", request.AppName). + Str("workingEnv", workingEnv). + Msg("TuneThresholds: Failed to start threshold update workflow") + return "", fmt.Errorf("failed to start threshold update workflow: %w", err) + } + + log.Info(). + Str("workflowID", workflowID). + Str("appName", request.AppName). + Str("workingEnv", workingEnv). + Msg("TuneThresholds: Threshold update workflow started successfully") + + return workflowID, nil +} diff --git a/horizon/internal/deployable/handler/environment_validation.go b/horizon/internal/deployable/handler/environment_validation.go new file mode 100644 index 00000000..41a4293f --- /dev/null +++ b/horizon/internal/deployable/handler/environment_validation.go @@ -0,0 +1,155 @@ +package handler + +import ( + "fmt" + "strings" + + "github.com/Meesho/BharatMLStack/horizon/internal/configs" + "github.com/rs/zerolog/log" + "github.com/spf13/viper" +) + +// GetSupportedEnvironments returns the whitelist of supported environments +// Reads from environment variable SUPPORTED_ENVIRONMENTS (comma-separated) +// Example: SUPPORTED_ENVIRONMENTS="gcp_stg,gcp_int,gcp_prd,stg,int,prd" +// If not set, returns empty slice (no environments allowed) +func GetSupportedEnvironments() []string { + envsStr := viper.GetString("SUPPORTED_ENVIRONMENTS") + if envsStr == "" { + log.Warn().Msg("SUPPORTED_ENVIRONMENTS not configured - no environments will be allowed for onboarding") + return []string{} + } + + // Split by comma and trim whitespace + envs := strings.Split(envsStr, ",") + supportedEnvs := make([]string, 0, len(envs)) + for _, env := range envs { + trimmed := strings.TrimSpace(env) + if trimmed != "" { + supportedEnvs = append(supportedEnvs, trimmed) + } + } + + log.Info(). + Strs("supportedEnvironments", supportedEnvs). + Msg("Loaded supported environments whitelist") + + return supportedEnvs +} + +// ValidateEnvironmentWhitelist checks if the given environment is in the whitelist +// Returns error if environment is not supported +func ValidateEnvironmentWhitelist(workingEnv string) error { + if workingEnv == "" { + return fmt.Errorf("workingEnv cannot be empty") + } + + supportedEnvs := GetSupportedEnvironments() + if len(supportedEnvs) == 0 { + return fmt.Errorf("no supported environments configured - set SUPPORTED_ENVIRONMENTS environment variable") + } + + for _, supportedEnv := range supportedEnvs { + if supportedEnv == workingEnv { + return nil // Environment is whitelisted + } + } + + return fmt.Errorf("environment '%s' is not in the supported environments whitelist. Supported environments: %v", workingEnv, supportedEnvs) +} + +// ValidateEnvironmentConfig checks if config.yaml exists for the given environment and service +// Returns error if config is missing +func ValidateEnvironmentConfig(serviceName, workingEnv string, serviceConfigLoader configs.ServiceConfigLoader) error { + if serviceConfigLoader == nil { + return fmt.Errorf("service config loader is not available - cannot validate config.yaml existence") + } + + // Try to load the config - if it fails, the config doesn't exist + _, err := serviceConfigLoader.LoadServiceConfig(serviceName, workingEnv) + if err != nil { + log.Error(). + Err(err). + Str("serviceName", serviceName). + Str("workingEnv", workingEnv). + Msg("Environment config validation failed - config.yaml not found") + return fmt.Errorf("config.yaml not found for environment '%s' and service '%s' (expected: horizon/configs/services/%s/%s/config.yaml): %w", workingEnv, serviceName, serviceName, workingEnv, err) + } + + log.Info(). + Str("serviceName", serviceName). + Str("workingEnv", workingEnv). + Msg("Environment config validation passed - config.yaml exists") + + return nil +} + +// ValidateMultiEnvironmentOnboarding performs atomic validation for multi-environment onboarding +// Validates: +// 1. All environments are in the whitelist +// 2. All environments have config.yaml files +// Returns error if any validation fails (atomic behavior - all must pass) +func ValidateMultiEnvironmentOnboarding(serviceName string, environments []EnvironmentConfig, serviceConfigLoader configs.ServiceConfigLoader) error { + if len(environments) == 0 { + return fmt.Errorf("environments array cannot be empty") + } + + log.Info(). + Str("serviceName", serviceName). + Int("environmentCount", len(environments)). + Msg("Validating multi-environment onboarding (atomic validation)") + + var validationErrors []string + + // Collect all unique environments for validation + envSet := make(map[string]bool) + for _, envConfig := range environments { + workingEnv := envConfig.WorkingEnv + if workingEnv == "" { + validationErrors = append(validationErrors, "environment has empty workingEnv") + continue + } + + // Check for duplicates + if envSet[workingEnv] { + validationErrors = append(validationErrors, "duplicate environment '"+workingEnv+"' in request") + continue + } + envSet[workingEnv] = true + + // Validate whitelist + if err := ValidateEnvironmentWhitelist(workingEnv); err != nil { + validationErrors = append(validationErrors, "environment '"+workingEnv+"': "+err.Error()) + continue + } + + // Validate config exists + if err := ValidateEnvironmentConfig(serviceName, workingEnv, serviceConfigLoader); err != nil { + validationErrors = append(validationErrors, "environment '"+workingEnv+"': "+err.Error()) + continue + } + + log.Info(). + Str("serviceName", serviceName). + Str("workingEnv", workingEnv). + Msg("Environment validation passed") + } + + // If any validation failed, return combined error (atomic behavior) + if len(validationErrors) > 0 { + errorMsg := fmt.Sprintf("multi-environment onboarding validation failed for %d environment(s): %s", len(validationErrors), strings.Join(validationErrors, "; ")) + log.Error(). + Str("serviceName", serviceName). + Int("failedEnvironments", len(validationErrors)). + Strs("errors", validationErrors). + Msg("Multi-environment onboarding validation failed - blocking all environments (atomic behavior)") + return fmt.Errorf("onboarding blocked: %s", errorMsg) + } + + log.Info(). + Str("serviceName", serviceName). + Int("validatedEnvironments", len(environments)). + Msg("Multi-environment onboarding validation passed - all environments are whitelisted and have config files") + + return nil +} diff --git a/horizon/internal/deployable/handler/init.go b/horizon/internal/deployable/handler/init.go new file mode 100644 index 00000000..5215eb55 --- /dev/null +++ b/horizon/internal/deployable/handler/init.go @@ -0,0 +1,18 @@ +package handler + +import ( + "fmt" + + "github.com/Meesho/BharatMLStack/horizon/internal/configs" + "github.com/rs/zerolog/log" +) + +func NewDeployable(version int, appConfig configs.Configs) (Config, error) { + switch version { + case 1: + return InitV1ConfigHandler(appConfig), nil + default: + log.Error().Msg("Invalid version for deployable handler") + return nil, fmt.Errorf("invalid version: %d", version) + } +} diff --git a/horizon/internal/deployable/handler/modelhandler.go b/horizon/internal/deployable/handler/modelhandler.go new file mode 100644 index 00000000..ac98b222 --- /dev/null +++ b/horizon/internal/deployable/handler/modelhandler.go @@ -0,0 +1,64 @@ +package handler + +import ( + "encoding/json" + "fmt" + + "github.com/Meesho/BharatMLStack/horizon/internal/repositories/sql/servicedeployableconfig" +) + +type InferflowHandler struct { + repo servicedeployableconfig.ServiceDeployableRepository +} + +func NewInferflowHandler( + repo servicedeployableconfig.ServiceDeployableRepository, +) *InferflowHandler { + return &InferflowHandler{ + repo: repo, + } +} + +func (h *InferflowHandler) CreateDeployable(request *DeployableRequest) error { + configJSON, err := json.Marshal(map[string]interface{}{ + "cpuRequest": request.CPURequest, + "cpuRequestUnit": request.CPURequestUnit, + "cpuLimit": request.CPULimit, + "cpuLimitUnit": request.CPULimitUnit, + "memoryRequest": request.MemoryRequest, + "memoryRequestUnit": request.MemoryRequestUnit, + "memoryLimit": request.MemoryLimit, + "memoryLimitUnit": request.MemoryLimitUnit, + "gpu_request": request.GPURequest, + "gpu_limit": request.GPULimit, + "min_replica": request.MinReplica, + "max_replica": request.MaxReplica, + }) + if err != nil { + return fmt.Errorf("failed to marshal config: %w", err) + } + + deployableConfig := &servicedeployableconfig.ServiceDeployableConfig{ + Name: request.AppName, + Service: request.ServiceName, + Host: request.AppName + "." + hostUrlSuffix, + Active: true, + CreatedBy: request.CreatedBy, + Config: configJSON, + DeployableRunningStatus: false, + DeployableHealth: "DEPLOYMENT_REASON_ARGO_APP_HEALTHY", + DeployableWorkFlowId: "", + MonitoringUrl: fmt.Sprintf("%s/d/dQ1Gux-Vk/inferflow?orgId=1&refresh=1m&var-service=%s", + grafanaBaseUrl, request.AppName), + WorkFlowStatus: "WORKFLOW_COMPLETED", + } + + if err := h.repo.Create(deployableConfig); err != nil { + return fmt.Errorf("failed to create deployable config: %w", err) + } + return nil +} + +func (h *InferflowHandler) UpdateDeployable(request *DeployableRequest) error { + return fmt.Errorf("UpdateDeployable is not implemented for InferflowHandler, use UpdateDeployableDBOnly instead") +} diff --git a/horizon/internal/deployable/handler/models.go b/horizon/internal/deployable/handler/models.go new file mode 100644 index 00000000..39a2a06c --- /dev/null +++ b/horizon/internal/deployable/handler/models.go @@ -0,0 +1,105 @@ +package handler + +// DeployableRequest represents the request body for creating a new deployable +// Supports both single environment (legacy) and multi-environment (new) formats +type DeployableRequest struct { + AppName string `json:"appName"` + ServiceName string `json:"service_name"` + CreatedBy string `json:"created_by"` + // Legacy fields (used when environments array is not provided) + MachineType string `json:"machine_type,omitempty"` + CPURequest string `json:"cpuRequest,omitempty"` + CPURequestUnit string `json:"cpuRequestUnit,omitempty"` + CPULimit string `json:"cpuLimit,omitempty"` + CPULimitUnit string `json:"cpuLimitUnit,omitempty"` + MemoryRequest string `json:"memoryRequest,omitempty"` + MemoryRequestUnit string `json:"memoryRequestUnit,omitempty"` + MemoryLimit string `json:"memoryLimit,omitempty"` + MemoryLimitUnit string `json:"memoryLimitUnit,omitempty"` + GPURequest string `json:"gpu_request,omitempty"` + GPULimit string `json:"gpu_limit,omitempty"` + MinReplica string `json:"min_replica,omitempty"` + MaxReplica string `json:"max_replica,omitempty"` + GCSBucketPath string `json:"gcs_bucket_path,omitempty"` + GCSTritonPath string `json:"gcs_triton_path,omitempty"` + TritonImageTag string `json:"triton_image_tag,omitempty"` + ServiceAccount string `json:"serviceAccount,omitempty"` + NodeSelectorValue string `json:"nodeSelectorValue,omitempty"` + DeploymentStrategy string `json:"deploymentStrategy,omitempty"` + ServiceType string `json:"service_type,omitempty"` // Service type: "httpstateless" (default) or "grpc" + PodAnnotations map[string]string `json:"podAnnotations,omitempty"` // Pod annotations (higher precedence than config.yaml) + // New multi-environment support + Environments []EnvironmentConfig `json:"environments,omitempty"` +} + +// EnvironmentConfig represents configuration for a specific environment +type EnvironmentConfig struct { + WorkingEnv string `json:"workingEnv" binding:"required"` // e.g., "gcp_stg", "gcp_prod", "gcp_int" + MachineType string `json:"machine_type,omitempty"` + CPURequest string `json:"cpuRequest,omitempty"` + CPURequestUnit string `json:"cpuRequestUnit,omitempty"` + CPULimit string `json:"cpuLimit,omitempty"` + CPULimitUnit string `json:"cpuLimitUnit,omitempty"` + MemoryRequest string `json:"memoryRequest,omitempty"` + MemoryRequestUnit string `json:"memoryRequestUnit,omitempty"` + MemoryLimit string `json:"memoryLimit,omitempty"` + MemoryLimitUnit string `json:"memoryLimitUnit,omitempty"` + GPURequest string `json:"gpu_request,omitempty"` + GPULimit string `json:"gpu_limit,omitempty"` + MinReplica string `json:"min_replica,omitempty"` + MaxReplica string `json:"max_replica,omitempty"` + GCSBucketPath string `json:"gcs_bucket_path,omitempty"` + GCSTritonPath string `json:"gcs_triton_path,omitempty"` + TritonImageTag string `json:"triton_image_tag,omitempty"` + ServiceAccount string `json:"serviceAccount,omitempty"` + NodeSelectorValue string `json:"nodeSelectorValue,omitempty"` + DeploymentStrategy string `json:"deploymentStrategy,omitempty"` + ServiceType string `json:"service_type,omitempty"` // Service type: "httpstateless" (default) or "grpc" + PodAnnotations map[string]string `json:"podAnnotations,omitempty"` // Pod annotations (higher precedence than config.yaml) +} + +// OnboardResponse represents the response from ringmaster onboarding API +type OnboardResponse struct { + DeploymentTriggered bool `json:"deployment-triggered"` + DeploymentRunID string `json:"deploymentRunID"` + DeploymentWorkflowID string `json:"deploymentWorkflowID"` + Onboarded bool `json:"onboarded"` + ServiceAccountBound bool `json:"serviceAccount-bound"` +} + +type DeployableConfigPayload struct { + MachineType string `json:"machine_type"` + CPURequest string `json:"cpuRequest"` + CPURequestUnit string `json:"cpuRequestUnit"` + CPULimit string `json:"cpuLimit"` + CPULimitUnit string `json:"cpuLimitUnit"` + MemoryRequest string `json:"memoryRequest"` + MemoryRequestUnit string `json:"memoryRequestUnit"` + MemoryLimit string `json:"memoryLimit"` + MemoryLimitUnit string `json:"memoryLimitUnit"` + GPURequest string `json:"gpu_request"` + GPULimit string `json:"gpu_limit"` + MinReplica string `json:"min_replica"` + MaxReplica string `json:"max_replica"` + GCSBucketPath string `json:"gcs_bucket_path"` + TritonImageTag string `json:"triton_image_tag"` + ServiceAccount string `json:"serviceAccount"` + NodeSelectorValue string `json:"nodeSelectorValue"` + GCSTritonPath string `json:"gcs_triton_path"` + CPUThreshold string `json:"cpu_threshold"` + GPUThreshold string `json:"gpu_threshold"` + DeploymentStrategy string `json:"deploymentStrategy"` +} + +// TuneThresholdsRequest represents a request to update CPU and/or GPU thresholds +// Both cpu_threshold and gpu_threshold are optional, but at least one must be provided +// machine_type is required and determines which thresholds can be updated: +// - For "GPU" machine type: Both CPU and GPU thresholds can be updated +// - For other machine types: Only CPU threshold can be updated (GPU threshold will be rejected) +type TuneThresholdsRequest struct { + AppName string `json:"appName" binding:"required"` // Application name (required) + ServiceName string `json:"service_name" binding:"required"` // Service name (required) + MachineType string `json:"machine_type" binding:"required"` // Machine type: "GPU" or other (required, determines if GPU threshold can be updated) + CPUThreshold string `json:"cpu_threshold"` // CPU threshold value (optional, but at least one threshold must be provided) + GPUThreshold string `json:"gpu_threshold"` // GPU threshold value (optional, only valid for "GPU" machine type) +} diff --git a/horizon/internal/deployable/handler/predatorhandler.go b/horizon/internal/deployable/handler/predatorhandler.go new file mode 100644 index 00000000..39e1172d --- /dev/null +++ b/horizon/internal/deployable/handler/predatorhandler.go @@ -0,0 +1,1123 @@ +package handler + +import ( + "encoding/json" + "fmt" + "strings" + "sync" + + "github.com/Meesho/BharatMLStack/horizon/internal/configs" + "github.com/Meesho/BharatMLStack/horizon/internal/workflow/activities" + workflowHandler "github.com/Meesho/BharatMLStack/horizon/internal/workflow/handler" + "github.com/spf13/viper" + + "github.com/Meesho/BharatMLStack/horizon/internal/repositories/sql/serviceconfig" + "github.com/Meesho/BharatMLStack/horizon/internal/repositories/sql/servicedeployableconfig" + "github.com/Meesho/BharatMLStack/horizon/pkg/argocd" + "github.com/Meesho/BharatMLStack/horizon/pkg/github" + "github.com/rs/zerolog/log" +) + +var ( + initOnce sync.Once + defaultCPUThreshold string + defaultGPUThreshold string + hostUrlSuffix string + grafanaBaseUrl string +) + +func Init(config configs.Configs) { + initOnce.Do(func() { + defaultCPUThreshold = config.DefaultCpuThreshold + defaultGPUThreshold = config.DefaultGpuThreshold + hostUrlSuffix = config.HostUrlSuffix + grafanaBaseUrl = config.GrafanaBaseUrl + }) +} + +type Handler struct { + repo servicedeployableconfig.ServiceDeployableRepository + serviceConfigRepo serviceconfig.ServiceConfigRepository // Deprecated: kept for backward compatibility + serviceConfigLoader configs.ServiceConfigLoader // New: config-as-code loader + workflowHandler workflowHandler.Handler // New: workflow handler for async onboarding +} + +func NewHandler( + repo servicedeployableconfig.ServiceDeployableRepository, + serviceConfigRepo serviceconfig.ServiceConfigRepository, +) *Handler { + return &Handler{ + repo: repo, + serviceConfigRepo: serviceConfigRepo, + workflowHandler: workflowHandler.GetWorkflowHandler(), // Initialize workflow handler + } +} + +// SetServiceConfigLoader sets the service config loader (config-as-code) +// This should be called during initialization if config-as-code is enabled +func (h *Handler) SetServiceConfigLoader(loader configs.ServiceConfigLoader) { + h.serviceConfigLoader = loader +} + +// loadServiceConfig loads service configuration from config-as-code (preferred) or database (fallback) +func (h *Handler) loadServiceConfig(serviceName, workingEnv string) (*serviceConfigData, error) { + // Validate environment whitelist first (before attempting to load config) + if err := ValidateEnvironmentWhitelist(workingEnv); err != nil { + log.Error(). + Err(err). + Str("serviceName", serviceName). + Str("workingEnv", workingEnv). + Msg("Environment whitelist validation failed - blocking onboarding") + return nil, fmt.Errorf("onboarding blocked: %w", err) + } + + // Atomic onboarding: Each environment MUST have its own config.yaml + // Fail immediately if config-as-code loader is available but config doesn't exist + // This enforces atomic onboarding logic - no fallback to shared configs + if h.serviceConfigLoader != nil { + log.Info(). + Str("serviceName", serviceName). + Str("workingEnv", workingEnv). + Msg("Loading service config from config-as-code (atomic onboarding - each environment must have its own config.yaml)") + + // Validate config exists (this will fail if config.yaml is missing) + if err := ValidateEnvironmentConfig(serviceName, workingEnv, h.serviceConfigLoader); err != nil { + log.Error(). + Err(err). + Str("serviceName", serviceName). + Str("workingEnv", workingEnv). + Msg("Environment config validation failed - blocking onboarding (atomic behavior)") + return nil, fmt.Errorf("onboarding blocked: %w", err) + } + + cacConfig, err := h.serviceConfigLoader.LoadServiceConfig(serviceName, workingEnv) + if err != nil { + log.Error(). + Err(err). + Str("serviceName", serviceName). + Str("workingEnv", workingEnv). + Msg("Failed to load service config from config-as-code - atomic onboarding requires each environment to have its own config.yaml") + return nil, fmt.Errorf("atomic onboarding violation: config.yaml not found for environment %s (expected: horizon/configs/services/%s/%s/config.yaml): %w", workingEnv, serviceName, workingEnv, err) + } + + log.Info(). + Str("serviceName", serviceName). + Str("workingEnv", workingEnv). + Msg("Service config loaded successfully from config-as-code") + return convertCACToServiceConfig(cacConfig, workingEnv), nil + } + + // Fallback to database only if config-as-code loader is not available (backward compatibility) + if h.serviceConfigRepo != nil { + log.Warn(). + Str("serviceName", serviceName). + Str("workingEnv", workingEnv). + Msg("Config-as-code loader not available, falling back to database (not recommended for multi-environment onboarding)") + + dbConfig, err := h.serviceConfigRepo.GetByName(serviceName) + if err != nil { + log.Error(). + Err(err). + Str("serviceName", serviceName). + Msg("Failed to load service config from database") + return nil, fmt.Errorf("failed to get service config: %w", err) + } + + log.Info(). + Str("serviceName", serviceName). + Msg("Service config loaded successfully from database") + return convertDBToServiceConfig(dbConfig, workingEnv), nil + } + + return nil, fmt.Errorf("no service config loader or repository available") +} + +// serviceConfigData represents the unified service config structure +type serviceConfigData struct { + RepoName string + BranchName string + HealthCheck string + AppPort int + IngressClass string + Domain string // Domain name for host generation (e.g., "meesho.int", "example.com") + TritonRepository string // Triton inference server image repository path without tag (from config.yaml, required) + TelegrafImage string // Complete Telegraf image path with tag (from config.yaml, optional) + InitContainerImage string // Complete init container image path with tag for GCS operations (from config.yaml, required if gcs_triton_path is enabled) + AppType string + PrimaryOwner string + SecondaryOwner string + Team string + BU string + PriorityV2 string + ServiceType map[string]bool + LivenessFailureThreshold string + LivenessPeriodSeconds string + LivenessSuccessThreshold string + LivenessTimeoutSeconds string + ReadinessFailureThreshold string + ReadinessPeriodSeconds string + ReadinessSuccessThreshold string + ReadinessTimeoutSeconds string + NodeSelector string + // Autoscaling configuration + ASEnabled string + ASPoll string + ASDownPeriod string + ASUpPeriod string + ASUpStableWindow string + ASDownStableWindow string + ASTriggerType string + ASTriggerMetric string + ASTriggerValue string + ASDownPodCount string + ASUpPodCount string + ASUpPodPercentage string + CPUThreshold string + // Deployment configuration + MaxSurge string + TerminationGracePeriodSeconds string + ContourResponseTimeout string + PodDistributionSkew string + EnableWebsocket string + AddHeadless string + CreateContourGateway string + PDBMinAvailable string + PDBMaxUnavailable string + PodAnnotations map[string]string +} + +// convertCACToServiceConfig converts config-as-code ServiceConfig to serviceConfigData +func convertCACToServiceConfig(cacConfig *configs.ServiceConfig, workingEnv string) *serviceConfigData { + // Read labels from Labels section first (preferred), then fall back to legacy fields + var bu, team, primaryOwner, secondaryOwner, priorityV2 string + if cacConfig.Labels != nil { + bu = cacConfig.Labels.BU + team = cacConfig.Labels.Team + primaryOwner = cacConfig.Labels.PrimaryOwner + secondaryOwner = cacConfig.Labels.SecondaryOwner + priorityV2 = cacConfig.Labels.PriorityV2 + } + // Fall back to legacy fields if Labels section is not present or fields are empty + if bu == "" { + bu = cacConfig.BU + } + if team == "" { + team = cacConfig.Team + } + if primaryOwner == "" { + primaryOwner = cacConfig.PrimaryOwner + } + if secondaryOwner == "" { + secondaryOwner = cacConfig.SecondaryOwner + } + if priorityV2 == "" { + priorityV2 = cacConfig.PriorityV2 + } + + result := &serviceConfigData{ + RepoName: cacConfig.RepoName, + BranchName: configs.GetBranchName(workingEnv), + HealthCheck: cacConfig.HealthCheck, + AppPort: cacConfig.AppPort, + IngressClass: cacConfig.IngressClass, + Domain: cacConfig.Domain, + TritonRepository: cacConfig.TritonRepository, // Triton image repository path without tag from config.yaml (required) + InitContainerImage: cacConfig.InitContainerImage, // Init container image path from config.yaml (required if gcs_triton_path is enabled) + AppType: cacConfig.AppType, + PrimaryOwner: primaryOwner, + SecondaryOwner: secondaryOwner, + Team: team, + BU: bu, + PriorityV2: priorityV2, + ServiceType: cacConfig.ServiceType, + LivenessFailureThreshold: cacConfig.LivenessFailureThreshold, + LivenessPeriodSeconds: cacConfig.LivenessPeriodSeconds, + LivenessSuccessThreshold: cacConfig.LivenessSuccessThreshold, + LivenessTimeoutSeconds: cacConfig.LivenessTimeoutSeconds, + ReadinessFailureThreshold: cacConfig.ReadinessFailureThreshold, + ReadinessPeriodSeconds: cacConfig.ReadinessPeriodSeconds, + ReadinessSuccessThreshold: cacConfig.ReadinessSuccessThreshold, + ReadinessTimeoutSeconds: cacConfig.ReadinessTimeoutSeconds, + NodeSelector: cacConfig.NodeSelector, + // Autoscaling configuration + ASEnabled: cacConfig.ASEnabled, + ASPoll: cacConfig.ASPoll, + ASDownPeriod: cacConfig.ASDownPeriod, + ASUpPeriod: cacConfig.ASUpPeriod, + ASUpStableWindow: cacConfig.ASUpStableWindow, + ASDownStableWindow: cacConfig.ASDownStableWindow, + ASTriggerType: cacConfig.ASTriggerType, + ASTriggerMetric: cacConfig.ASTriggerMetric, + ASTriggerValue: cacConfig.ASTriggerValue, + ASDownPodCount: cacConfig.ASDownPodCount, + ASUpPodCount: cacConfig.ASUpPodCount, + ASUpPodPercentage: cacConfig.ASUpPodPercentage, + CPUThreshold: cacConfig.CPUThreshold, + // Deployment configuration + MaxSurge: cacConfig.MaxSurge, + TerminationGracePeriodSeconds: cacConfig.TerminationGracePeriodSeconds, + ContourResponseTimeout: cacConfig.ContourResponseTimeout, + PodDistributionSkew: cacConfig.PodDistributionSkew, + EnableWebsocket: cacConfig.EnableWebsocket, + AddHeadless: cacConfig.AddHeadless, + CreateContourGateway: cacConfig.CreateContourGateway, + PDBMinAvailable: cacConfig.PDBMinAvailable, + PDBMaxUnavailable: cacConfig.PDBMaxUnavailable, + PodAnnotations: cacConfig.PodAnnotations, + } + + log.Info(). + Str("bu", result.BU). + Str("team", result.Team). + Str("priorityV2", result.PriorityV2). + Str("primaryOwner", result.PrimaryOwner). + Str("secondaryOwner", result.SecondaryOwner). + Msg("convertCACToServiceConfig: Converted config-as-code to serviceConfigData") + + return result +} + +// convertDBToServiceConfig converts database ServiceConfig to serviceConfigData +func convertDBToServiceConfig(dbConfig *serviceconfig.ServiceConfig, workingEnv string) *serviceConfigData { + // Parse service type config from JSON + serviceType := make(map[string]bool) + if len(dbConfig.Config) > 0 { + var serviceTypeConfig map[string]interface{} + if err := json.Unmarshal(dbConfig.Config, &serviceTypeConfig); err == nil { + for k, v := range serviceTypeConfig { + if boolVal, ok := v.(bool); ok { + serviceType[k] = boolVal + } else if strVal, ok := v.(string); ok { + serviceType[k] = strVal == "true" + } + } + } + } + + // Use branch name from config or derive from workingEnv + branchName := dbConfig.BranchName + if branchName == "" { + branchName = configs.GetBranchName(workingEnv) + } + + // Set default probe values for database configs (backward compatibility) + // These defaults match the ones in ServiceConfig.ApplyDefaults() + livenessFailureThreshold := "5" + livenessPeriodSeconds := "10" + livenessSuccessThreshold := "1" + livenessTimeoutSeconds := "2" + readinessFailureThreshold := "5" + readinessPeriodSeconds := "10" + readinessSuccessThreshold := "1" + readinessTimeoutSeconds := "2" + + // Set default autoscaling values for database configs (backward compatibility) + asEnabled := "true" + asPoll := "30" + asDownPeriod := "300" + asUpPeriod := "60" + asUpStableWindow := "300" + asDownStableWindow := "1800" + asTriggerType := "AverageValue" + asTriggerMetric := "cpu" + asTriggerValue := "50" + asDownPodCount := "2" + asUpPodCount := "2" + asUpPodPercentage := "10" + cpuThreshold := "50" + + // Set default deployment values for database configs (backward compatibility) + maxSurge := "50" + terminationGracePeriodSeconds := "300" + contourResponseTimeout := "false" + podDistributionSkew := "false" + enableWebsocket := "false" + addHeadless := "false" + createContourGateway := "false" + pdbMinAvailable := "" + pdbMaxUnavailable := "10%" + + // Set default nodeSelector based on environment (matches RingMaster logic) + // Use GetEnvConfig to handle unknown environments gracefully + envConfig := github.GetEnvConfig(workingEnv) + configEnv := envConfig["config_env"] + nodeSelector := "dedicated" // Default + if configEnv == "int" { + nodeSelector = "cloud.google.com/compute-class" + } + + return &serviceConfigData{ + RepoName: dbConfig.RepoName, + BranchName: branchName, + HealthCheck: dbConfig.HealthCheck, + AppPort: dbConfig.AppPort, + IngressClass: dbConfig.IngressClass, + AppType: dbConfig.AppType, + PrimaryOwner: dbConfig.PrimaryOwner, + SecondaryOwner: dbConfig.SecondaryOwner, + Team: dbConfig.Team, + BU: dbConfig.BU, + PriorityV2: dbConfig.PriorityV2, + ServiceType: serviceType, + LivenessFailureThreshold: livenessFailureThreshold, + LivenessPeriodSeconds: livenessPeriodSeconds, + LivenessSuccessThreshold: livenessSuccessThreshold, + LivenessTimeoutSeconds: livenessTimeoutSeconds, + ReadinessFailureThreshold: readinessFailureThreshold, + ReadinessPeriodSeconds: readinessPeriodSeconds, + ReadinessSuccessThreshold: readinessSuccessThreshold, + ReadinessTimeoutSeconds: readinessTimeoutSeconds, + NodeSelector: nodeSelector, + // Autoscaling configuration + ASEnabled: asEnabled, + ASPoll: asPoll, + ASDownPeriod: asDownPeriod, + ASUpPeriod: asUpPeriod, + ASUpStableWindow: asUpStableWindow, + ASDownStableWindow: asDownStableWindow, + ASTriggerType: asTriggerType, + ASTriggerMetric: asTriggerMetric, + ASTriggerValue: asTriggerValue, + ASDownPodCount: asDownPodCount, + ASUpPodCount: asUpPodCount, + ASUpPodPercentage: asUpPodPercentage, + CPUThreshold: cpuThreshold, + // Deployment configuration + MaxSurge: maxSurge, + TerminationGracePeriodSeconds: terminationGracePeriodSeconds, + ContourResponseTimeout: contourResponseTimeout, + PodDistributionSkew: podDistributionSkew, + EnableWebsocket: enableWebsocket, + AddHeadless: addHeadless, + CreateContourGateway: createContourGateway, + PDBMinAvailable: pdbMinAvailable, + PDBMaxUnavailable: pdbMaxUnavailable, + PodAnnotations: make(map[string]string), // Database doesn't store podAnnotations, use empty map + } +} + +func (h *Handler) CreateDeployable(request *DeployableRequest, workingEnv string) (string, error) { + log.Info(). + Str("appName", request.AppName). + Str("serviceName", request.ServiceName). + Str("workingEnv", workingEnv). + Str("createdBy", request.CreatedBy). + Msg("CreateDeployable: Starting deployable creation") + + // 1. Get service config (from config-as-code or database) + serviceConfig, err := h.loadServiceConfig(request.ServiceName, workingEnv) + if err != nil { + log.Error(). + Err(err). + Str("appName", request.AppName). + Str("serviceName", request.ServiceName). + Str("workingEnv", workingEnv). + Msg("CreateDeployable: Failed to get service config") + return "", fmt.Errorf("failed to get service config: %w", err) + } + + log.Info(). + Str("appName", request.AppName). + Str("serviceName", request.ServiceName). + Int("appPort", serviceConfig.AppPort). + Str("repoName", serviceConfig.RepoName). + Int("podAnnotationsCount", len(serviceConfig.PodAnnotations)). + Interface("podAnnotations", serviceConfig.PodAnnotations). + Msg("CreateDeployable: Service config retrieved successfully") + + // 2. Check ArgoCD configuration for the environment (optional - logs warning if not found) + // ArgoCD configuration is optional - system will use defaults if not configured + if err := argocd.ValidateArgoCDConfiguration(workingEnv); err != nil { + log.Warn(). + Err(err). + Str("appName", request.AppName). + Str("serviceName", request.ServiceName). + Str("workingEnv", workingEnv). + Msg("CreateDeployable: ArgoCD configuration not found (optional - will use defaults if available)") + // Continue - this is optional, not mandatory + } else { + log.Info(). + Str("appName", request.AppName). + Str("workingEnv", workingEnv). + Msg("CreateDeployable: ArgoCD configuration found") + } + + // 3. Create initial DB entry + log.Info(). + Str("appName", request.AppName). + Msg("CreateDeployable: Marshaling deployable configuration to JSON") + + configJSON, err := json.Marshal(map[string]interface{}{ + "machine_type": request.MachineType, + "cpuRequest": request.CPURequest, + "cpuRequestUnit": request.CPURequestUnit, + "cpuLimit": request.CPULimit, + "cpuLimitUnit": request.CPULimitUnit, + "memoryRequest": request.MemoryRequest, + "memoryRequestUnit": request.MemoryRequestUnit, + "memoryLimit": request.MemoryLimit, + "memoryLimitUnit": request.MemoryLimitUnit, + "gpu_request": request.GPURequest, + "gpu_limit": request.GPULimit, + "min_replica": request.MinReplica, + "max_replica": request.MaxReplica, + "deploymentStrategy": request.DeploymentStrategy, + "cpu_threshold": defaultCPUThreshold, + "gpu_threshold": defaultGPUThreshold, + "gcs_bucket_path": request.GCSBucketPath, + "gcs_triton_path": request.GCSTritonPath, + "triton_image_tag": request.TritonImageTag, + "serviceAccount": request.ServiceAccount, + "nodeSelectorValue": request.NodeSelectorValue, + }) + if err != nil { + log.Error(). + Err(err). + Str("appName", request.AppName). + Str("serviceName", request.ServiceName). + Msg("CreateDeployable: Failed to marshal deployable configuration to JSON") + return "", fmt.Errorf("failed to marshal config: %w", err) + } + + log.Info(). + Str("appName", request.AppName). + Int("configSize", len(configJSON)). + Msg("CreateDeployable: Configuration marshaled successfully") + + // Standard format: Always use {env}-{appName} for database entry name + // This applies to both single and multi-environment deployables + // Extract environment suffix from workingEnv (e.g., "gcp_stg" -> "stg", "gcp_int" -> "int") + if workingEnv == "" { + return "", fmt.Errorf("workingEnv is required for deployable creation") + } + + // Generate host using domain from config.yaml: . + // Each environment defines its own domain in config.yaml, so no need to inject env into host + domain := serviceConfig.Domain + if domain == "" { + // Fallback to hostUrlSuffix if domain not provided (backward compatibility) + log.Warn(). + Str("appName", request.AppName). + Str("workingEnv", workingEnv). + Msg("Domain not found in config.yaml, using hostUrlSuffix fallback") + domain = hostUrlSuffix + } + + // Host format: . (domain comes from each environment's config.yaml) + host := fmt.Sprintf("%s.%s", request.AppName, domain) + + log.Info(). + Str("appName", request.AppName). + Str("dbEntryName", request.AppName). + Str("host", host). + Str("workingEnv", workingEnv). + Msg("CreateDeployable: Using unique database entry name and host per environment") + + // if serviceConfig.AppPort != 0 { + // host = host + ":" + strconv.Itoa(serviceConfig.AppPort) + // } + deployableConfig := &servicedeployableconfig.ServiceDeployableConfig{ + Name: request.AppName, + Service: request.ServiceName, + Host: host, // Use environment-prefixed host for database uniqueness + Active: true, + CreatedBy: request.CreatedBy, + Config: configJSON, + DeployableRunningStatus: false, + DeployableHealth: "DEPLOYMENT_REASON_ARGO_APP_HEALTH_DEGRADED", + DeployableWorkFlowId: "", + MonitoringUrl: fmt.Sprintf("%s/d/a2605923-52c4-4834-bdae-97570966b765/model-inference-service?orgId=1&var-service=%s&var-query0=", + grafanaBaseUrl, request.AppName), // Monitoring URL uses original appName + WorkFlowStatus: "WORKFLOW_NOT_STARTED", + } + + log.Info(). + Str("appName", request.AppName). + Str("host", deployableConfig.Host). + Str("service", deployableConfig.Service). + Msg("CreateDeployable: Creating deployable entry in database") + + if err := h.repo.Create(deployableConfig); err != nil { + log.Error(). + Err(err). + Str("appName", request.AppName). + Str("host", deployableConfig.Host). + Str("service", deployableConfig.Service). + Str("workingEnv", workingEnv). + Msg("CreateDeployable: Failed to create deployable config in database") + return "", fmt.Errorf("failed to create deployable config: %w", err) + } + + log.Info(). + Str("appName", request.AppName). + Int("deployableID", deployableConfig.ID). + Msg("CreateDeployable: Deployable entry created in database successfully") + + // 3. Prepare workflow payload (same structure as ringmaster payload) + log.Info(). + Str("appName", request.AppName). + Str("bu", serviceConfig.BU). + Str("team", serviceConfig.Team). + Str("priorityV2", serviceConfig.PriorityV2). + Msg("CreateDeployable: Preparing workflow payload with service config values") + + workflowPayload := map[string]interface{}{ + "appName": request.AppName, + "service_name": request.ServiceName, // Add service_name for template conditional logic (e.g., skip externalSecret for predator) + "primaryOwner": serviceConfig.PrimaryOwner, + "repoName": serviceConfig.RepoName, + "branchName": serviceConfig.BranchName, + "secondaryOwner": serviceConfig.SecondaryOwner, + "healthCheck": serviceConfig.HealthCheck, + "host": host, // Use host generated from domain in config.yaml + "domain": domain, // Add domain to payload for UpdateValuesProperties activity + "appPort": serviceConfig.AppPort, + "team": serviceConfig.Team, + "bu": serviceConfig.BU, + "priorityV2": serviceConfig.PriorityV2, + "appType": serviceConfig.AppType, + "ingress_class": serviceConfig.IngressClass, + "triton_repository": serviceConfig.TritonRepository, // Triton image repository path without tag from config.yaml (required) + "init_container_image": serviceConfig.InitContainerImage, // Init container image path from config.yaml (required if gcs_triton_path is enabled) + "machine_type": request.MachineType, + "cpuRequest": request.CPURequest, + "cpuRequestUnit": request.CPURequestUnit, + "cpuLimit": request.CPULimit, + "cpuLimitUnit": request.CPULimitUnit, + "memoryRequest": request.MemoryRequest, + "memoryRequestUnit": request.MemoryRequestUnit, + "memoryLimit": request.MemoryLimit, + "memoryLimitUnit": request.MemoryLimitUnit, + "gpu_request": request.GPURequest, + "gpu_limit": request.GPULimit, + "as_min": request.MinReplica, + "as_max": request.MaxReplica, + "gcs_bucket_path": request.GCSBucketPath, + "triton_image_tag": request.TritonImageTag, + "created_by": request.CreatedBy, + } + + // Handle service_type: Get from request payload, default to "httpstateless" if not provided + // Priority: 1. Request payload (preserve exact value including comma-separated like "httpstateless,grpc"), 2. Service config from config.yaml, 3. Default "httpstateless" + serviceType := request.ServiceType + if serviceType == "" { + // Check if service_type is set in config.yaml (as map[string]bool) + // If grpc is true in config, use "grpc", otherwise default to "httpstateless" + if serviceConfig.ServiceType != nil && serviceConfig.ServiceType["grpc"] { + serviceType = "grpc" + } else { + serviceType = "httpstateless" // Default + } + } + + // Preserve the exact service_type value from request (can be "httpstateless", "grpc", or "httpstateless,grpc") + // This allows comma-separated values to be passed through correctly + workflowPayload["service_type"] = serviceType + + // Check if grpc is enabled (handles comma-separated values like "httpstateless,grpc") + isGrpc := false + if serviceType != "" { + serviceTypes := strings.Split(strings.ReplaceAll(serviceType, " ", ""), ",") + for _, st := range serviceTypes { + if strings.EqualFold(st, "grpc") { + isGrpc = true + break + } + } + } + + // Also set service_type_* flags for backward compatibility with templates + // This maintains compatibility with existing template logic that checks service_type_grpc, etc. + log.Info(). + Str("appName", request.AppName). + Str("serviceType", serviceType). + Bool("isGrpc", isGrpc). + Int("serviceTypeCount", len(serviceConfig.ServiceType)). + Msg("CreateDeployable: Adding service type configuration") + + // Set service_type_grpc flag if grpc is enabled (even in comma-separated format) + workflowPayload["service_type_grpc"] = isGrpc + // Set service_type_httpstateless flag + hasHttpStateless := false + if serviceType != "" { + serviceTypes := strings.Split(strings.ReplaceAll(serviceType, " ", ""), ",") + for _, st := range serviceTypes { + if strings.EqualFold(st, "httpstateless") { + hasHttpStateless = true + break + } + } + } + workflowPayload["service_type_httpstateless"] = hasHttpStateless + + // Also preserve any other service_type flags from config.yaml + for k, v := range serviceConfig.ServiceType { + if k != "grpc" && k != "httpstateless" { + workflowPayload[fmt.Sprintf("service_type_%s", k)] = v + } + } + + // Add remaining fields + workflowPayload["serviceAccount"] = request.ServiceAccount + workflowPayload["nodeSelectorValue"] = request.NodeSelectorValue + workflowPayload["deploymentStrategy"] = request.DeploymentStrategy + workflowPayload["gcs_triton_path"] = request.GCSTritonPath + + // Check if GCS fields are "NA" or empty, if so use localModelPath from config.yaml + gcsBucketPath := strings.TrimSpace(request.GCSBucketPath) + gcsTritonPath := strings.TrimSpace(request.GCSTritonPath) + serviceAccount := strings.TrimSpace(request.ServiceAccount) + + // Check if any GCS-related field is "NA" or empty + useLocalModelPath := false + if gcsBucketPath == "" || strings.EqualFold(gcsBucketPath, "NA") || + gcsTritonPath == "" || strings.EqualFold(gcsTritonPath, "NA") || + serviceAccount == "" || strings.EqualFold(serviceAccount, "NA") { + useLocalModelPath = true + log.Info(). + Str("appName", request.AppName). + Str("gcs_bucket_path", gcsBucketPath). + Str("gcs_triton_path", gcsTritonPath). + Str("serviceAccount", serviceAccount). + Msg("GCS fields are NA or empty, will use localModelPath from config.yaml for local development") + } + + if useLocalModelPath { + workflowPayload["localModelPath"] = viper.GetString("LOCAL_MODEL_PATH") + log.Info(). + Str("appName", request.AppName). + Str("localModelPath", viper.GetString("LOCAL_MODEL_PATH")). + Msg("Using localModelPath from config.yaml for local development") + } + + // Add probe configuration from serviceConfig (read from config.yaml) + // These values come from config.yaml and have defaults applied in ApplyDefaults() + workflowPayload["liveness_failure_threshold"] = serviceConfig.LivenessFailureThreshold + workflowPayload["liveness_period_seconds"] = serviceConfig.LivenessPeriodSeconds + workflowPayload["liveness_success_threshold"] = serviceConfig.LivenessSuccessThreshold + workflowPayload["liveness_timeout_seconds"] = serviceConfig.LivenessTimeoutSeconds + workflowPayload["readiness_failure_threshold"] = serviceConfig.ReadinessFailureThreshold + workflowPayload["readiness_period_seconds"] = serviceConfig.ReadinessPeriodSeconds + workflowPayload["readiness_success_threshold"] = serviceConfig.ReadinessSuccessThreshold + workflowPayload["readiness_timeout_seconds"] = serviceConfig.ReadinessTimeoutSeconds + + // Add nodeSelector from serviceConfig (read from config.yaml or set based on environment) + // If specified in config.yaml, use it; otherwise, it's set based on environment in convertCACToServiceConfig/convertDBToServiceConfig + if serviceConfig.NodeSelector != "" { + workflowPayload["nodeSelector"] = serviceConfig.NodeSelector + } else { + // Set default based on environment (matches RingMaster logic) + // Use GetEnvConfig to handle unknown environments gracefully + envConfig := github.GetEnvConfig(workingEnv) + configEnv := envConfig["config_env"] + if configEnv == "int" { + workflowPayload["nodeSelector"] = "cloud.google.com/compute-class" + } else { + workflowPayload["nodeSelector"] = "dedicated" + } + } + + // Add autoscaling configuration from serviceConfig (read from config.yaml) + workflowPayload["as_enabled"] = serviceConfig.ASEnabled + workflowPayload["as_poll"] = serviceConfig.ASPoll + workflowPayload["as_down_period"] = serviceConfig.ASDownPeriod + workflowPayload["as_up_period"] = serviceConfig.ASUpPeriod + workflowPayload["as_up_stable_window"] = serviceConfig.ASUpStableWindow + workflowPayload["as_down_stable_window"] = serviceConfig.ASDownStableWindow + workflowPayload["as_trigger_type"] = serviceConfig.ASTriggerType + workflowPayload["as_trigger_metric"] = serviceConfig.ASTriggerMetric + workflowPayload["as_trigger_value"] = serviceConfig.ASTriggerValue + workflowPayload["as_down_pod_count"] = serviceConfig.ASDownPodCount + workflowPayload["as_up_pod_count"] = serviceConfig.ASUpPodCount + workflowPayload["as_up_pod_percentage"] = serviceConfig.ASUpPodPercentage + workflowPayload["cpuThreshold"] = serviceConfig.CPUThreshold + + // Add deployment configuration from serviceConfig (read from config.yaml) + workflowPayload["maxSurge"] = serviceConfig.MaxSurge + workflowPayload["terminationGracePeriodSeconds"] = serviceConfig.TerminationGracePeriodSeconds + workflowPayload["contourResponseTimeout"] = serviceConfig.ContourResponseTimeout + workflowPayload["podDistributionSkew"] = serviceConfig.PodDistributionSkew + workflowPayload["enableWebsocket"] = serviceConfig.EnableWebsocket + workflowPayload["addHeadless"] = serviceConfig.AddHeadless + workflowPayload["createContourGateway"] = serviceConfig.CreateContourGateway + workflowPayload["pdbMinAvailable"] = serviceConfig.PDBMinAvailable + workflowPayload["pdbMaxUnavailable"] = serviceConfig.PDBMaxUnavailable + + // Add podAnnotations from serviceConfig (read from config.yaml) + // These will be merged with payload overrides in CreateValuesYaml + log.Info(). + Str("appName", request.AppName). + Interface("podAnnotationsIsNil", serviceConfig.PodAnnotations == nil). + Int("podAnnotationsLen", len(serviceConfig.PodAnnotations)). + Interface("podAnnotations", serviceConfig.PodAnnotations). + Msg("CreateDeployable: Checking podAnnotations from serviceConfig") + + if len(serviceConfig.PodAnnotations) > 0 { + workflowPayload["pod_annotations_from_config"] = serviceConfig.PodAnnotations + log.Info(). + Str("appName", request.AppName). + Int("annotationCount", len(serviceConfig.PodAnnotations)). + Interface("annotations", serviceConfig.PodAnnotations). + Msg("CreateDeployable: Adding podAnnotations from config.yaml (will be used as defaults)") + } else { + log.Warn(). + Str("appName", request.AppName). + Interface("podAnnotationsIsNil", serviceConfig.PodAnnotations == nil). + Int("podAnnotationsLen", len(serviceConfig.PodAnnotations)). + Msg("CreateDeployable: No podAnnotations found in config.yaml (empty or nil)") + } + + // Add podAnnotations from request (higher precedence than config.yaml) + // These will override config.yaml annotations in CreateValuesYaml merge logic + if len(request.PodAnnotations) > 0 { + workflowPayload["pod_annotations"] = request.PodAnnotations + log.Info(). + Str("appName", request.AppName). + Int("annotationCount", len(request.PodAnnotations)). + Msg("CreateDeployable: Adding podAnnotations from request (will override config.yaml)") + } + + // 4. Start workflow asynchronously (non-blocking, same pattern as RingMaster) + log.Info(). + Str("appName", request.AppName). + Str("workingEnv", workingEnv). + Str("createdBy", request.CreatedBy). + Int("payloadKeys", len(workflowPayload)). + Msg("CreateDeployable: Starting onboarding workflow") + + workflowID, err := h.workflowHandler.StartOnboardingWorkflow(workflowPayload, request.CreatedBy, workingEnv) + if err != nil { + log.Error(). + Err(err). + Str("appName", request.AppName). + Str("serviceName", request.ServiceName). + Str("workingEnv", workingEnv). + Str("createdBy", request.CreatedBy). + Int("deployableID", deployableConfig.ID). + Msg("CreateDeployable: Failed to start onboarding workflow - updating deployable status to WORKFLOW_START_FAILED") + + // Update deployable status to indicate workflow start failed + deployableConfig.WorkFlowStatus = "WORKFLOW_START_FAILED" + if updateErr := h.repo.Update(deployableConfig); updateErr != nil { + log.Error(). + Err(updateErr). + Str("appName", request.AppName). + Int("deployableID", deployableConfig.ID). + Str("workflowStatus", "WORKFLOW_START_FAILED"). + Msg("CreateDeployable: Failed to update deployable status after workflow start failure") + } else { + log.Info(). + Str("appName", request.AppName). + Int("deployableID", deployableConfig.ID). + Msg("CreateDeployable: Deployable status updated to WORKFLOW_START_FAILED") + } + return "", fmt.Errorf("failed to start onboarding workflow: %w", err) + } + + log.Info(). + Str("appName", request.AppName). + Str("workflowID", workflowID). + Str("workingEnv", workingEnv). + Msg("CreateDeployable: Onboarding workflow started successfully") + + // 5. Update deployable with workflow ID (non-blocking - workflow executes in background) + log.Info(). + Str("appName", request.AppName). + Str("workflowID", workflowID). + Int("deployableID", deployableConfig.ID). + Msg("CreateDeployable: Updating deployable with workflow ID") + + deployableConfig.DeployableWorkFlowId = workflowID + deployableConfig.WorkFlowStatus = "WORKFLOW_RUNNING" + if err := h.repo.Update(deployableConfig); err != nil { + log.Error(). + Err(err). + Str("appName", request.AppName). + Str("workflowID", workflowID). + Int("deployableID", deployableConfig.ID). + Str("workflowStatus", "WORKFLOW_RUNNING"). + Msg("CreateDeployable: Failed to update deployable with workflow ID in database") + return "", fmt.Errorf("failed to update deployable with workflow ID: %w", err) + } + + log.Info(). + Str("appName", request.AppName). + Str("workflowID", workflowID). + Int("deployableID", deployableConfig.ID). + Msg("CreateDeployable: Deployable updated with workflow ID successfully") + + log.Info(). + Str("appName", request.AppName). + Str("workflowID", workflowID). + Str("workingEnv", workingEnv). + Msg("Onboarding workflow started successfully (non-blocking)") + + return workflowID, nil +} + +func (h *Handler) UpdateDeployable(request *DeployableRequest, workingEnv string) error { + // 1. Get service config (from config-as-code or database) + serviceConfig, err := h.loadServiceConfig(request.ServiceName, workingEnv) + if err != nil { + return fmt.Errorf("failed to get service config: %w", err) + } + + // 2. Construct database entry name using standard format: {env}-{appName} + // This applies to both single and multi-environment deployables + if workingEnv == "" { + return fmt.Errorf("workingEnv is required for deployable update") + } + + log.Info(). + Str("appName", request.AppName). + Str("dbEntryName", request.AppName). + Str("workingEnv", workingEnv). + Msg("UpdateDeployable: Looking up deployable with environment-prefixed name") + + // 3. Get existing deployable configs by service using environment-prefixed name + existingConfig, err := h.repo.GetByNameAndService(request.AppName, request.ServiceName) + if err != nil { + return fmt.Errorf("failed to get existing deployable configs: %w", err) + } + + if existingConfig == nil { + return fmt.Errorf("deployable config not found for app: %s (searched as: %s) in environment: %s", request.AppName, request.AppName, workingEnv) + } + + var config DeployableConfigPayload + if err := json.Unmarshal(existingConfig.Config, &config); err != nil { + return fmt.Errorf("failed to unmarshal existing config: %w", err) + } + + // 3. Update DB entry + configJSON, err := json.Marshal(map[string]interface{}{ + "machine_type": request.MachineType, + "cpuRequest": request.CPURequest, + "cpuRequestUnit": request.CPURequestUnit, + "cpuLimit": request.CPULimit, + "cpuLimitUnit": request.CPULimitUnit, + "memoryRequest": request.MemoryRequest, + "memoryRequestUnit": request.MemoryRequestUnit, + "memoryLimit": request.MemoryLimit, + "memoryLimitUnit": request.MemoryLimitUnit, + "gpu_request": request.GPURequest, + "gpu_limit": request.GPULimit, + "min_replica": request.MinReplica, + "max_replica": request.MaxReplica, + "gcs_bucket_path": request.GCSBucketPath, + "triton_image_tag": request.TritonImageTag, + "serviceAccount": request.ServiceAccount, + "nodeSelectorValue": request.NodeSelectorValue, + "cpu_threshold": config.CPUThreshold, + "deploymentStrategy": request.DeploymentStrategy, + "gpu_threshold": config.GPUThreshold, + "gcs_triton_path": request.GCSTritonPath, + }) + if err != nil { + return fmt.Errorf("failed to marshal config: %w", err) + } + + existingConfig.Config = configJSON + existingConfig.CreatedBy = request.CreatedBy + existingConfig.WorkFlowStatus = "WORKFLOW_NOT_STARTED" + + if err := h.repo.Update(existingConfig); err != nil { + return fmt.Errorf("failed to update deployable config: %w", err) + } + + // Generate host using domain from config.yaml: . + // Each environment defines its own domain in config.yaml, so no need to inject env into host + domain := serviceConfig.Domain + if domain == "" { + // Fallback to hostUrlSuffix if domain not provided (backward compatibility) + log.Warn(). + Str("appName", request.AppName). + Str("workingEnv", workingEnv). + Msg("Domain not found in config.yaml, using hostUrlSuffix fallback") + domain = hostUrlSuffix + } + + // Host format: . (domain comes from each environment's config.yaml) + host := fmt.Sprintf("%s.%s", request.AppName, domain) + + // 4. Prepare workflow payload for updating GitHub files + // This follows the same structure as CreateDeployable for consistency + workflowPayload := map[string]interface{}{ + "appName": request.AppName, + "service_name": request.ServiceName, // Add service_name for template conditional logic (e.g., skip externalSecret for predator) + "primaryOwner": serviceConfig.PrimaryOwner, + "repoName": serviceConfig.RepoName, + "branchName": serviceConfig.BranchName, + "secondaryOwner": serviceConfig.SecondaryOwner, + "healthCheck": serviceConfig.HealthCheck, + "host": host, // Use host generated from domain in config.yaml + "domain": domain, // Add domain to payload for UpdateValuesProperties activity + "appPort": serviceConfig.AppPort, + "team": serviceConfig.Team, + "bu": serviceConfig.BU, + "priorityV2": serviceConfig.PriorityV2, + "appType": serviceConfig.AppType, + "ingress_class": serviceConfig.IngressClass, + "triton_repository": serviceConfig.TritonRepository, // Triton image repository path without tag from config.yaml (required) + "init_container_image": serviceConfig.InitContainerImage, // Init container image path from config.yaml (required if gcs_triton_path is enabled) + "machine_type": request.MachineType, + "cpuRequest": request.CPURequest, + "cpuRequestUnit": request.CPURequestUnit, + "cpuLimit": request.CPULimit, + "cpuLimitUnit": request.CPULimitUnit, + "memoryRequest": request.MemoryRequest, + "memoryRequestUnit": request.MemoryRequestUnit, + "memoryLimit": request.MemoryLimit, + "memoryLimitUnit": request.MemoryLimitUnit, + "gpu_request": request.GPURequest, + "gpu_limit": request.GPULimit, + "as_min": request.MinReplica, + "as_max": request.MaxReplica, + "gcs_bucket_path": request.GCSBucketPath, + "triton_image_tag": request.TritonImageTag, + "created_by": request.CreatedBy, + "serviceAccount": request.ServiceAccount, + "nodeSelectorValue": request.NodeSelectorValue, + "deploymentStrategy": request.DeploymentStrategy, + "gcs_triton_path": request.GCSTritonPath, + } + + // Handle service_type: Get from request payload, default to "httpstateless" if not provided + serviceType := request.ServiceType + if serviceType == "" { + // Check if service_type is set in config.yaml (as map[string]bool) + if serviceConfig.ServiceType != nil && serviceConfig.ServiceType["grpc"] { + serviceType = "grpc" + } else { + serviceType = "httpstateless" // Default + } + } + workflowPayload["service_type"] = serviceType + + // Check if grpc is enabled (handles comma-separated values like "httpstateless,grpc") + isGrpc := false + if serviceType != "" { + serviceTypes := strings.Split(strings.ReplaceAll(serviceType, " ", ""), ",") + for _, st := range serviceTypes { + if strings.EqualFold(st, "grpc") { + isGrpc = true + break + } + } + } + workflowPayload["service_type_grpc"] = isGrpc + + // Set service_type_httpstateless flag + hasHttpStateless := false + if serviceType != "" { + serviceTypes := strings.Split(strings.ReplaceAll(serviceType, " ", ""), ",") + for _, st := range serviceTypes { + if strings.EqualFold(st, "httpstateless") { + hasHttpStateless = true + break + } + } + } + workflowPayload["service_type_httpstateless"] = hasHttpStateless + + // Also preserve any other service_type flags from config.yaml + for k, v := range serviceConfig.ServiceType { + if k != "grpc" && k != "httpstateless" { + workflowPayload[fmt.Sprintf("service_type_%s", k)] = v + } + } + + // Add probe configuration from serviceConfig (read from config.yaml) + workflowPayload["liveness_failure_threshold"] = serviceConfig.LivenessFailureThreshold + workflowPayload["liveness_period_seconds"] = serviceConfig.LivenessPeriodSeconds + workflowPayload["liveness_success_threshold"] = serviceConfig.LivenessSuccessThreshold + workflowPayload["liveness_timeout_seconds"] = serviceConfig.LivenessTimeoutSeconds + workflowPayload["readiness_failure_threshold"] = serviceConfig.ReadinessFailureThreshold + workflowPayload["readiness_period_seconds"] = serviceConfig.ReadinessPeriodSeconds + workflowPayload["readiness_success_threshold"] = serviceConfig.ReadinessSuccessThreshold + workflowPayload["readiness_timeout_seconds"] = serviceConfig.ReadinessTimeoutSeconds + + // Add nodeSelector from serviceConfig (read from config.yaml or set based on environment) + if serviceConfig.NodeSelector != "" { + workflowPayload["nodeSelector"] = serviceConfig.NodeSelector + } else { + // Set default based on environment + envConfig := github.GetEnvConfig(workingEnv) + configEnv := envConfig["config_env"] + if configEnv == "int" { + workflowPayload["nodeSelector"] = "cloud.google.com/compute-class" + } else { + workflowPayload["nodeSelector"] = "dedicated" + } + } + + // Add autoscaling configuration from serviceConfig (read from config.yaml) + workflowPayload["as_enabled"] = serviceConfig.ASEnabled + workflowPayload["as_poll"] = serviceConfig.ASPoll + workflowPayload["as_down_period"] = serviceConfig.ASDownPeriod + workflowPayload["as_up_period"] = serviceConfig.ASUpPeriod + workflowPayload["as_up_stable_window"] = serviceConfig.ASUpStableWindow + workflowPayload["as_down_stable_window"] = serviceConfig.ASDownStableWindow + workflowPayload["as_trigger_type"] = serviceConfig.ASTriggerType + workflowPayload["as_trigger_metric"] = serviceConfig.ASTriggerMetric + workflowPayload["as_trigger_value"] = serviceConfig.ASTriggerValue + workflowPayload["as_down_pod_count"] = serviceConfig.ASDownPodCount + workflowPayload["as_up_pod_count"] = serviceConfig.ASUpPodCount + workflowPayload["as_up_pod_percentage"] = serviceConfig.ASUpPodPercentage + workflowPayload["cpuThreshold"] = serviceConfig.CPUThreshold + + // Add deployment configuration from serviceConfig (read from config.yaml) + workflowPayload["maxSurge"] = serviceConfig.MaxSurge + workflowPayload["terminationGracePeriodSeconds"] = serviceConfig.TerminationGracePeriodSeconds + workflowPayload["contourResponseTimeout"] = serviceConfig.ContourResponseTimeout + workflowPayload["podDistributionSkew"] = serviceConfig.PodDistributionSkew + workflowPayload["enableWebsocket"] = serviceConfig.EnableWebsocket + workflowPayload["addHeadless"] = serviceConfig.AddHeadless + workflowPayload["createContourGateway"] = serviceConfig.CreateContourGateway + workflowPayload["pdbMinAvailable"] = serviceConfig.PDBMinAvailable + workflowPayload["pdbMaxUnavailable"] = serviceConfig.PDBMaxUnavailable + + // Add podAnnotations from serviceConfig (read from config.yaml) + if len(serviceConfig.PodAnnotations) > 0 { + workflowPayload["pod_annotations_from_config"] = serviceConfig.PodAnnotations + } + + // Add podAnnotations from request if provided (higher precedence than config.yaml) + if len(request.PodAnnotations) > 0 { + workflowPayload["pod_annotations"] = request.PodAnnotations + } + + // 5. Update GitHub files directly (values.yaml and values_properties.yaml) + // This replaces the RingMaster API call with direct GitHub updates + log.Info(). + Str("appName", request.AppName). + Str("workingEnv", workingEnv). + Msg("UpdateDeployable: Updating GitHub files (values.yaml and values_properties.yaml)") + + // Update values.yaml + if err := activities.CreateValuesYaml(workflowPayload, workingEnv); err != nil { + log.Error(). + Err(err). + Str("appName", request.AppName). + Str("workingEnv", workingEnv). + Msg("UpdateDeployable: Failed to update values.yaml") + return fmt.Errorf("failed to update values.yaml: %w", err) + } + + // Update values_properties.yaml + if err := activities.UpdateValuesProperties(workflowPayload, workingEnv); err != nil { + log.Error(). + Err(err). + Str("appName", request.AppName). + Str("workingEnv", workingEnv). + Msg("UpdateDeployable: Failed to update values_properties.yaml") + return fmt.Errorf("failed to update values_properties.yaml: %w", err) + } + + log.Info(). + Str("appName", request.AppName). + Str("workingEnv", workingEnv). + Msg("UpdateDeployable: Successfully updated deployable and GitHub files") + + return nil +} diff --git a/horizon/internal/deployable/init.go b/horizon/internal/deployable/init.go new file mode 100644 index 00000000..d2b1fbb6 --- /dev/null +++ b/horizon/internal/deployable/init.go @@ -0,0 +1 @@ +package deployable diff --git a/horizon/internal/deployable/router/router.go b/horizon/internal/deployable/router/router.go new file mode 100644 index 00000000..9ea47e5d --- /dev/null +++ b/horizon/internal/deployable/router/router.go @@ -0,0 +1,37 @@ +package router + +import ( + "sync" + + "github.com/Meesho/BharatMLStack/horizon/internal/configs" + "github.com/Meesho/BharatMLStack/horizon/internal/deployable/controller" + "github.com/Meesho/BharatMLStack/horizon/pkg/httpframework" +) + +var ( + initDeployableRouterOnce sync.Once +) + +// Init expects http framework to be initialized before calling this function +func Init(cfg configs.Configs) { + controller.SetAppConfig(cfg) // Set config in controller for handler initialization + initDeployableRouterOnce.Do(func() { + deployableRegistryApi := httpframework.Instance().Group("/api/v1/horizon/deployable-registry/deployables") + { + deployableRegistryApi.POST("", controller.NewConfigController().CreateDeployable) + deployableRegistryApi.PUT("", controller.NewConfigController().UpdateDeployable) + deployableRegistryApi.POST("/refresh", controller.NewConfigController().RefreshDeployable) + } + + deployableDiscoveryApi := httpframework.Instance().Group("/api/v1/horizon/deployable-discovery/deployables") + { + deployableDiscoveryApi.GET("/metadata", controller.NewConfigController().GetMetaData) + deployableDiscoveryApi.GET("", controller.NewConfigController().GetDeployablesByService) + + } + deployableRegistry := httpframework.Instance().Group("/api/v1/horizon/deployable-registry") + { + deployableRegistry.PUT("/deployables/tune-thresholds", controller.NewConfigController().TuneThresholds) + } + }) +} diff --git a/horizon/internal/dns/client/client_stub.go b/horizon/internal/dns/client/client_stub.go new file mode 100644 index 00000000..996fefc8 --- /dev/null +++ b/horizon/internal/dns/client/client_stub.go @@ -0,0 +1,29 @@ +//go:build !meesho + +package client + +import "fmt" + +// DNSClient interface for DNS operations +// This interface is defined in the stub to maintain compatibility +// The actual implementation is in the internal configs repo +type DNSClient interface { + CreateCloudDNSRecord(appName, bu, ingressClass, priorityV2 string, isServiceGrpc bool, workingEnv string) error + CreateCoreDNSRecord(appName, bu, ingressClass, priorityV2 string, isServiceGrpc, isMultiZone bool, workingEnv string) error +} + +// NewDNSClient creates a new DNS client (stub implementation for open-source builds) +// This is a no-op for open-source builds - DNS functionality is not available +func NewDNSClient() DNSClient { + return &dnsClientStub{} +} + +type dnsClientStub struct{} + +func (c *dnsClientStub) CreateCloudDNSRecord(appName, bu, ingressClass, priorityV2 string, isServiceGrpc bool, workingEnv string) error { + return fmt.Errorf("DNS functionality is not available in open-source builds. Provide organization-specific implementations to enable DNS operations") +} + +func (c *dnsClientStub) CreateCoreDNSRecord(appName, bu, ingressClass, priorityV2 string, isServiceGrpc, isMultiZone bool, workingEnv string) error { + return fmt.Errorf("DNS functionality is not available in open-source builds. Provide organization-specific implementations to enable DNS operations") +} diff --git a/horizon/internal/dns/controller/controller_stub.go b/horizon/internal/dns/controller/controller_stub.go new file mode 100644 index 00000000..d58312fd --- /dev/null +++ b/horizon/internal/dns/controller/controller_stub.go @@ -0,0 +1,34 @@ +//go:build !meesho + +package controller + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/rs/zerolog/log" +) + +type Controller struct{} + +func NewController() *Controller { + return &Controller{} +} + +// CreateCloudDNS is a stub implementation for open-source builds +func (c *Controller) CreateCloudDNS(ctx *gin.Context) { + log.Warn().Msg("DNS endpoints are not available in open-source builds. Provide organization-specific implementations to enable DNS functionality.") + ctx.JSON(http.StatusNotImplemented, gin.H{ + "status": "fail", + "message": "DNS endpoints are not available in open-source builds. Provide organization-specific implementations to enable DNS functionality.", + }) +} + +// CreateCoreDNS is a stub implementation for open-source builds +func (c *Controller) CreateCoreDNS(ctx *gin.Context) { + log.Warn().Msg("DNS endpoints are not available in open-source builds. Provide organization-specific implementations to enable DNS functionality.") + ctx.JSON(http.StatusNotImplemented, gin.H{ + "status": "fail", + "message": "DNS endpoints are not available in open-source builds. Provide organization-specific implementations to enable DNS functionality.", + }) +} diff --git a/horizon/internal/dns/handler/handler_stub.go b/horizon/internal/dns/handler/handler_stub.go new file mode 100644 index 00000000..3b47496e --- /dev/null +++ b/horizon/internal/dns/handler/handler_stub.go @@ -0,0 +1,37 @@ +//go:build !meesho + +package handler + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/rs/zerolog/log" +) + +type DNSHandler interface { + CreateCloudDNS(ctx *gin.Context) + CreateCoreDNS(ctx *gin.Context) +} + +type dnsHandlerStub struct{} + +func NewDNSHandler() DNSHandler { + return &dnsHandlerStub{} +} + +func (h *dnsHandlerStub) CreateCloudDNS(ctx *gin.Context) { + log.Warn().Msg("DNS endpoints are not available in open-source builds. Provide organization-specific implementations to enable DNS functionality.") + ctx.JSON(http.StatusNotImplemented, gin.H{ + "status": "fail", + "message": "DNS endpoints are not available in open-source builds. Provide organization-specific implementations to enable DNS functionality.", + }) +} + +func (h *dnsHandlerStub) CreateCoreDNS(ctx *gin.Context) { + log.Warn().Msg("DNS endpoints are not available in open-source builds. Provide organization-specific implementations to enable DNS functionality.") + ctx.JSON(http.StatusNotImplemented, gin.H{ + "status": "fail", + "message": "DNS endpoints are not available in open-source builds. Provide organization-specific implementations to enable DNS functionality.", + }) +} diff --git a/horizon/internal/dns/router_stub.go b/horizon/internal/dns/router_stub.go new file mode 100644 index 00000000..8860bc3b --- /dev/null +++ b/horizon/internal/dns/router_stub.go @@ -0,0 +1,10 @@ +//go:build !meesho + +package dns + +// Init initializes the DNS router (stub implementation for open-source builds) +// This is a no-op for open-source builds - DNS endpoints are not available +// To enable DNS functionality, provide organization-specific implementations +func Init() { + // DNS router is not available in open-source builds +} diff --git a/horizon/internal/externalcall/airflow_client.go b/horizon/internal/externalcall/airflow_client.go new file mode 100644 index 00000000..2aa70e38 --- /dev/null +++ b/horizon/internal/externalcall/airflow_client.go @@ -0,0 +1,234 @@ +package externalcall + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "sync" + "time" + + "github.com/rs/zerolog/log" +) + +type AirflowClient interface { + TriggerDAG(dagRunID string) (*AirflowResponse, error) + ListDAGRuns(dagID string) (*AirflowDAGRunsResponse, error) + GetDAGRun(dagID, dagRunID string) (*AirflowDAGRun, error) +} + +type airflowClientImpl struct { + BaseURL string + HTTPClient *http.Client + Username string + Password string +} + +type AirflowDAGRunsResponse struct { + DAGRuns []AirflowDAGRun `json:"dag_runs"` + TotalEntries int `json:"total_entries"` +} + +type AirflowDAGRun struct { + DagID string `json:"dag_id"` + DagRunID string `json:"dag_run_id"` + State string `json:"state"` + ExecutionDate time.Time `json:"execution_date"` + StartDate *time.Time `json:"start_date"` + EndDate *time.Time `json:"end_date"` + ExternalTrigger bool `json:"external_trigger"` + RunType string `json:"run_type"` +} + +var ( + airflowOnce sync.Once + airflowInstance AirflowClient +) + +func InitAirflowClient(baseURL string, username string, password string) AirflowClient { + airflowOnce.Do(func() { + airflowInstance = &airflowClientImpl{ + BaseURL: baseURL, + HTTPClient: &http.Client{ + Timeout: 30 * time.Second, + }, + Username: username, + Password: password, + } + }) + return airflowInstance +} + +func GetAirflowClient() AirflowClient { + return airflowInstance +} + +type AirflowResponse struct { + Status string `json:"status"` + DagRunID string `json:"dag_run_id"` + Message string `json:"message,omitempty"` + Error string `json:"error,omitempty"` +} + +type AirflowTriggerRequest struct { + DagRunID string `json:"dag_run_id"` +} + +func (a *airflowClientImpl) TriggerDAG(dagRunID string) (*AirflowResponse, error) { + url := fmt.Sprintf("%s/api/v1/dags/%s/dagRuns", a.BaseURL, dagRunID) + + headers := map[string]string{ + "Content-Type": "application/json", + "Accept": "application/json", + } + + timestamp := time.Now().Format(time.RFC3339) + payload := AirflowTriggerRequest{ + DagRunID: dagRunID + "_" + timestamp, + } + + jsonData, err := json.Marshal(payload) + if err != nil { + return nil, fmt.Errorf("failed to marshal request payload: %w", err) + } + + req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData)) + if err != nil { + return nil, fmt.Errorf("failed to create HTTP request: %w", err) + } + + // Set headers + for key, value := range headers { + req.Header.Set(key, value) + } + + // Set basic auth + req.SetBasicAuth(a.Username, a.Password) + + log.Info(). + Str("url", url). + Str("dag_run_id", dagRunID). + Msg("Triggering Airflow DAG") + + resp, err := a.HTTPClient.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to make HTTP request: %w", err) + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %w", err) + } + + if resp.StatusCode != http.StatusOK { + log.Error(). + Int("status_code", resp.StatusCode). + Str("response_body", string(body)). + Msg("Airflow DAG trigger failed") + return &AirflowResponse{ + Status: "error", + Error: fmt.Sprintf("HTTP %d: %s", resp.StatusCode, string(body)), + }, nil + } + + var response AirflowResponse + if err := json.Unmarshal(body, &response); err != nil { + return nil, fmt.Errorf("failed to unmarshal response: %w", err) + } + + log.Info(). + Str("dag_run_id", dagRunID). + Str("status", response.Status). + Msg("Airflow DAG triggered successfully") + + return &response, nil +} + +func (a *airflowClientImpl) ListDAGRuns(dagID string) (*AirflowDAGRunsResponse, error) { + url := fmt.Sprintf("%s/api/v1/dags/%s/dagRuns", a.BaseURL, dagID) + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, fmt.Errorf("failed to create HTTP request: %w", err) + } + + req.Header.Set("Accept", "application/json") + req.SetBasicAuth(a.Username, a.Password) + + log.Info(). + Str("url", url). + Str("dag_id", dagID). + Msg("Listing Airflow DAG runs") + + resp, err := a.HTTPClient.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to make HTTP request: %w", err) + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %w", err) + } + + if resp.StatusCode != http.StatusOK { + log.Error(). + Int("status_code", resp.StatusCode). + Str("response_body", string(body)). + Msg("Failed to list Airflow DAG runs") + return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body)) + } + + var response AirflowDAGRunsResponse + if err := json.Unmarshal(body, &response); err != nil { + return nil, fmt.Errorf("failed to unmarshal response: %w", err) + } + + return &response, nil +} + +func (a *airflowClientImpl) GetDAGRun(dagID, dagRunID string) (*AirflowDAGRun, error) { + url := fmt.Sprintf("%s/api/v1/dags/%s/dagRuns/%s", a.BaseURL, dagID, dagRunID) + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, fmt.Errorf("failed to create HTTP request: %w", err) + } + + req.Header.Set("Accept", "application/json") + req.SetBasicAuth(a.Username, a.Password) + + log.Debug(). + Str("url", url). + Str("dag_id", dagID). + Str("dag_run_id", dagRunID). + Msg("Getting Airflow DAG run status") + + resp, err := a.HTTPClient.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to make HTTP request: %w", err) + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %w", err) + } + + if resp.StatusCode != http.StatusOK { + log.Error(). + Int("status_code", resp.StatusCode). + Str("response_body", string(body)). + Msg("Failed to get Airflow DAG run") + return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body)) + } + + var dagRun AirflowDAGRun + if err := json.Unmarshal(body, &dagRun); err != nil { + return nil, fmt.Errorf("failed to unmarshal response: %w", err) + } + + return &dagRun, nil +} diff --git a/horizon/internal/externalcall/branch_config.go b/horizon/internal/externalcall/branch_config.go new file mode 100644 index 00000000..e1ffba95 --- /dev/null +++ b/horizon/internal/externalcall/branch_config.go @@ -0,0 +1,36 @@ +package externalcall + +import ( + "github.com/Meesho/BharatMLStack/horizon/internal/configs" +) + +// buildBranchConfig builds a map of environment names to branch names from config +// Only includes environments where a branch is explicitly configured +func buildBranchConfig(config configs.Configs) map[string]string { + branchConfig := make(map[string]string) + + // Define environment to config field mappings + envBranchMappings := []struct { + env string + branch string + }{ + {"prd", config.GitHubBranchPrd}, + {"gcp_prd", config.GitHubBranchGcpPrd}, + {"int", config.GitHubBranchInt}, + {"gcp_int", config.GitHubBranchGcpInt}, + {"dev", config.GitHubBranchDev}, + {"gcp_dev", config.GitHubBranchGcpDev}, + {"gcp_stg", config.GitHubBranchGcpStg}, + {"ftr", config.GitHubBranchFtr}, + {"gcp_ftr", config.GitHubBranchGcpFtr}, + } + + // Only add non-empty branch configurations + for _, mapping := range envBranchMappings { + if mapping.branch != "" { + branchConfig[mapping.env] = mapping.branch + } + } + + return branchConfig +} diff --git a/horizon/internal/externalcall/config.go b/horizon/internal/externalcall/config.go new file mode 100644 index 00000000..af6980cc --- /dev/null +++ b/horizon/internal/externalcall/config.go @@ -0,0 +1,9 @@ +package externalcall + +// Config represents the configuration structure for deployable resources +// This type is kept for backward compatibility with existing code +type Config struct { + MinReplica string `json:"min_replica"` + MaxReplica string `json:"max_replica"` + RunningStatus string `json:"running_status"` +} diff --git a/horizon/internal/externalcall/feature_validation_client.go b/horizon/internal/externalcall/feature_validation_client.go new file mode 100644 index 00000000..14df028c --- /dev/null +++ b/horizon/internal/externalcall/feature_validation_client.go @@ -0,0 +1,249 @@ +package externalcall + +import ( + "fmt" + "strings" + + ofshandler "github.com/Meesho/BharatMLStack/horizon/internal/online-feature-store/handler" +) + +type FeatureValidationClient interface { + ValidateOnlineFeatures(entity string, token string) (*OnlineValidationResponse, error) + ValidateOfflineFeatures(offlineFeatures []string, token string) (*OfflineValidationResponse, error) +} + +type featureValidationClientImpl struct { + ofsHandler ofshandler.Config +} + +var ( + Client FeatureValidationClient +) + +// InitFeatureValidationClient initializes the feature validation client with local OFS handler +func InitFeatureValidationClient() { + // Initialize the client to use local online feature store handler + Client = &featureValidationClientImpl{ + ofsHandler: ofshandler.InitV1ConfigHandler(), + } +} + +// OnlineValidationResponse represents the response from online feature validation +type OnlineValidationResponse []struct { + EntityLabel string `json:"entity-label"` + FeatureGroupLabel string `json:"feature-group-label"` + ID int `json:"id"` + ActiveVersion string `json:"active-version"` + Features map[string]struct { + Labels interface{} `json:"labels"` + } `json:"features"` + StoreID string `json:"store-id"` + DataType string `json:"data-type"` + TTLInSeconds int `json:"ttl-in-seconds"` + TTLInEpoch int `json:"ttl-in-epoch"` + JobID string `json:"job-id"` + InMemoryCacheEnabled bool `json:"in-memory-cache-enabled"` + DistributedCacheEnabled bool `json:"distributed-cache-enabled"` + LayoutVersion int `json:"layout-version"` +} + +// OfflineValidationResponse represents the response from offline feature validation +type OfflineValidationResponse struct { + Error string `json:"error"` + Data map[string]string `json:"data"` +} + +func (f *featureValidationClientImpl) ValidateOnlineFeatures(entity string, token string) (*OnlineValidationResponse, error) { + // Call local online feature store handler directly + featureGroups, err := f.ofsHandler.RetrieveFeatureGroups(entity) + if err != nil { + return nil, fmt.Errorf("failed to retrieve feature groups: %w", err) + } + + // Convert the OFS response to the expected format + var response OnlineValidationResponse + for _, fg := range *featureGroups { + responseItem := struct { + EntityLabel string `json:"entity-label"` + FeatureGroupLabel string `json:"feature-group-label"` + ID int `json:"id"` + ActiveVersion string `json:"active-version"` + Features map[string]struct { + Labels interface{} `json:"labels"` + } `json:"features"` + StoreID string `json:"store-id"` + DataType string `json:"data-type"` + TTLInSeconds int `json:"ttl-in-seconds"` + TTLInEpoch int `json:"ttl-in-epoch"` + JobID string `json:"job-id"` + InMemoryCacheEnabled bool `json:"in-memory-cache-enabled"` + DistributedCacheEnabled bool `json:"distributed-cache-enabled"` + LayoutVersion int `json:"layout-version"` + }{ + EntityLabel: fg.EntityLabel, + FeatureGroupLabel: fg.FeatureGroupLabel, + ID: fg.Id, + ActiveVersion: fg.ActiveVersion, + StoreID: fg.StoreId, + DataType: string(fg.DataType), + TTLInSeconds: fg.TtlInSeconds, + JobID: fg.JobId, + InMemoryCacheEnabled: fg.InMemoryCacheEnabled, + DistributedCacheEnabled: fg.DistributedCacheEnabled, + LayoutVersion: fg.LayoutVersion, + } + + // Convert features map + responseItem.Features = make(map[string]struct { + Labels interface{} `json:"labels"` + }) + for version, feature := range fg.Features { + responseItem.Features[version] = struct { + Labels interface{} `json:"labels"` + }{ + Labels: feature.Labels, + } + } + + response = append(response, responseItem) + } + + return &response, nil +} + +func (f *featureValidationClientImpl) ValidateOfflineFeatures(offlineFeatures []string, token string) (*OfflineValidationResponse, error) { + // Call local online feature store handler directly + request := ofshandler.GetOnlineFeatureMappingRequest{ + OfflineFeatureList: offlineFeatures, + } + + mappingResponse, err := f.ofsHandler.GetOnlineFeatureMapping(request) + if err != nil { + return nil, fmt.Errorf("failed to get online feature mapping: %w", err) + } + + // Convert the response to the expected format + response := &OfflineValidationResponse{ + Error: mappingResponse.Error, + Data: mappingResponse.Data, + } + + return response, nil +} + +// ParseFeatureString parses feature strings and determines validation requirements +// Returns featureType, entity, gf (entity:featureGroup), featureName, isValid +// +// Feature types and validation rules: +// 1. PARENT_OFFLINE_FEATURE|FEATURE -> validate as OFFLINE_FEATURE +// 2. OFFLINE_FEATURE|FEATURE -> validate as OFFLINE_FEATURE +// 3. ONLINE_FEATURE|FEATURE -> validate as ONLINE_FEATURE +// 4. PARENT_ONLINE_FEATURE|FEATURE -> validate as ONLINE_FEATURE +// 5. DEFAULT_FEATURE|FEATURE -> no validation needed +// 6. PARENT_DEFAULT_FEATURE|FEATURE -> no validation needed +// 7. MODEL_FEATURE|FEATURE -> no validation needed +// 8. CALIBRATION|FEATURE -> no validation needed +// 9. RTP_FEATURE|ENTITY:FEATURE_GROUP:FEATURE -> validate using pricing service +// 10. PARENT_RTP_FEATURE|ENTITY:FEATURE_GROUP:FEATURE -> validate using pricing service +func ParseFeatureString(feature string) (featureType, entity, gf, featureName string, isValid bool) { + parts := strings.Split(feature, "|") + if len(parts) < 2 { + return "", "", "", "", false + } + + featureType = strings.TrimSpace(parts[0]) + featureData := strings.TrimSpace(parts[1]) + + switch featureType { + case "PARENT_OFFLINE_FEATURE": + // Add PARENT| prefix for validation but keep original feature name + remainingParts := strings.Join(parts[1:], "|") + validationFeature := "PARENT|" + remainingParts + return featureType, "", validationFeature, remainingParts, true + + case "OFFLINE_FEATURE": + // These need offline validation - return everything after the first pipe + remainingParts := strings.Join(parts[1:], "|") + return featureType, "", remainingParts, remainingParts, true + + case "ONLINE_FEATURE", "PARENT_ONLINE_FEATURE": + // These need online validation - extract entity from e:fg:f format + featureParts := strings.Split(featureData, ":") + if len(featureParts) == 3 { + entity := featureParts[0] // e + gf := featureParts[1] + ":" + featureParts[2] // fg:f + featureName := featureParts[2] // f + return featureType, entity, gf, featureName, true + } + return featureType, "", featureData, featureData, false + + case "DEFAULT_FEATURE", "PARENT_DEFAULT_FEATURE", "MODEL_FEATURE", "CALIBRATION": + // These don't need API validation - they are correct by default + // Return everything after the first pipe + remainingParts := strings.Join(parts[1:], "|") + return featureType, "", remainingParts, remainingParts, true + + case "RTP_FEATURE", "PARENT_RTP_FEATURE": + // These need pricing service validation - extract entity from e:fg:f format + featureData := strings.TrimSpace(parts[1]) + featureParts := strings.Split(featureData, ":") + if len(featureParts) == 3 { + entity := featureParts[0] // e + gf := featureParts[1] + ":" + featureParts[2] // fg:f + featureName := featureParts[2] // f + return featureType, entity, gf, featureName, true + } + return featureType, "", featureData, featureData, false + + default: + // Unknown feature type + return "", "", "", "", false + } +} + +// ValidateFeatureExists checks if a feature exists in the validation response +func ValidateFeatureExists(featureName string, response *OnlineValidationResponse) bool { + if response == nil { + return false + } + featureParts := strings.Split(featureName, ":") + + fg := featureParts[0] + feature := featureParts[1] + + for _, featureGroup := range *response { + if featureGroup.FeatureGroupLabel != fg { + continue + } + + // Iterate through all versions in features (e.g., "2", "3", etc.) + for _, featureVersion := range featureGroup.Features { + // Handle the labels field which can be a comma-separated string + switch labels := featureVersion.Labels.(type) { + case string: + // Labels is a comma-separated string - split and check + labelList := strings.Split(labels, ",") + for _, label := range labelList { + if strings.TrimSpace(label) == feature { + return true + } + } + case []string: + // Labels is an array of strings + for _, label := range labels { + if strings.TrimSpace(label) == feature { + return true + } + } + case []interface{}: + // Labels is an array of interfaces (convert to strings) + for _, labelInterface := range labels { + if labelStr, ok := labelInterface.(string); ok && strings.TrimSpace(labelStr) == feature { + return true + } + } + } + } + } + return false +} diff --git a/horizon/internal/externalcall/gcs_client.go b/horizon/internal/externalcall/gcs_client.go new file mode 100644 index 00000000..2eed96a3 --- /dev/null +++ b/horizon/internal/externalcall/gcs_client.go @@ -0,0 +1,883 @@ +package externalcall + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "os" + "path" + "path/filepath" + "regexp" + "strings" + "sync" + "time" + + "cloud.google.com/go/storage" + "github.com/rs/zerolog/log" + "google.golang.org/api/iterator" +) + +type GCSClientInterface interface { + ReadFile(bucket, objectPath string) ([]byte, error) + TransferFolder(srcBucket, srcPath, srcModelName, destBucket, destPath, destModelName string) error + TransferAndDeleteFolder(srcBucket, srcPath, srcModelName, destBucket, destPath, destModelName string) error + TransferFolderWithSplitSources(modelBucket, modelPath, configBucket, configPath, srcModelName, destBuckt, destPath, destModelName string) error + DeleteFolder(bucket, modelPath, modelName string) error + ListFolders(bucket, prefix string) ([]string, error) + UploadFile(bucket, objectPath string, data []byte) error + CheckFileExists(bucket, objectPath string) (bool, error) + CheckFolderExists(bucket, folderPrefix string) (bool, error) + UploadFolderFromLocal(srcFolderPath, bucket, destPath string) error + GetFolderInfo(bucket, folderPrefix string) (*GCSFolderInfo, error) + ListFoldersWithTimestamp(bucket, prefix string) ([]GCSFolderInfo, error) + FindFileWithSuffix(bucket, folderPath, suffix string) (bool, string, error) + ListFilesWithSuffix(bucket, folderPath, suffix string) ([]string, error) +} + +const ( + maxRetries = 3 + initialBackoffTime = 2 * time.Second + maxConcurrentFiles = 10 // Maximum number of files to transfer concurrently +) + +type GCSFolderInfo struct { + Name string `json:"name"` + Path string `json:"path"` + Created time.Time `json:"created"` + Updated time.Time `json:"updated"` + Size int64 `json:"size"` + FileCount int `json:"file_count"` +} + +type GCSClient struct { + client *storage.Client + ctx context.Context +} + +func CreateGCSClient() GCSClientInterface { + ctx := context.Background() + + // Check for Application Default Credentials path + credsPath := os.Getenv("CLOUDSDK_CONFIG") + if credsPath == "" { + home := os.Getenv("HOME") + if home == "" { + home = "/root" + } + credsPath = home + "/.config/gcloud" + } + adcPath := credsPath + "/application_default_credentials.json" + + // Check if ADC file exists + _, err := os.Stat(adcPath) + adcExists := err == nil + + log.Info(). + Str("CLOUDSDK_CONFIG", os.Getenv("CLOUDSDK_CONFIG")). + Str("HOME", os.Getenv("HOME")). + Str("adcPath", adcPath). + Bool("adcFileExists", adcExists). + Msg("Creating GCS client with Application Default Credentials") + + if !adcExists { + log.Warn(). + Str("adcPath", adcPath). + Msg("ADC credentials file not found - GCS operations may fail. Run 'gcloud auth application-default login' on host and ensure ~/.config/gcloud is mounted") + } + + client, err := storage.NewClient(ctx) + if err != nil { + log.Error(). + Err(err). + Str("adcPath", adcPath). + Bool("adcFileExists", adcExists). + Msg("Failed to create GCS client - check ADC credentials") + return &GCSClient{ + client: nil, + ctx: ctx, + } + } + + // Log successful creation with credential info + log.Info(). + Str("adcPath", adcPath). + Bool("adcFileExists", adcExists). + Msg("GCS client created successfully with Application Default Credentials") + + return &GCSClient{ + client: client, + ctx: ctx, + } +} + +// ObjectVisitor is called for each object. Return an error to stop iteration. +// Return a special sentinel error like ErrStopIteration to stop without error. +type ObjectVisitor func(attrs *storage.ObjectAttrs) error + +var ErrStopIteration = errors.New("stop iteration") + +// ObjectFilter returns true if the object should be included. +type ObjectFilter func(attrs *storage.ObjectAttrs) bool + +func (g *GCSClient) ReadFile(bucket, objectPath string) ([]byte, error) { + rc, err := g.client.Bucket(bucket).Object(objectPath).NewReader(g.ctx) + if err != nil { + return nil, fmt.Errorf("failed to create object reader: %w", err) + } + defer rc.Close() + + buf := new(bytes.Buffer) + if _, err := io.Copy(buf, rc); err != nil { + return nil, fmt.Errorf("failed to read object into buffer: %w", err) + } + return buf.Bytes(), nil +} + +func (g *GCSClient) TransferFolder(srcBucket, srcPath, srcModelName, destBucket, destPath, destModelName string) error { + prefix := path.Join(srcPath, srcModelName) + if !strings.HasSuffix(prefix, "/") { + prefix += "/" + } + + // Phase 1: Collect all objects and separate config files from regular files + var regularFiles []storage.ObjectAttrs + var configFiles []storage.ObjectAttrs + + isConfigFile := func(attrs *storage.ObjectAttrs) bool { + return strings.HasSuffix(attrs.Name, "config.pbtxt") + } + + configFiles, regularFiles, err := g.partitionObjects(srcBucket, prefix, isConfigFile) + if err != nil { + return fmt.Errorf("failed to list source bucket: %w", err) + } + + if len(regularFiles) == 0 && len(configFiles) == 0 { + return fmt.Errorf("no files found at source location: gs://%s/%s", srcBucket, prefix) + } + + log.Info().Msgf("Starting two-phase transfer: %d regular files, %d config files", len(regularFiles), len(configFiles)) + + // Phase 1: Fast direct transfer of regular files (no local download) + if len(regularFiles) > 0 { + if err := g.transferRegularFiles(regularFiles, srcBucket, destBucket, destPath, destModelName, prefix); err != nil { + return fmt.Errorf("failed to transfer regular files: %w", err) + } + } + + // Phase 2: Handle config files with model name replacement + if len(configFiles) > 0 { + if err := g.transferConfigFiles(configFiles, srcBucket, destBucket, destPath, destModelName, prefix); err != nil { + return fmt.Errorf("failed to transfer config files: %w", err) + } + } + + log.Info().Msgf("Successfully completed two-phase transfer") + return nil +} + +// transferRegularFiles performs fast direct GCS-to-GCS transfers for regular files +func (g *GCSClient) transferRegularFiles(files []storage.ObjectAttrs, srcBucket, destBucket, destPath, destModelName, prefix string) error { + log.Info().Msgf("Phase 1: Starting fast direct transfer of %d regular files", len(files)) + + // Use worker pool for concurrent direct transfers + semaphore := make(chan struct{}, maxConcurrentFiles) + var wg sync.WaitGroup + var mu sync.Mutex + var transferErrors []error + + for _, objAttrs := range files { + wg.Add(1) + go func(obj storage.ObjectAttrs) { + defer wg.Done() + semaphore <- struct{}{} // Acquire semaphore + defer func() { <-semaphore }() // Release semaphore + + if err := g.transferSingleRegularFile(obj, srcBucket, destBucket, destPath, destModelName, prefix); err != nil { + mu.Lock() + transferErrors = append(transferErrors, fmt.Errorf("failed to transfer %s: %w", obj.Name, err)) + mu.Unlock() + } + }(objAttrs) + } + + wg.Wait() + + if len(transferErrors) > 0 { + return fmt.Errorf("regular file transfer completed with %d errors: %v", len(transferErrors), transferErrors[0]) + } + + log.Info().Msgf("Phase 1 completed: Successfully transferred %d regular files", len(files)) + return nil +} + +// transferSingleRegularFile performs direct GCS-to-GCS copy for a single regular file +func (g *GCSClient) transferSingleRegularFile(objAttrs storage.ObjectAttrs, srcBucket, destBucket, destPath, destModelName, prefix string) error { + // Calculate destination path + relPath := strings.TrimPrefix(objAttrs.Name, prefix) + relPath = strings.TrimPrefix(relPath, "/") + destObjectPath := path.Join(destPath, destModelName, relPath) + + // Use direct GCS-to-GCS copy operation + srcObj := g.client.Bucket(srcBucket).Object(objAttrs.Name) + destObj := g.client.Bucket(destBucket).Object(destObjectPath) + + _, err := destObj.CopierFrom(srcObj).Run(g.ctx) + if err != nil { + return fmt.Errorf("failed to copy object directly: %w", err) + } + + log.Debug().Msgf("Direct transfer completed: %s -> %s", objAttrs.Name, destObjectPath) + return nil +} + +// transferConfigFiles handles config.pbtxt files with model name replacement +func (g *GCSClient) transferConfigFiles(files []storage.ObjectAttrs, srcBucket, destBucket, destPath, destModelName, prefix string) error { + log.Info().Msgf("Phase 2: Processing %d config files with model name replacement", len(files)) + + for _, objAttrs := range files { + if err := g.transferSingleConfigFile(objAttrs, srcBucket, destBucket, destPath, destModelName, prefix); err != nil { + return fmt.Errorf("failed to transfer config file %s: %w", objAttrs.Name, err) + } + } + + log.Info().Msgf("Phase 2 completed: Successfully processed %d config files", len(files)) + return nil +} + +// transferSingleConfigFile handles a single config.pbtxt file with model name replacement +func (g *GCSClient) transferSingleConfigFile(objAttrs storage.ObjectAttrs, srcBucket, destBucket, destPath, destModelName, prefix string) error { + // Calculate destination path + relPath := strings.TrimPrefix(objAttrs.Name, prefix) + relPath = strings.TrimPrefix(relPath, "/") + destObjectPath := path.Join(destPath, destModelName, relPath) + + // Download config file content + srcReader, err := g.client.Bucket(srcBucket).Object(objAttrs.Name).NewReader(g.ctx) + if err != nil { + return fmt.Errorf("failed to create source reader for config: %w", err) + } + defer srcReader.Close() + + // Read config content (config files are typically small) + content, err := io.ReadAll(srcReader) + if err != nil { + return fmt.Errorf("failed to read config file: %w", err) + } + + // Replace model name + log.Info().Msgf("Processing config.pbtxt file: %s -> %s", objAttrs.Name, destObjectPath) + modified := replaceModelNameInConfig(content, destModelName) + + // Upload modified content + destWriter := g.client.Bucket(destBucket).Object(destObjectPath).NewWriter(g.ctx) + defer destWriter.Close() + + _, err = destWriter.Write(modified) + if err != nil { + return fmt.Errorf("failed to write modified config: %w", err) + } + + log.Debug().Msgf("Config file processed: %s -> %s", objAttrs.Name, destObjectPath) + return nil +} + +func (g *GCSClient) TransferFolderWithSplitSources(modelBucket, modelPath, configBucket, configPath, srcModelName, destBucket, destPath, destModelName string) error { + modelPrefix := path.Join(modelPath, srcModelName) + if !strings.HasSuffix(modelPrefix, "/") { + modelPrefix += "/" + } + + regularFiles, err := g.listObjects(modelBucket, modelPrefix, func(attrs *storage.ObjectAttrs) bool { + return !strings.HasSuffix(attrs.Name, "config.pbtxt") + }) + if err != nil { + return fmt.Errorf("failed to read regular files from model source: %w", err) + } + + log.Info().Msgf("TransferFolderWithSplitSources: Found %d regular files in model source gs://%s/%s", + len(regularFiles), modelBucket, modelPrefix) + + configPrefix := path.Join(configPath, srcModelName) + if !strings.HasSuffix(configPrefix, "/") { + configPrefix += "/" + } + + configFiles, err := g.listObjects(configBucket, configPrefix, func(attrs *storage.ObjectAttrs) bool { + return strings.HasSuffix(attrs.Name, "config.pbtxt") + }) + if err != nil { + return fmt.Errorf("failed to read config files from config source: %w", err) + } + + log.Info().Msgf("TransferFolderWithSplitSources: Found %d config files in config source gs://%s/%s", + len(configFiles), configBucket, configPrefix) + + if len(regularFiles) == 0 && len(configFiles) == 0 { + return fmt.Errorf("transferFolderWithSplitSources: No objects found to transfer") + } + + regularFilesTransferred := false + if len(regularFiles) > 0 { + if err := g.transferRegularFilesFromSource(regularFiles, modelBucket, destBucket, destPath, destModelName, modelPrefix); err != nil { + return fmt.Errorf("failed to transfer regular files from model source: %w", err) + } + regularFilesTransferred = true + } + + if len(configFiles) > 0 { + if err := g.transferConfigFilesFromSource(configFiles, configBucket, destBucket, destPath, destModelName, configPrefix); err != nil { + if regularFilesTransferred { + log.Warn().Err(err).Msgf("Config file transfer failed, reverting transfer by deleting destination folder gs://%s/%s/%s", + destBucket, destPath, destModelName) + if revertErr := g.DeleteFolder(destBucket, destPath, destModelName); revertErr != nil { + log.Error().Err(revertErr).Msgf("Failed to revert transfer by deleting destination folder gs://%s/%s/%s", + destBucket, destPath, destModelName) + return fmt.Errorf("failed to transfer config files from config source: %w; revert also failed: %w", err, revertErr) + } + log.Info().Msgf("Successfully reverted transfer by deleting destination folder") + } + return fmt.Errorf("failed to transfer config files from config source: %w", err) + } + } + + log.Info().Msgf("TransferFolderWithSplitSources: Successfully completed split-source transfer for model %s -> %s", + srcModelName, destModelName) + return nil +} + +func (g *GCSClient) transferRegularFilesFromSource(files []storage.ObjectAttrs, srcBucket, destBucket, destPath, destModelName, prefix string) error { + log.Info().Msgf("Transferring %d regular files from model source", len(files)) + + semaphore := make(chan struct{}, maxConcurrentFiles) + var wg sync.WaitGroup + errChan := make(chan error, len(files)) + + for _, objAttrs := range files { + wg.Add(1) + go func(obj storage.ObjectAttrs) { + defer wg.Done() + semaphore <- struct{}{} + defer func() { <-semaphore }() + + if err := g.transferSingleRegularFile(obj, srcBucket, destBucket, destPath, destModelName, prefix); err != nil { + errChan <- fmt.Errorf("failed to transfer %s: %w", obj.Name, err) + } + }(objAttrs) + } + + wg.Wait() + + if errCount := len(errChan); errCount > 0 { + errs := make([]error, 0, errCount) + for i := 0; i < errCount; i++ { + errs = append(errs, <-errChan) + } + var b strings.Builder + b.WriteString(fmt.Sprintf("regular file transfer completed with %d errors:\n", len(errs))) + for i, e := range errs { + if i > 0 { + b.WriteString("\n") + } + b.WriteString(e.Error()) + } + return fmt.Errorf("%s", b.String()) + } + + return nil +} + +func (g *GCSClient) transferConfigFilesFromSource(files []storage.ObjectAttrs, srcBucket, destBucket, destPath, destModelName, prefix string) error { + log.Info().Msgf("Transferring %d config files from config source", len(files)) + + for _, objAttrs := range files { + if err := g.transferSingleConfigFile(objAttrs, srcBucket, destBucket, destPath, destModelName, prefix); err != nil { + return fmt.Errorf("failed to transfer config file %s: %w", objAttrs.Name, err) + } + } + + return nil +} + +func (g *GCSClient) DeleteFolder(bucket, modelPath, modelName string) error { + // Ensure the prefix ends with "/" to avoid matching partial directory names + prefix := path.Join(modelPath, modelName) + + if prefix != "" && !strings.HasSuffix(prefix, "/") { + prefix += "/" + } + + log.Debug().Msgf("Deleting objects with prefix: %s", prefix) + it := g.client.Bucket(bucket).Objects(g.ctx, &storage.Query{Prefix: prefix}) + + for { + objAttrs, err := it.Next() + if err == iterator.Done { + break + } + if err != nil { + return fmt.Errorf("failed to list objects for deletion: %w", err) + } + + err = g.client.Bucket(bucket).Object(objAttrs.Name).Delete(g.ctx) + if err != nil { + log.Printf("failed to delete object %s: %v", objAttrs.Name, err) + continue + } + log.Debug().Msgf("deleted object: %s", objAttrs.Name) + } + return nil +} + +func (g *GCSClient) TransferAndDeleteFolder(srcBucket, srcPath, srcModelName, destBucket, destPath, destModelName string) error { + err := g.TransferFolder(srcBucket, srcPath, srcModelName, destBucket, destPath, destModelName) + if err != nil { + log.Error().Err(err).Msg("Failed to transfer folder") + return fmt.Errorf("failed to transfer folder: %w", err) + } + + backoffTime := initialBackoffTime + for i := 0; i < maxRetries; i++ { + err = g.DeleteFolder(srcBucket, srcPath, srcModelName) + if err == nil { + break + } + log.Error().Err(err).Msgf("Attempt %d: Failed to delete source folder, retrying...", i+1) + time.Sleep(backoffTime) + backoffTime *= 2 + } + + if err != nil { + log.Error().Err(err).Msg("Failed to delete source folder after retries, attempting rollback") + rollbackErr := g.TransferFolder(destBucket, destPath, destModelName, srcBucket, srcPath, srcModelName) + if rollbackErr != nil { + log.Error().Err(rollbackErr).Msg("Rollback failed") + return fmt.Errorf("failed to rollback the transfer after deletion failure: %w", rollbackErr) + } + log.Info().Msg("Rollback successful, but deletion still failed") + return fmt.Errorf("failed to delete source folder after successful transfer: %w", err) + } + log.Info().Msg("Successfully transferred and deleted folder") + return nil +} + +// replaceModelNameInConfig modifies only the top-level `name:` field in config.pbtxt content +// It replaces only the first occurrence to avoid modifying nested names in inputs/outputs/instance_groups +func replaceModelNameInConfig(data []byte, destModelName string) []byte { + content := string(data) + lines := strings.Split(content, "\n") + + for i, line := range lines { + trimmed := strings.TrimSpace(line) + // Match top-level "name:" field - should be at the start of line (or minimal indentation) + // Skip nested names which are typically indented with 2+ spaces + if strings.HasPrefix(trimmed, "name:") { + // Check indentation: top-level fields have minimal/no indentation + leadingWhitespace := len(line) - len(strings.TrimLeft(line, " \t")) + // Skip if heavily indented (nested field) + if leadingWhitespace >= 2 { + continue + } + + // Match the first occurrence of name: "value" pattern + namePattern := regexp.MustCompile(`name\s*:\s*"([^"]+)"`) + matches := namePattern.FindStringSubmatch(line) + if len(matches) > 1 { + oldModelName := matches[1] + // Replace only the FIRST occurrence to avoid replacing nested names + loc := namePattern.FindStringIndex(line) + if loc != nil { + // Replace only the matched portion (first occurrence) + before := line[:loc[0]] + matched := line[loc[0]:loc[1]] + after := line[loc[1]:] + // Replace the value inside quotes while preserving the "name:" format + valuePattern := regexp.MustCompile(`"([^"]+)"`) + valueReplaced := valuePattern.ReplaceAllString(matched, fmt.Sprintf(`"%s"`, destModelName)) + lines[i] = before + valueReplaced + after + } else { + // Fallback: replace all (shouldn't happen with valid input) + lines[i] = namePattern.ReplaceAllString(line, fmt.Sprintf(`name: "%s"`, destModelName)) + } + log.Info().Msgf("Replacing top-level model name in config.pbtxt: '%s' -> '%s'", oldModelName, destModelName) + break + } + } + } + + return []byte(strings.Join(lines, "\n")) +} + +func (g *GCSClient) ListFolders(bucket, prefix string) ([]string, error) { + if g.client == nil { + return nil, fmt.Errorf("GCS client not initialized properly") + } + + var folders []string + seenFolders := make(map[string]bool) + + // Ensure prefix ends with a trailing slash + if !strings.HasSuffix(prefix, "/") { + prefix += "/" + } + + log.Info().Msgf("Listing folders in GCS bucket %s with prefix %s", bucket, prefix) + + err := g.forEachObject(bucket, prefix, func(attrs *storage.ObjectAttrs) error { + if attrs.Name == "" { + return nil + } + trimmed := strings.TrimPrefix(attrs.Name, prefix) + parts := strings.SplitN(trimmed, "/", 2) + if len(parts) > 1 { + folderName := parts[0] + if !seenFolders[folderName] { + folders = append(folders, folderName) + seenFolders[folderName] = true + } + } + return nil + }) + if err != nil { + return nil, err + } + + return folders, nil +} + +func (g *GCSClient) UploadFile(bucket, objectPath string, data []byte) error { + if g.client == nil { + return fmt.Errorf("GCS client not initialized properly") + } + + writer := g.client.Bucket(bucket).Object(objectPath).NewWriter(g.ctx) + if _, err := io.Copy(writer, bytes.NewReader(data)); err != nil { + writer.Close() + return fmt.Errorf("failed to write file to GCS: %w", err) + } + + if err := writer.Close(); err != nil { + return fmt.Errorf("failed to close writer: %w", err) + } + + return nil +} + +func (g *GCSClient) CheckFileExists(bucket, objectPath string) (bool, error) { + if g.client == nil { + return false, fmt.Errorf("GCS client not initialized properly") + } + + _, err := g.client.Bucket(bucket).Object(objectPath).Attrs(g.ctx) + if err != nil { + if err == storage.ErrObjectNotExist { + return false, nil + } + return false, fmt.Errorf("failed to check file existence: %w", err) + } + return true, nil +} + +func (g *GCSClient) CheckFolderExists(bucket, folderPrefix string) (bool, error) { + if g.client == nil { + return false, fmt.Errorf("GCS client not initialized properly") + } + + // Ensure folderPrefix ends with "/" + if !strings.HasSuffix(folderPrefix, "/") { + folderPrefix += "/" + } + + var exists bool + err := g.forEachObject(bucket, folderPrefix, func(attrs *storage.ObjectAttrs) error { + exists = true + return ErrStopIteration // Found one, stop iteration + }) + if err != nil { + return false, fmt.Errorf("failed to check folder existence: %w", err) + } + return exists, nil +} + +func (g *GCSClient) UploadFolderFromLocal(srcFolderPath, bucket, destPath string) error { + if g.client == nil { + return fmt.Errorf("GCS client not initialized properly") + } + + // Walk through the source folder and upload all files + return filepath.Walk(srcFolderPath, func(filePath string, info os.FileInfo, err error) error { + if err != nil { + return fmt.Errorf("error walking path %s: %w", filePath, err) + } + + // Skip directories + if info.IsDir() { + return nil + } + + // Read file content + data, err := os.ReadFile(filePath) + if err != nil { + return fmt.Errorf("failed to read file %s: %w", filePath, err) + } + + // Calculate relative path from source folder + relPath, err := filepath.Rel(srcFolderPath, filePath) + if err != nil { + return fmt.Errorf("failed to calculate relative path: %w", err) + } + + // Convert to forward slashes for GCS + relPath = strings.ReplaceAll(relPath, "\\", "/") + + // Upload to GCS + gcsPath := path.Join(destPath, relPath) + if err := g.UploadFile(bucket, gcsPath, data); err != nil { + return fmt.Errorf("failed to upload file %s to GCS: %w", relPath, err) + } + + log.Info().Msgf("Uploaded file: %s to gs://%s/%s", relPath, bucket, gcsPath) + return nil + }) +} + +func (g *GCSClient) GetFolderInfo(bucket, folderPrefix string) (*GCSFolderInfo, error) { + if g.client == nil { + return nil, fmt.Errorf("GCS client not initialized properly") + } + + // Ensure folderPrefix ends with "/" + if !strings.HasSuffix(folderPrefix, "/") { + folderPrefix += "/" + } + + var folderInfo GCSFolderInfo + folderInfo.Name = strings.TrimSuffix(path.Base(folderPrefix), "/") + folderInfo.Path = fmt.Sprintf("gs://%s/%s", bucket, strings.TrimSuffix(folderPrefix, "/")) + folderInfo.Created = time.Now() + folderInfo.Updated = time.Time{} + + err := g.forEachObject(bucket, folderPrefix, func(attrs *storage.ObjectAttrs) error { + folderInfo.FileCount++ + folderInfo.Size += attrs.Size + + if attrs.Created.Before(folderInfo.Created) { + folderInfo.Created = attrs.Created + } + if attrs.Updated.After(folderInfo.Updated) { + folderInfo.Updated = attrs.Updated + } + return nil + }) + if err != nil { + return nil, err + } + + if folderInfo.FileCount == 0 { + return nil, fmt.Errorf("folder not found or empty") + } + + return &folderInfo, nil +} + +func (g *GCSClient) ListFoldersWithTimestamp(bucket, prefix string) ([]GCSFolderInfo, error) { + if g.client == nil { + return nil, fmt.Errorf("GCS client not initialized properly") + } + + var folders []GCSFolderInfo + seenFolders := make(map[string]*GCSFolderInfo) + + // Ensure prefix ends with a trailing slash + if !strings.HasSuffix(prefix, "/") { + prefix += "/" + } + + log.Info().Msgf("Listing folders with timestamps in GCS bucket %s with prefix %s", bucket, prefix) + err := g.forEachObject(bucket, prefix, func(attrs *storage.ObjectAttrs) error { + if attrs.Name == "" { + return nil + } + trimmed := strings.TrimPrefix(attrs.Name, prefix) + parts := strings.SplitN(trimmed, "/", 2) + if len(parts) > 1 { + folderName := parts[0] + + if folderInfo, exists := seenFolders[folderName]; !exists { + seenFolders[folderName] = &GCSFolderInfo{ + Name: folderName, + Path: fmt.Sprintf("gs://%s/%s%s", bucket, prefix, folderName), + Created: attrs.Created, + Updated: attrs.Updated, + Size: attrs.Size, + FileCount: 1, + } + } else { + folderInfo.FileCount++ + folderInfo.Size += attrs.Size + if attrs.Created.Before(folderInfo.Created) { + folderInfo.Created = attrs.Created + } + if attrs.Updated.After(folderInfo.Updated) { + folderInfo.Updated = attrs.Updated + } + } + } + return nil + }) + if err != nil { + return nil, err + } + + // Convert map to slice + for _, folderInfo := range seenFolders { + folders = append(folders, *folderInfo) + } + + return folders, nil +} + +// FindFileWithSuffix finds the first file with the specified suffix in the given folder path +// Returns (exists, filename, error) +func (g *GCSClient) FindFileWithSuffix(bucket, folderPath, suffix string) (bool, string, error) { + if g.client == nil { + return false, "", fmt.Errorf("GCS client not initialized properly") + } + + // Ensure folderPath ends with "/" + if !strings.HasSuffix(folderPath, "/") { + folderPath += "/" + } + + log.Info().Msgf("Searching for files with suffix '%s' in GCS bucket %s with prefix %s", suffix, bucket, folderPath) + + var foundFile string + err := g.forEachObject(bucket, folderPath, func(attrs *storage.ObjectAttrs) error { + fileName := path.Base(attrs.Name) + if strings.HasSuffix(fileName, suffix) { + log.Info().Msgf("Found file with suffix '%s': %s", suffix, fileName) + foundFile = fileName + return ErrStopIteration + } + return nil + }) + if err != nil { + return false, "", fmt.Errorf("failed to list objects: %w", err) + } + if foundFile == "" { + return false, "", fmt.Errorf("no file found with suffix '%s' in %s/%s", suffix, bucket, folderPath) + } + return true, foundFile, nil +} + +// ListFilesWithSuffix returns all object paths (full GCS object keys) ending with the given +// suffix under folderPath. Unlike FindFileWithSuffix which returns only the first match, +// this method collects every matching file path. Directory markers are skipped. +func (g *GCSClient) ListFilesWithSuffix(bucket, folderPath, suffix string) ([]string, error) { + if g.client == nil { + return nil, fmt.Errorf("GCS client not initialized properly") + } + + if !strings.HasSuffix(folderPath, "/") { + folderPath += "/" + } + + log.Info().Msgf("Listing files with suffix '%s' in GCS bucket %s with prefix %s", suffix, bucket, folderPath) + + var files []string + err := g.forEachObject(bucket, folderPath, func(attrs *storage.ObjectAttrs) error { + // Skip directory markers + if strings.HasSuffix(attrs.Name, "/") { + return nil + } + if strings.HasSuffix(attrs.Name, suffix) { + files = append(files, attrs.Name) + } + return nil + }) + if err != nil { + return nil, fmt.Errorf("failed to list files with suffix '%s': %w", suffix, err) + } + + log.Info().Msgf("Found %d files with suffix '%s' under %s/%s", len(files), suffix, bucket, folderPath) + return files, nil +} + +// forEachObject iterates over all objects with the given prefix and calls the visitor for each. +func (g *GCSClient) forEachObject(bucket, prefix string, visitor ObjectVisitor) error { + if g.client == nil { + return fmt.Errorf("GCS client not initialized properly") + } + + it := g.client.Bucket(bucket).Objects(g.ctx, &storage.Query{Prefix: prefix}) + for { + objAttrs, err := it.Next() + if err == iterator.Done { + break + } + if err != nil { + return fmt.Errorf("failed to list objects: %w", err) + } + + if err := visitor(objAttrs); err != nil { + if errors.Is(err, ErrStopIteration) { + return nil + } + return err + } + } + return nil +} + +// listObjects returns all objects matching the prefix, optionally filtered. +// Pass nil for filter to include all objects (except directory markers). +func (g *GCSClient) listObjects(bucket, prefix string, filter ObjectFilter) ([]storage.ObjectAttrs, error) { + if g.client == nil { + return nil, fmt.Errorf("GCS client not initialized properly") + } + + var objects []storage.ObjectAttrs + + err := g.forEachObject(bucket, prefix, func(attrs *storage.ObjectAttrs) error { + // Skip directory markers by default + if strings.HasSuffix(attrs.Name, "/") { + return nil + } + + if filter == nil || filter(attrs) { + objects = append(objects, *attrs) + } + return nil + }) + + if err != nil { + return nil, err + } + return objects, nil +} + +// partitionObjects separates objects into two groups based on a predicate. +// Objects matching the predicate go into the first slice, others into the second. +func (g *GCSClient) partitionObjects(bucket, prefix string, predicate ObjectFilter) (matching, notMatching []storage.ObjectAttrs, err error) { + if g.client == nil { + return nil, nil, fmt.Errorf("GCS client not initialized properly") + } + + err = g.forEachObject(bucket, prefix, func(attrs *storage.ObjectAttrs) error { + // Skip directory markers + if strings.HasSuffix(attrs.Name, "/") { + return nil + } + + if predicate(attrs) { + matching = append(matching, *attrs) + } else { + notMatching = append(notMatching, *attrs) + } + return nil + }) + + return matching, notMatching, err +} diff --git a/horizon/internal/externalcall/gcs_client_test.go b/horizon/internal/externalcall/gcs_client_test.go new file mode 100644 index 00000000..f92ed913 --- /dev/null +++ b/horizon/internal/externalcall/gcs_client_test.go @@ -0,0 +1,127 @@ +package externalcall + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestReplaceModelNameInConfig(t *testing.T) { + tests := []struct { + name string + data []byte + destModelName string + expectContains string + }{ + { + name: "replaces top-level name only", + data: []byte(`name: "old_model" +instance_group { + name: "old_model" +} +`), + destModelName: "new_model", + expectContains: `name: "new_model"`, + }, + { + name: "preserves nested name with indentation", + data: []byte(`name: "top_level" + instance_group { + name: "nested_name" + } +`), + destModelName: "replaced", + expectContains: `name: "replaced"`, + }, + { + name: "single line config", + data: []byte(`name: "single_model"` + "\n"), + destModelName: "replaced_model", + expectContains: `name: "replaced_model"`, + }, + { + name: "no name field returns unchanged", + data: []byte(`platform: "tensorflow" +version: 1 +`), + destModelName: "any", + expectContains: `platform: "tensorflow"`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := replaceModelNameInConfig(tt.data, tt.destModelName) + assert.Contains(t, string(got), tt.expectContains) + }) + } +} + +func TestErrStopIteration(t *testing.T) { + assert.Error(t, ErrStopIteration) +} + +func TestGCSClient_NilClient_ListFolders(t *testing.T) { + g := &GCSClient{client: nil, ctx: context.Background()} + folders, err := g.ListFolders("bucket", "prefix/") + require.Error(t, err) + assert.Nil(t, folders) + assert.Contains(t, err.Error(), "not initialized") +} + +func TestGCSClient_NilClient_UploadFile(t *testing.T) { + g := &GCSClient{client: nil, ctx: context.Background()} + err := g.UploadFile("bucket", "path/obj", []byte("data")) + require.Error(t, err) + assert.Contains(t, err.Error(), "not initialized") +} + +func TestGCSClient_NilClient_CheckFileExists(t *testing.T) { + g := &GCSClient{client: nil, ctx: context.Background()} + exists, err := g.CheckFileExists("bucket", "path/obj") + require.Error(t, err) + assert.False(t, exists) + assert.Contains(t, err.Error(), "not initialized") +} + +func TestGCSClient_NilClient_CheckFolderExists(t *testing.T) { + g := &GCSClient{client: nil, ctx: context.Background()} + exists, err := g.CheckFolderExists("bucket", "folder/") + require.Error(t, err) + assert.False(t, exists) + assert.Contains(t, err.Error(), "not initialized") +} + +func TestGCSClient_NilClient_GetFolderInfo(t *testing.T) { + g := &GCSClient{client: nil, ctx: context.Background()} + info, err := g.GetFolderInfo("bucket", "folder/") + require.Error(t, err) + assert.Nil(t, info) + assert.Contains(t, err.Error(), "not initialized") +} + +func TestGCSClient_NilClient_ListFoldersWithTimestamp(t *testing.T) { + g := &GCSClient{client: nil, ctx: context.Background()} + folders, err := g.ListFoldersWithTimestamp("bucket", "prefix/") + require.Error(t, err) + assert.Nil(t, folders) + assert.Contains(t, err.Error(), "not initialized") +} + +func TestGCSClient_NilClient_FindFileWithSuffix(t *testing.T) { + g := &GCSClient{client: nil, ctx: context.Background()} + exists, name, err := g.FindFileWithSuffix("bucket", "folder/", ".pbtxt") + require.Error(t, err) + assert.False(t, exists) + assert.Empty(t, name) + assert.Contains(t, err.Error(), "not initialized") +} + +func TestGCSFolderInfo_ZeroValue(t *testing.T) { + var info GCSFolderInfo + assert.Empty(t, info.Name) + assert.Empty(t, info.Path) + assert.Zero(t, info.FileCount) + assert.Zero(t, info.Size) +} diff --git a/horizon/internal/externalcall/github_client.go b/horizon/internal/externalcall/github_client.go new file mode 100644 index 00000000..c481e902 --- /dev/null +++ b/horizon/internal/externalcall/github_client.go @@ -0,0 +1,42 @@ +package externalcall + +import ( + "sync" + + "github.com/Meesho/BharatMLStack/horizon/pkg/github" +) + +var ( + initGitHubOnce sync.Once +) + +// InitGitHubClient initializes the GitHub client and configuration +// This follows the same pattern as other client initializations in this package +// Note: helmChartRepo, infraHelmChartRepo, argoRepo parameters removed - not used +// All repository operations use REPOSITORY_NAME environment variable +func InitGitHubClient( + appID int64, + installationID int64, + privateKey []byte, + owner string, + commitAuthor string, + commitEmail string, + victoriaMetricsServerAddress string, + branchConfig map[string]string, +) { + initGitHubOnce.Do(func() { + config := github.GitHubConfig{ + AppID: appID, + InstallationID: installationID, + PrivateKey: privateKey, + Owner: owner, + // HelmChartRepo, InfraHelmChartRepo, ArgoRepo removed - not used + // All repository operations use REPOSITORY_NAME environment variable + CommitAuthor: commitAuthor, + CommitEmail: commitEmail, + VictoriaMetricsServerAddress: victoriaMetricsServerAddress, + BranchConfig: branchConfig, + } + _ = github.InitGitHub(config) // Errors are logged within the GitHub package + }) +} diff --git a/horizon/internal/externalcall/github_client_test.go b/horizon/internal/externalcall/github_client_test.go new file mode 100644 index 00000000..6ebdea99 --- /dev/null +++ b/horizon/internal/externalcall/github_client_test.go @@ -0,0 +1,153 @@ +package externalcall + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestInitGitHubClient(t *testing.T) { + tests := []struct { + name string + appID int64 + installationID int64 + privateKey []byte + owner string + commitAuthor string + commitEmail string + victoriaMetricsAddress string + description string + }{ + { + name: "Test 1: Initialize with complete configuration", + appID: 12345, + installationID: 67890, + privateKey: []byte("/path/to/key.pem"), + owner: "test-org", + commitAuthor: "test-bot", + commitEmail: "test@example.com", + victoriaMetricsAddress: "http://test-vm:8481/select/100/prometheus/", + description: "Complete configuration should initialize all components", + }, + { + name: "Test 2: Initialize without client credentials", + appID: 0, + installationID: 0, + privateKey: []byte(""), + owner: "test-org", + commitAuthor: "test-bot", + commitEmail: "test@example.com", + victoriaMetricsAddress: "http://test-vm:8481/select/100/prometheus/", + description: "Configuration should work without client credentials", + }, + { + name: "Test 3: Initialize with empty strings (use defaults)", + appID: 0, + installationID: 0, + privateKey: []byte(""), + owner: "", + commitAuthor: "", + commitEmail: "", + victoriaMetricsAddress: "", + description: "Empty strings should use default values", + }, + { + name: "Test 4: Initialize with partial configuration", + appID: 0, + installationID: 0, + privateKey: []byte(""), + owner: "custom-org", + commitAuthor: "", + commitEmail: "custom@example.com", + victoriaMetricsAddress: "", + description: "Partial config should work with defaults for missing values", + }, + { + name: "Test 5: Initialize with only VictoriaMetrics address", + appID: 0, + installationID: 0, + privateKey: []byte(""), + owner: "", + commitAuthor: "", + commitEmail: "", + victoriaMetricsAddress: "http://custom-vm:8481/select/100/prometheus/", + description: "VictoriaMetrics address should be configurable independently", + }, + { + name: "Test 6: Initialize with zero AppID (should skip client init)", + appID: 0, + installationID: 67890, + privateKey: []byte("/path/to/key.pem"), + owner: "test-org", + commitAuthor: "", + commitEmail: "", + victoriaMetricsAddress: "", + description: "Zero AppID should skip client initialization", + }, + { + name: "Test 7: Initialize with only owner and email", + appID: 0, + installationID: 0, + privateKey: []byte(""), + owner: "my-org", + commitAuthor: "", + commitEmail: "devops@myorg.com", + victoriaMetricsAddress: "", + description: "Should work with minimal configuration", + }, + { + name: "Test 8: Initialize with invalid credentials path", + appID: 12345, + installationID: 67890, + privateKey: []byte("/nonexistent/path.pem"), + owner: "test-org", + commitAuthor: "", + commitEmail: "", + victoriaMetricsAddress: "", + description: "Invalid credentials should not crash, errors logged internally", + }, + { + name: "Test 9: Initialize multiple times (should only initialize once)", + appID: 0, + installationID: 0, + privateKey: []byte(""), + owner: "test-org", + commitAuthor: "test-bot", + commitEmail: "test@example.com", + victoriaMetricsAddress: "http://test-vm:8481/select/100/prometheus/", + description: "Multiple calls should be idempotent due to sync.Once", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Reset state by calling init with test parameters + // Note: sync.Once will prevent re-initialization in same test run + // This is expected behavior - initialization should be idempotent + InitGitHubClient( + tt.appID, + tt.installationID, + tt.privateKey, + tt.owner, + tt.commitAuthor, + tt.commitEmail, + tt.victoriaMetricsAddress, + nil, // branchConfig - nil for tests (uses defaults) + ) + // If we get here without panic, initialization succeeded + // The actual initialization logic is tested in the GitHub package + assert.True(t, true, tt.description) + }) + } +} + +func TestInitGitHubClient_Idempotent(t *testing.T) { + // Test that multiple calls to InitGitHubClient are idempotent + InitGitHubClient(0, 0, []byte(""), "org1", "author1", "email1", "", nil) + InitGitHubClient(0, 0, []byte(""), "org2", "author2", "email2", "", nil) + InitGitHubClient(0, 0, []byte(""), "org1", "author1", "email1", "", nil) + InitGitHubClient(0, 0, []byte(""), "org2", "author2", "email2", "", nil) + // Second call should not change the configuration due to sync.Once + // This is expected behavior - initialization should happen only once + assert.True(t, true, "Multiple initialization calls should be idempotent") +} diff --git a/horizon/internal/externalcall/init.go b/horizon/internal/externalcall/init.go new file mode 100644 index 00000000..1c6258de --- /dev/null +++ b/horizon/internal/externalcall/init.go @@ -0,0 +1,97 @@ +package externalcall + +import ( + "strings" + + "github.com/Meesho/BharatMLStack/horizon/internal/configs" + "github.com/Meesho/BharatMLStack/horizon/pkg/github" + "github.com/spf13/viper" +) + +func Init(config configs.Configs) { + InitPrometheusClient(config.VmselectStartDaysAgo, config.VmselectBaseUrl, config.VmselectApiKey) + InitSlackClient(config.SlackWebhookUrl, config.SlackChannel, config.SlackCcTags, config.DefaultModelPath) + // RingMaster client initialization removed - DNS operations now use internal configs with build tags + + branchConfig := buildBranchConfig(config) + privateKey := viper.GetString("GITHUB_PRIVATE_KEY") + privateKeyBytes := normalizePEMKey(privateKey) + InitGitHubClient( + config.GitHubAppID, + config.GitHubInstallationID, + privateKeyBytes, + config.GitHubOwner, + config.GitHubCommitAuthor, + config.GitHubCommitEmail, + config.VictoriaMetricsServerAddress, + branchConfig, + ) + + // Initialize repository and branch for all GitHub commits + // This ensures all commits (onboarding and threshold updates) go to the same repository and branch + // These values come from REPOSITORY_NAME and BRANCH_NAME environment variables (mandatory) + github.InitRepositoryAndBranch(config.RepositoryName, config.BranchName) + + // Initialize working environment from config + // This is used by Predator handler and other components that need workingEnv + workingEnv := viper.GetString("WORKING_ENV") + if workingEnv != "" { + InitWorkingEnvironment(workingEnv) + } + + // Initialize feature validation client with local online feature store + InitFeatureValidationClient() + // Initialize pricing client - provides both raw data types and RTP format + PricingClient.InitPricingClient() + InitAirflowClient(config.AirflowBaseUrl, config.AirflowUsername, config.AirflowPassword) + InitPrismV2Client(config.PrismBaseUrl, config.PrismAppUserID) + +} + +// normalizePEMKey converts env/JSON-friendly key into valid PEM (newlines required). +// Handles two common cases: +// 1. Key with literal "\n" (backslash-n) from JSON/env — replaced with real newlines. +// 2. Key pasted as a single line with spaces — spaces in the base64 body are replaced +// with newlines while preserving spaces inside the BEGIN/END markers. +func normalizePEMKey(raw string) []byte { + if raw == "" { + return nil + } + + // Step 1: replace literal escaped newlines with real newlines. + s := strings.ReplaceAll(raw, "\\n", "\n") + + // Step 2: if the string already contains real newlines, assume valid PEM. + if strings.Contains(s, "\n") { + return []byte(strings.TrimSpace(s)) + } + + // Step 3: single-line key — split into header / body / footer and + // replace spaces only in the base64 body. + beginIdx := strings.Index(s, "-----") + if beginIdx < 0 { + return []byte(s) + } + // Locate end of "-----BEGIN ... -----" + rest := s[beginIdx+5:] + endOfBegin := strings.Index(rest, "-----") + if endOfBegin < 0 { + return []byte(s) + } + headerEnd := beginIdx + 5 + endOfBegin + 5 + + // Locate "-----END ... -----" + endMarkerStart := strings.LastIndex(s, "-----END") + if endMarkerStart < 0 || endMarkerStart <= headerEnd { + return []byte(s) + } + + header := s[beginIdx:headerEnd] + footer := strings.TrimSpace(s[endMarkerStart:]) + body := strings.TrimSpace(s[headerEnd:endMarkerStart]) + + // Spaces in the body separate base64-encoded lines — replace with newlines. + body = strings.ReplaceAll(body, " ", "\n") + + return []byte(header + "\n" + body + "\n" + footer + "\n") +} diff --git a/horizon/internal/externalcall/pricing_client_stub.go b/horizon/internal/externalcall/pricing_client_stub.go new file mode 100644 index 00000000..84204b1a --- /dev/null +++ b/horizon/internal/externalcall/pricing_client_stub.go @@ -0,0 +1,58 @@ +//go:build !meesho + +package externalcall + +import ( + "errors" + + "github.com/rs/zerolog/log" +) + +// PricingFeatureClient interface for pricing feature validation +type PricingFeatureClient interface { + GetDataTypes(entity string) (*PricingDataTypesResponse, error) + GetFeatureGroupDataTypeMap() (map[string]string, error) + InitPricingClient() +} + +const ( + RTPColonDelimiter = ":" + RTPUnderscoreDelimiter = "_" +) + +// PricingDataTypesResponse represents the response from pricing feature service +type PricingDataTypesResponse struct { + Entities []struct { + Entity string `json:"entity"` + FeatureGroups []struct { + Label string `json:"label"` + Features []string `json:"features"` + DataType string `json:"dataType"` + } `json:"featureGroups"` + } `json:"entities"` +} + +type pricingFeatureClientImpl struct{} + +var PricingClient PricingFeatureClient = &pricingFeatureClientImpl{} + +// GetDataTypes calls the pricing service to get data types for features +func (p *pricingFeatureClientImpl) GetDataTypes(entity string) (*PricingDataTypesResponse, error) { + return nil, errors.New("pricing client GetDataTypes is not supported in open-source builds. Provide organization-specific implementations to enable pricing client functionality") +} + +// GetFeatureGroupDataTypeMap returns an error for stub implementation +func (p *pricingFeatureClientImpl) GetFeatureGroupDataTypeMap() (map[string]string, error) { + log.Warn().Msg("pricing client GetFeatureGroupDataTypeMap is not supported in open-source builds. Provide organization-specific implementations to enable pricing client functionality") + return nil, errors.New("pricing client GetFeatureGroupDataTypeMap is not supported in open-source builds. Provide organization-specific implementations to enable pricing client functionality") +} + +// ValidatePricingFeatureExists checks if a pricing feature exists in the response +func ValidatePricingFeatureExists(featureName string, response *PricingDataTypesResponse) bool { + log.Warn().Msgf("pricing client ValidatePricingFeatureExists is not supported in open-source builds. Provide organization-specific implementations to enable pricing client functionality") + return false +} + +func (p *pricingFeatureClientImpl) InitPricingClient() { + log.Warn().Msgf("pricing client Init is not supported in open-source builds. Provide organization-specific implementations to enable pricing client functionality") +} diff --git a/horizon/internal/externalcall/prism_client.go b/horizon/internal/externalcall/prism_client.go new file mode 100644 index 00000000..161b0e88 --- /dev/null +++ b/horizon/internal/externalcall/prism_client.go @@ -0,0 +1,308 @@ +package externalcall + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "sync" + "time" + + "github.com/rs/zerolog/log" +) + +// PrismV2Client interface defines the operations for Prism V2 job management +type PrismV2Client interface { + GetStepParameters(jobID int, stepID int) (*PrismV2StepResponse, error) + UpdateStepParameters(jobID int, stepID int, modifications map[string]interface{}) error + UpdateStepParametersAndTrigger(jobID int, stepID int, modifications map[string]interface{}) error +} + +type prismV2ClientImpl struct { + BaseURL string + HTTPClient *http.Client + AppUserID string +} + +var ( + prismV2Once sync.Once + prismV2Instance PrismV2Client +) + +// GetPrismV2Client returns a singleton instance of the Prism V2 client +func InitPrismV2Client(baseURL string, appUserID string) PrismV2Client { + prismV2Once.Do(func() { + prismV2Instance = &prismV2ClientImpl{ + BaseURL: baseURL, + HTTPClient: &http.Client{ + Timeout: 30 * time.Second, + }, + AppUserID: appUserID, + } + }) + log.Info(). + Str("base_url", baseURL). + Str("app_user_id", appUserID). + Msg("Prism V2 client initialized") + return prismV2Instance +} + +func GetPrismV2Client() PrismV2Client { + return prismV2Instance +} + +// PrismV2StepResponse represents the response from getting step information +type PrismV2StepResponse struct { + Description string `json:"description"` + Parameters map[string]interface{} `json:"parameters"` +} + +// PrismV2UpdateRequest represents the request to update step parameters +type PrismV2UpdateRequest struct { + Parameters string `json:"parameters"` + Description string `json:"description"` +} + +// GetStepParameters retrieves the step parameters for a job +func (p *prismV2ClientImpl) GetStepParameters(jobID int, stepID int) (*PrismV2StepResponse, error) { + url := fmt.Sprintf("%s/api/v2/jobs/%d/step/%d", p.BaseURL, jobID, stepID) + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, fmt.Errorf("failed to create HTTP request: %w", err) + } + + req.Header.Set("app-user-id", p.AppUserID) + + log.Info(). + Str("url", url). + Int("job_id", jobID). + Int("step_id", stepID). + Msg("Getting Prism V2 step parameters") + + resp, err := p.HTTPClient.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to make HTTP request: %w", err) + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %w", err) + } + + if resp.StatusCode != http.StatusOK { + log.Error(). + Int("status_code", resp.StatusCode). + Str("response_body", string(body)). + Msg("Failed to get Prism V2 step parameters") + return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body)) + } + + var response PrismV2StepResponse + if err := json.Unmarshal(body, &response); err != nil { + return nil, fmt.Errorf("failed to unmarshal response: %w", err) + } + + log.Info(). + Int("job_id", jobID). + Int("step_id", stepID). + Msg("Successfully retrieved Prism V2 step parameters") + + return &response, nil +} + +// UnmarshalJSON implements custom unmarshaling to handle parameters as either string or map +func (p *PrismV2StepResponse) UnmarshalJSON(data []byte) error { + // First, unmarshal into a temporary struct that accepts parameters as interface{} + var temp struct { + Description string `json:"description"` + Parameters interface{} `json:"parameters"` + } + + if err := json.Unmarshal(data, &temp); err != nil { + return fmt.Errorf("failed to unmarshal response: %w", err) + } + + p.Description = temp.Description + + // Handle parameters - it might be a string or a map + switch v := temp.Parameters.(type) { + case string: + // Parameters is a JSON string, need to unmarshal it + if err := json.Unmarshal([]byte(v), &p.Parameters); err != nil { + return fmt.Errorf("failed to unmarshal parameters string to map: %w", err) + } + case map[string]interface{}: + // Parameters is already a map + p.Parameters = v + case nil: + // Parameters is null + p.Parameters = make(map[string]interface{}) + default: + // Try to marshal and unmarshal as a workaround for other types + bytes, err := json.Marshal(v) + if err != nil { + return fmt.Errorf("failed to marshal parameters: %w", err) + } + if err := json.Unmarshal(bytes, &p.Parameters); err != nil { + return fmt.Errorf("failed to unmarshal parameters to map: %w", err) + } + } + + return nil +} + +// UpdateStepParameters updates the step parameters for a job +func (p *prismV2ClientImpl) UpdateStepParameters(jobID int, stepID int, modifications map[string]interface{}) error { + // First, get the current step parameters + stepResponse, err := p.GetStepParameters(jobID, stepID) + if err != nil { + return fmt.Errorf("failed to get step parameters: %w", err) + } + + // Extract the actual params from the nested structure + // The parameters map contains a "params" key with a JSON string value + var actualParams map[string]interface{} + + if stepResponse.Parameters != nil { + if paramsValue, exists := stepResponse.Parameters["params"]; exists { + // paramsValue is a JSON string, need to unmarshal it + if paramsStr, ok := paramsValue.(string); ok { + if err := json.Unmarshal([]byte(paramsStr), &actualParams); err != nil { + log.Warn().Err(err).Msg("Failed to unmarshal existing params, creating new params map") + actualParams = make(map[string]interface{}) + } + } else { + // If params is not a string, try to use it directly as a map + if paramsMap, ok := paramsValue.(map[string]interface{}); ok { + actualParams = paramsMap + } else { + actualParams = make(map[string]interface{}) + } + } + } else { + // No "params" key, check if parameters itself is the params map + actualParams = stepResponse.Parameters + } + } + + if actualParams == nil { + actualParams = make(map[string]interface{}) + } + + // Merge modifications with existing params + for k, v := range modifications { + actualParams[k] = v + } + + log.Debug(). + Interface("actual_params", actualParams). + Interface("modifications", modifications). + Msg("Merged parameters with modifications") + + // Marshal the actual params back to a JSON string + paramsJSON, err := json.Marshal(actualParams) + if err != nil { + return fmt.Errorf("failed to marshal actual params: %w", err) + } + + // Create the nested structure: parameters contains "params" as a JSON string + parametersWrapper := map[string]interface{}{ + "params": string(paramsJSON), + } + + // Serialize the wrapper to JSON string for the update request + parametersJSON, err := json.Marshal(parametersWrapper) + if err != nil { + return fmt.Errorf("failed to marshal parameters wrapper: %w", err) + } + + // Prepare description (use existing or default) + description := stepResponse.Description + if description == "" { + description = "Updated step parameters" + } + + // Create update request + updateRequest := PrismV2UpdateRequest{ + Parameters: string(parametersJSON), + Description: description, + } + + url := fmt.Sprintf("%s/api/v2/jobs/%d/step/%d", p.BaseURL, jobID, stepID) + + jsonData, err := json.Marshal(updateRequest) + if err != nil { + return fmt.Errorf("failed to marshal update request: %w", err) + } + + req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData)) + if err != nil { + return fmt.Errorf("failed to create HTTP request: %w", err) + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("app-user-id", p.AppUserID) + + log.Info(). + Str("url", url). + Int("job_id", jobID). + Int("step_id", stepID). + Msg("Updating Prism V2 step parameters") + + resp, err := p.HTTPClient.Do(req) + if err != nil { + return fmt.Errorf("failed to make HTTP request: %w", err) + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("failed to read response body: %w", err) + } + + if resp.StatusCode != http.StatusOK { + log.Error(). + Int("status_code", resp.StatusCode). + Str("response_body", string(body)). + Msg("Failed to update Prism V2 step parameters") + return fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body)) + } + + log.Info(). + Int("job_id", jobID). + Int("step_id", stepID). + Msg("Successfully updated Prism V2 step parameters") + + return nil +} + +// UpdateStepParametersAndTrigger updates step parameters and then triggers the Airflow job +func (p *prismV2ClientImpl) UpdateStepParametersAndTrigger(jobID int, stepID int, modifications map[string]interface{}) error { + // First, update the step parameters + if err := p.UpdateStepParameters(jobID, stepID, modifications); err != nil { + return fmt.Errorf("failed to update step parameters: %w", err) + } + + // Then, trigger the Airflow DAG + airflowClient := GetAirflowClient() + dagRunID := fmt.Sprintf("prism_job_%d_step_%d_%d", jobID, stepID, time.Now().Unix()) + + airflowResponse, err := airflowClient.TriggerDAG(dagRunID) + if err != nil { + return fmt.Errorf("failed to trigger Airflow DAG: %w", err) + } + + if airflowResponse.Status == "error" { + return fmt.Errorf("airflow DAG trigger failed: %s", airflowResponse.Error) + } + + log.Info(). + Int("job_id", jobID). + Int("step_id", stepID). + Str("dag_run_id", dagRunID). + Msg("Successfully updated parameters and triggered Airflow DAG") + + return nil +} diff --git a/horizon/internal/externalcall/prometheus_client.go b/horizon/internal/externalcall/prometheus_client.go new file mode 100644 index 00000000..890ed3b3 --- /dev/null +++ b/horizon/internal/externalcall/prometheus_client.go @@ -0,0 +1,341 @@ +package externalcall + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + "sync" + "time" +) + +type PrometheusClient interface { + GetModelNames(serviceName string) ([]string, error) + GetInferflowConfigNames(serviceName string) ([]string, error) + GetNumerixConfigNames() ([]string, error) + GetPredatorModelTraffic(serviceName string, daysAgo int) (map[string]PredatorModelTraffic, error) +} + +type prometheusClientImpl struct { + BaseURL string + HTTPClient *http.Client + APIKey string +} + +var ( + prometheusOnce sync.Once + prometheusInstance PrometheusClient + vmselectStartDaysAgo int + vmselectBaseUrl string + vmselectApiKey string + initPrometheusOnce sync.Once +) + +func InitPrometheusClient(VmselectStartDaysAgo int, VmselectBaseUrl string, VmselectApiKey string) { + initPrometheusOnce.Do(func() { + vmselectStartDaysAgo = VmselectStartDaysAgo + vmselectBaseUrl = VmselectBaseUrl + vmselectApiKey = VmselectApiKey + }) +} + +func GetPrometheusClient() PrometheusClient { + prometheusOnce.Do(func() { + prometheusInstance = &prometheusClientImpl{ + BaseURL: vmselectBaseUrl, + HTTPClient: &http.Client{ + Timeout: 10 * time.Second, + }, + APIKey: vmselectApiKey, + } + }) + return prometheusInstance +} + +type prometheusResponse struct { + Status string `json:"status"` + IsPartial bool `json:"isPartial"` + Data struct { + ResultType string `json:"resultType"` + Result []struct { + Metric struct { + ModelName string `json:"model_name"` + } `json:"metric"` + } `json:"result"` + } `json:"data"` +} + +type prometheusInferflowConfigResponse struct { + Status string `json:"status"` + IsPartial bool `json:"isPartial"` + Data struct { + ResultType string `json:"resultType"` + Result []struct { + Metric struct { + ModelID string `json:"model_id"` + } `json:"metric"` + } `json:"result"` + } `json:"data"` +} + +type prometheusNumerixConfigResponse struct { + Status string `json:"status"` + IsPartial bool `json:"isPartial"` + Data struct { + ResultType string `json:"resultType"` + Result []struct { + Metric struct { + ComputeID string `json:"compute_id"` + } `json:"metric"` + } `json:"result"` + } `json:"data"` +} + +type PredatorModelResponse struct { + Status string `json:"status"` + IsPartial bool `json:"isPartial"` + Data struct { + ResultType string `json:"resultType"` + Result []struct { + Metric struct { + Model string `json:"model"` + } `json:"metric"` + Values [][]interface{} `json:"values"` // [[timestamp, "value"], ...] + } `json:"result"` + } `json:"data"` +} + +// PredatorModelTraffic holds model name and its traffic data +type PredatorModelTraffic struct { + ModelName string + TotalTraffic float64 // Sum of all values + DataPoints int // Number of data points +} + +func (p *prometheusClientImpl) GetModelNames(serviceName string) ([]string, error) { + end := time.Now().Unix() + daysAgo := vmselectStartDaysAgo + start := end - int64(daysAgo*24*60*60) + step := "1h40m" + + query := fmt.Sprintf( + "sum by (model_name)(rate(modelinferenceorchestrator_retrievemodelinferencescore_request_total_value{service=\"%s\"}[1m]))", + serviceName, + ) + + url := fmt.Sprintf("%s/select/100/prometheus/api/v1/query_range?query=%s&start=%d&end=%d&step=%s", + p.BaseURL, + escapePrometheusQuery(query), + start, + end, + step, + ) + + req, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("api-key", p.APIKey) + + resp, err := p.HTTPClient.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to call Prometheus: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + bodyBytes, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("prometheus call failed, status: %d, body: %s", resp.StatusCode, string(bodyBytes)) + } + + var pr prometheusResponse + if err := json.NewDecoder(resp.Body).Decode(&pr); err != nil { + return nil, fmt.Errorf("failed to decode Prometheus response: %w", err) + } + + var modelNames []string + for _, item := range pr.Data.Result { + modelNames = append(modelNames, item.Metric.ModelName) + } + + return modelNames, nil +} + +// GetPredatorModelTraffic returns models with their traffic data for the past N days +func (p *prometheusClientImpl) GetPredatorModelTraffic(serviceName string, daysAgo int) (map[string]PredatorModelTraffic, error) { + end := time.Now().Unix() + start := end - int64(daysAgo*24*60*60) + step := "1m" + + query := fmt.Sprintf( + "sum by (model)(increase(nv_inference_count{service=\"%s\"}[1m]))", + serviceName, + ) + + url := fmt.Sprintf("%s/prometheus/api/v1/query_range?query=%s&start=%d&end=%d&step=%s", + p.BaseURL, + escapePrometheusQuery(query), + start, + end, + step, + ) + + req, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("api-key", p.APIKey) + + resp, err := p.HTTPClient.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to call Prometheus: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + bodyBytes, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("prometheus call failed, status: %d, body: %s", resp.StatusCode, string(bodyBytes)) + } + + var pr PredatorModelResponse + if err := json.NewDecoder(resp.Body).Decode(&pr); err != nil { + return nil, fmt.Errorf("failed to decode Prometheus response: %w", err) + } + + // Parse results into map + result := make(map[string]PredatorModelTraffic) + for _, item := range pr.Data.Result { + modelName := item.Metric.Model + if modelName == "" { + continue + } + + var totalTraffic float64 + dataPoints := 0 + + for _, valueArr := range item.Values { + if len(valueArr) >= 2 { + if valueStr, ok := valueArr[1].(string); ok { + if val, err := strconv.ParseFloat(valueStr, 64); err == nil { + totalTraffic += val + dataPoints++ + } + } + } + } + + result[modelName] = PredatorModelTraffic{ + ModelName: modelName, + TotalTraffic: totalTraffic, + DataPoints: dataPoints, + } + } + + return result, nil +} + +func (p *prometheusClientImpl) GetInferflowConfigNames(serviceName string) ([]string, error) { + end := time.Now().Unix() + daysAgo := vmselectStartDaysAgo + start := end - int64(daysAgo*24*60*60) + step := "1h40m" + + query := fmt.Sprintf( + "sum by (model_id)(rate(inferflow_retrievemodelscore_request_total_value{service=\"%s\"}[1m]))", + serviceName, + ) + + url := fmt.Sprintf("%s/select/100/prometheus/api/v1/query_range?query=%s&start=%d&end=%d&step=%s", + p.BaseURL, + escapePrometheusQuery(query), + start, + end, + step, + ) + + req, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("api-key", p.APIKey) + + resp, err := p.HTTPClient.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to call Prometheus: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + bodyBytes, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("prometheus call failed, status: %d, body: %s", resp.StatusCode, string(bodyBytes)) + } + + var pr prometheusInferflowConfigResponse + if err := json.NewDecoder(resp.Body).Decode(&pr); err != nil { + return nil, fmt.Errorf("failed to decode Prometheus response: %w", err) + } + + var modelNames []string + for _, item := range pr.Data.Result { + modelNames = append(modelNames, item.Metric.ModelID) + } + + return modelNames, nil +} + +func (p *prometheusClientImpl) GetNumerixConfigNames() ([]string, error) { + end := time.Now().Unix() + daysAgo := vmselectStartDaysAgo + start := end - int64(daysAgo*24*60*60) + step := "1h40m" + + query := "sum by (compute_id)(rate(numerix_numerix_computation_request_total_value[1m]))" + + url := fmt.Sprintf("%s/select/100/prometheus/api/v1/query_range?query=%s&start=%d&end=%d&step=%s", + p.BaseURL, + escapePrometheusQuery(query), + start, + end, + step, + ) + + req, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("api-key", p.APIKey) + + resp, err := p.HTTPClient.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to call Prometheus: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + bodyBytes, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("prometheus call failed, status: %d, body: %s", resp.StatusCode, string(bodyBytes)) + } + + var pr prometheusNumerixConfigResponse + if err := json.NewDecoder(resp.Body).Decode(&pr); err != nil { + return nil, fmt.Errorf("failed to decode Prometheus response: %w", err) + } + + var computeIDs []string + for _, item := range pr.Data.Result { + computeIDs = append(computeIDs, item.Metric.ComputeID) + } + + return computeIDs, nil +} + +func escapePrometheusQuery(query string) string { + // Spaces, quotes, braces and brackets are URL-encoded + return url.QueryEscape(query) +} diff --git a/horizon/internal/externalcall/skye_trigger_client.go b/horizon/internal/externalcall/skye_trigger_client.go new file mode 100644 index 00000000..7d4acd50 --- /dev/null +++ b/horizon/internal/externalcall/skye_trigger_client.go @@ -0,0 +1,61 @@ +package externalcall + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "time" +) + +// SkyeTriggerDAGRunID is the synthetic dag_run_id stored when using skye-trigger instead of Airflow. +// checkAirflowDAGRunStatus returns success when it sees this ID without calling Airflow. +const SkyeTriggerDAGRunID = "skye-trigger" + +// TriggerSkyeTrigger calls the skye-trigger service POST /trigger with entity, model, variants. +// Used when UseSkyeTriggerInsteadOfAirflow is true (OSS quick-start). +func TriggerSkyeTrigger(baseURL, entity, model string, variants []string, environment string) error { + if baseURL == "" { + return fmt.Errorf("skye_trigger_url is required") + } + url := baseURL + "/trigger" + if len(baseURL) > 0 && baseURL[len(baseURL)-1] == '/' { + url = baseURL + "trigger" + } + body := map[string]interface{}{ + "entity": entity, + "model_name": model, + "variants": variants, + "environment": environment, + } + if environment == "" { + body["environment"] = "local" + } + payload, err := json.Marshal(body) + if err != nil { + return fmt.Errorf("marshal skye-trigger request: %w", err) + } + req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(payload)) + if err != nil { + return fmt.Errorf("create skye-trigger request: %w", err) + } + req.Header.Set("Content-Type", "application/json") + client := &http.Client{Timeout: 120 * time.Second} + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("skye-trigger request: %w", err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("skye-trigger returned status %d", resp.StatusCode) + } + var result struct { + OK bool `json:"ok"` + Error string `json:"error"` + } + _ = json.NewDecoder(resp.Body).Decode(&result) + if !result.OK && result.Error != "" { + return fmt.Errorf("skye-trigger: %s", result.Error) + } + return nil +} diff --git a/horizon/internal/externalcall/slack_client.go b/horizon/internal/externalcall/slack_client.go new file mode 100644 index 00000000..8e767963 --- /dev/null +++ b/horizon/internal/externalcall/slack_client.go @@ -0,0 +1,197 @@ +package externalcall + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "strings" + "sync" + "time" +) + +var ( + initSlackOnce sync.Once + slackWebhookUrl string + slackChannel string + slackCcTags string + defaultModelPath string +) + +func InitSlackClient(SlackWebhookUrl string, SlackChannel string, SlackCcTags string, DefaultModelPath string) { + initSlackOnce.Do(func() { + slackWebhookUrl = SlackWebhookUrl + slackChannel = SlackChannel + slackCcTags = SlackCcTags + defaultModelPath = DefaultModelPath + }) +} + +type SlackClient interface { + SendCleanupNotification(deployableName string, deletedModels []string) error + SendNumerixConfigCleanupNotification(deployableName string, deletedConfigs []string) error + SendInferflowConfigCleanupNotification(deployableName string, deletedConfigs []string) error +} + +type slackClientImpl struct { + WebhookURL string + Channel string + BackupPath string + CCTags string + InactiveDays int + HTTPClient *http.Client +} + +var ( + slackOnce sync.Once + slackInstance SlackClient +) + +func GetSlackClient() SlackClient { + slackOnce.Do(func() { + slackInstance = &slackClientImpl{ + WebhookURL: slackWebhookUrl, + Channel: slackChannel, + BackupPath: defaultModelPath, + CCTags: slackCcTags, + InactiveDays: vmselectStartDaysAgo, + HTTPClient: &http.Client{ + Timeout: 5 * time.Second, + }, + } + }) + return slackInstance +} + +type slackPayload struct { + Username string `json:"username"` + IconEmoji string `json:"icon_emoji"` + Channel string `json:"channel"` + Text string `json:"text"` +} + +func (s *slackClientImpl) SendCleanupNotification(deployableName string, deletedModels []string) error { + modelsText := strings.Join(deletedModels, "\n") + ccTags := strings.Join(strings.Fields(s.CCTags), "\n") // handles space-separated tags + message := fmt.Sprintf(`Prod Expt Models cleanup for %s + +Following models are deleted from prod experiment GCS repo and moved to backup folder (%s) as they aren't receiving any traffic in last %d days: +%s + +cc: +%s`, deployableName, s.BackupPath, s.InactiveDays, modelsText, ccTags) + + payload := slackPayload{ + Username: "Models Monitoring", + IconEmoji: ":computer:", + Channel: s.Channel, + Text: message, + } + + bodyBytes, err := json.Marshal(payload) + if err != nil { + return fmt.Errorf("failed to marshal slack payload: %w", err) + } + + req, err := http.NewRequest(http.MethodPost, s.WebhookURL, bytes.NewBuffer(bodyBytes)) + if err != nil { + return fmt.Errorf("failed to create slack request: %w", err) + } + req.Header.Set("Content-Type", "application/json") + + resp, err := s.HTTPClient.Do(req) + if err != nil { + return fmt.Errorf("failed to send slack request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("slack webhook returned non-OK status: %d", resp.StatusCode) + } + + return nil +} + +func (s *slackClientImpl) SendNumerixConfigCleanupNotification(deployableName string, deletedConfigs []string) error { + configsText := strings.Join(deletedConfigs, "\n") + ccTags := strings.Join(strings.Fields(s.CCTags), "\n") // handles space-separated tags + message := fmt.Sprintf(`Prod Numerix Config cleanup for %s + +Following Numerix configs are deleted from prod Numerix config registry as they aren't receiving any traffic in last %d days: +%s + +cc: +%s`, deployableName, s.InactiveDays, configsText, ccTags) + + payload := slackPayload{ + Username: "Numerix Config Monitoring", + IconEmoji: ":computer:", + Channel: s.Channel, + Text: message, + } + + bodyBytes, err := json.Marshal(payload) + if err != nil { + return fmt.Errorf("failed to marshal slack payload: %w", err) + } + + req, err := http.NewRequest(http.MethodPost, s.WebhookURL, bytes.NewBuffer(bodyBytes)) + if err != nil { + return fmt.Errorf("failed to create slack request: %w", err) + } + req.Header.Set("Content-Type", "application/json") + + resp, err := s.HTTPClient.Do(req) + if err != nil { + return fmt.Errorf("failed to send slack request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("slack webhook returned non-OK status: %d", resp.StatusCode) + } + + return nil +} + +func (s *slackClientImpl) SendInferflowConfigCleanupNotification(deployableName string, deletedConfigs []string) error { + configsText := strings.Join(deletedConfigs, "\n") + ccTags := strings.Join(strings.Fields(s.CCTags), "\n") // handles space-separated tags + message := fmt.Sprintf(`Prod Inferflow Config cleanup for %s + +Following Inferflow configs are deleted from prod Inferflow config registry as they aren't receiving any traffic in last %d days: +%s + +cc: +%s`, deployableName, s.InactiveDays, configsText, ccTags) + + payload := slackPayload{ + Username: "Inferflow Config Monitoring", + IconEmoji: ":computer:", + Channel: s.Channel, + Text: message, + } + + bodyBytes, err := json.Marshal(payload) + if err != nil { + return fmt.Errorf("failed to marshal slack payload: %w", err) + } + + req, err := http.NewRequest(http.MethodPost, s.WebhookURL, bytes.NewBuffer(bodyBytes)) + if err != nil { + return fmt.Errorf("failed to create slack request: %w", err) + } + req.Header.Set("Content-Type", "application/json") + + resp, err := s.HTTPClient.Do(req) + if err != nil { + return fmt.Errorf("failed to send slack request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("slack webhook returned non-OK status: %d", resp.StatusCode) + } + + return nil +} diff --git a/horizon/internal/externalcall/working_env.go b/horizon/internal/externalcall/working_env.go new file mode 100644 index 00000000..45a480d4 --- /dev/null +++ b/horizon/internal/externalcall/working_env.go @@ -0,0 +1,27 @@ +package externalcall + +import "sync" + +var ( + // Working environment - generalized variable (not RingMaster specific) + workingEnvironment string + initWorkingEnvOnce sync.Once +) + +// InitWorkingEnvironment initializes the working environment from config +func InitWorkingEnvironment(workingEnv string) { + initWorkingEnvOnce.Do(func() { + workingEnvironment = workingEnv + }) +} + +// GetWorkingEnvironment returns the working environment (generalized, not RingMaster specific) +func GetWorkingEnvironment() string { + return workingEnvironment +} + +// GetRingmasterEnvironment returns the working environment (deprecated - use GetWorkingEnvironment instead) +// Kept for backward compatibility +func GetRingmasterEnvironment() string { + return GetWorkingEnvironment() +} diff --git a/horizon/internal/inferflow/README.md b/horizon/internal/inferflow/README.md new file mode 100644 index 00000000..e5cdeaef --- /dev/null +++ b/horizon/internal/inferflow/README.md @@ -0,0 +1,38 @@ +# Inferflow + +Inferflow handles inferflow config lifecycle operations: onboarding, review/approval, promote, edit, clone, scale-up, delete, cancel, list (get-all / get-all-requests), validation, functional testing, and feature schema generation. + +## Package layout + +``` +internal/inferflow/ +├── controller/ (wires routes to handler) +├── handler/ (Config implementation + helpers) +├── etcd/ (ETCD config read/write for inferflow) +├── route/ +├── handler/proto/ and generated Go +├── init.go +└── README.md +``` + +--- + +## Handler package structure + +| File | Purpose | +|------|--------| +| **config.go** | Defines the **Config** interface (public API). Implemented by `*InferFlow`. | +| **init.go** | Singleton init: `InitV1ConfigHandler()` returns Config. | +| **models.go** | Request/response types, payloads, and shared structs. | +| **inferflow.go** | **InferFlow** struct, `InitV1ConfigHandler()`, and **public entrypoints** that implement Config (e.g. `Onboard`, `Review`, `Promote`, `Edit`, `Clone`, `Delete`, `ScaleUp`, `Cancel`, `GetAll`, `GetAllRequests`, `ValidateRequest`, `GenerateFunctionalTestRequest`, `ExecuteFuncitonalTestRequest`, `GetLatestRequest`, `GetLoggingTTL`, `GetFeatureSchema`). | +| **inferflow_constants.go** | All `const` values: request types, statuses, method names, defaults, delimiters, etc. | +| **inferflow_review.go** | Review/approval and DB/ETCD write flow: `handleRejectedRequest`, `handleApprovedRequest`, rollback helpers (`rollbackApprovedRequest`, `rollbackPromoteRequest`, `rollbackEditRequest`, `rollbackCreatedConfigs`, `rollbackDeletedConfigs`), and create/update helpers (`createOrUpdateDiscoveryConfig`, `createOrUpdateInferFlowConfig`, `createOrUpdateEtcdConfig`). | +| **inferflow_fetch.go** | Batch fetch helpers used by `GetAll`: `batchFetchDiscoveryConfigs`, `batchFetchRingMasterConfigs`. | +| **inferflow_validation.go** | Package-level `ValidateInferFlowConfig`; method `ValidateOnboardRequest` (used by Onboard/Edit/Clone). | +| **inferflow_functional_testing.go** | Functional test entrypoints: `GenerateFunctionalTestRequest`, `ExecuteFuncitonalTestRequest` (gRPC/proto and test-result update logic). | +| **inferflow_helpers.go** | Small helpers: `GetDerivedConfigID`, `GetLoggingTTL`. | +| **adaptor.go** | DB/ETCD payload adaptors: `AdaptToEtcdInferFlowConfig`, `AdaptOnboardRequestToDBPayload`, `AdaptFromDbToInferFlowConfig`, `AdaptFromDbToConfigMapping`, etc. | +| **config_builder.go** | Build inferflow config from request: `GetInferflowConfig`, component/ranker/reranker building, and related constants (e.g. `COLON_DELIMITER`, `PIPE_DELIMITER`, `MODEL_FEATURE`). | +| **schema_adapter.go** | Feature schema from inferflow config: `BuildFeatureSchemaFromInferflow`, `ProcessResponseConfigFromInferflow`. | +| **proto/** | Inferflow gRPC proto and generated Go. | +| **inferflow_test.go** | Unit tests for the handler (same package). | diff --git a/horizon/internal/inferflow/controller/controller.go b/horizon/internal/inferflow/controller/controller.go new file mode 100644 index 00000000..3696e95c --- /dev/null +++ b/horizon/internal/inferflow/controller/controller.go @@ -0,0 +1,380 @@ +package controller + +import ( + "sync" + + "strings" + + handler "github.com/Meesho/BharatMLStack/horizon/internal/inferflow/handler" + "github.com/Meesho/BharatMLStack/horizon/pkg/api" + "github.com/gin-gonic/gin" +) + +type Config interface { + Onboard(ctx *gin.Context) + Promote(ctx *gin.Context) + Edit(ctx *gin.Context) + Clone(ctx *gin.Context) + Delete(ctx *gin.Context) + ScaleUp(ctx *gin.Context) + Cancel(ctx *gin.Context) + Review(ctx *gin.Context) + GetAll(ctx *gin.Context) + GetAllRequests(ctx *gin.Context) + ValidateRequest(ctx *gin.Context) + GenerateFunctionalTestRequest(ctx *gin.Context) + ExecuteFuncitonalTestRequest(ctx *gin.Context) + GetLatestRequest(ctx *gin.Context) + GetLoggingTTL(ctx *gin.Context) + GetFeatureSchema(ctx *gin.Context) +} + +var ( + configController Config + once sync.Once +) + +type V1 struct { + Config handler.Config +} + +func NewConfigController() Config { + if configController == nil { + once.Do(func() { + configController = &V1{ + Config: handler.NewConfigHandler(1), + } + }) + } + return configController +} + +func (c *V1) Onboard(ctx *gin.Context) { + var request handler.InferflowOnboardRequest + var emptyResponse string + if err := ctx.ShouldBindJSON(&request); err != nil { + ctx.JSON(api.NewBadRequestError(err.Error()).StatusCode, handler.Response{ + Error: err.Error(), + Data: handler.Message{Message: emptyResponse}, + }) + return + } + + token := ctx.GetHeader("Authorization") + token = strings.TrimPrefix(token, "Bearer ") + response, err := c.Config.Onboard(request, token) + if err != nil { + ctx.JSON(api.NewBadRequestError(err.Error()).StatusCode, handler.Response{ + Error: err.Error(), + Data: handler.Message{Message: emptyResponse}, + }) + return + } + ctx.JSON(200, response) +} + +func (c *V1) Promote(ctx *gin.Context) { + var request handler.PromoteConfigRequest + var emptyResponse string + if err := ctx.ShouldBindJSON(&request); err != nil { + ctx.JSON(api.NewBadRequestError(err.Error()).StatusCode, handler.Response{ + Error: err.Error(), + Data: handler.Message{Message: emptyResponse}, + }) + return + } + + response, err := c.Config.Promote(request) + if err != nil { + ctx.JSON(api.NewBadRequestError(err.Error()).StatusCode, handler.Response{ + Error: err.Error(), + Data: handler.Message{Message: emptyResponse}, + }) + return + } + ctx.JSON(200, response) +} + +func (c *V1) Edit(ctx *gin.Context) { + var request handler.EditConfigOrCloneConfigRequest + var emptyResponse string + if err := ctx.ShouldBindJSON(&request); err != nil { + ctx.JSON(api.NewBadRequestError(err.Error()).StatusCode, handler.Response{ + Error: err.Error(), + Data: handler.Message{Message: emptyResponse}, + }) + return + } + + token := ctx.GetHeader("Authorization") + token = strings.TrimPrefix(token, "Bearer ") + response, err := c.Config.Edit(request, token) + if err != nil { + ctx.JSON(api.NewBadRequestError(err.Error()).StatusCode, handler.Response{ + Error: err.Error(), + Data: handler.Message{Message: emptyResponse}, + }) + return + } + ctx.JSON(200, response) +} + +func (c *V1) Clone(ctx *gin.Context) { + var request handler.EditConfigOrCloneConfigRequest + var emptyResponse string + if err := ctx.ShouldBindJSON(&request); err != nil { + ctx.JSON(api.NewBadRequestError(err.Error()).StatusCode, handler.Response{ + Error: err.Error(), + Data: handler.Message{Message: emptyResponse}, + }) + return + } + + token := ctx.GetHeader("Authorization") + token = strings.TrimPrefix(token, "Bearer ") + response, err := c.Config.Clone(request, token) + if err != nil { + ctx.JSON(api.NewBadRequestError(err.Error()).StatusCode, handler.Response{ + Error: err.Error(), + Data: handler.Message{Message: emptyResponse}, + }) + return + } + ctx.JSON(200, response) +} + +func (c *V1) Delete(ctx *gin.Context) { + var request handler.DeleteConfigRequest + var emptyResponse string + id := ctx.Query("id") + request.ConfigID = id + request.CreatedBy = ctx.GetString("email") + if err := ctx.ShouldBindJSON(&request); err != nil { + ctx.JSON(api.NewBadRequestError(err.Error()).StatusCode, handler.Response{ + Error: err.Error(), + Data: handler.Message{Message: emptyResponse}, + }) + return + } + + response, err := c.Config.Delete(request) + if err != nil { + ctx.JSON(api.NewBadRequestError(err.Error()).StatusCode, handler.Response{ + Error: err.Error(), + Data: handler.Message{Message: emptyResponse}, + }) + return + } + ctx.JSON(200, response) +} + +func (c *V1) ScaleUp(ctx *gin.Context) { + var request handler.ScaleUpConfigRequest + var emptyResponse string + if err := ctx.ShouldBindJSON(&request); err != nil { + ctx.JSON(api.NewBadRequestError(err.Error()).StatusCode, handler.Response{ + Error: err.Error(), + Data: handler.Message{Message: emptyResponse}, + }) + return + } + + response, err := c.Config.ScaleUp(request) + if err != nil { + ctx.JSON(api.NewBadRequestError(err.Error()).StatusCode, handler.Response{ + Error: err.Error(), + Data: handler.Message{Message: emptyResponse}, + }) + return + } + ctx.JSON(200, response) +} + +func (c *V1) Cancel(ctx *gin.Context) { + var request handler.CancelConfigRequest + var emptyResponse string + if err := ctx.ShouldBindJSON(&request); err != nil { + ctx.JSON(api.NewBadRequestError(err.Error()).StatusCode, handler.Response{ + Error: err.Error(), + Data: handler.Message{Message: emptyResponse}, + }) + return + } + + response, err := c.Config.Cancel(request) + if err != nil { + ctx.JSON(api.NewInternalServerError(err.Error()).StatusCode, handler.Response{ + Error: err.Error(), + Data: handler.Message{Message: emptyResponse}, + }) + return + } + + ctx.JSON(200, response) +} + +func (c *V1) GetAll(ctx *gin.Context) { + var emptyResponse []handler.ConfigTable + response, err := c.Config.GetAll() + if err != nil { + ctx.JSON(api.NewInternalServerError(err.Error()).StatusCode, handler.GetAllResponse{ + Error: err.Error(), + Data: emptyResponse, + }) + return + } + + ctx.JSON(200, response) +} + +func (c *V1) Review(ctx *gin.Context) { + var request handler.ReviewRequest + var emptyResponse string + if err := ctx.ShouldBindJSON(&request); err != nil { + ctx.JSON(api.NewBadRequestError(err.Error()).StatusCode, handler.Response{ + Error: err.Error(), + Data: handler.Message{Message: emptyResponse}, + }) + return + } + + response, err := c.Config.Review(request) + if err != nil { + ctx.JSON(api.NewBadRequestError(err.Error()).StatusCode, handler.Response{ + Error: err.Error(), + Data: handler.Message{Message: emptyResponse}, + }) + return + } + ctx.JSON(200, response) +} + +func (c *V1) GetAllRequests(ctx *gin.Context) { + role := ctx.GetString("role") + email := ctx.GetString("email") + + var emptyResponse []handler.RequestConfig + + request := handler.GetAllRequestConfigsRequest{ + Email: email, + Role: role, + } + + response, err := c.Config.GetAllRequests(request) + if err != nil { + ctx.JSON(api.NewBadRequestError(err.Error()).StatusCode, handler.GetAllRequestConfigsResponse{ + Error: err.Error(), + Data: emptyResponse, + }) + return + } + ctx.JSON(200, response) +} + +func (c *V1) ValidateRequest(ctx *gin.Context) { + + var emptyResponse string + requestID := ctx.Param("request_id") + if requestID == "" { + ctx.JSON(api.NewBadRequestError("request_id is required").StatusCode, handler.Response{ + Error: "request_id is required", + Data: handler.Message{Message: emptyResponse}, + }) + return + } + + var request handler.ValidateRequest = handler.ValidateRequest{ + ConfigID: requestID, + } + + token := ctx.GetHeader("Authorization") + token = strings.TrimPrefix(token, "Bearer ") + response, err := c.Config.ValidateRequest(request, token) + if err != nil { + ctx.JSON(api.NewBadRequestError(err.Error()).StatusCode, handler.Response{ + Error: err.Error(), + Data: handler.Message{Message: emptyResponse}, + }) + return + } + ctx.JSON(200, response) +} + +func (c *V1) GenerateFunctionalTestRequest(ctx *gin.Context) { + var request handler.GenerateRequestFunctionalTestingRequest + + if err := ctx.ShouldBindJSON(&request); err != nil { + ctx.JSON(api.NewBadRequestError(err.Error()).StatusCode, handler.GenerateRequestFunctionalTestingResponse{ + Error: err.Error(), + }) + return + } + + response, err := c.Config.GenerateFunctionalTestRequest(request) + if err != nil { + ctx.JSON(api.NewBadRequestError(err.Error()).StatusCode, response) + } + ctx.JSON(200, response) +} + +func (c *V1) ExecuteFuncitonalTestRequest(ctx *gin.Context) { + var request handler.ExecuteRequestFunctionalTestingRequest + + if err := ctx.ShouldBindJSON(&request); err != nil { + ctx.JSON(api.NewBadRequestError(err.Error()).StatusCode, handler.ExecuteRequestFunctionalTestingResponse{ + Error: err.Error(), + }) + return + } + + response, err := c.Config.ExecuteFuncitonalTestRequest(request) + if err != nil { + ctx.JSON(api.NewBadRequestError(err.Error()).StatusCode, response) + return + } + ctx.JSON(200, response) +} + +func (c *V1) GetLatestRequest(ctx *gin.Context) { + var emptyResponse string + requestID := ctx.Param("config_id") + if requestID == "" { + ctx.JSON(api.NewBadRequestError("config_id is required").StatusCode, handler.Response{ + Error: "config_id is required", + Data: handler.Message{Message: emptyResponse}, + }) + return + } + response, err := c.Config.GetLatestRequest(requestID) + if err != nil { + ctx.JSON(api.NewBadRequestError(err.Error()).StatusCode, handler.Response{ + Error: err.Error(), + Data: handler.Message{Message: emptyResponse}, + }) + return + } + ctx.JSON(200, response) +} + +func (c *V1) GetLoggingTTL(ctx *gin.Context) { + var emptyResponse []int + response, err := c.Config.GetLoggingTTL() + if err != nil { + ctx.JSON(api.NewBadRequestError(err.Error()).StatusCode, handler.GetLoggingTTLResponse{ + Data: emptyResponse, + }) + } + ctx.JSON(200, response) +} + +func (c *V1) GetFeatureSchema(ctx *gin.Context) { + response, err := c.Config.GetFeatureSchema(handler.FeatureSchemaRequest{ + ModelConfigId: ctx.Query("model_config_id"), + Version: strings.TrimSpace(ctx.Query("version")), + }) + if err != nil { + ctx.JSON(api.NewBadRequestError(err.Error()).StatusCode, "Error getting feature schema") + return + } + ctx.JSON(200, response) +} diff --git a/horizon/internal/inferflow/etcd/config.go b/horizon/internal/inferflow/etcd/config.go new file mode 100644 index 00000000..840533a9 --- /dev/null +++ b/horizon/internal/inferflow/etcd/config.go @@ -0,0 +1,11 @@ +package etcd + +import mapset "github.com/deckarep/golang-set/v2" + +type Manager interface { + GetComponentData(componentName string) *ComponentData + CreateConfig(serviceName string, ConfigId string, InferflowConfig InferflowConfig) error + UpdateConfig(serviceName string, ConfigId string, InferflowConfig InferflowConfig) error + DeleteConfig(serviceName string, ConfigId string) error + GetConfiguredEndpoints(serviceDeployableName string) mapset.Set[string] +} diff --git a/horizon/internal/inferflow/etcd/etcd.go b/horizon/internal/inferflow/etcd/etcd.go new file mode 100644 index 00000000..e3768fc4 --- /dev/null +++ b/horizon/internal/inferflow/etcd/etcd.go @@ -0,0 +1,114 @@ +package etcd + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/Meesho/BharatMLStack/horizon/internal/inferflow" + mapset "github.com/deckarep/golang-set/v2" + + "github.com/Meesho/BharatMLStack/horizon/pkg/etcd" + "github.com/rs/zerolog/log" +) + +type Etcd struct { + inferflowInstance etcd.Etcd + horizonInstance etcd.Etcd + appName string + env string +} + +const ( + commaDelimiter = "," +) + +func NewEtcdInstance() *Etcd { + return &Etcd{ + inferflowInstance: etcd.Instance()[inferflow.InferflowAppName], + horizonInstance: etcd.Instance()[inferflow.HorizonAppName], + appName: inferflow.InferflowAppName, + env: inferflow.AppEnv, + } +} + +func (e *Etcd) GetInferflowEtcdInstance() *ModelConfigRegistery { + instance, ok := e.inferflowInstance.GetConfigInstance().(*ModelConfigRegistery) + if !ok { + log.Panic().Msg("invalid etcd instance") + } + return instance +} + +func (e *Etcd) GetHorizonEtcdInstance() *HorizonRegistry { + instance, ok := e.horizonInstance.GetConfigInstance().(*HorizonRegistry) + if !ok { + log.Panic().Msg("invalid etcd instance") + } + return instance +} + +func (e *Etcd) GetComponentData(componentName string) *ComponentData { + registry := e.GetHorizonEtcdInstance() + if registry == nil { + log.Error().Msg("GetComponentData called on nil registry") + return nil + } + + component, ok := registry.Inferflow.InferflowComponents[componentName] + if !ok { + log.Error().Msgf("component data for '%s' not found in registry", componentName) + return nil + } + + return &component +} + +func (e *Etcd) CreateConfig(serviceName string, ConfigId string, InferflowConfig InferflowConfig) error { + // Marshal the struct directly to preserve field order as defined in the struct + configJson, err := json.Marshal(InferflowConfig) + if err != nil { + return err + } + + return e.inferflowInstance.CreateNode(fmt.Sprintf("/config/%s/services/%s/model-config/config-map/%s", e.appName, serviceName, ConfigId), string(configJson)) +} + +func (e *Etcd) UpdateConfig(serviceName string, ConfigId string, InferflowConfig InferflowConfig) error { + // Marshal the struct directly to preserve field order as defined in the struct + configJson, err := json.Marshal(InferflowConfig) + if err != nil { + return err + } + return e.inferflowInstance.SetValue(fmt.Sprintf("/config/%s/services/%s/model-config/config-map/%s", e.appName, serviceName, ConfigId), string(configJson)) +} + +func (e *Etcd) DeleteConfig(serviceName string, ConfigId string) error { + return e.inferflowInstance.DeleteNode(fmt.Sprintf("/config/%s/services/%s/model-config/config-map/%s", e.appName, serviceName, ConfigId)) +} + +func (e *Etcd) GetConfiguredEndpoints(serviceDeployableName string) mapset.Set[string] { + validEndpoints := mapset.NewSet[string]() + instance := e.GetInferflowEtcdInstance() + if instance == nil { + return validEndpoints + } + + inferflowConfig, exists := instance.Services[serviceDeployableName] + if !exists { + log.Warn().Msgf("service '%s' not found in etcd registry", serviceDeployableName) + return validEndpoints + } + + predatorHosts := inferflowConfig.ModelConfig.ServiceConfig.PredatorHosts + if predatorHosts == "" { + return validEndpoints + } + + for _, endpoint := range strings.Split(predatorHosts, commaDelimiter) { + if cleanedEndpoint := strings.TrimSpace(endpoint); cleanedEndpoint != "" { + validEndpoints.Add(cleanedEndpoint) + } + } + return validEndpoints +} diff --git a/horizon/internal/inferflow/etcd/models.go b/horizon/internal/inferflow/etcd/models.go new file mode 100644 index 00000000..6f8a0435 --- /dev/null +++ b/horizon/internal/inferflow/etcd/models.go @@ -0,0 +1,165 @@ +package etcd + +type ModelConfigRegistery struct { + Services map[string]InferflowConfigs `json:"services"` +} + +type HorizonRegistry struct { + Inferflow InferflowComponents `json:"inferflow"` +} + +type InferflowComponents struct { + InferflowComponents map[string]ComponentData `json:"inferflow-components"` +} + +type InferflowConfigs struct { + ModelConfig ModelConfigData `json:"model-config"` +} + +type ModelConfigData struct { + ConfigMap map[string]InferflowConfig `json:"config-map"` + ServiceConfig ServiceConfigData `json:"service-config"` +} + +type ServiceConfigData struct { + PredatorHosts string `json:"predator-hosts"` +} + +type NumerixComponent struct { + Component string `json:"component"` + ComponentID string `json:"component_id"` + ScoreCol string `json:"score_col"` + ComputeID string `json:"compute_id"` + ScoreMapping map[string]string `json:"score_mapping"` + DataType string `json:"data_type"` + SlateComponent bool `json:"slate_component"` +} + +type PredatorInput struct { + Name string `json:"name"` + Features []string `json:"features"` + Dims []int `json:"shape"` + DataType string `json:"data_type"` +} + +type PredatorOutput struct { + Name string `json:"name"` + ModelScores []string `json:"model_scores"` + ModelScoresDims [][]int `json:"model_scores_dims"` + DataType string `json:"data_type"` +} + +type RoutingConfig struct { + ModelName string `json:"model_name"` + ModelEndpoint string `json:"model_endpoint"` + RoutingPercentage float32 `json:"routing_percentage"` +} + +type PredatorComponent struct { + Component string `json:"component"` + ComponentID string `json:"component_id"` + ModelName string `json:"model_name"` + Calibration string `json:"calibration,omitempty"` + ModelEndPoint string `json:"model_end_point"` + Deadline int `json:"deadline"` + BatchSize int `json:"batch_size"` + Inputs []PredatorInput `json:"inputs"` + Outputs []PredatorOutput `json:"outputs"` + RoutingConfig []RoutingConfig `json:"route_config,omitempty"` + SlateComponent bool `json:"slate_component"` +} + +type RTPComponent struct { + Component string `json:"component"` + ComponentID string `json:"component_id"` + CompositeID bool `json:"composite_id"` + FSKeys []FSKey `json:"fs_keys"` + FSRequest *FSRequest `json:"fs_request"` + FSFlattenRespKeys []string `json:"fs_flatten_resp_keys"` + ColNamePrefix string `json:"col_name_prefix"` + CompCacheEnabled bool `json:"comp_cache_enabled"` +} + +type SeenScoreComponent struct { + Component string `json:"component"` + ComponentID string `json:"component_id,omitempty"` + ColNamePrefix string `json:"col_name_prefix,omitempty"` + FSKeys []FSKey `json:"fs_keys"` + FSRequest *FSRequest `json:"fs_request"` +} + +type FinalResponseConfig struct { + LoggingPerc int `json:"logging_perc"` + ModelSchemaPerc int `json:"model_schema_features_perc"` + Features []string `json:"features"` + LogSelectiveFeatures bool `json:"log_features"` + LogBatchSize int `json:"log_batch_size"` +} + +type FSKey struct { + Schema string `json:"schema"` + Col string `json:"col"` +} + +type FSFeatureGroup struct { + Label string `json:"label"` + Features []string `json:"features"` + DataType string `json:"data_type"` +} + +type FSRequest struct { + Label string `json:"label"` + FeatureGroups []FSFeatureGroup `json:"featureGroups"` +} + +type FeatureComponent struct { + Component string `json:"component"` + ComponentID string `json:"component_id"` + ColNamePrefix string `json:"col_name_prefix,omitempty"` + CompCacheEnabled bool `json:"comp_cache_enabled"` + CompCacheTTL int `json:"comp_cache_ttl,omitempty"` + CompositeID bool `json:"composite_id,omitempty"` + FSKeys []FSKey `json:"fs_keys"` + FSRequest *FSRequest `json:"fs_request"` + FSFlattenRespKeys []string `json:"fs_flatten_resp_keys"` +} + +type ComponentConfig struct { + CacheEnabled bool `json:"cache_enabled"` + CacheTTL int `json:"cache_ttl"` + CacheVersion int `json:"cache_version"` + FeatureComponents []FeatureComponent `json:"feature_components"` + RTPComponents []RTPComponent `json:"real_time_pricing_feature_components,omitempty"` + PredatorComponents []PredatorComponent `json:"predator_components"` + NumerixComponents []NumerixComponent `json:"numerix_components"` + SeenScoreComponents []SeenScoreComponent `json:"seen_score_components"` +} + +type DagExecutionConfig struct { + ComponentDependency map[string][]string `json:"component_dependency"` +} + +type InferflowConfig struct { + DagExecutionConfig DagExecutionConfig `json:"dag_execution_config"` + ComponentConfig ComponentConfig `json:"component_config"` + ResponseConfig FinalResponseConfig `json:"response_config"` +} + +type ComponentData struct { + ComponentID string `json:"component-id"` + CompositeID bool `json:"composite-id"` + ExecutionDependency string `json:"execution-dependency"` + FSFlattenResKeys map[string]string `json:"fs-flatten-res-keys"` + FSIdSchemaToValueColumns map[string]FSIdSchemaToValueColumnPair `json:"fs-id-schema-to-value-columns"` + Overridecomponent map[string]OverrideComponent `json:"override-component"` +} + +type OverrideComponent struct { + ComponentId string `json:"component-id"` +} + +type FSIdSchemaToValueColumnPair struct { + Schema string `json:"schema"` + ValueCol string `json:"value-col"` + DataType string `json:"data-type"` +} diff --git a/horizon/internal/inferflow/handler/adaptor.go b/horizon/internal/inferflow/handler/adaptor.go new file mode 100644 index 00000000..a7cddb4a --- /dev/null +++ b/horizon/internal/inferflow/handler/adaptor.go @@ -0,0 +1,775 @@ +package handler + +import ( + etcdModel "github.com/Meesho/BharatMLStack/horizon/internal/inferflow/etcd" + dbModel "github.com/Meesho/BharatMLStack/horizon/internal/repositories/sql/inferflow" + "github.com/rs/zerolog/log" +) + +type ConfigMapProvider interface { + GetConfigMapping() dbModel.ConfigMapping +} + +func AdaptOnboardRequestToDBPayload(req interface{}, inferflowConfig InferflowConfig, onboardPayload OnboardPayload) (dbModel.Payload, error) { + var payload dbModel.Payload + + payload.ConfigMapping = AdaptToDBConfig(req) + + dbResponseConfig := AdaptToDBResponseConfig(inferflowConfig) + + dbPredatorComponents := AdaptToDBPredatorComponent(inferflowConfig) + + dbNumerixComponents := AdaptToDBNumerixComponent(inferflowConfig) + + // Use interface for internal component adaptation (RTP, SeenScore) + dbRTPComponents := InternalComponentBuilderInstance.AdaptToDBRTPComponent(inferflowConfig) + dbSeenScoreComponents := InternalComponentBuilderInstance.AdaptToDBSeenScoreComponent(inferflowConfig) + + featureComponents := AdaptToDBFeatureComponent(inferflowConfig) + + dbComponentConfig := AdaptToDBComponentConfig(inferflowConfig, featureComponents, dbNumerixComponents, dbPredatorComponents, dbRTPComponents, dbSeenScoreComponents) + + dbDagExecutionConfig := AdaptToDBDagExecutionConfig(inferflowConfig) + + payload.ConfigValue = AdaptToDBConfigValue(dbDagExecutionConfig, dbComponentConfig, dbResponseConfig) + payload.RequestPayload = AdaptToDBOnboardPayload(onboardPayload) + + return payload, nil +} + +func AdaptEditRequestToDBPayload(req interface{}, inferflowConfig InferflowConfig, onboardPayload OnboardPayload) (dbModel.Payload, error) { + var payload dbModel.Payload + + payload.ConfigMapping = AdaptToDBConfig(req) + + dbResponseConfig := AdaptToDBResponseConfig(inferflowConfig) + + dbPredatorComponents := AdaptToDBPredatorComponent(inferflowConfig) + + dbNumerixComponents := AdaptToDBNumerixComponent(inferflowConfig) + + // Use interface for internal component adaptation (RTP, SeenScore) + dbRTPComponents := InternalComponentBuilderInstance.AdaptToDBRTPComponent(inferflowConfig) + dbSeenScoreComponents := InternalComponentBuilderInstance.AdaptToDBSeenScoreComponent(inferflowConfig) + + featureComponents := AdaptToDBFeatureComponent(inferflowConfig) + + dbComponentConfig := AdaptToDBComponentConfig(inferflowConfig, featureComponents, dbNumerixComponents, dbPredatorComponents, dbRTPComponents, dbSeenScoreComponents) + + dbDagExecutionConfig := AdaptToDBDagExecutionConfig(inferflowConfig) + + payload.ConfigValue = AdaptToDBConfigValue(dbDagExecutionConfig, dbComponentConfig, dbResponseConfig) + + payload.RequestPayload = AdaptToDBOnboardPayload(onboardPayload) + + return payload, nil +} + +func AdaptCloneConfigRequestToDBPayload(req interface{}, inferflowConfig InferflowConfig, onboardPayload OnboardPayload) (dbModel.Payload, error) { + var payload dbModel.Payload + + payload.ConfigMapping = AdaptToDBConfig(req) + + dbResponseConfig := AdaptToDBResponseConfig(inferflowConfig) + + dbPredatorComponents := AdaptToDBPredatorComponent(inferflowConfig) + + dbNumerixComponents := AdaptToDBNumerixComponent(inferflowConfig) + + // Use interface for internal component adaptation (RTP, SeenScore) + dbRTPComponents := InternalComponentBuilderInstance.AdaptToDBRTPComponent(inferflowConfig) + dbSeenScoreComponents := InternalComponentBuilderInstance.AdaptToDBSeenScoreComponent(inferflowConfig) + + featureComponents := AdaptToDBFeatureComponent(inferflowConfig) + + dbComponentConfig := AdaptToDBComponentConfig(inferflowConfig, featureComponents, dbNumerixComponents, dbPredatorComponents, dbRTPComponents, dbSeenScoreComponents) + + dbDagExecutionConfig := AdaptToDBDagExecutionConfig(inferflowConfig) + + payload.ConfigValue = AdaptToDBConfigValue(dbDagExecutionConfig, dbComponentConfig, dbResponseConfig) + payload.RequestPayload = AdaptToDBOnboardPayload(onboardPayload) + + return payload, nil +} + +func AdaptPromoteRequestToDBPayload(req interface{}, requestPayload RequestConfig) (dbModel.Payload, error) { + var payload dbModel.Payload + + payload.ConfigMapping = AdaptToDBConfig(req) + + inferflowConfig := req.(PromoteConfigRequest).Payload.ConfigValue + + dbResponseConfig := AdaptToDBResponseConfig(inferflowConfig) + + dbPredatorComponents := AdaptToDBPredatorComponent(inferflowConfig) + + dbNumerixComponents := AdaptToDBNumerixComponent(inferflowConfig) + + // Use interface for internal component adaptation (RTP, SeenScore) + dbRTPComponents := InternalComponentBuilderInstance.AdaptToDBRTPComponent(inferflowConfig) + dbSeenScoreComponents := InternalComponentBuilderInstance.AdaptToDBSeenScoreComponent(inferflowConfig) + + featureComponents := AdaptToDBFeatureComponent(inferflowConfig) + + dbComponentConfig := AdaptToDBComponentConfig(inferflowConfig, featureComponents, dbNumerixComponents, dbPredatorComponents, dbRTPComponents, dbSeenScoreComponents) + + dbDagExecutionConfig := AdaptToDBDagExecutionConfig(inferflowConfig) + + payload.ConfigValue = AdaptToDBConfigValue(dbDagExecutionConfig, dbComponentConfig, dbResponseConfig) + + payload.RequestPayload = AdaptToDBOnboardPayload(requestPayload.Payload.RequestPayload) + + return payload, nil +} + +func AdaptScaleUpRequestToDBPayload(req interface{}, requestPayload RequestConfig) (dbModel.Payload, error) { + var payload dbModel.Payload + + payload.ConfigMapping = AdaptToDBConfig(req) + + inferflowConfig := req.(ScaleUpConfigRequest).Payload.ConfigValue + + dbResponseConfig := AdaptToDBResponseConfig(inferflowConfig) + + dbPredatorComponents := AdaptToDBPredatorComponent(inferflowConfig) + + dbNumerixComponents := AdaptToDBNumerixComponent(inferflowConfig) + + // Use interface for internal component adaptation (RTP, SeenScore) + dbRTPComponents := InternalComponentBuilderInstance.AdaptToDBRTPComponent(inferflowConfig) + dbSeenScoreComponents := InternalComponentBuilderInstance.AdaptToDBSeenScoreComponent(inferflowConfig) + + featureComponents := AdaptToDBFeatureComponent(inferflowConfig) + + dbComponentConfig := AdaptToDBComponentConfig(inferflowConfig, featureComponents, dbNumerixComponents, dbPredatorComponents, dbRTPComponents, dbSeenScoreComponents) + + dbDagExecutionConfig := AdaptToDBDagExecutionConfig(inferflowConfig) + + payload.ConfigValue = AdaptToDBConfigValue(dbDagExecutionConfig, dbComponentConfig, dbResponseConfig) + payload.RequestPayload = AdaptToDBOnboardPayload(requestPayload.Payload.RequestPayload) + + return payload, nil +} + +func AdaptToDBConfig(req interface{}) dbModel.ConfigMapping { + + if provider, ok := req.(ConfigMapProvider); ok { + return provider.GetConfigMapping() + } else { + log.Warn().Msgf("AdaptToDBConfig received a type that does not implement ConfigMapProvider: %T", req) + return dbModel.ConfigMapping{} + } +} + +func AdaptToDBResponseConfig(inferflowConfig InferflowConfig) dbModel.ResponseConfig { + return dbModel.ResponseConfig{ + LoggingPerc: inferflowConfig.ResponseConfig.LoggingPerc, + ModelSchemaPerc: inferflowConfig.ResponseConfig.ModelSchemaPerc, + Features: inferflowConfig.ResponseConfig.Features, + LogSelectiveFeatures: inferflowConfig.ResponseConfig.LogSelectiveFeatures, + LogBatchSize: inferflowConfig.ResponseConfig.LogBatchSize, + LoggingTTL: inferflowConfig.ResponseConfig.LoggingTTL, + } +} + +func AdaptToDBPredatorComponent(inferflowConfig InferflowConfig) []dbModel.PredatorComponent { + var predatorComponents []dbModel.PredatorComponent + + for _, ranker := range inferflowConfig.ComponentConfig.PredatorComponents { + dbInputs := make([]dbModel.PredatorInput, len(ranker.Inputs)) + for i, input := range ranker.Inputs { + dbInputs[i] = dbModel.PredatorInput{ + Name: input.Name, + Features: input.Features, + Dims: input.Dims, + DataType: input.DataType, + } + } + + dbOutputs := make([]dbModel.PredatorOutput, len(ranker.Outputs)) + for i, output := range ranker.Outputs { + dbOutputs[i] = dbModel.PredatorOutput{ + Name: output.Name, + ModelScores: output.ModelScores, + ModelScoresDims: output.ModelScoresDims, + DataType: output.DataType, + } + } + + routingConfig := make([]dbModel.RoutingConfig, len(ranker.RoutingConfig)) + + for i, config := range ranker.RoutingConfig { + + routingConfig[i] = dbModel.RoutingConfig{ + + ModelName: config.ModelName, + + ModelEndpoint: config.ModelEndpoint, + + RoutingPercentage: config.RoutingPercentage, + } + + } + + predatorComp := dbModel.PredatorComponent{ + Component: ranker.Component, + ComponentID: ranker.ComponentID, + Calibration: ranker.Calibration, + ModelName: ranker.ModelName, + ModelEndPoint: ranker.ModelEndPoint, + Deadline: ranker.Deadline, + BatchSize: ranker.BatchSize, + Inputs: dbInputs, + Outputs: dbOutputs, + RoutingConfig: routingConfig, + SlateComponent: ranker.SlateComponent, + } + predatorComponents = append(predatorComponents, predatorComp) + } + + return predatorComponents +} + +func AdaptToDBNumerixComponent(inferflowConfig InferflowConfig) []dbModel.NumerixComponent { + var NumerixComponents []dbModel.NumerixComponent + + for _, reRanker := range inferflowConfig.ComponentConfig.NumerixComponents { + NumerixComp := dbModel.NumerixComponent{ + Component: reRanker.Component, + ComponentID: reRanker.ComponentID, + ScoreCol: reRanker.ScoreCol, + ComputeID: reRanker.ComputeID, + ScoreMapping: reRanker.ScoreMapping, + DataType: reRanker.DataType, + SlateComponent: reRanker.SlateComponent, + } + NumerixComponents = append(NumerixComponents, NumerixComp) + } + + return NumerixComponents +} + + +func AdaptToDBFeatureComponent(inferflowConfig InferflowConfig) []dbModel.FeatureComponent { + var featureComponents []dbModel.FeatureComponent + + for _, fc := range inferflowConfig.ComponentConfig.FeatureComponents { + fsKeys := make([]dbModel.FSKey, len(fc.FSKeys)) + for i, key := range fc.FSKeys { + fsKeys[i] = dbModel.FSKey{ + Schema: key.Schema, + Col: key.Col, + } + } + + fsFeatureGroups := make([]dbModel.FSFeatureGroup, len(fc.FSRequest.FeatureGroups)) + for i, grp := range fc.FSRequest.FeatureGroups { + fsFeatureGroups[i] = dbModel.FSFeatureGroup{ + Label: grp.Label, + Features: grp.Features, + DataType: grp.DataType, + } + } + + fsRequest := dbModel.FSRequest{ + Label: fc.FSRequest.Label, + FeatureGroups: fsFeatureGroups, + } + + comp := dbModel.FeatureComponent{ + Component: fc.Component, + ComponentID: fc.ComponentID, + ColNamePrefix: fc.ColNamePrefix, + CompCacheEnabled: fc.CompCacheEnabled, + CompositeID: fc.CompositeID, + FSKeys: fsKeys, + FSRequest: &fsRequest, + FSFlattenRespKeys: fc.FSFlattenRespKeys, + } + featureComponents = append(featureComponents, comp) + } + + return featureComponents +} + +func AdaptToDBComponentConfig(inferflowConfig InferflowConfig, featureComponents []dbModel.FeatureComponent, NumerixComponents []dbModel.NumerixComponent, predatorComponents []dbModel.PredatorComponent, rtpComponents []dbModel.RTPComponent, seenScoreComponents []dbModel.SeenScoreComponent) dbModel.ComponentConfig { + return dbModel.ComponentConfig{ + CacheEnabled: inferflowConfig.ComponentConfig.CacheEnabled, + CacheTTL: inferflowConfig.ComponentConfig.CacheTTL, + CacheVersion: inferflowConfig.ComponentConfig.CacheVersion, + FeatureComponents: featureComponents, + PredatorComponents: predatorComponents, + NumerixComponents: NumerixComponents, + RTPComponents: rtpComponents, + SeenScoreComponents: seenScoreComponents, + } +} + +func AdaptToDBDagExecutionConfig(inferflowConfig InferflowConfig) dbModel.DagExecutionConfig { + return dbModel.DagExecutionConfig{ + ComponentDependency: inferflowConfig.DagExecutionConfig.ComponentDependency, + } +} + +func AdaptToDBConfigValue(dagExecutionConfig dbModel.DagExecutionConfig, componentConfig dbModel.ComponentConfig, responseConfig dbModel.ResponseConfig) dbModel.InferflowConfig { + return dbModel.InferflowConfig{ + DagExecutionConfig: dagExecutionConfig, + ComponentConfig: componentConfig, + ResponseConfig: responseConfig, + } +} + +func AdaptToDBOnboardPayload(onboardPayload OnboardPayload) dbModel.OnboardPayload { + dbOnboardPayload := dbModel.OnboardPayload{ + RealEstate: onboardPayload.RealEstate, + Tenant: onboardPayload.Tenant, + ConfigIdentifier: onboardPayload.ConfigIdentifier, + Rankers: make([]dbModel.OnboardRanker, len(onboardPayload.Rankers)), + ReRankers: make([]dbModel.OnboardReRanker, len(onboardPayload.ReRankers)), + Response: dbModel.ResponseConfig{ + LoggingPerc: onboardPayload.Response.PrismLoggingPerc, + ModelSchemaPerc: onboardPayload.Response.RankerSchemaFeaturesInResponsePerc, + Features: onboardPayload.Response.ResponseFeatures, + LogSelectiveFeatures: onboardPayload.Response.LogSelectiveFeatures, + LogBatchSize: onboardPayload.Response.LogBatchSize, + LoggingTTL: onboardPayload.Response.LoggingTTL, + }, + ConfigMapping: dbModel.ConfigMapping{ + AppToken: onboardPayload.ConfigMapping.AppToken, + ConnectionConfigID: onboardPayload.ConfigMapping.ConnectionConfigID, + DeployableID: onboardPayload.ConfigMapping.DeployableID, + ResponseDefaultValues: onboardPayload.ConfigMapping.ResponseDefaultValues, + }, + } + + for i, ranker := range onboardPayload.Rankers { + dbOnboardPayload.Rankers[i] = dbModel.OnboardRanker{ + ModelName: ranker.ModelName, + Calibration: ranker.Calibration, + EndPoint: ranker.EndPoint, + EntityID: ranker.EntityID, + Inputs: make([]dbModel.PredatorInput, len(ranker.Inputs)), + Outputs: make([]dbModel.PredatorOutput, len(ranker.Outputs)), + BatchSize: ranker.BatchSize, + Deadline: ranker.Deadline, + RoutingConfig: make([]dbModel.RoutingConfig, len(ranker.RoutingConfig)), + SlateComponent: ranker.SlateComponent, + } + for j, input := range ranker.Inputs { + dbOnboardPayload.Rankers[i].Inputs[j] = dbModel.PredatorInput{ + Name: input.Name, + Features: input.Features, + Dims: input.Dims, + DataType: input.DataType, + } + } + + for k, config := range ranker.RoutingConfig { + dbOnboardPayload.Rankers[i].RoutingConfig[k] = dbModel.RoutingConfig{ + ModelName: config.ModelName, + ModelEndpoint: config.ModelEndpoint, + RoutingPercentage: config.RoutingPercentage, + } + } + for j, output := range ranker.Outputs { + dbOnboardPayload.Rankers[i].Outputs[j] = dbModel.PredatorOutput{ + Name: output.Name, + ModelScores: output.ModelScores, + ModelScoresDims: output.ModelScoresDims, + DataType: output.DataType, + } + } + } + for i, reRanker := range onboardPayload.ReRankers { + dbOnboardPayload.ReRankers[i] = dbModel.OnboardReRanker{ + Score: reRanker.Score, + EqID: reRanker.EqID, + EqVariables: reRanker.EqVariables, + DataType: reRanker.DataType, + EntityID: reRanker.EntityID, + SlateComponent: reRanker.SlateComponent, + } + } + return dbOnboardPayload +} + +func AdaptFromDbToInferFlowConfig(dbConfig dbModel.InferflowConfig) InferflowConfig { + return InferflowConfig{ + DagExecutionConfig: AdaptFromDbToDagExecutionConfig(dbConfig.DagExecutionConfig), + ComponentConfig: AdaptFromDbToComponentConfig(dbConfig.ComponentConfig), + ResponseConfig: AdaptFromDbToResponseConfig(dbConfig.ResponseConfig), + } +} + +func AdaptFromDbToOnboardPayload(dbOnboardPayload dbModel.OnboardPayload) OnboardPayload { + onboardPayload := OnboardPayload{ + RealEstate: dbOnboardPayload.RealEstate, + Tenant: dbOnboardPayload.Tenant, + ConfigIdentifier: dbOnboardPayload.ConfigIdentifier, + Rankers: []Ranker{}, + ReRankers: []ReRanker{}, + Response: ResponseConfig{ + PrismLoggingPerc: dbOnboardPayload.Response.LoggingPerc, + RankerSchemaFeaturesInResponsePerc: dbOnboardPayload.Response.ModelSchemaPerc, + ResponseFeatures: dbOnboardPayload.Response.Features, + LogSelectiveFeatures: dbOnboardPayload.Response.LogSelectiveFeatures, + LogBatchSize: dbOnboardPayload.Response.LogBatchSize, + LoggingTTL: dbOnboardPayload.Response.LoggingTTL, + }, + ConfigMapping: ConfigMapping{ + AppToken: dbOnboardPayload.ConfigMapping.AppToken, + ConnectionConfigID: dbOnboardPayload.ConfigMapping.ConnectionConfigID, + DeployableID: dbOnboardPayload.ConfigMapping.DeployableID, + ResponseDefaultValues: dbOnboardPayload.ConfigMapping.ResponseDefaultValues, + }, + } + + for i, predatorComponent := range dbOnboardPayload.Rankers { + onboardPayload.Rankers = append(onboardPayload.Rankers, Ranker{ + ModelName: predatorComponent.ModelName, + Calibration: predatorComponent.Calibration, + EndPoint: predatorComponent.EndPoint, + Inputs: make([]Input, len(predatorComponent.Inputs)), + Outputs: make([]Output, len(predatorComponent.Outputs)), + EntityID: predatorComponent.EntityID, + BatchSize: predatorComponent.BatchSize, + Deadline: predatorComponent.Deadline, + RoutingConfig: make([]RoutingConfig, len(predatorComponent.RoutingConfig)), + SlateComponent: predatorComponent.SlateComponent, + }) + for j, input := range predatorComponent.Inputs { + onboardPayload.Rankers[i].Inputs[j] = Input{ + Name: input.Name, + Features: input.Features, + Dims: input.Dims, + DataType: input.DataType, + } + } + for j, output := range predatorComponent.Outputs { + onboardPayload.Rankers[i].Outputs[j] = Output{ + Name: output.Name, + ModelScores: output.ModelScores, + ModelScoresDims: output.ModelScoresDims, + DataType: output.DataType, + } + } + + for k, config := range predatorComponent.RoutingConfig { + onboardPayload.Rankers[i].RoutingConfig[k] = RoutingConfig{ + ModelName: config.ModelName, + ModelEndpoint: config.ModelEndpoint, + RoutingPercentage: config.RoutingPercentage, + } + } + } + + for _, reRanker := range dbOnboardPayload.ReRankers { + onboardPayload.ReRankers = append(onboardPayload.ReRankers, ReRanker{ + Score: reRanker.Score, + EqID: reRanker.EqID, + EqVariables: reRanker.EqVariables, + DataType: reRanker.DataType, + EntityID: reRanker.EntityID, + SlateComponent: reRanker.SlateComponent, + }) + } + + return onboardPayload +} + +func AdaptFromDbToComponentConfig(dbComponentConfig dbModel.ComponentConfig) *ComponentConfig { + return &ComponentConfig{ + CacheEnabled: dbComponentConfig.CacheEnabled, + CacheTTL: dbComponentConfig.CacheTTL, + CacheVersion: dbComponentConfig.CacheVersion, + FeatureComponents: AdaptFromDbToFeatureComponent(dbComponentConfig.FeatureComponents), + PredatorComponents: AdaptFromDbToPredatorComponent(dbComponentConfig.PredatorComponents), + NumerixComponents: AdaptFromDbToNumerixComponent(dbComponentConfig.NumerixComponents), + RTPComponents: InternalComponentBuilderInstance.AdaptFromDbToRTPComponent(dbComponentConfig.RTPComponents), + SeenScoreComponents: InternalComponentBuilderInstance.AdaptFromDbToSeenScoreComponent(dbComponentConfig.SeenScoreComponents), + } +} + +func AdaptFromDbToResponseConfig(dbResponseConfig dbModel.ResponseConfig) *FinalResponseConfig { + return &FinalResponseConfig{ + LoggingPerc: dbResponseConfig.LoggingPerc, + ModelSchemaPerc: dbResponseConfig.ModelSchemaPerc, + Features: dbResponseConfig.Features, + LogSelectiveFeatures: dbResponseConfig.LogSelectiveFeatures, + LogBatchSize: dbResponseConfig.LogBatchSize, + LoggingTTL: dbResponseConfig.LoggingTTL, + } +} + +func AdaptFromDbToDagExecutionConfig(dbDagExecutionConfig dbModel.DagExecutionConfig) *DagExecutionConfig { + return &DagExecutionConfig{ + ComponentDependency: dbDagExecutionConfig.ComponentDependency, + } +} + +func AdaptFromDbToPredatorComponent(dbPredatorComponents []dbModel.PredatorComponent) []PredatorComponent { + + var predatorComponents []PredatorComponent + for _, predatorComponent := range dbPredatorComponents { + dbInputs := make([]PredatorInput, len(predatorComponent.Inputs)) + for i, input := range predatorComponent.Inputs { + dbInputs[i] = PredatorInput{ + Name: input.Name, + Features: input.Features, + Dims: input.Dims, + DataType: input.DataType, + } + } + + dbOutputs := make([]PredatorOutput, len(predatorComponent.Outputs)) + for i, output := range predatorComponent.Outputs { + dbOutputs[i] = PredatorOutput{ + Name: output.Name, + ModelScores: output.ModelScores, + ModelScoresDims: output.ModelScoresDims, + DataType: output.DataType, + } + } + + routingConfig := make([]RoutingConfig, len(predatorComponent.RoutingConfig)) + + for i, config := range predatorComponent.RoutingConfig { + routingConfig[i] = RoutingConfig{ + ModelName: config.ModelName, + ModelEndpoint: config.ModelEndpoint, + RoutingPercentage: config.RoutingPercentage, + } + } + + predatorComponent := PredatorComponent{ + Component: predatorComponent.Component, + ComponentID: predatorComponent.ComponentID, + ModelName: predatorComponent.ModelName, + ModelEndPoint: predatorComponent.ModelEndPoint, + Calibration: predatorComponent.Calibration, + Deadline: predatorComponent.Deadline, + BatchSize: predatorComponent.BatchSize, + Inputs: dbInputs, + Outputs: dbOutputs, + RoutingConfig: routingConfig, + SlateComponent: predatorComponent.SlateComponent, + } + predatorComponents = append(predatorComponents, predatorComponent) + } + return predatorComponents +} + +func AdaptFromDbToNumerixComponent(dbNumerixComponents []dbModel.NumerixComponent) []NumerixComponent { + + var NumerixComponents []NumerixComponent + for _, numerixComponent := range dbNumerixComponents { + numerixComponent := NumerixComponent{ + Component: numerixComponent.Component, + ComponentID: numerixComponent.ComponentID, + ScoreCol: numerixComponent.ScoreCol, + ComputeID: numerixComponent.ComputeID, + ScoreMapping: numerixComponent.ScoreMapping, + DataType: numerixComponent.DataType, + SlateComponent: numerixComponent.SlateComponent, + } + NumerixComponents = append(NumerixComponents, numerixComponent) + } + return NumerixComponents +} + + +func AdaptFromDbToFeatureComponent(dbFeatureComponents []dbModel.FeatureComponent) []FeatureComponent { + var featureComponents []FeatureComponent + for _, fc := range dbFeatureComponents { + fsKeys := make([]FSKey, len(fc.FSKeys)) + for i, key := range fc.FSKeys { + fsKeys[i] = FSKey{ + Schema: key.Schema, + Col: key.Col, + } + } + + fsFeatureGroups := make([]FSFeatureGroup, len(fc.FSRequest.FeatureGroups)) + for i, grp := range fc.FSRequest.FeatureGroups { + fsFeatureGroups[i] = FSFeatureGroup{ + Label: grp.Label, + Features: grp.Features, + DataType: grp.DataType, + } + } + + fsRequest := FSRequest{ + Label: fc.FSRequest.Label, + FeatureGroups: fsFeatureGroups, + } + + comp := FeatureComponent{ + Component: fc.Component, + ComponentID: fc.ComponentID, + ColNamePrefix: fc.ColNamePrefix, + CompCacheEnabled: fc.CompCacheEnabled, + CompositeID: fc.CompositeID, + FSKeys: fsKeys, + FSRequest: &fsRequest, + FSFlattenRespKeys: fc.FSFlattenRespKeys, + } + featureComponents = append(featureComponents, comp) + } + return featureComponents +} + +func AdaptFromDbToConfigMapping(dbMapping dbModel.ConfigMapping) ConfigMapping { + return ConfigMapping{ + AppToken: dbMapping.AppToken, + ConnectionConfigID: dbMapping.ConnectionConfigID, + DeployableID: dbMapping.DeployableID, + ResponseDefaultValues: dbMapping.ResponseDefaultValues, + } +} + +func AdaptToEtcdInferFlowConfig(dpConfig dbModel.InferflowConfig) etcdModel.InferflowConfig { + return etcdModel.InferflowConfig{ + DagExecutionConfig: AdaptToEtcdDagExecutionConfig(dpConfig.DagExecutionConfig), + ComponentConfig: AdaptToEtcdComponentConfig(dpConfig.ComponentConfig), + ResponseConfig: AdaptToEtcdResponseConfig(dpConfig.ResponseConfig), + } +} + +func AdaptToEtcdComponentConfig(dbComponentConfig dbModel.ComponentConfig) etcdModel.ComponentConfig { + return etcdModel.ComponentConfig{ + CacheEnabled: dbComponentConfig.CacheEnabled, + CacheTTL: dbComponentConfig.CacheTTL, + CacheVersion: dbComponentConfig.CacheVersion, + FeatureComponents: AdaptToEtcdFeatureComponent(dbComponentConfig.FeatureComponents), + PredatorComponents: AdaptToEtcdPredatorComponent(dbComponentConfig.PredatorComponents), + NumerixComponents: AdaptToEtcdNumerixComponent(dbComponentConfig.NumerixComponents), + RTPComponents: InternalComponentBuilderInstance.AdaptToEtcdRTPComponent(dbComponentConfig.RTPComponents), + SeenScoreComponents: InternalComponentBuilderInstance.AdaptToEtcdSeenScoreComponent(dbComponentConfig.SeenScoreComponents), + } +} + +func AdaptToEtcdResponseConfig(dbResponseConfig dbModel.ResponseConfig) etcdModel.FinalResponseConfig { + return etcdModel.FinalResponseConfig{ + LoggingPerc: dbResponseConfig.LoggingPerc, + ModelSchemaPerc: dbResponseConfig.ModelSchemaPerc, + Features: dbResponseConfig.Features, + LogSelectiveFeatures: dbResponseConfig.LogSelectiveFeatures, + LogBatchSize: dbResponseConfig.LogBatchSize, + } +} + +func AdaptToEtcdDagExecutionConfig(dbDagExecutionConfig dbModel.DagExecutionConfig) etcdModel.DagExecutionConfig { + return etcdModel.DagExecutionConfig{ + ComponentDependency: dbDagExecutionConfig.ComponentDependency, + } +} + +func AdaptToEtcdPredatorComponent(dbPredatorComponents []dbModel.PredatorComponent) []etcdModel.PredatorComponent { + + var predatorComponents []etcdModel.PredatorComponent + for _, predatorComponent := range dbPredatorComponents { + dbInputs := make([]etcdModel.PredatorInput, len(predatorComponent.Inputs)) + for i, input := range predatorComponent.Inputs { + dbInputs[i] = etcdModel.PredatorInput{ + Name: input.Name, + Features: input.Features, + Dims: input.Dims, + DataType: input.DataType, + } + } + + dbOutputs := make([]etcdModel.PredatorOutput, len(predatorComponent.Outputs)) + for i, output := range predatorComponent.Outputs { + dbOutputs[i] = etcdModel.PredatorOutput{ + Name: output.Name, + ModelScores: output.ModelScores, + ModelScoresDims: output.ModelScoresDims, + DataType: output.DataType, + } + } + routingConfig := make([]etcdModel.RoutingConfig, len(predatorComponent.RoutingConfig)) + + for i, config := range predatorComponent.RoutingConfig { + routingConfig[i] = etcdModel.RoutingConfig{ + ModelName: config.ModelName, + ModelEndpoint: config.ModelEndpoint, + RoutingPercentage: config.RoutingPercentage, + } + } + + predatorComponent := etcdModel.PredatorComponent{ + Component: predatorComponent.Component, + ComponentID: predatorComponent.ComponentID, + Calibration: predatorComponent.Calibration, + ModelName: predatorComponent.ModelName, + ModelEndPoint: predatorComponent.ModelEndPoint, + Deadline: predatorComponent.Deadline, + BatchSize: predatorComponent.BatchSize, + Inputs: dbInputs, + Outputs: dbOutputs, + RoutingConfig: routingConfig, + SlateComponent: predatorComponent.SlateComponent, + } + predatorComponents = append(predatorComponents, predatorComponent) + } + return predatorComponents +} + +func AdaptToEtcdNumerixComponent(dbNumerixComponents []dbModel.NumerixComponent) []etcdModel.NumerixComponent { + + var NumerixComponents []etcdModel.NumerixComponent + for _, NumerixComponent := range dbNumerixComponents { + NumerixComponent := etcdModel.NumerixComponent{ + Component: NumerixComponent.Component, + ComponentID: NumerixComponent.ComponentID, + ScoreCol: NumerixComponent.ScoreCol, + ComputeID: NumerixComponent.ComputeID, + ScoreMapping: NumerixComponent.ScoreMapping, + DataType: NumerixComponent.DataType, + SlateComponent: NumerixComponent.SlateComponent, + } + NumerixComponents = append(NumerixComponents, NumerixComponent) + } + return NumerixComponents +} + + +func AdaptToEtcdFeatureComponent(dbFeatureComponents []dbModel.FeatureComponent) []etcdModel.FeatureComponent { + var featureComponents []etcdModel.FeatureComponent + for _, fc := range dbFeatureComponents { + fsKeys := make([]etcdModel.FSKey, len(fc.FSKeys)) + for i, key := range fc.FSKeys { + fsKeys[i] = etcdModel.FSKey{ + Schema: key.Schema, + Col: key.Col, + } + } + + fsFeatureGroups := make([]etcdModel.FSFeatureGroup, len(fc.FSRequest.FeatureGroups)) + for i, grp := range fc.FSRequest.FeatureGroups { + fsFeatureGroups[i] = etcdModel.FSFeatureGroup{ + Label: grp.Label, + Features: grp.Features, + DataType: grp.DataType, + } + } + + fsRequest := etcdModel.FSRequest{ + Label: fc.FSRequest.Label, + FeatureGroups: fsFeatureGroups, + } + + comp := etcdModel.FeatureComponent{ + Component: fc.Component, + ComponentID: fc.ComponentID, + ColNamePrefix: fc.ColNamePrefix, + CompCacheEnabled: fc.CompCacheEnabled, + CompositeID: fc.CompositeID, + FSKeys: fsKeys, + FSRequest: &fsRequest, + FSFlattenRespKeys: fc.FSFlattenRespKeys, + } + featureComponents = append(featureComponents, comp) + } + return featureComponents +} diff --git a/horizon/internal/inferflow/handler/component_builder_internal_stub.go b/horizon/internal/inferflow/handler/component_builder_internal_stub.go new file mode 100644 index 00000000..8a2053c9 --- /dev/null +++ b/horizon/internal/inferflow/handler/component_builder_internal_stub.go @@ -0,0 +1,104 @@ +//go:build !meesho + +package handler + +import ( + etcd "github.com/Meesho/BharatMLStack/horizon/internal/inferflow/etcd" + dbModel "github.com/Meesho/BharatMLStack/horizon/internal/repositories/sql/inferflow" + mapset "github.com/deckarep/golang-set/v2" +) + +// internalComponentBuilderStub is the stub implementation for open-source builds. +// It has NO knowledge of RTP, SEEN Score, or any other internal features. +// All internal processing is a no-op. +type internalComponentBuilderStub struct{} + +func init() { + InternalComponentBuilderInstance = &internalComponentBuilderStub{} +} + +// IsEnabled returns false - internal components not available in open-source builds +func (s *internalComponentBuilderStub) IsEnabled() bool { + return false +} + +// ProcessFeatures returns empty results - no internal features in open-source builds +func (s *internalComponentBuilderStub) ProcessFeatures( + initialFeatures mapset.Set[string], + featureDataTypes map[string]string, +) (mapset.Set[string], map[string]string, error) { + return mapset.NewSet[string](), make(map[string]string), nil +} + +// ClassifyFeature returns false - no features are internal in open-source builds +func (s *internalComponentBuilderStub) ClassifyFeature(feature string) (string, bool) { + return "", false +} + +// GetInternalComponents returns empty slices - no internal components in open-source builds +func (s *internalComponentBuilderStub) GetInternalComponents( + request InferflowOnboardRequest, + internalFeatures mapset.Set[string], + etcdConfig etcd.Manager, + token string, +) ([]RTPComponent, []SeenScoreComponent, error) { + return []RTPComponent{}, []SeenScoreComponent{}, nil +} + +// FetchInternalComponentFeatures returns empty results - no internal components in open-source builds +func (s *internalComponentBuilderStub) FetchInternalComponentFeatures( + internalFeatures mapset.Set[string], + etcdConfig etcd.Manager, +) (mapset.Set[string], mapset.Set[string], map[string]string, error) { + return mapset.NewSet[string](), mapset.NewSet[string](), make(map[string]string), nil +} + +// FetchMissingInternalDataTypes is a no-op - no internal features in open-source builds +func (s *internalComponentBuilderStub) FetchMissingInternalDataTypes( + featureToDataType map[string]string, + internalFeatures mapset.Set[string], +) error { + return nil +} + +// AddInternalDependenciesToDAG is a no-op - no internal components in open-source builds +func (s *internalComponentBuilderStub) AddInternalDependenciesToDAG( + rtpComponents []RTPComponent, + seenScoreComponents []SeenScoreComponent, + featureComponents []FeatureComponent, + dagConfig *DagExecutionConfig, +) { + // No-op for open-source builds +} + +// ============= Adaptor Stub Methods ============= + +// AdaptToDBRTPComponent returns empty slice - no RTP components in open-source builds +func (s *internalComponentBuilderStub) AdaptToDBRTPComponent(inferflowConfig InferflowConfig) []dbModel.RTPComponent { + return []dbModel.RTPComponent{} +} + +// AdaptToDBSeenScoreComponent returns empty slice - no SeenScore components in open-source builds +func (s *internalComponentBuilderStub) AdaptToDBSeenScoreComponent(inferflowConfig InferflowConfig) []dbModel.SeenScoreComponent { + return []dbModel.SeenScoreComponent{} +} + +// AdaptFromDbToRTPComponent returns empty slice - no RTP components in open-source builds +func (s *internalComponentBuilderStub) AdaptFromDbToRTPComponent(dbRTPComponents []dbModel.RTPComponent) []RTPComponent { + return []RTPComponent{} +} + +// AdaptFromDbToSeenScoreComponent returns empty slice - no SeenScore components in open-source builds +func (s *internalComponentBuilderStub) AdaptFromDbToSeenScoreComponent(dbSeenScoreComponents []dbModel.SeenScoreComponent) []SeenScoreComponent { + return []SeenScoreComponent{} +} + +// AdaptToEtcdRTPComponent returns empty slice - no RTP components in open-source builds +func (s *internalComponentBuilderStub) AdaptToEtcdRTPComponent(dbRTPComponents []dbModel.RTPComponent) []etcd.RTPComponent { + return []etcd.RTPComponent{} +} + +// AdaptToEtcdSeenScoreComponent returns empty slice - no SeenScore components in open-source builds +func (s *internalComponentBuilderStub) AdaptToEtcdSeenScoreComponent(dbSeenScoreComponents []dbModel.SeenScoreComponent) []etcd.SeenScoreComponent { + return []etcd.SeenScoreComponent{} +} diff --git a/horizon/internal/inferflow/handler/config.go b/horizon/internal/inferflow/handler/config.go new file mode 100644 index 00000000..2c543893 --- /dev/null +++ b/horizon/internal/inferflow/handler/config.go @@ -0,0 +1,20 @@ +package handler + +type Config interface { + Onboard(request InferflowOnboardRequest, token string) (Response, error) + Review(request ReviewRequest) (Response, error) + Promote(request PromoteConfigRequest) (Response, error) + Edit(request EditConfigOrCloneConfigRequest, token string) (Response, error) + Clone(request EditConfigOrCloneConfigRequest, token string) (Response, error) + Delete(request DeleteConfigRequest) (Response, error) + ScaleUp(request ScaleUpConfigRequest) (Response, error) + Cancel(request CancelConfigRequest) (Response, error) + GetAll() (GetAllResponse, error) + GetAllRequests(request GetAllRequestConfigsRequest) (GetAllRequestConfigsResponse, error) + ValidateRequest(request ValidateRequest, token string) (Response, error) + GenerateFunctionalTestRequest(request GenerateRequestFunctionalTestingRequest) (GenerateRequestFunctionalTestingResponse, error) + ExecuteFuncitonalTestRequest(request ExecuteRequestFunctionalTestingRequest) (ExecuteRequestFunctionalTestingResponse, error) + GetLatestRequest(requestID string) (GetLatestRequestResponse, error) + GetLoggingTTL() (GetLoggingTTLResponse, error) + GetFeatureSchema(FeatureSchemaRequest) (FeatureSchemaResponse, error) +} diff --git a/horizon/internal/inferflow/handler/config_builder.go b/horizon/internal/inferflow/handler/config_builder.go new file mode 100644 index 00000000..4aa09532 --- /dev/null +++ b/horizon/internal/inferflow/handler/config_builder.go @@ -0,0 +1,1329 @@ +package handler + +import ( + "fmt" + "sort" + "strconv" + "strings" + + ofsHandler "github.com/Meesho/BharatMLStack/horizon/internal/online-feature-store/handler" + + "github.com/Meesho/BharatMLStack/horizon/internal/inferflow/etcd" + mapset "github.com/deckarep/golang-set/v2" + "github.com/rs/zerolog/log" +) + +const ( + PARENT = "PARENT" + DEFAULT_FEATURE = "DEFAULT" + ONLINE_FEATURE = "ONLINE" + MODEL_FEATURE = "MODEL" + OFFLINE_FEATURE = "OFFLINE" + CALIBRATION = "CALIBRATION" + PCTR_CALIBRATION = "PCTR_CALIBRATION" + PCVR_CALIBRATION = "PCVR_CALIBRATION" + PIPE_DELIMITER = "|" + UNDERSCORE_DELIMITER = "_" + COLON_DELIMITER = ":" + COMMA_DELIMITER = "," + featureClassOffline = "offline" + featureClassOnline = "online" + featureClassDefault = "default" + featureClassModel = "model" + featureClassPCVRCalibration = "pcvr_calibration" + featureClassPCTRCalibration = "pctr_calibration" + featureClassInvalid = "invalid" + COMPONENT_NAME_PREFIX = "composite_key_gen_" + FEATURE_INITIALIZER = "feature_initializer" +) + +func (m *InferFlow) GetInferflowConfig(request InferflowOnboardRequest, token string) (InferflowConfig, error) { + entityIDs := extractEntityIDs(request) + + featureList, featureToDataType, internalFeatures, pcvrCalibrationFeatures, pctrCalibrationFeatures, predatorAndNumerixOutputsToDataType, offlineToOnlineMapping, err := GetFeatureList(request, m.EtcdConfig, token, entityIDs) + if err != nil { + return InferflowConfig{}, err + } + + predatorComponents, err := GetPredatorComponents(request, offlineToOnlineMapping) + if err != nil { + return InferflowConfig{}, err + } + + NumerixComponents, err := GetNumerixComponents(request, offlineToOnlineMapping, predatorAndNumerixOutputsToDataType, featureToDataType) + if err != nil { + return InferflowConfig{}, err + } + + responseConfig, err := GetResponseConfigs(&request) + if err != nil { + return InferflowConfig{}, err + } + + // Get internal components (RTP, SEEN Score, etc.) - only available in meesho builds + rtpComponents, seenScoreComponents, err := InternalComponentBuilderInstance.GetInternalComponents(request, internalFeatures, m.EtcdConfig, token) + if err != nil { + return InferflowConfig{}, err + } + + featureComponents, err := GetFeatureComponents(request, featureList, pcvrCalibrationFeatures, pctrCalibrationFeatures, m.EtcdConfig, token, entityIDs) + if err != nil { + return InferflowConfig{}, err + } + + componentConfig, err := GetComponentConfig(featureComponents, rtpComponents, seenScoreComponents, NumerixComponents, predatorComponents) + if err != nil { + return InferflowConfig{}, err + } + + dagExecutionConfig, err := GetDagExecutionConfig(request, featureComponents, rtpComponents, seenScoreComponents, NumerixComponents, predatorComponents, m.EtcdConfig) + if err != nil { + return InferflowConfig{}, err + } + + mpConfig := InferflowConfig{ + DagExecutionConfig: dagExecutionConfig, + ComponentConfig: componentConfig, + ResponseConfig: responseConfig, + } + + return mpConfig, nil +} + +// GetFeatureList extracts features from the request and fetches the component features from the etcd config +// and returns a set of features, a map of feature to data type, a map of offline feature to online feature +// and an error if any. +func GetFeatureList(request InferflowOnboardRequest, etcdConfig etcd.Manager, token string, entityIDs map[string]bool) (mapset.Set[string], map[string]string, mapset.Set[string], mapset.Set[string], mapset.Set[string], map[string]string, map[string]string, error) { + initialFeatures, featureToDataType, predatorAndIrisOutputsToDataType := extractFeatures(request, entityIDs) + + // Process internal features first (RTP, SEEN Score, etc.) - only available in meesho builds + internalFeatures, internalFeatureToDataType, err := InternalComponentBuilderInstance.ProcessFeatures(initialFeatures, featureToDataType) + if err != nil { + return nil, nil, nil, nil, nil, nil, nil, err + } + + // Remove internal features from initial features before standard classification + for f := range internalFeatures.Iter() { + initialFeatures.Remove(f) + } + + offlineFeatures, onlineFeatures, defaultFeatures, pctrCalibrationFeatures, pcvrCalibrationFeatures, newFeatureToDataType, err := classifyFeatures(initialFeatures, featureToDataType) + if err != nil { + return nil, nil, nil, nil, nil, nil, nil, err + } + + offlineToOnlineMapping, err := mapOfflineFeatures(offlineFeatures, token) + if err != nil { + return nil, nil, nil, nil, nil, nil, nil, err + } + for f, dtype := range newFeatureToDataType { + featureToDataType[f] = dtype + } + for f, dtype := range internalFeatureToDataType { + featureToDataType[f] = dtype + } + + features := mapset.NewSet[string]() + for offlineFeature, onlineFeature := range offlineToOnlineMapping { + features.Add(onlineFeature) + featureToDataType[onlineFeature] = featureToDataType[offlineFeature] + delete(featureToDataType, offlineFeature) + } + + for _, f := range onlineFeatures.ToSlice() { + features.Add(f) + } + + // Fetch internal component features (only available in meesho builds) + internalFSFeatures, newInternalFeatures, internalComponentFeatureToDataType, err := InternalComponentBuilderInstance.FetchInternalComponentFeatures(internalFeatures, etcdConfig) + if err != nil { + return nil, nil, nil, nil, nil, nil, nil, err + } + + // Add FS features to the main features set + for _, f := range internalFSFeatures.ToSlice() { + features.Add(f) + } + + // Add newly discovered internal features + for _, f := range newInternalFeatures.ToSlice() { + internalFeatures.Add(f) + } + + for f, dtype := range internalComponentFeatureToDataType { + featureToDataType[f] = dtype + } + + // Fetch component features from regular FS components + componentFSFeatures, newfeatureToDataType, err := fetchComponentFeatures(features, pctrCalibrationFeatures, pcvrCalibrationFeatures, etcdConfig, request.Payload.RealEstate, token) + if err != nil { + return nil, nil, nil, nil, nil, nil, nil, err + } + + // Add FS features to the main features set + for _, f := range componentFSFeatures.ToSlice() { + features.Add(f) + } + + for f, dtype := range newfeatureToDataType { + featureToDataType[f] = dtype + } + + for _, f := range defaultFeatures.ToSlice() { + if _, exists := predatorAndIrisOutputsToDataType[f]; exists { + continue + } + if featureToDataType[f] == "" { + featureToDataType[f] = "String" + } + } + + if err := fetchMissingDatatypes(featureToDataType, internalFeatures, pctrCalibrationFeatures, pcvrCalibrationFeatures, onlineFeatures, token); err != nil { + return nil, nil, nil, nil, nil, nil, nil, err + } + + return features, featureToDataType, internalFeatures, pctrCalibrationFeatures, pcvrCalibrationFeatures, predatorAndIrisOutputsToDataType, offlineToOnlineMapping, nil +} + +func extractEntityIDs(request InferflowOnboardRequest) map[string]bool { + entityIDs := make(map[string]bool) + for _, ranker := range request.Payload.Rankers { + entityID := strings.Join(ranker.EntityID, COLON_DELIMITER) + entityIDs[entityID] = false + } + for _, reRanker := range request.Payload.ReRankers { + entityID := strings.Join(reRanker.EntityID, COLON_DELIMITER) + entityIDs[entityID] = false + } + return entityIDs +} + +// extractFeatures extracts features from the request and returns a set of features and a map of +// feature to data type, the features in it will have atleast two parts after split by PIPE_DELIMITER +// and the feature should not be the entity_id +func extractFeatures(request InferflowOnboardRequest, entityIDs map[string]bool) (mapset.Set[string], map[string]string, map[string]string) { + features := mapset.NewSet[string]() + featureToDataType := make(map[string]string) + outputFeatures := mapset.NewSet[string]() + predatorOutputs := mapset.NewSet[string]() + predatorAndNumerixOutputsToDataType := make(map[string]string) + + for _, ranker := range request.Payload.Rankers { + for _, output := range ranker.Outputs { + for _, ms := range output.ModelScores { + predatorOutputs.Add(ms) + predatorAndNumerixOutputsToDataType[ms] = output.DataType + } + } + } + + addFeature := func(feature, dtype string) { + if _, ok := entityIDs[feature]; ok { + return + } + if parts := strings.Split(feature, PIPE_DELIMITER); len(parts) >= 2 && !predatorOutputs.Contains(feature) { + features.Add(feature) + featureToDataType[feature] = dtype + } + } + + for _, ranker := range request.Payload.Rankers { + for _, input := range ranker.Inputs { + for _, feature := range input.Features { + addFeature(feature, "") + } + } + + for _, output := range ranker.Outputs { + outputFeatures.Add(output.Name) + } + } + + for _, reRanker := range request.Payload.ReRankers { + for _, feature := range reRanker.EqVariables { + if _, ok := entityIDs[feature]; ok { + continue + } + parts := strings.Split(feature, PIPE_DELIMITER) + if len(parts) < 2 { + continue + } + featureName := parts[1] + if predatorOutputs.Contains(feature) || predatorOutputs.Contains(featureName) { + continue + } + features.Add(feature) + featureToDataType[feature] = "" + } + predatorAndNumerixOutputsToDataType[reRanker.Score] = reRanker.DataType + } + + return features, featureToDataType, predatorAndNumerixOutputsToDataType +} + +func fetchMissingDatatypes( + featureToDataType map[string]string, + internalFeatures mapset.Set[string], + pctrCalibrationFeatures mapset.Set[string], + pcvrCalibrationFeatures mapset.Set[string], + onlineFeatures mapset.Set[string], + token string, +) error { + hasEmptyDatatype := false + for _, dtype := range featureToDataType { + if dtype == "" { + hasEmptyDatatype = true + break + } + } + if !hasEmptyDatatype { + return nil + } + + horizonFeatures := make(map[string]struct{ label, group string }) + + for feature, dtype := range featureToDataType { + if dtype != "" { + continue + } + + parts := strings.Split(feature, COLON_DELIMITER) + + // Calibration features + if pctrCalibrationFeatures.Contains(feature) { + if len(parts) >= 3 { + label := parts[1] + group := parts[2] + horizonFeatures[feature] = struct{ label, group string }{label, group} + } + continue + } + if pcvrCalibrationFeatures.Contains(feature) { + if len(parts) >= 3 { + label := parts[1] + group := parts[2] + horizonFeatures[feature] = struct{ label, group string }{label, group} + } + continue + } + + // Skip internal features - they are handled by the internal component builder + if internalFeatures.Contains(feature) { + continue + } + + // Check if it's an online feature + if onlineFeatures.Contains(feature) { + if len(parts) >= 2 { + if strings.HasPrefix(feature, "parent:") && len(parts) == 2 { + continue + } + label := parts[0] + group := parts[1] + horizonFeatures[feature] = struct{ label, group string }{label, group} + } + continue + } + } + + // Fetch missing internal feature data types (only available in meesho builds) + if err := InternalComponentBuilderInstance.FetchMissingInternalDataTypes(featureToDataType, internalFeatures); err != nil { + return err + } + + // Query Horizon API for remaining features grouped by label + if len(horizonFeatures) > 0 { + labelToGroups := make(map[string]mapset.Set[string]) + + for _, labelGroup := range horizonFeatures { + if labelToGroups[labelGroup.label] == nil { + labelToGroups[labelGroup.label] = mapset.NewSet[string]() + } + labelToGroups[labelGroup.label].Add(labelGroup.group) + } + + labelToGroupDataType := make(map[string]map[string]string) + for label := range labelToGroups { + featureGroupDataTypeMap, err := GetFeatureGroupDataTypeMap(label, token) + if err != nil { + continue + } + labelToGroupDataType[label] = featureGroupDataTypeMap + } + + for feature, labelGroup := range horizonFeatures { + if featureToDataType[feature] != "" { + continue + } + + if groupMap, exists := labelToGroupDataType[labelGroup.label]; exists { + if dataType, exists := groupMap[labelGroup.group]; exists { + featureToDataType[feature] = dataType + } + } + } + } + + return nil +} + +// classifyFeatures classifies features into offline, online and default features +// and returns a set of features for each class and a map of feature to data type +// Note: Internal features (RTP, SEEN Score) are handled separately by InternalComponentBuilder +func classifyFeatures( + featureList mapset.Set[string], + featureDataTypes map[string]string, +) (mapset.Set[string], mapset.Set[string], mapset.Set[string], mapset.Set[string], mapset.Set[string], map[string]string, error) { + defaultFeatures := mapset.NewSet[string]() + modelFeatures := mapset.NewSet[string]() + onlineFeatures := mapset.NewSet[string]() + offlineFeatures := mapset.NewSet[string]() + pctrCalibrationFeatures := mapset.NewSet[string]() + pcvrCalibrationFeatures := mapset.NewSet[string]() + newFeatureToDataType := make(map[string]string) + + add := func(name, originalFeature string, featureType string) error { + if err := AddFeatureToSet(&defaultFeatures, &modelFeatures, &onlineFeatures, &offlineFeatures, &pctrCalibrationFeatures, &pcvrCalibrationFeatures, name, featureType); err != nil { + return fmt.Errorf("error classifying feature: %w", err) + } + newFeatureToDataType[name] = featureDataTypes[originalFeature] + return nil + } + + for feature := range featureList.Iter() { + // Check if this is an internal feature - skip if so (handled by InternalComponentBuilder) + if _, isInternal := InternalComponentBuilderInstance.ClassifyFeature(feature); isInternal { + continue + } + + transformedFeature, featureType, err := transformFeature(feature) + if err != nil { + return nil, nil, nil, nil, nil, nil, err + } + + if err := add(transformedFeature, feature, featureType); err != nil { + return nil, nil, nil, nil, nil, nil, err + } + } + + return offlineFeatures, onlineFeatures, defaultFeatures, pctrCalibrationFeatures, pcvrCalibrationFeatures, newFeatureToDataType, nil +} + +func AddFeatureToSet(defaultFeatures, modelFeatures, onlineFeatures, offlineFeatures, pctrCalibrationFeatures, pcvrCalibrationFeatures *mapset.Set[string], feature string, featureType string) error { + allSets := map[string]*mapset.Set[string]{ + featureClassDefault: defaultFeatures, + featureClassModel: modelFeatures, + featureClassOnline: onlineFeatures, + featureClassOffline: offlineFeatures, + featureClassPCTRCalibration: pctrCalibrationFeatures, + featureClassPCVRCalibration: pcvrCalibrationFeatures, + } + + for setType, set := range allSets { + if setType != featureType && (*set).Contains(feature) { + return fmt.Errorf("feature '%s' already exists in %s features, cannot add to %s features", feature, setType, featureType) + } + } + + targetSet, exists := allSets[featureType] + if !exists { + return fmt.Errorf("invalid feature type '%s' for feature '%s'", featureType, feature) + } + + (*targetSet).Add(feature) + return nil +} + +// transformFeature transforms the feature to either online, offline or default feature +// and returns the transformed feature and the feature type. +// Note: Internal features (RTP, SEEN Score) are handled by InternalComponentBuilder.ClassifyFeature() +func transformFeature(feature string) (string, string, error) { + parts := strings.Split(feature, PIPE_DELIMITER) + if len(parts) < 2 { + return "", featureClassInvalid, fmt.Errorf("feature %s is invalid", feature) + } + + featureTypes := strings.Split(parts[0], UNDERSCORE_DELIMITER) + featureName := parts[1] + + if parts[0] == PCTR_CALIBRATION { + return strings.ToLower(PCTR_CALIBRATION) + COLON_DELIMITER + featureName, featureClassPCTRCalibration, nil + } + if parts[0] == PCVR_CALIBRATION { + return strings.ToLower(PCVR_CALIBRATION) + COLON_DELIMITER + featureName, featureClassPCVRCalibration, nil + } + + if featureTypes[0] == PARENT { + if len(featureTypes) > 1 { + newFeature := strings.ToLower(featureTypes[0]) + COLON_DELIMITER + featureName + switch featureTypes[1] { + case DEFAULT_FEATURE: + return newFeature, featureClassDefault, nil + case MODEL_FEATURE: + return newFeature, featureClassModel, nil + case ONLINE_FEATURE, CALIBRATION: + return newFeature, featureClassOnline, nil + case OFFLINE_FEATURE: + return newFeature, featureClassOffline, nil + case PCVR_CALIBRATION: + return newFeature, featureClassPCVRCalibration, nil + case PCTR_CALIBRATION: + return newFeature, featureClassPCTRCalibration, nil + } + } + } + + switch featureTypes[0] { + case DEFAULT_FEATURE: + return featureName, featureClassDefault, nil + case MODEL_FEATURE: + return featureName, featureClassModel, nil + case ONLINE_FEATURE, CALIBRATION: + return featureName, featureClassOnline, nil + case OFFLINE_FEATURE: + return featureName, featureClassOffline, nil + case PCVR_CALIBRATION: + return featureName, featureClassPCVRCalibration, nil + case PCTR_CALIBRATION: + return featureName, featureClassPCTRCalibration, nil + } + + return featureName, featureClassDefault, nil +} + +// mapOfflineFeatures maps the offline features to the online features +// and returns a map of offline feature to online feature +func mapOfflineFeatures(offlineFeatureList mapset.Set[string], token string) (map[string]string, error) { + return GetOnlineFeatureMapping(offlineFeatureList, token) +} + +func getComponentDataOrDefault(etcdConfig etcd.Manager, componentName string) *etcd.ComponentData { + componentData := etcdConfig.GetComponentData(componentName) + if componentData == nil { + log.Error().Msgf("missing component '%s', using default values", componentName) + defaultComponentID := componentName + "_id" + return &etcd.ComponentData{ + ComponentID: defaultComponentID, + CompositeID: false, + ExecutionDependency: FEATURE_INITIALIZER, + FSFlattenResKeys: map[string]string{ + "0": defaultComponentID, + }, + FSIdSchemaToValueColumns: map[string]etcd.FSIdSchemaToValueColumnPair{ + "0": { + Schema: defaultComponentID, + ValueCol: defaultComponentID, + DataType: "FP32", //Not being used currently TODO: figure out better handling + }, + }, + Overridecomponent: make(map[string]etcd.OverrideComponent), + } + } + + if componentData.FSFlattenResKeys == nil { + componentData.FSFlattenResKeys = make(map[string]string) + } + if componentData.FSIdSchemaToValueColumns == nil { + componentData.FSIdSchemaToValueColumns = make(map[string]etcd.FSIdSchemaToValueColumnPair) + } + if componentData.Overridecomponent == nil { + componentData.Overridecomponent = make(map[string]etcd.OverrideComponent) + } + + return componentData +} + +// fetchComponentFeatures fetches the component features from the etcd config +// and returns the features set and a map of feature to data type +func fetchComponentFeatures(features mapset.Set[string], pctrCalibrationFeatures mapset.Set[string], pcvrCalibrationFeatures mapset.Set[string], etcdConfig etcd.Manager, realEstate string, token string) (mapset.Set[string], map[string]string, error) { + componentList := getComponentList(features, pctrCalibrationFeatures, pcvrCalibrationFeatures) + fsFeatures := mapset.NewSet[string]() + featureToDataType := make(map[string]string) + + for _, component := range componentList.ToSlice() { + componentData := getComponentDataOrDefault(etcdConfig, component) + + for _, pair := range componentData.FSIdSchemaToValueColumns { + if strings.Contains(pair.ValueCol, COLON_DELIMITER) { + fsFeatures.Add(pair.ValueCol) + featureToDataType[pair.ValueCol] = pair.DataType + } + } + + if override, hasOverride := componentData.Overridecomponent[realEstate]; hasOverride { + fsFeatures.Add(override.ComponentId) + parts := strings.Split(override.ComponentId, COLON_DELIMITER) + var label, group string + + if len(parts) == 3 { + label, group = parts[0], parts[1] + } else if len(parts) == 4 { + label, group = parts[1], parts[2] + } else { + return nil, nil, fmt.Errorf("component data: invalid override component id: %s", override.ComponentId) + } + + featureGroupDataTypeMap, err := GetFeatureGroupDataTypeMap(label, token) + if err != nil { + return nil, nil, fmt.Errorf("component data: error getting feature group data type map: %w", err) + } + + if dataType, exists := featureGroupDataTypeMap[group]; exists { + featureToDataType[override.ComponentId] = dataType + } else { + return nil, nil, fmt.Errorf("component data: feature group data type not found for %s: %s", override.ComponentId, group) + } + } + } + + return fsFeatures, featureToDataType, nil +} + +// getComponentList gets the component list from the features +// and returns a set of component names. The component name can be of these given types: +// 1. parent_