Tối ưu hóa bộ nhớ JVM cho các thành phần Apache Druid
Điều chỉnh Heap Size cho Historical và Broker
Thành phần Historical lưu trữ dữ liệu dưới dạng Segment trên disk, trong khi Broker xử lý truy vấn SQL. Cả hai cần lượng RAM lớn để cache dữ liệu nóng (Hot Data) vào bộ nhớ, giảm thời gian truy cập disk.
Định nghĩa tham số JVM trong file cấu hình của Historical và Broker để tận dụng tối đa RAM của server, nhưng luôn chừa khoảng 20% RAM cho hệ điều hành và các tiến trình khác.
Tìm file cấu hình JVM của Historical (thường nằm trong thư mục bin của Druid hoặc thư mục config tùy phiên bản bạn build). Dưới đây là ví dụ cho file /opt/druid/bin/historical.jvm.options:
XMS=4g
XMS=16g
Xmx=16g
XX:+UseG1GC
XX:MaxGCPauseMillis=200
XX:+ParallelRefProcEnabled
XX:+DisableExplicitGC
XX:+HeapDumpOnOutOfMemoryError
XX:HeapDumpPath=/var/log/druid/heap_dump_historical_%p_%t.hprof
Trong đó: Xmx đặt giới hạn trên của Heap (16GB), XMS đặt kích thước ban đầu bằng Xmx để tránh overhead mở rộng heap. G1GC được chọn để tối ưu thời gian GC cho các heap lớn. HeapDumpOnOutOfMemoryError giúp tự động dump bộ nhớ khi crash để phân tích.
Thực hiện tương tự cho file Broker /opt/druid/bin/broker.jvm.options với mức heap phù hợp với số lượng truy vấn đồng thời:
XMS=2g
Xmx=8g
XX:+UseG1GC
XX:MaxGCPauseMillis=200
XX:+ParallelRefProcEnabled
XX:+DisableExplicitGC
XX:+HeapDumpOnOutOfMemoryError
XX:HeapDumpPath=/var/log/druid/heap_dump_broker_%p_%t.hprof
Khởi động lại dịch vụ Historical và Broker sau khi thay đổi:
systemctl restart druid-historical
systemctl restart druid-broker
Kiểm tra lại kích thước heap đang chạy thực tế bằng lệnh jstat hoặc xem trong Druid Console tại phần Metrics của từng node.
Tối ưu JVM cho Coordinator và Indexer
Coordinator cần bộ nhớ để giữ trạng thái của toàn bộ cluster (metadata), còn Indexer cần bộ nhớ lớn để thực hiện tính toán (compaction, rollup) khi nạp dữ liệu.
Chỉnh sửa file /opt/druid/bin/coordinator.jvm.options:
XMS=2g
Xmx=4g
XX:+UseG1GC
XX:MaxGCPauseMillis=200
XX:+DisableExplicitGC
XX:+HeapDumpOnOutOfMemoryError
XX:HeapDumpPath=/var/log/druid/heap_dump_coordinator_%p_%t.hprof
Chỉnh sửa file /opt/druid/bin/indexer.jvm.options (tùy thuộc vào loại task, Indexer thường cần nhiều RAM hơn Coordinator):
XMS=4g
Xmx=12g
XX:+UseG1GC
XX:MaxGCPauseMillis=200
XX:+ParallelRefProcEnabled
XX:+DisableExplicitGC
XX:+HeapDumpOnOutOfMemoryError
XX:HeapDumpPath=/var/log/druid/heap_dump_indexer_%p_%t.hprof
Khởi động lại các dịch vụ:
systemctl restart druid-coordinator
systemctl restart druid-indexer
Verify: Truy cập Druid Console, vào tab "Tasks" hoặc "Cluster" để xem các task đang chạy không bị OOM (Out Of Memory) và thời gian GC thấp hơn trước khi tối ưu.
Phân tích log và xử lý sự cố TimeOut/Memory Leak
Xác định lỗi TimeOut trong truy vấn
Lỗi TimeOut thường xảy ra khi truy vấn SQL quá phức tạp, dữ liệu quá lớn, hoặc Historical không kịp phản hồi trước khi Broker cắt mạch. Nguyên nhân phổ biến là cấu hình timeout mặc định quá thấp so với workload thực tế.
Định nghĩa thời gian chờ tối đa cho Broker và Historical trong file cấu hình chính /opt/druid/conf/druid/cluster/_common/common.runtime.properties hoặc file riêng broker.runtime.properties.
Thêm hoặc chỉnh sửa các tham số sau vào /opt/druid/conf/druid/cluster/_common/broker.runtime.properties:
druid.query.maxTime=300000
druid.query.maxRows=10000000
druid.broker.http.timeout=300000
Tham số maxTime (300000ms = 5 phút) cho phép truy vấn chạy lâu hơn. maxRows giới hạn số dòng trả về để tránh tràn bộ nhớ client. timeout là thời gian chờ phản hồi từ Historical.
Đồng thời, chỉnh sửa /opt/druid/conf/druid/cluster/_common/historical.runtime.properties để Historical xử lý nhanh hơn:
druid.historical.httpServer.numThreads=20
druid.historical.cache.size=20000000000
druid.historical.cache.memory.size=10000000000
Tăng số luồng (numThreads) và kích thước cache (memory.size) giúp Historical phục vụ nhiều request song song và giữ dữ liệu nóng trong RAM.
Khởi động lại dịch vụ để áp dụng:
systemctl restart druid-broker
systemctl restart druid-historical
Verify: Chạy lại truy vấn SQL gây lỗi trước đó trên Druid Console. Nếu thành công, log sẽ không còn xuất hiện dòng "Query timeout" hoặc "Read timed out".
Phát hiện và xử lý Memory Leak
Memory Leak trong Druid thường biểu hiện qua việc sử dụng RAM tăng dần theo thời gian (dù không có truy vấn mới) và cuối cùng gây OOM Kill. Nguyên nhân thường do garbage collector không dọn dẹp kịp hoặc lỗi logic trong custom extension.
Sử dụng lệnh jmap để chụp ảnh bộ nhớ (Heap Dump) của tiến trình đang bị lỗi:
ps -ef | grep druid-historical | grep -v grep | awk '{print $2}'
jmap -dump:format=b,file=/tmp/historical_heap_dump.hprof
Thay <PID_HISTORICAL> bằng số PID thực tế. Lệnh này tạo file dump nhị phân để phân tích.
Phân tích file dump bằng công cụ Eclipse Memory Analyzer (MAT) hoặc trực tiếp trên terminal nếu đã cài libjvm:
mat /tmp/historical_heap_dump.hprof
Tuy nhiên, cách nhanh nhất để xác nhận leak là theo dõi log GC. Tìm file log /var/log/druid/historical.log và lọc các dòng GC:
grep "GC" /var/log/druid/historical.log | tail -n 50
Nếu thấy thời gian GC (Full GC) ngày càng tăng và chiếm nhiều CPU, hoặc Heap size tăng liên tục mà không giảm xuống về mức ban đầu sau khi GC, đó là dấu hiệu Memory Leak.
Biện pháp khắc phục: Restart dịch vụ để giải phóng bộ nhớ tạm thời, sau đó kiểm tra các extension custom hoặc tăng kích thước Heap như đã hướng dẫn ở phần trên.
Verify: Theo dõi graph "Heap Usage" trên Druid Console hoặc Grafana. Đường cong phải ổn định, không tăng liên tục theo thời gian.
Troubleshooting: Đồng bộ dữ liệu và mất dữ liệu
Xử lý khi Historical không đồng bộ với Coordinator
Khi Historical không nhận được thông báo về Segment mới từ Coordinator, dữ liệu sẽ không hiển thị trên Console. Nguyên nhân thường do mất kết nối ZooKeeper hoặc phiên bản Segment không khớp.
Đầu tiên, kiểm tra trạng thái kết nối ZooKeeper của Historical:
grep "zookeeper" /var/log/druid/historical.log | tail -n 20
Nếu thấy lỗi "Connection loss" hoặc "Session expired", cần kiểm tra dịch vụ ZooKeeper có đang chạy và ổn định không:
systemctl status zookeeper
zookeeperCli.sh ls /druid/cluster
Kiểm tra đường dẫn HDFS (hoặc S3) nơi lưu Segment. Historical cần quyền đọc (read access) đến các file segment.
Chạy lệnh kiểm tra quyền truy cập file mẫu trên HDFS:
hdfs dfs -ls /druid/segments/your_datasource/2024-01-01T00:00:00.000Z_2024-01-02T00:00:00.000Z
Nếu Historical không thể load Segment do lỗi phiên bản (version mismatch), hãy reset trạng thái cache của Historical bằng cách xóa thư mục cache và khởi động lại:
rm -rf /opt/druid/var/cache/*
systemctl restart druid-historical
Để ép Coordinator phân phối lại Segment cho Historical, hãy thực hiện lệnh reload trên Druid Console hoặc dùng API:
curl -X POST http://:8083/druid/coordinator/v1/configs/your_datasource?action=reload
Verify: Vào Druid Console, chọn Datasource -> Segments. Kiểm tra trạng thái "Historical" chuyển từ "Not loaded" sang "Loaded".
Xử lý mất dữ liệu (Data Loss) do Indexer lỗi
Dữ liệu bị mất thường xảy ra khi task Indexer bị lỗi giữa chừng và không có cơ chế lưu trữ tạm (staging) hoặc task bị xóa nhầm. Druid có cơ chế lưu task vào ZooKeeper và HDFS.
Truy cập Druid Console, vào tab "Tasks", lọc trạng thái là "FAILED". Xem chi tiết task bị lỗi để tìm nguyên nhân (timeout, OOM, lỗi schema).
Nếu dữ liệu chưa được commit vào HDFS/S3, hãy kiểm tra thư mục staging (thường là /druid/staging trên HDFS hoặc S3 bucket). Nếu file còn ở đây, dữ liệu chưa mất hoàn toàn.
Lệnh kiểm tra thư mục staging trên HDFS:
hdfs dfs -ls /druid/staging
Nếu task bị lỗi do cấu hình, hãy sửa lại cấu hình task (JSON spec) và chạy lại task nạp dữ liệu (Ingest Task). Druid hỗ trợ idempotent, chạy lại task với cùng một ID hoặc cùng một range thời gian sẽ không tạo dữ liệu trùng lặp nếu cấu hình đúng.
Trong trường hợp Segment đã được tạo nhưng Historical không load được, hãy kiểm tra log Historical xem có lỗi parse hay không. Nếu cần thiết, xóa Segment bị lỗi khỏi HDFS và chạy lại Indexer:
hdfs dfs -rm -r /druid/segments/your_datasource/2024-01-01T00:00:00.000Z_2024-01-02T00:00:00.000Z
Khởi động lại Indexer để xử lý lại task:
systemctl restart druid-indexer
Verify: Kiểm tra lại số lượng dòng (row count) của Datasource trên Druid Console so với file nguồn gốc. Đảm bảo dữ liệu đã được nạp đầy đủ.
Điều hướng series:
Mục lục: Series: Triển khai Database OLAP với Apache Druid trên Ubuntu 24.04
« Phần 7: Nạp dữ liệu mẫu và kiểm tra truy vấn SQL cơ bản