Trong bảy bài đầu tiên của series, chúng ta đã tìm hiểu về các khía cạnh khác nhau của tối ưu hóa cơ sở dữ liệu SQL. Bài viết này sẽ chuyển hướng sang thế giới NoSQL và Redis - những hệ thống cơ sở dữ liệu được thiết kế để giải quyết các thách thức mà cơ sở dữ liệu quan hệ truyền thống gặp phải khi xử lý dữ liệu lớn, phân tán và có cấu trúc linh hoạt.

Các cơ sở dữ liệu NoSQL (Not Only SQL) có nhiều loại khác nhau, mỗi loại được tối ưu hóa cho các use cases cụ thể. Trong bài viết này, chúng ta sẽ tập trung vào các hệ thống phổ biến nhất: MongoDB (document store), Cassandra (column-family store), và Redis (key-value store với nhiều tính năng nâng cao).

MongoDB: Document design, aggregation pipeline optimization, sharding

MongoDB là cơ sở dữ liệu NoSQL phổ biến nhất, lưu trữ dữ liệu dưới dạng documents JSON-like (BSON). Nó được thiết kế để cung cấp hiệu năng cao, tính sẵn sàng cao và khả năng mở rộng tự động.

MongoDB Architecture


  graph TD
    A[MongoDB Deployment] --> B[Replica Set]
    A --> C[Sharded Cluster]

    B --> D[Primary Node]
    B --> E[Secondary Node 1]
    B --> F[Secondary Node 2]

    C --> G[Config Servers<br>Replica Set]
    C --> H[Mongos Routers]
    C --> I[Shard 1<br>Replica Set]
    C --> J[Shard 2<br>Replica Set]
    C --> K[Shard N<br>Replica Set]

Document Design Optimization

Thiết kế document hợp lý là yếu tố quan trọng nhất ảnh hưởng đến hiệu năng MongoDB:

  1. Embedding vs Referencing:

MongoDB cho phép hai cách để biểu diễn mối quan hệ giữa các dữ liệu:


  graph TD
    A[Data Modeling] --> B[Embedding<br>Nested Documents]
    A --> C[Referencing<br>Document References]

    B --> D[Pros:<br>- Single query retrieval<br>- Better read performance]
    B --> E[Cons:<br>- Document size limit<br>- Duplication]

    C --> F[Pros:<br>- No duplication<br>- Smaller documents]
    C --> G[Cons:<br>- Multiple queries<br>- Join in application]

Embedding (Nested Documents):

// Embedding comments trong post
{
  "_id": ObjectId("5f8a76b3e6b5a1d8e77c1234"),
  "title": "MongoDB Optimization",
  "content": "This is a post about MongoDB...",
  "author": {
    "name": "John Doe",
    "email": "[email protected]"
  },
  "comments": [
    {
      "user": "Alice",
      "text": "Great post!",
      "date": ISODate("2023-01-15T10:30:00Z")
    },
    {
      "user": "Bob",
      "text": "Thanks for sharing",
      "date": ISODate("2023-01-15T14:20:00Z")
    }
  ]
}

Referencing (Document References):

// Post document
{
  "_id": ObjectId("5f8a76b3e6b5a1d8e77c1234"),
  "title": "MongoDB Optimization",
  "content": "This is a post about MongoDB...",
  "author_id": ObjectId("5f8a76b3e6b5a1d8e77c5678")
}

// Author document
{
  "_id": ObjectId("5f8a76b3e6b5a1d8e77c5678"),
  "name": "John Doe",
  "email": "[email protected]"
}

// Comment documents
{
  "_id": ObjectId("5f8a76b3e6b5a1d8e77c9012"),
  "post_id": ObjectId("5f8a76b3e6b5a1d8e77c1234"),
  "user": "Alice",
  "text": "Great post!",
  "date": ISODate("2023-01-15T10:30:00Z")
}

Nguyên tắc lựa chọn:

  • Sử dụng embedding khi:

    • Dữ liệu “con” luôn được truy cập cùng với dữ liệu “cha”
    • Dữ liệu “con” không tăng trưởng không giới hạn
    • Cần hiệu năng đọc cao
  • Sử dụng referencing khi:

    • Dữ liệu “con” có thể được truy cập độc lập
    • Dữ liệu “con” tăng trưởng không giới hạn
    • Cần tránh duplication
  1. Document Size và Structure:
  • Giới hạn kích thước document: MongoDB có giới hạn 16MB cho mỗi document
  • Tránh arrays quá lớn: Arrays tăng trưởng không giới hạn có thể gây vấn đề
  • Sử dụng field names ngắn gọn: Trong collections lớn, field names ngắn giúp tiết kiệm không gian đáng kể
// Thay vì
{
  "very_long_descriptive_field_name": "value",
  "another_unnecessarily_long_field_name": 123
}

// Nên sử dụng
{
  "vldn": "value",  // very_long_descriptive_field_name
  "aulfn": 123      // another_unnecessarily_long_field_name
}
  1. Schema Validation:

Mặc dù MongoDB là schemaless, việc sử dụng schema validation giúp đảm bảo tính nhất quán của dữ liệu:

db.createCollection("products", {
  validator: {
    $jsonSchema: {
      bsonType: "object",
      required: ["name", "price", "category"],
      properties: {
        name: {
          bsonType: "string",
          description: "must be a string and is required",
        },
        price: {
          bsonType: "number",
          minimum: 0,
          description: "must be a non-negative number and is required",
        },
        category: {
          bsonType: "string",
          description: "must be a string and is required",
        },
        tags: {
          bsonType: "array",
          items: {
            bsonType: "string",
          },
        },
      },
    },
  },
});

Indexing Strategies trong MongoDB

Indexes trong MongoDB tương tự như trong SQL databases, nhưng có một số đặc điểm riêng:

  1. Types of Indexes:
  • Single Field Index:
db.products.createIndex({ name: 1 }); // 1 for ascending, -1 for descending
  • Compound Index:
db.products.createIndex({ category: 1, price: -1 });
  • Multikey Index (cho arrays):
db.products.createIndex({ tags: 1 });
  • Text Index:
db.products.createIndex({ description: "text" });
  • Geospatial Index:
db.locations.createIndex({ coordinates: "2dsphere" });
  • Hashed Index:
db.users.createIndex({ _id: "hashed" });
  1. Index Properties:
  • Unique Index:
db.users.createIndex({ email: 1 }, { unique: true });
  • Partial Index:
db.orders.createIndex(
  { orderDate: 1 },
  { partialFilterExpression: { status: "active" } }
);
  • TTL Index (Time-To-Live):
db.sessions.createIndex(
  { lastModified: 1 },
  { expireAfterSeconds: 3600 } // Auto-delete after 1 hour
);
  1. Index Analysis và Optimization:
  • Explain Plan:
db.products
  .find({ category: "electronics", price: { $gt: 100 } })
  .sort({ price: -1 })
  .explain("executionStats");
  • Index Statistics:
db.products.stats();
db.products.aggregate([{ $indexStats: {} }]);
  • Missing Indexes (slow queries):
db.currentOp({
  secs_running: { $gt: 3 },
  op: "query",
});
  1. Index Best Practices:
  • Tạo indexes hỗ trợ các queries phổ biến
  • Đặt các fields có high cardinality trước trong compound indexes
  • Đặt các fields sử dụng trong equality conditions trước fields sử dụng trong range conditions
  • Tránh tạo quá nhiều indexes (mỗi index làm chậm write operations)
  • Sử dụng covered queries khi có thể (queries chỉ trả về indexed fields)

Aggregation Pipeline Optimization

Aggregation Pipeline là công cụ mạnh mẽ để xử lý và phân tích dữ liệu trong MongoDB:

  1. Pipeline Stages Order:

Thứ tự các stages trong pipeline ảnh hưởng lớn đến hiệu năng:


  graph LR
    A["$match<br>(Filter early)"] --> B["$project<br>(Reduce fields)"]
    B --> C["$unwind<br>(Expand arrays)"]
    C --> D["$group<br>(Aggregate data)"]
    D --> E["$sort<br>(Order results)"]
    E --> F["$limit<br>(Reduce output)"]
// Không tối ưu
db.orders.aggregate([
  { $unwind: "$items" },
  { $match: { status: "completed", "items.price": { $gt: 100 } } },
  { $group: { _id: "$customer_id", total: { $sum: "$items.price" } } },
  { $sort: { total: -1 } },
  { $limit: 10 },
]);

// Tối ưu
db.orders.aggregate([
  { $match: { status: "completed" } }, // Filter sớm
  { $unwind: "$items" },
  { $match: { "items.price": { $gt: 100 } } },
  { $group: { _id: "$customer_id", total: { $sum: "$items.price" } } },
  { $sort: { total: -1 } },
  { $limit: 10 },
]);

Redis: Memory Management, Persistence, Clustering

Redis là cơ sở dữ liệu key-value store với nhiều tính năng nâng cao, được thiết kế để cung cấp hiệu năng cao và độ tin cậy cao.

Redis Memory Management (tiếp)

  1. Memory Policies:
  • Maxmemory: Giới hạn memory usage
  • Eviction policies: Cách xử lý khi đạt giới hạn memory
# Cấu hình trong redis.conf
maxmemory 2gb
maxmemory-policy allkeys-lru

Các eviction policies phổ biến:

  • noeviction: Trả về lỗi khi memory đầy
  • allkeys-lru: Xóa least recently used keys
  • volatile-lru: Xóa least recently used keys có expiration
  • allkeys-random: Xóa keys ngẫu nhiên
  • volatile-ttl: Xóa keys có expiration sắp hết hạn

  graph TD
    A[Redis Memory Full] --> B{Has Expiration?}
    B -->|Yes| C{Policy?}
    B -->|No| D{Policy?}

    C -->|volatile-lru| E[Evict LRU Key<br>with Expiration]
    C -->|volatile-ttl| F[Evict Key with<br>Shortest TTL]
    C -->|volatile-random| G[Evict Random Key<br>with Expiration]

    D -->|allkeys-lru| H[Evict LRU Key]
    D -->|allkeys-random| I[Evict Random Key]
    D -->|noeviction| J[Return Error]
  1. Redis Memory Fragmentation:

Memory fragmentation xảy ra khi có sự khác biệt giữa memory được cấp phát và memory thực sự sử dụng:

# Kiểm tra fragmentation ratio
INFO memory
# mem_fragmentation_ratio = used_memory_rss / used_memory
  • Ratio > 1.5: Fragmentation cao
  • Ratio < 1.0: Redis đang swap, hiệu năng kém

Giải pháp:

  • Restart Redis server (trong maintenance window)
  • Sử dụng activedefrag trong Redis 4.0+
# Cấu hình trong redis.conf
activedefrag yes

Redis Persistence Options

Redis cung cấp nhiều options để lưu trữ dữ liệu xuống disk:


  graph TD
    A[Redis Persistence] --> B[RDB<br>Point-in-time Snapshots]
    A --> C[AOF<br>Append-only File]
    A --> D[Hybrid<br>RDB+AOF]

    B --> B1[Pros:<br>- Compact files<br>- Faster restart<br>- Good for backups]
    B --> B2[Cons:<br>- Potential data loss<br>- Fork process overhead]

    C --> C1[Pros:<br>- Better durability<br>- Append-only operations]
    C --> C2[Cons:<br>- Larger files<br>- Slower restart<br>- Potential slower writes]

    D --> D1[Pros:<br>- Best of both worlds]
    D --> D2[Cons:<br>- More complex<br>- More disk space]
  1. RDB (Redis Database):
# Cấu hình trong redis.conf
save 900 1      # Save after 900 sec if at least 1 key changed
save 300 10     # Save after 300 sec if at least 10 keys changed
save 60 10000   # Save after 60 sec if at least 10000 keys changed

# Manual snapshot
SAVE      # Blocking
BGSAVE    # Non-blocking (fork)
  1. AOF (Append-Only File):
# Cấu hình trong redis.conf
appendonly yes
appendfsync everysec  # Options: always, everysec, no

# Rewrite AOF file (compact)
BGREWRITEAOF
  1. Hybrid Approach (RDB + AOF):
# Cấu hình trong redis.conf
appendonly yes
aof-use-rdb-preamble yes
  1. Persistence Best Practices:
  • Production servers: Hybrid approach với appendfsync everysec
  • Cache-only use case: Disable persistence hoặc infrequent RDB
  • Critical data: AOF với appendfsync always (hiệu năng thấp hơn)
  • Backup strategy: Sử dụng RDB snapshots

Redis Clustering và High Availability

Redis cung cấp nhiều options cho high availability và scaling:

  1. Redis Sentinel:

  graph TD
    A[Client] --> B[Sentinel 1]
    A --> C[Sentinel 2]
    A --> D[Sentinel 3]

    B --- C
    C --- D
    D --- B

    B --> E[Redis Master]
    C --> E
    D --> E

    E --> F[Redis Replica 1]
    E --> G[Redis Replica 2]

    B -.-> F
    B -.-> G
    C -.-> F
    C -.-> G
    D -.-> F
    D -.-> G
# Cấu hình sentinel.conf
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
  1. Redis Cluster:

Redis Cluster là một hệ thống phân tán và đồng bộ dữ liệu giữa các Redis nodes. Nó cung cấp khả năng mở rộng và độ tin cậy cao.


  graph TD
    A[Redis Cluster] --> B[Node 1]
    A --> C[Node 2]
    A --> D[Node 3]
    A --> E[Node 4]
    A --> F[Node 5]
    A --> G[Node 6]

    B --> H[Redis Master]
    C --> H
    D --> H
    E --> H
    F --> H
    G --> H

Redis Cluster cung cấp các tính năng như:

  • Phân tán dữ liệu giữa các nodes
  • Đồng bộ dữ liệu giữa các nodes
  • Tính năng quản lý độc lập của các nodes

Redis Cluster có thể được cấu hình bằng cách sử dụng redis-cli hoặc cấu hình trong redis.conf.

Redis Cluster cung cấp các tính năng như:

  • Phân tán dữ liệu giữa các nodes
  • Đồng bộ dữ liệu giữa các nodes
  • Tính năng quản lý độc lập của các nodes

Redis Cluster có thể được cấu hình bằng cách sử dụng redis-cli hoặc cấu hình trong redis.conf.