3 điểm bởi GN⁺ 2023-08-30 | 1 bình luận | Chia sẻ qua WhatsApp
  • Từ cảnh báo thiếu dung lượng lưu trữ, nhóm đã tiến hành dọn dẹp PostgreSQL và thu hồi hơn 70GB dung lượng chưa tối ưu/không sử dụng mà không xóa index hay dữ liệu
  • Ban đầu, họ kiểm tra các kỹ thuật thu hồi phổ biến như index không dùng, bloat của bảng/index, REINDEX CONCURRENTLY, pg_repack, và B-Tree deduplication của PostgreSQL 13
  • Khác biệt mang tính quyết định là PostgreSQL, không giống Oracle, cũng lập chỉ mục cho giá trị NULL; khi đổi index toàn phần trên cancelled_by_user_id — nơi phần lớn giá trị là NULL — thành partial index, kích thước giảm từ 769MB xuống dưới 5MB
  • Tìm các index một cột có pg_stats.null_frac cao để chọn ứng viên partial index với điều kiện WHERE column IS NOT NULL, rồi sau khi reset bộ đếm thống kê, dùng pg_stat_all_indexes để xác nhận mức sử dụng thực tế
  • Trong Django, nullable ForeignKey có thể tạo B-Tree index mặc định, nên cần chỉ định db_index=False và tự định nghĩa partial index bằng models.Index(..., condition=Q(...__isnull=False))

Dọn dẹp bắt đầu từ cảnh báo thiếu dung lượng lưu trữ

  • Cứ vài tháng, hệ thống giám sát database lại phát cảnh báo thiếu dung lượng lưu trữ; thông thường cách xử lý là bổ sung storage
  • Khi đó, do đang trong giai đoạn cách ly nên tải hệ thống thấp, là thời điểm phù hợp để thử các tác vụ dọn dẹp vốn nặng hơn bình thường
  • Kết quả là thu hồi được hơn 70GB dung lượng chưa tối ưu/không sử dụng mà không xóa index hay dữ liệu
  • Chỉ với việc tái cấu trúc index và bảng theo cách thông thường đã giảm được nhiều dung lượng, và sau đó một phát hiện ngoài dự kiến giúp loại bỏ thêm khoảng 20GB giá trị index không dùng

Những nguyên nhân được nghi ngờ trước tiên

  • Index không sử dụng

    • Index không sử dụng được tạo để phục vụ hiệu năng truy vấn, nhưng vẫn chiếm dung lượng và có thể làm chậm INSERTUPDATE
    • Trong pg_stat_all_indexes, tìm các index có idx_scan = 0, idx_tup_read = 0, idx_tup_fetch = 0 để xác định các index chưa từng được scan hoặc fetch kể từ lần reset thống kê gần nhất
    • Có những trường hợp nhìn bề ngoài như không dùng nhưng không nên xóa
      • Khi optimizer của PostgreSQL sử dụng metadata chứ không trực tiếp dùng index
      • Index đảm bảo ràng buộc unique hoặc primary key của các bảng đã lâu không được cập nhật
    • Cần rà soát từng index trong danh sách để xác định index nào thực sự có thể xóa; bộ đếm thống kê được quản lý theo cách reset sau khi kiểm tra
    • Trong trường hợp này, vì công việc tương tự đã được thực hiện định kỳ nên không có index không sử dụng nào để xóa
  • Bloat của index và bảng

    • Khi PostgreSQL cập nhật một hàng, nó đánh dấu tuple cũ là dead và thêm tuple mới vào vị trí khác, nên có thể phát sinh bloat
    • Bloat ảnh hưởng không chỉ đến bảng mà cả index, khiến hệ thống dùng nhiều dung lượng lưu trữ hơn mức thực sự cần thiết
    • Việc ước tính bloat không đơn giản, nên sử dụng các truy vấn từ PostgreSQL wiki và pgsql-bloat-estimation để ước tính bloat của bảng và B-Tree index

Cách giảm bloat của index

  • Tạo lại/tái cấu trúc index

    • Để loại bỏ bloat của index, cần tạo lại index
    • Nếu drop rồi create trực tiếp, có thể tạo index mới ở dạng đã tối ưu
    • REINDEX của PostgreSQL tái cấu trúc index hiện có tại chỗ
    • Cách tạo lại thông thường sẽ lock bảng và chặn thay đổi, nên trong môi trường production, REINDEX INDEX CONCURRENTLY index_name phù hợp hơn
    • REINDEX CONCURRENTLY tạo một index mới có hậu tố _ccnew, đồng bộ các thay đổi, rồi khi hoàn tất sẽ thay thế index cũ và xóa index cũ
    • Nếu quá trình tái cấu trúc bị gián đoạn giữa chừng, index mới có thể còn lại ở trạng thái invalid và chiếm dung lượng, nên cần tìm và xóa các index có _ccnew nhưng không có indisvalid
  • B-Tree deduplication trong PostgreSQL 13

    • PostgreSQL 13 giới thiệu B-Tree Deduplication, giúp lưu trữ các giá trị trùng lặp trong B-Tree index hiệu quả hơn
    • Cho đến PostgreSQL 12, dù có nhiều giá trị trùng lặp, từng giá trị trùng vẫn được lưu lặp lại trong leaf của index, có thể tốn nhiều dung lượng
    • Khi deduplication được bật trong PostgreSQL 13, giá trị trùng lặp chỉ được lưu một lần, nên có thể ảnh hưởng lớn đến kích thước các index có nhiều giá trị trùng
    • Trong PostgreSQL 13, deduplicate_items = ON là mặc định; nếu nâng cấp từ phiên bản cũ, cần tái cấu trúc index bằng REINDEX để nhận được đầy đủ hiệu quả
    • Trong ví dụ 1 triệu hàng, index trên cột không unique có nhiều giá trị trùng giảm từ 21MB xuống 6840kB khi áp dụng deduplication, còn index trên cột unique không đổi ở mức 21MB
    • Trong trường hợp này, PostgreSQL 13 khi đó vẫn còn mới và nhà cung cấp cloud chưa hỗ trợ, nên không thể giảm dung lượng bằng deduplication

Bloat của bảng và pg_repack

  • Hạn chế khi loại bỏ bloat của bảng

    • Bảng cũng có thể bị bloat và phân mảnh do dead tuple
    • Khác với index, bảng khó có thể đơn giản tạo lại; cần tạo bảng mới, chuyển dữ liệu, đồng bộ, rồi tạo lại index, constraint và referential constraint
    • VACUUM FULL table_name có thể thu hồi dung lượng do bloat và dead tuple chiếm dụng, nhưng cần lock bảng
    • Việc tạo lại bảng và VACUUM FULL có thể dẫn đến khối lượng phát triển lớn hoặc downtime
  • Sử dụng pg_repack

    • pg_repack là một extension PostgreSQL dùng để tái cấu trúc bảng và index mà không cần downtime
    • Để sử dụng, tạo extension bằng CREATE EXTENSION pg_repack;, rồi chạy từ console theo dạng pg_repack -k --table table_name db_name
    • pg_repack tạo bảng mới, nạp dữ liệu gốc trong khi đồng bộ với dữ liệu mới, sau đó tái cấu trúc index và cuối cùng hoán đổi hai bảng
    • Khi áp dụng vào production, có một số ràng buộc
      • Cần khoảng dung lượng lưu trữ bổ sung tương đương kích thước bảng và index mục tiêu tái cấu trúc
      • Nếu repack thất bại hoặc bị dừng thủ công, các object trung gian có thể còn lại và cần dọn dẹp thủ công
      • Nếu đã đang thiếu dung lượng lưu trữ, yêu cầu dung lượng bổ sung có thể khiến cách này không phù hợp, nên cần theo dõi dung lượng trống và lên kế hoạch trước

Phát hiện ngoài dự kiến: PostgreSQL cũng lập chỉ mục cho NULL

  • Sau khi loại bỏ index không sử dụng và dọn bloat bảng/index bằng các kỹ thuật thông thường, vẫn còn dung lượng có thể giảm
  • Một trong những bảng lớn nhất lưu dữ liệu giao dịch, đồng thời ghi nhận các trường hợp người dùng hủy sau khi thanh toán và được hoàn tiền
  • Bảng giao dịch có foreign key cho cả người dùng mua hàng và người dùng hủy, và mỗi trường đều được định nghĩa B-Tree index
    • Người dùng mua hàng có ràng buộc NOT NULL, nên mọi hàng đều có giá trị
    • Người dùng hủy là nullable, và vì việc hủy hiếm xảy ra nên phần lớn giá trị là NULL
  • Dự kiến index người dùng hủy sẽ nhỏ hơn nhiều so với index người dùng mua hàng, nhưng kích thước thực tế lại bằng nhau
  • Tác giả từng học rằng trong Oracle, NULLs are not indexed, nhưng trong PostgreSQL, giá trị NULL cũng được lập chỉ mục
  • Index toàn phần hiện có như sau
CREATE INDEX transaction_cancelled_by_ix
ON transactions(cancelled_by_user_id);
  • Giả thuyết được kiểm chứng bằng cách đổi sang partial index loại trừ giá trị NULL
DROP INDEX transaction_cancelled_by_ix;

CREATE INDEX transaction_cancelled_by_part_ix
ON transactions(cancelled_by_user_id)
WHERE cancelled_by_user_id IS NOT NULL;
  • Sau khi reindex, kích thước index toàn phần là 769MB, và hơn 99% giá trị là NULL
  • Partial index loại trừ NULL có kích thước dưới 5MB, tức giảm hơn 99% kích thước index
  • Sau khi reset thống kê bảng và quan sát trong một thời gian, index mới được sử dụng như index cũ, và hơn 760MB indexed tuple không dùng đã được loại bỏ mà không làm giảm hiệu năng

Tìm ứng viên partial index

  • Sau khi đạt kết quả tốt với một partial index, tác giả tìm các index tương tự bằng cách tìm index trên những cột có pg_stats.null_frac cao
  • null_fractỷ lệ NULL trong các giá trị của cột đó theo ước tính của PostgreSQL
  • Truy vấn tìm ứng viên áp dụng các điều kiện sau
    • Loại trừ primary key vì không thể là partial
    • Loại trừ các index đã là partial index
    • Loại trừ composite index
    • Chỉ bao gồm các index có kích thước trên 10MB
  • Trong ví dụ kết quả, tx_cancelled_by_ix có kích thước 1418MB và null_frac là 96,15%, nên xuất hiện như một ứng viên có khả năng tiết kiệm khoảng 1363MB
  • Việc loại trừ toàn bộ NULL khỏi index không phải lúc nào cũng có lợi
    • Các truy vấn tìm NULL bằng điều kiện IS NULL có thể hưởng lợi từ index trên giá trị NULL
    • Không chỉ giá trị không NULL, cả những giá trị khác hầu như không được truy vấn cũng có thể bị loại trừ bằng partial index
  • Con số 20GB trong tiêu đề khả thi vì cấu trúc hệ thống là nếu giảm khoảng 10GB ở database primary thì replica cũng giảm dung lượng lưu trữ tương tự

Migration sang partial index trong Django ORM

  • Nullable ForeignKey và index ngầm định

    • Nếu không chỉ định db_index=False, Django sẽ ngầm tạo B-Tree index cho trường models.ForeignKey
    • Nếu không cấu hình gì cho nullable ForeignKey lưu người dùng hủy, Django sẽ tạo index toàn phần
    • Để tạo partial index, cần đặt db_index=False cho trường FK và thêm models.Index có điều kiện vào Meta.indexes
    class Transaction(models.Model):
        cancelled_by_user = models.ForeignKey(
            to=User,
            null=True,
            on_delete=models.CASCADE,
            db_index=False,
        )
    
        class Meta:
            indexes = (
                models.Index(
                    fields=('cancelled_by_user_id', ),
                    name='%(class_name)s_cancelled_by_part_ix',
                    condition=Q(cancelled_by_user_id__isnull=False),
                ),
            )
    
    • Nullable foreign key là một ứng viên partial index tốt
    • Để tránh việc tính năng ngầm định âm thầm tạo index, tác giả tạo Django check nhằm buộc luôn phải khai báo db_index trên foreign key
  • Đổi index toàn phần sang partial index mà không downtime

    • Khi thay index toàn phần bằng partial index, cần thực hiện theo từng bước để tránh downtime hoặc suy giảm hiệu năng
    • Sửa model Django liên quan để đổi index toàn phần thành partial index, nhưng không chạy nguyên xi migration do Django tạo
    • Migration đó có thể vô hiệu hóa ràng buộc FK, drop index toàn phần hiện có rồi tạo partial index mới, gây downtime và suy giảm hiệu năng
    • Dùng ./manage.py sqlmigrate để tạo SQL, chỉ trích xuất phần CREATE INDEX, sửa thành CONCURRENTLY, rồi chạy thủ công trên database
    • Vì index toàn phần cũ vẫn còn, các truy vấn vẫn có thể tiếp tục dùng index cũ trong lúc tạo partial index
    • Để kiểm tra index partial mới có được dùng hay không, reset bộ đếm thống kê của index toàn phần bằng pg_stat_reset_single_table_counters(<full index oid>)
    • Sau đó quan sát idx_scan, idx_tup_read, idx_tup_fetch trong pg_stat_all_indexes để so sánh mức sử dụng giữa index toàn phần và partial index
    • Khi xác định partial index được sử dụng, drop index toàn phần và kiểm tra dung lượng lưu trữ thực sự thu hồi được
    • Sau khi trạng thái database khớp với trạng thái model, đăng ký Django migration là đã chạy bằng ./manage.py migrate --fake
    • Trong các môi trường ít cần cân nhắc downtime như dev, QA, staging, Django migration sẽ chạy bình thường và thay index toàn phần bằng partial index

Tổng kết

  • Chỉ tối ưu disk, tham số lưu trữ và cấu hình là chưa đủ để cải thiện hiệu năng và dung lượng vượt một giới hạn nhất định
  • Phần cải thiện cuối cùng nằm ở chính các object của database; trong trường hợp này là thay đổi định nghĩa index
  • Quy trình đã áp dụng có thể rút gọn thành ba bước
    • Loại bỏ index không sử dụng
    • Repack bảng và index, và nếu có thể thì bật B-Tree deduplication
    • Dùng partial index để chỉ lập chỉ mục những giá trị cần thiết
  • Việc dọn dẹp như vậy có thể là một lựa chọn thực tế để có thêm vài ngày trước khi phải provision thêm storage

1 bình luận

 
GN⁺ 2023-08-30
Ý kiến trên Hacker News
  • Dù có nhiều dung lượng lưu trữ thì làm cho chỉ mục nhỏ lại vẫn là điều tốt. Như vậy nhiều dữ liệu hơn có thể nằm trong hot set.
    Tuy nhiên, từ góc nhìn vận hành cơ sở dữ liệu ở quy mô TB, “chỉ cần gắn thêm dung lượng lưu trữ” cũng luôn là một lựa chọn hợp lý. Đặc biệt nếu không ở trên cloud; với phần cứng tự sở hữu, SSD NVMe doanh nghiệp khoảng $80/TB, còn RAM DDR4 khoảng $1.20/GB. Nếu ước tính 4 giờ kỹ thuật vào khoảng $1000, bạn có thể mua 800GB RAM hoặc 12TB lưu trữ

    • hiệu ứng nhân hệ số giữa dung lượng danh nghĩa và dung lượng thực tế phải mua cho toàn bộ hệ thống. Không phải chỉ mua 1TB, mà tối thiểu cần từ 2 máy chủ failover live trở lên, cộng thêm nhiều tầng backup và khôi phục thảm họa.
      Chỉ riêng backup hằng tuần trong 1 năm cũng biến 1TB thành 50TB, kể cả dùng lưu trữ offline cũng vậy. Ở công ty chúng tôi, việc gắn thêm dung lượng cho DB live khá dễ nên đã làm như vậy suốt vài năm, nhưng việc di chuyển các bản backup nhiều TB ngày càng phiền phức, đến mức dù phải tốn chi phí kỹ thuật thì cũng cần giảm dữ liệu production
    • Cách so sánh này không hay lắm. Thời gian kỹ thuật có khả năng là trả một lần rồi vấn đề được giải quyết, còn 10 ổ SSD cần không gian rack, điện, khe PCIe, thay thế đúng lúc, phần mềm quản lý, và phần lớn trở thành chi phí lặp lại.
      Nếu chỉ làm một lần, hạ tầng hiện có có thể hấp thụ các chi phí như chỗ trống trong rack hoặc khe PCIe dư, nhưng kiểu khấu hao này chỉ hoạt động tốt ở quy mô nhỏ. Ngoài ra, khi hệ thống càng lớn, do chi phí quản lý và độ trễ tăng lên, hiệu năng trên mỗi đơn vị thiết bị tất yếu sẽ giảm. Nếu cứ giải quyết bằng cách tiếp tục phình to hệ thống, toàn bộ hệ thống sẽ ngày càng ì ạch rồi có thể trở nên không thể vận hành. Ngược lại, giải pháp giảm tài nguyên hệ thống cần thiết sẽ nâng hiệu năng trên mỗi đơn vị tài nguyên và tự nó tạo ra một tài sản có chất lượng tốt hơn
    • Tùy vào quy mô và độ phức tạp, nhưng nếu không kiểm soát mức sử dụng tài nguyên thì chi phí có thể tăng theo cấp số nhân. Điều này thậm chí có thể xảy ra khi doanh nghiệp không tăng trưởng, chỉ vì yêu cầu ngày càng phức tạp hơn.
      Một tối ưu hóa giúp tiết kiệm 1TB hôm nay có thể giúp tiết kiệm 2TB sau vài năm, và chỉ vài quyết định như vậy cũng có thể tạo ra chênh lệch hơn một bậc độ lớn. Ở quy mô lớn hơn thì điều này càng đúng, và cuối cùng luôn phải cân bằng
    • Khó mà mua thêm 800GB RAM có thể lắp vào máy chủ hiện tại chỉ với $1000. Loại RAM đó thường khoảng $3~6/GB; hàng chưa được chứng nhận là khoảng $3, còn RAM được nhà sản xuất bo mạch chủ chứng nhận khoảng $6. So với cấu hình RAM cơ bản, chi phí khe DIMM cũng cộng thêm khoảng $0.50~$1/GB.
      Lựa chọn “ném phần cứng vào” đôi khi có thể khôn ngoan, nhưng trong đa số trường hợp, tôi cho rằng khó có thể biến máy chủ 128GB RAM hôm nay thành 1TB RAM vào ngày mai chỉ với $1K
    • Ngay cả trên cloud, nếu không có ý tưởng cụ thể để dùng ít dung lượng lưu trữ hơn, thì thường câu trả lời là provision thêm dung lượng lưu trữ.
      Tuy nhiên, vì đây không phải chi phí một lần mà là chi phí hằng tháng nên phép tính phức tạp hơn một chút. Tất nhiên, nếu cơ sở dữ liệu tăng trưởng với tốc độ đều đặn thì việc provision một lần on-premises thực ra cũng gần giống chi phí hằng tháng
  • Bài viết có nhắc đến khử trùng lặp B-tree có trong PostgreSQL 13 nhưng không có trong bản 12 đang dùng, đồng thời nói rằng phần lớn giá trị ở một số chỉ mục khóa ngoại là NULL.
    Tôi tò mò liệu khử trùng lặp B-tree có giúp được trong trường hợp này không. Nhìn mục 63.4.2 trong tài liệu PostgreSQL 13 thì có vẻ là có: “B-Tree deduplication is just as effective with ‘duplicates’ that contain a NULL value...” Tất nhiên có lẽ sẽ không hiệu quả bằng chỉ mục một phần mà bài viết đã áp dụng, nhưng vẫn thú vị.
    https://www.postgresql.org/docs/13/btree-implementation.html

    • Trong một cuộc thảo luận trước đó, có người đã tính toán rằng pg12 tốn 16 byte cho mỗi NULL, còn pg13 là 6.32 byte cho mỗi NULL. Đây là mức tiết kiệm khá đáng kể.
      https://news.ycombinator.com/item?id=25989467
  • Bài này hữu ích không chỉ vì phần “phát hiện” chỉ mục một phần là điểm chính, mà còn vì nó tổng hợp khá toàn diện các kỹ thuật nên biết khi lo ngại PostgreSQL đang dùng không gian kém hiệu quả.
    Tuy vậy, nếu là startup hoặc công ty giai đoạn đầu, điều đáng nhớ là với các vấn đề lưu trữ kiểu này, gần như lúc nào chọn gắn thêm dung lượng đĩa cũng tốt hơn so với bận tâm tối ưu kích thước. Lập trình viên thì đắt, còn đĩa thì rẻ

    • Lời khuyên này hay. Ở giai đoạn đầu, nhìn chung nên giữ mọi thứ đơn giản nhất có thể.
      Trước đây khi làm ở một startup tăng trưởng nhanh, một trong các nhà sáng lập cứ khăng khăng chỉ tiếp tục nâng cấp một máy chủ duy nhất; hệ thống vẫn có dự phòng và backup, nhưng kiến trúc đơn giản nên dễ quản lý, gỡ lỗi và khôi phục. Trong 5 năm đầu của công ty, toàn bộ hệ thống chạy trên một máy chủ mà vẫn tăng trưởng theo cấp số nhân và phục vụ hàng triệu người dùng trên toàn thế giới. Sau khi chứng kiến điều đó, tôi tin chắc rằng chỉ nên nâng cấp khi cần, theo cách đơn giản và thẳng nhất
    • Có nhiều nhấn mạnh theo hướng chỉ cần gắn thêm dung lượng lưu trữ, nhưng việc lập chỉ mục không cần thiết cũng ảnh hưởng đến hiệu năng ghi và đọc, và trong một số trường hợp ảnh hưởng khá lớn.
      Dùng chỉ mục một phần phù hợp rõ ràng với use case, chẳng hạn khi đa số giá trị là NULL, gần với mô hình hóa đúng hơn và không nên xem đó là tối ưu hóa vội vàng hay lãng phí thời gian của lập trình viên
  • Trong bối cảnh tương tự, các script này khá hữu ích.
    https://github.com/NikolayS/postgres_dba
    Tôi đã có thể thu hồi khoảng 10% dung lượng lưu trữ của một bảng lớn, tức khoảng 100GB, bằng cách sắp xếp lại thứ tự cột

  • Tuần trước, chỉ với reindexpg_repack, tôi đã thu hồi 200GB từ một DB 600GB. Bảng tệ nhất có dữ liệu 17GB nhưng chỉ mục 142GB, và sau khi reindex thì giảm còn 21GB.
    Bảng này bị lập chỉ mục quá mức, với nhiều chỉ mục trên nhiều tổ hợp cột. Một trong những nguyên nhân của chỉ mục khổng lồ có vẻ là phân bố dữ liệu. Từ năm 2015 đã liên tục có chèn, cập nhật, xóa; dữ liệu càng cũ càng có khả năng bị xóa cao hơn nên dữ liệu các năm gần đây nhiều hơn, nhưng vẫn còn khoảng 0.1% là dữ liệu năm 2015. Tôi nghĩ phân bố lệch với đuôi dài như vậy có thể đã khiến vacuum khó xử lý tình trạng phình to chỉ mục hơn

  • Thời điểm đó cũng đã được thảo luận: An unexpected find that freed 20GB of unused index space in PostgreSQL - https://news.ycombinator.com/item?id=25988871 - Tháng 2/2021, 78 bình luận

  • Rất khuyến nghị pganalyze.com để tìm các index không dùng, cơ hội tối ưu hóa và các truy vấn có độ trễ cao

  • Hóa ra câu “tiết kiệm 20GB chỉ bằng một mẹo lạ” thật sự không hề phóng đại. Trong các môi trường Oracle / SQL Server cũ, tôi thường nhận được nhiều yêu cầu muốn dùng một dạng index đặc thù của PostgreSQL mà gần như chưa từng nghe tới, nhưng tôi không biết rằng cả giá trị NULL cũng được index

    • Nghe nói đó là điều mới thì tôi cũng thấy yên tâm phần nào vì mình đã không nghĩ ra. Có vẻ chúng tôi cũng có vài index lớn có thể xử lý theo cách tương tự
  • Trong dự án quy mô lớn đầu tiên tôi tham gia, khi dataset lớn dần lên, đội ngũ không hiểu vì sao các tác vụ lại chậm đi
    Với index, thời gian chèn trên mỗi bản ghi là log(n). Khi DB kiểm thử từ 1.000 bản ghi tăng lên gần 65k, thời gian chèn tăng 60% (2^10 so với 2^16). Thành công khiến mọi thứ chậm lại, và việc nâng cấp server cũng có giới hạn. Thêm vài index nữa vì một tính năng mơ hồ mà business muốn, thế là giờ mọi thứ tăng gấp đôi

    • Chỉ là một con số rất nhỏ bị nhân đôi thôi. Nếu đó thật sự là vấn đề thì dùng ổ đĩa nhanh hơn là được. Có quá nhiều trường hợp cố chạy DB trên EBS có IOPS thấp hơn cả PC của tôi năm 2015
      Tôi cũng đang quản lý nhiều DB có hàng trăm triệu bản ghi và hơn 40 index trên mỗi bảng/collection
    • Với những index nhỏ như vậy, việc chèn lẽ ra vẫn phải rất nhanh chứ nhỉ
      Tìm kiếm nhị phân trong một trang B-tree mất dưới 100 chu kỳ. Việc duyệt B-tree trên 100 triệu bản ghi vẫn nên ở mức micro giây, và phần tìm kiếm nhị phân bên trên cũng sẽ ở mức micro giây, nếu không phải vài trăm nano giây
  • Nguyên lý này có áp dụng khi một hoặc vài giá trị rất phổ biến không? Ví dụ 90% của một cột số nguyên là 0

    • Đúng. Postgres theo dõi các giá trị phổ biến nhất. Nếu biết 0 là giá trị phổ biến và where val = 0 sẽ giữ lại 90% số hàng, nó có thể chọn quét bảng thay vì dùng index
    • Index hiệu quả nhất khi biểu diễn một tập giá trị duy nhất. Nó vẫn hữu ích cho việc nhóm, nhưng nếu các nhóm chỉ chiếm một tỷ lệ cực nhỏ thì sẽ lãng phí nhiều không gian và chu kỳ để dò index
    • Có thể tạo index riêng cho trường hợp bằng 0 và khác 0 không?
    • Chỉ khi giá trị chung được biểu diễn bằng NULL; nói chung thì đó không phải là ý hay