Magento_Quote Performance
Magento_Quote Performance
Magento_Quote Performance Optimization
Overview
This document provides comprehensive performance optimization strategies for the Magento_Quote module, covering cart operations, totals calculation, database queries, caching, and scalability patterns for high-traffic stores.
Target Version: Magento 2.4.7+ | Adobe Commerce & Open Source PHP Version: 8.2+
Performance Baseline
Typical Cart Operation Timings (Single Item)
| Operation | Time (ms) | Database Queries | Cache Impact |
|---|---|---|---|
| Load Quote | 5-15 | 3-5 | Read from session |
| Add Item | 50-100 | 8-12 | Invalidate cart section |
| Update Quantity | 40-80 | 6-10 | Invalidate cart section |
| Apply Coupon | 60-120 | 10-15 | Invalidate cart section |
| Totals Collection | 30-80 | 5-10 | No FPC impact |
| Place Order | 200-500 | 25-40 | No FPC impact |
Large Cart Performance (100+ Items)
| Operation | Time (ms) | Scaling Factor |
|---|---|---|
| Load Quote | 50-150 | Linear O(n) |
| Totals Collection | 500-2000 | O(n * m) collectors |
| Apply Discount | 800-3000 | O(n * rules) |
| Place Order | 2000-8000 | Linear O(n) |
1. Database Optimization
1.1 Essential Indexes
Ensure these indexes exist for optimal quote queries:
-- Quote table indexes
ALTER TABLE quote
ADD INDEX idx_customer_active (customer_id, is_active, store_id),
ADD INDEX idx_updated_at (updated_at),
ADD INDEX idx_reserved_order (reserved_order_id);
-- Quote item indexes
ALTER TABLE quote_item
ADD INDEX idx_quote_id (quote_id),
ADD INDEX idx_product_id (product_id),
ADD INDEX idx_sku (sku);
-- Quote address indexes
ALTER TABLE quote_address
ADD INDEX idx_quote_id (quote_id),
ADD INDEX idx_customer_address_id (customer_address_id);
-- Quote payment indexes
ALTER TABLE quote_payment
ADD INDEX idx_quote_id (quote_id);
-- Quote ID mask indexes (guest carts)
ALTER TABLE quote_id_mask
ADD UNIQUE INDEX idx_masked_id (masked_id),
ADD INDEX idx_quote_id (quote_id);
Verification:
-- Check if indexes exist
SHOW INDEX FROM quote WHERE Key_name = 'idx_customer_active';
SHOW INDEX FROM quote_item WHERE Key_name = 'idx_quote_id';
1.2 Query Optimization
Avoid SELECT *
<?php
// BAD - Loads all columns
$collection = $this->quoteCollectionFactory->create();
$quotes = $collection->getItems();
// GOOD - Load only needed columns
$collection = $this->quoteCollectionFactory->create();
$collection->addFieldToSelect(['entity_id', 'customer_id', 'items_count', 'grand_total']);
$quotes = $collection->getItems();
Use Batch Loading
<?php
declare(strict_types=1);
namespace Vendor\Module\Service;
use Magento\Quote\Model\ResourceModel\Quote\CollectionFactory;
/**
* Batch load quotes with items
*/
class BatchQuoteLoaderService
{
public function __construct(
private readonly CollectionFactory $quoteCollectionFactory
) {}
/**
* Load multiple quotes with items in single query
*
* @param array $quoteIds
* @return array
*/
public function loadQuotesWithItems(array $quoteIds): array
{
$collection = $this->quoteCollectionFactory->create();
$collection->addFieldToFilter('entity_id', ['in' => $quoteIds]);
// OPTIMIZATION: Eager load items to avoid N+1 queries
foreach ($collection as $quote) {
$quote->getItemsCollection(); // Loads all items in single query per quote
}
return $collection->getItems();
}
}
1.3 Connection Pooling
Configure persistent database connections:
// app/etc/env.php
return [
'db' => [
'connection' => [
'default' => [
'host' => 'localhost',
'dbname' => 'magento',
'username' => 'magento',
'password' => 'password',
'model' => 'mysql4',
'engine' => 'innodb',
'initStatements' => 'SET NAMES utf8;',
'active' => '1',
'persistent' => '1', // Enable persistent connections
'driver_options' => [
PDO::MYSQL_ATTR_LOCAL_INFILE => 1,
PDO::ATTR_EMULATE_PREPARES => false
]
]
]
]
];
2. Totals Calculation Optimization
2.1 Collector Ordering
Optimize collector execution order to minimize redundant calculations:
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Sales:etc/sales.xsd">
<section name="quote">
<group name="totals">
<!-- Optimal execution order -->
<item name="subtotal" instance="Magento\Quote\Model\Quote\Address\Total\Subtotal" sort_order="100"/>
<item name="shipping" instance="Magento\Quote\Model\Quote\Address\Total\Shipping" sort_order="200"/>
<!-- Custom collectors between 200-300 if they affect tax calculation -->
<item name="tax" instance="Magento\Tax\Model\Sales\Total\Quote\Tax" sort_order="300"/>
<item name="discount" instance="Magento\SalesRule\Model\Quote\Discount" sort_order="400"/>
<!-- Grand total must be last -->
<item name="grand_total" instance="Magento\Quote\Model\Quote\Address\Total\Grand" sort_order="500"/>
</group>
</section>
</config>
2.2 Conditional Collector Execution
Skip expensive collectors when not needed:
<?php
declare(strict_types=1);
namespace Vendor\Module\Model\Quote\Address\Total;
use Magento\Quote\Model\Quote;
use Magento\Quote\Api\Data\ShippingAssignmentInterface;
use Magento\Quote\Model\Quote\Address\Total;
/**
* Optimized custom fee collector
*/
class CustomFee extends \Magento\Quote\Model\Quote\Address\Total\AbstractTotal
{
protected string $_code = 'custom_fee';
public function __construct(
private readonly \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
) {}
public function collect(
Quote $quote,
ShippingAssignmentInterface $shippingAssignment,
Total $total
): self {
parent::collect($quote, $shippingAssignment, $total);
// OPTIMIZATION: Skip if feature disabled
if (!$this->scopeConfig->isSetFlag('vendor/custom_fee/enabled')) {
return $this;
}
// OPTIMIZATION: Skip if virtual quote (no shipping)
if ($quote->isVirtual()) {
return $this;
}
// OPTIMIZATION: Skip if no items
$items = $shippingAssignment->getItems();
if (!count($items)) {
return $this;
}
// Calculate fee only if necessary
$fee = $this->calculateFee($total);
$total->setTotalAmount($this->getCode(), $fee);
$total->setBaseTotalAmount($this->getCode(), $fee);
return $this;
}
/**
* Lazy calculation - only when needed
*/
private function calculateFee(Total $total): float
{
// Expensive calculation here
return 10.00;
}
}
2.3 Totals Caching
Cache totals for read-heavy scenarios:
<?php
declare(strict_types=1);
namespace Vendor\Module\Plugin\Quote;
use Magento\Quote\Model\Quote;
use Magento\Quote\Model\Quote\TotalsCollector;
use Magento\Quote\Model\Quote\Address\Total;
/**
* Cache quote totals
*/
class CacheTotalsExtend
{
private const CACHE_LIFETIME = 300; // 5 minutes
private const CACHE_TAG = 'quote_totals';
public function __construct(
private readonly \Magento\Framework\App\CacheInterface $cache,
private readonly \Magento\Framework\Serialize\SerializerInterface $serializer
) {}
/**
* Cache totals collection result
*
* @param TotalsCollector $subject
* @param callable $proceed
* @param Quote $quote
* @return Total
*/
public function aroundCollect(
TotalsCollector $subject,
callable $proceed,
Quote $quote
): Total {
// Generate cache key based on quote state
$cacheKey = $this->getCacheKey($quote);
// Try cache first
$cachedData = $this->cache->load($cacheKey);
if ($cachedData) {
return $this->serializer->unserialize($cachedData);
}
// Calculate totals
$totals = $proceed($quote);
// Cache result
$this->cache->save(
$this->serializer->serialize($totals),
$cacheKey,
[self::CACHE_TAG, 'quote_' . $quote->getId()],
self::CACHE_LIFETIME
);
return $totals;
}
/**
* Generate cache key from quote state
*
* @param Quote $quote
* @return string
*/
private function getCacheKey(Quote $quote): string
{
$keyData = [
'quote_id' => $quote->getId(),
'updated_at' => $quote->getUpdatedAt(),
'items_count' => $quote->getItemsCount(),
'coupon_code' => $quote->getCouponCode(),
'customer_group_id' => $quote->getCustomerGroupId(),
'store_id' => $quote->getStoreId()
];
return self::CACHE_TAG . '_' . md5($this->serializer->serialize($keyData));
}
}
Configuration:
<type name="Magento\Quote\Model\Quote\TotalsCollector">
<plugin name="vendor_cache_totals"
type="Vendor\Module\Plugin\Quote\CacheTotalsExtend"
sortOrder="10"/>
</type>
3. Caching Strategies
3.1 Full Page Cache (FPC) Exclusions
Cart data is customer-specific and must be excluded from FPC:
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<!-- Cart block excluded from FPC -->
<referenceBlock name="minicart">
<arguments>
<argument name="cache_lifetime" xsi:type="null"/>
<argument name="cache_key" xsi:type="null"/>
</arguments>
</referenceBlock>
</body>
</page>
3.2 Customer Section Data
Use section data for dynamic cart updates without full page reload:
// Load cart data via section API
require(['Magento_Customer/js/customer-data'], function (customerData) {
var cart = customerData.get('cart');
// Subscribe to changes
cart.subscribe(function (updatedCart) {
console.log('Cart updated:', updatedCart);
console.log('Items count:', updatedCart.summary_count);
console.log('Subtotal:', updatedCart.subtotal);
});
// Trigger reload
customerData.reload(['cart']);
});
Backend Implementation:
<?php
declare(strict_types=1);
namespace Magento\Checkout\CustomerData;
use Magento\Customer\CustomerData\SectionSourceInterface;
/**
* Cart section data provider
*/
class Cart implements SectionSourceInterface
{
public function __construct(
private readonly \Magento\Checkout\Model\Session $checkoutSession,
private readonly \Magento\Checkout\Helper\Data $checkoutHelper
) {}
/**
* Get cart section data
*
* OPTIMIZATION: Return minimal data, defer heavy calculations
*
* @return array
*/
public function getSectionData(): array
{
$quote = $this->checkoutSession->getQuote();
if (!$quote->getId()) {
return [
'summary_count' => 0,
'subtotal' => 0,
'items' => []
];
}
// Return lightweight data
return [
'summary_count' => $quote->getItemsQty(),
'subtotal' => $quote->getSubtotal(),
'subtotal_formatted' => $this->checkoutHelper->formatPrice($quote->getSubtotal()),
'items' => $this->getItemsData($quote), // Only summary, not full item data
'extra_actions' => $this->checkoutHelper->getCartExtraActions()
];
}
/**
* Get minimal item data
*
* @param \Magento\Quote\Model\Quote $quote
* @return array
*/
private function getItemsData(\Magento\Quote\Model\Quote $quote): array
{
$items = [];
foreach ($quote->getAllVisibleItems() as $item) {
$items[] = [
'item_id' => $item->getId(),
'product_name' => $item->getName(),
'qty' => $item->getQty(),
'product_price_value' => $item->getPrice()
];
}
return $items;
}
}
3.3 Redis Session Storage
Use Redis for session storage to improve quote access performance:
// app/etc/env.php
return [
'session' => [
'save' => 'redis',
'redis' => [
'host' => '127.0.0.1',
'port' => '6379',
'password' => '',
'timeout' => '2.5',
'persistent_identifier' => '',
'database' => '2',
'compression_threshold' => '2048',
'compression_library' => 'gzip',
'log_level' => '4',
'max_concurrency' => '20',
'break_after_frontend' => '5',
'break_after_adminhtml' => '30',
'first_lifetime' => '600',
'bot_first_lifetime' => '60',
'bot_lifetime' => '7200',
'disable_locking' => '0',
'min_lifetime' => '60',
'max_lifetime' => '2592000'
]
]
];
4. Query Reduction Techniques
4.1 Lazy Loading Prevention
<?php
declare(strict_types=1);
namespace Vendor\Module\Service;
use Magento\Quote\Api\CartRepositoryInterface;
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
/**
* Eager load products to prevent N+1 queries
*/
class OptimizedCartLoaderService
{
public function __construct(
private readonly CartRepositoryInterface $cartRepository,
private readonly CollectionFactory $productCollectionFactory
) {}
/**
* Load quote with all related data in minimal queries
*
* @param int $quoteId
* @return \Magento\Quote\Api\Data\CartInterface
*/
public function loadQuoteOptimized(int $quoteId): \Magento\Quote\Api\Data\CartInterface
{
// Load quote (1 query)
$quote = $this->cartRepository->get($quoteId);
// Collect all product IDs (1 query for items)
$productIds = [];
foreach ($quote->getAllItems() as $item) {
$productIds[] = $item->getProductId();
}
// Eager load all products (1 query)
if (!empty($productIds)) {
$productCollection = $this->productCollectionFactory->create();
$productCollection->addFieldToFilter('entity_id', ['in' => $productIds]);
$productCollection->addAttributeToSelect(['name', 'sku', 'price', 'image']);
$products = $productCollection->getItems();
// Attach products to items (no additional queries)
foreach ($quote->getAllItems() as $item) {
if (isset($products[$item->getProductId()])) {
$item->setProduct($products[$item->getProductId()]);
}
}
}
// Total queries: 3 (quote, items, products) instead of 1 + N
return $quote;
}
}
4.2 Collection Optimization
<?php
declare(strict_types=1);
namespace Vendor\Module\Service;
use Magento\Quote\Model\ResourceModel\Quote\CollectionFactory;
/**
* Optimized quote collection loading
*/
class QuoteCollectionService
{
public function __construct(
private readonly CollectionFactory $quoteCollectionFactory
) {}
/**
* Load active customer quotes efficiently
*
* @param int $customerId
* @return array
*/
public function getActiveQuotes(int $customerId): array
{
$collection = $this->quoteCollectionFactory->create();
// OPTIMIZATION: Filter at database level
$collection->addFieldToFilter('customer_id', $customerId);
$collection->addFieldToFilter('is_active', 1);
// OPTIMIZATION: Select only needed fields
$collection->addFieldToSelect([
'entity_id',
'created_at',
'updated_at',
'items_count',
'items_qty',
'grand_total',
'store_id'
]);
// OPTIMIZATION: Limit results
$collection->setPageSize(10);
$collection->setCurPage(1);
// OPTIMIZATION: Order by most recent
$collection->setOrder('updated_at', 'DESC');
return $collection->getItems();
}
}
5. Large Cart Optimization
5.1 Item Pagination
For carts with 100+ items, paginate item display:
<?php
declare(strict_types=1);
namespace Vendor\Module\Block\Cart;
use Magento\Framework\View\Element\Template;
/**
* Paginated cart items display
*/
class Items extends Template
{
private const ITEMS_PER_PAGE = 20;
public function __construct(
Template\Context $context,
private readonly \Magento\Checkout\Model\Session $checkoutSession,
array $data = []
) {
parent::__construct($context, $data);
}
/**
* Get items for current page
*
* @return array
*/
public function getItems(): array
{
$quote = $this->checkoutSession->getQuote();
$allItems = $quote->getAllVisibleItems();
$currentPage = (int)$this->getRequest()->getParam('p', 1);
$offset = ($currentPage - 1) * self::ITEMS_PER_PAGE;
return array_slice($allItems, $offset, self::ITEMS_PER_PAGE);
}
/**
* Get total pages
*
* @return int
*/
public function getTotalPages(): int
{
$quote = $this->checkoutSession->getQuote();
$totalItems = $quote->getItemsCount();
return (int)ceil($totalItems / self::ITEMS_PER_PAGE);
}
}
5.2 Async Totals Calculation
Calculate totals asynchronously for very large carts:
<?php
declare(strict_types=1);
namespace Vendor\Module\Service;
use Magento\Quote\Api\CartRepositoryInterface;
use Magento\Framework\MessageQueue\PublisherInterface;
/**
* Async totals calculation for large carts
*/
class AsyncTotalsService
{
private const LARGE_CART_THRESHOLD = 50;
public function __construct(
private readonly CartRepositoryInterface $cartRepository,
private readonly PublisherInterface $publisher
) {}
/**
* Calculate totals async if cart is large
*
* @param int $quoteId
* @return bool True if calculated sync, false if queued
*/
public function calculateTotals(int $quoteId): bool
{
$quote = $this->cartRepository->get($quoteId);
if ($quote->getItemsCount() < self::LARGE_CART_THRESHOLD) {
// Small cart - calculate synchronously
$quote->collectTotals();
$this->cartRepository->save($quote);
return true;
}
// Large cart - queue for async processing
$this->publisher->publish('quote.totals.calculate', json_encode([
'quote_id' => $quoteId
]));
return false;
}
}
Message Queue Consumer:
<?php
declare(strict_types=1);
namespace Vendor\Module\Model\MessageQueue;
use Magento\Quote\Api\CartRepositoryInterface;
/**
* Process async totals calculation
*/
class TotalsCalculationConsumer
{
public function __construct(
private readonly CartRepositoryInterface $cartRepository,
private readonly \Psr\Log\LoggerInterface $logger
) {}
/**
* Process message
*
* @param string $message
* @return void
*/
public function process(string $message): void
{
$data = json_decode($message, true);
$quoteId = $data['quote_id'];
try {
$quote = $this->cartRepository->get($quoteId);
$quote->collectTotals();
$this->cartRepository->save($quote);
$this->logger->info('Async totals calculated', ['quote_id' => $quoteId]);
} catch (\Exception $e) {
$this->logger->error('Async totals calculation failed', [
'quote_id' => $quoteId,
'error' => $e->getMessage()
]);
}
}
}
6. Scaling Strategies
6.1 Read Replicas
Offload quote reads to database replicas:
// app/etc/env.php
return [
'db' => [
'connection' => [
'default' => [
'host' => 'db-master.example.com',
'dbname' => 'magento',
'username' => 'magento',
'password' => 'password',
'active' => '1'
],
'checkout' => [
'host' => 'db-replica-checkout.example.com',
'dbname' => 'magento',
'username' => 'magento_read',
'password' => 'password',
'active' => '1'
]
],
'slave_connection' => [
'checkout' => [
'host' => 'db-replica-checkout.example.com',
'dbname' => 'magento',
'username' => 'magento_read',
'password' => 'password',
'active' => '1'
]
]
],
'resource' => [
'default_setup' => [
'connection' => 'default'
],
'checkout' => [
'connection' => 'checkout'
]
]
];
6.2 Horizontal Scaling with Sticky Sessions
For multi-server deployments, use sticky sessions to keep customer on same server:
Nginx Configuration:
upstream backend {
ip_hash; # Sticky sessions based on IP
server app1.example.com:9000;
server app2.example.com:9000;
server app3.example.com:9000;
}
server {
location / {
fastcgi_pass backend;
# ... other config
}
}
Alternative: Redis Session Sharing:
All servers share Redis for session storage (configured in section 3.3).
7. Monitoring and Profiling
7.1 Query Logging
Enable slow query log for quote operations:
-- MySQL configuration
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 0.5; -- Log queries > 500ms
SET GLOBAL log_queries_not_using_indexes = 'ON';
7.2 New Relic Instrumentation
Add custom transactions for quote operations:
<?php
declare(strict_types=1);
namespace Vendor\Module\Plugin\Quote;
use Magento\Quote\Api\CartRepositoryInterface;
use Magento\Quote\Api\Data\CartInterface;
/**
* New Relic instrumentation
*/
class NewRelicMonitoringExtend
{
/**
* Monitor quote save performance
*
* @param CartRepositoryInterface $subject
* @param callable $proceed
* @param CartInterface $quote
* @return void
*/
public function aroundSave(
CartRepositoryInterface $subject,
callable $proceed,
CartInterface $quote
): void {
if (extension_loaded('newrelic')) {
newrelic_start_transaction(ini_get('newrelic.appname'));
newrelic_name_transaction('quote/save');
newrelic_add_custom_parameter('quote_id', $quote->getId());
newrelic_add_custom_parameter('items_count', $quote->getItemsCount());
newrelic_add_custom_parameter('grand_total', $quote->getGrandTotal());
}
$startTime = microtime(true);
try {
$proceed($quote);
} finally {
$executionTime = (microtime(true) - $startTime) * 1000; // Convert to ms
if (extension_loaded('newrelic')) {
newrelic_custom_metric('Custom/Quote/Save/Duration', $executionTime);
newrelic_end_transaction();
}
}
}
}
7.3 Performance Metrics Collection
<?php
declare(strict_types=1);
namespace Vendor\Module\Model\Metrics;
/**
* Collect quote performance metrics
*/
class QuoteMetricsCollector
{
private array $metrics = [];
public function __construct(
private readonly \Psr\Log\LoggerInterface $logger
) {}
/**
* Record metric
*
* @param string $operation
* @param float $duration
* @param array $context
* @return void
*/
public function record(string $operation, float $duration, array $context = []): void
{
$this->metrics[] = [
'operation' => $operation,
'duration_ms' => round($duration * 1000, 2),
'context' => $context,
'timestamp' => time()
];
// Log slow operations
if ($duration > 1.0) { // > 1 second
$this->logger->warning('Slow quote operation detected', [
'operation' => $operation,
'duration_ms' => round($duration * 1000, 2),
'context' => $context
]);
}
}
/**
* Get collected metrics
*
* @return array
*/
public function getMetrics(): array
{
return $this->metrics;
}
/**
* Clear metrics
*
* @return void
*/
public function clear(): void
{
$this->metrics = [];
}
}
8. Best Practices Summary
Do's
- Use Repository Pattern - Always use
CartRepositoryInterfacefor persistence - Batch Operations - Group multiple item additions before
collectTotals() - Eager Load - Preload related data to avoid N+1 queries
- Index Properly - Ensure database indexes on frequently queried columns
- Cache Aggressively - Cache totals, section data, and expensive calculations
- Monitor Queries - Track slow queries and optimize them
- Test at Scale - Performance test with 100+ items in cart
Don'ts
- Don't Call collectTotals() Multiple Times - Batch changes first
- **Don't Use SELECT *** - Load only needed columns
- Don't Bypass Repositories - Avoid direct model save()
- Don't Load Full Collections - Use pagination and filtering
- Don't Trust Client Data - Always validate server-side
- Don't Block on Heavy Operations - Use async processing for large carts
- Don't Ignore Indexes - Missing indexes cause table scans
9. Performance Checklist
Pre-Production
- [ ] Database indexes created and verified
- [ ] Redis configured for sessions and cache
- [ ] Slow query log enabled
- [ ] New Relic or equivalent APM configured
- [ ] Load testing performed with realistic cart sizes
- [ ] Totals collectors have optimal sort_order
- [ ] Custom collectors skip when not needed
- [ ] Section data returns minimal payload
- [ ] Query count verified for key operations
Monitoring
- [ ] Track average cart operation times
- [ ] Monitor database query count per request
- [ ] Alert on slow totals collection (> 500ms)
- [ ] Monitor cart abandonment rate
- [ ] Track large cart frequency (100+ items)
- [ ] Monitor Redis memory usage
- [ ] Track session storage performance
Assumptions: - Magento 2.4.7+ with PHP 8.2+ - MySQL 8.0+ or MariaDB 10.6+ - Redis 6.0+ for session and cache - Multiple application servers for scaling - CDN for static content
Why This Approach: Performance optimization requires measurement-driven decisions. Caching strategies prevent redundant calculations. Database optimization reduces query overhead. Monitoring enables proactive issue detection.
Security Impact: - Caching must not expose customer data - Session sharing requires secure Redis configuration - Monitoring should not log sensitive data - Read replicas must enforce same access controls
Performance Impact: - Proper indexing: 50-80% faster queries - Redis sessions: 30-50% faster quote access - Totals caching: 60-90% faster on repeated access - Eager loading: 40-70% reduction in query count
Backward Compatibility: Performance optimizations should be transparent to API consumers. Caching must invalidate correctly. Async processing requires fallback for real-time needs.
Tests to Add: - Performance benchmarks for key operations - Load tests with 100+ items in cart - Cache invalidation tests - Query count assertions - Scalability tests with concurrent users
Docs to Update: - Performance tuning guide - Infrastructure requirements - Monitoring dashboard setup - Scaling architecture diagrams