PostgresQ ನಲ್ಲಿ HyperLogLog ಬಳಸಿ ವಿಡಿಯೋ హీಟ್ಮ್ಯಾಪ್ಗಳನ್ನು ನಿರ್ಮಿಸುವುದು
ವೀಕ್ಷಕರು ವಿಡಿಯೋವನ್ನು ಎಲ್ಲಿ ನೋಡುವುದನ್ನು ನಿಲ್ಲಿಸುತ್ತಾರೆ ಎಂಬುದನ್ನು ಎಡಿಟರ್ಗಳಿಗೆ ನಿಖರವಾಗಿ ತೋರಿಸಲು ನೀವು ಬಯಸುತ್ತೀರಿ.
ಇದನ್ನು ಮಾಡಲು, ನಿಮಗೆ ಒಂದು హీಟ್ಮ್ಯಾಪ್ ಬೇಕು. ಈ హీಟ್ಮ್ಯಾಪ್ ವಿಡಿಯೋದ ಪ್ರತಿ ಸೆಕೆಂಡಿಗೆ ಎಷ್ಟು ವಿಶಿಷ್ಟ (unique) ವೀಕ್ಷಕರಿರಿದ್ದಾರೆ ಎಂಬುದನ್ನು ತೋರಿಸುತ್ತದೆ.
TrendVidStream ನಲ್ಲಿ, ನಾವು ಪ್ರತಿದಿನ 40 ಮಿಲಿಯನ್ ಇವೆಂಟ್ಗಳನ್ನು ಪ್ರಕ್ರಿಯೆಗೊಳಿಸುತ್ತೇವೆ. ನಾವು PHP 8.4 ಮತ್ತು Postgres ಎಂಬ ಸರಳ ಸ್ಟ್ಯಾಕ್ ಅನ್ನು ಬಳಸುತ್ತೇವೆ.
ಸಮಸ್ಯೆ ಎಂದರೆ ಕಾರ್ಡಿನಾಲಿಟಿ (cardinality). ಪ್ರತಿ ಸೆಕೆಂಡಿಗೆ ವಿಶಿಷ್ಟ ವೀಕ್ಷಕರನ್ನು ಎಣಿಸುವುದು ಕಷ್ಟಕರವಾಗಿದೆ.
ಸರಳವಾದ ವಿಧಾನ (The naive way):
- ರೊ (raw) ಇವೆಂಟ್ಸ್ ಟೇಬಲ್ ಬಳಸಿ.
COUNT(DISTINCT viewer_hash)ಅನ್ನು ರನ್ ಮಾಡಿ.- ವಿಡಿಯೋ ಮತ್ತು ಟೈಮ್ ಬಕೆಟ್ (time bucket) ಮೂಲಕ ಗ್ರೂಪ್ ಮಾಡಿ.
ಇದು ಬೇಗನೆ ವಿಫಲವಾಯಿತು. 90 ದಿನಗಳ ಡೇಟಾಕ್ಕಾಗಿ, ನಿಮಗೆ 3.6 ಬಿಲಿಯನ್ ಸಾಲುಗಳು (rows) ಬೇಕಾಗುತ್ತವೆ. ಕ್ವೇರಿಗಳು (queries) 9 ಸೆಕೆಂಡುಗಳನ್ನು ತೆಗೆದುಕೊಳ್ಳುತ್ತಿದ್ದವು. ಅಲ್ಲದೆ, ವಿಶಿಷ್ಟ ಎಣಿಕೆಗಳು ರೋಲ್ ಅಪ್ (roll up) ಆಗುವುದಿಲ್ಲ. "ಪ್ರತಿ ಗಂಟೆಯ ವಿಶಿಷ್ಟ ಎಣಿಕೆಗಳನ್ನು" ಕೂಡಿಸಿ "ದಿನದ ವಿಶಿಷ್ಟ ಎಣಿಕೆ" ಪಡೆಯಲು ಸಾಧ್ಯವಿಲ್ಲ, ಏಕೆಂದರೆ ಅದೇ ವ್ಯಕ್ತಿ ಎರಡರಲ್ಲೂ ಕಾಣಿಸಿಕೊಳ್ಳಬಹುದು.
ಪರಿಹಾರ: HyperLogLog (HLL).
HLL ಒಂದು ಡೇಟಾಸೆಟ್ನ ಸಣ್ಣ "ಸ್ಕೆಚ್" (sketch) ಅನ್ನು ರಚಿಸುತ್ತದೆ. ಎಷ್ಟು ಮಿಲಿಯನ್ ಜನರು ವೀಕ್ಷಿಸಿದರೂ ಸಹ, ಇದು ಕೇವಲ 10 KB ಅಷ್ಟು ಸಣ್ಣದಾಗಿರುತ್ತದೆ.
ಹೀಟ್ಮ್ಯಾಪ್ಗಳಿಗೆ HLL ಏಕೆ ಕೆಲಸ ಮಾಡುತ್ತದೆ:
- ಸ್ಥಿರ ಗಾತ್ರ (Fixed size): ಇದು ಸಣ್ಣದಾಗಿರುತ್ತದೆ.
- ವಿಲೀನಗೊಳಿಸಬಹುದಾದ (Mergeable): ಗ್ಲೋಬಲ್ ಎಣಿಕೆಯನ್ನು ಪಡೆಯಲು ನೀವು ಎರಡು ಸ್ಕೆಚ್ಗಳನ್ನು ಯೂನಿಯನ್ (union) ಮಾಡಬಹುದು. ಇದು ರೋಲ್ ಅಪ್ ಸಮಸ್ಯೆಯನ್ನು ಪರಿಹರಿಸುತ್ತದೆ.
ನಮ್ಮ ಅನುಷ್ಠಾನ (Our Implementation):
ನಾವು postgresql-hll ಎಕ್ಸ್ಟೆನ್ಶನ್ ಬಳಸುತ್ತೇವೆ. ನಾವು ಎರಡು ಟೇಬಲ್ಗಳನ್ನು ಬಳಸುತ್ತೇವೆ.
- ಒಂದು ಸ್ಟೇಜಿಂಗ್ ಟೇಬಲ್ (Staging table): ಇದು ಅಪೆಂಡ್-ಓನ್ಲಿ (append-only) ಟೇಬಲ್ ಆಗಿದ್ದು, ಇಲ್ಲಿ PHP ರೊ ಹಾರ್ಟ್ಬೀಟ್ಗಳನ್ನು (raw heartbeats) ಬರೆಯುತ್ತದೆ. ಬರವಣಿಗೆಯ ವೇಗವನ್ನು ಕಾಪಾಡಿಕೊಳ್ಳಲು ನಾವು ಇಲ್ಲಿ ಇಂಡೆಕ್ಸ್ಗಳನ್ನು ಬಳಸುವುದಿಲ್ಲ.
- ಒಂದು ಸ್ಕೆಚ್ ಟೇಬಲ್ (Sketch table): ಇದು HLL ಡೇಟಾವನ್ನು ಸಂಗ್ರಹಿಸುತ್ತದೆ.
ಸ್ಟೇಜಿಂಗ್ನಿಂದ ಸ್ಕೆಚ್ ಟೇಬಲ್ಗೆ ಡೇಟಾವನ್ನು ವರ್ಗಾಯಿಸಲು ನಾವು ಕ್ರೋನ್ ಜಾಬ್ (cron job) ಬಳಸುತ್ತೇವೆ. ನಾವು ಈ ಕಮಾಂಡ್ ಅನ್ನು ಬಳಸುತ್ತೇವೆ:
INSERT INTO video_heatmap (...)
SELECT ..., hll_add_agg(hll_hash_text(viewer_hash), 14, 5)
FROM beacon_staging
GROUP BY ...
ON CONFLICT (...)
DO UPDATE SET viewers = video_heatmap.viewers || EXCLUDED.viewers;
"||" ಆಪರೇಟರ್ ಹೊಸ ಡೇಟಾವನ್ನು ಅಸ್ತಿತ್ವದಲ್ಲಿರುವ ಸ್ಕೆಚ್ನೊಂದಿಗೆ ವಿಲೀನಗೊಳಿಸುತ್ತದೆ.
ಫಲಿತಾಂಶಗಳು:
- ಕ್ವೇರಿ ವೇಗ (Query speed): ನಮ್ಮ హీಟ್ಮ್ಯಾಪ್ ಕ್ವೇರಿಗಳು 15 ರಿಂದ 40 ms ತೆಗೆದುಕೊಳ್ಳುತ್ತವೆ. ಹಳೆಯ ವಿಧಾನವು 9 ಸೆಕೆಂಡುಗಳನ್ನು ತೆಗೆದುಕೊಳ್ಳುತ್ತಿತ್ತು.
- ಸ್ಟೋರೇಜ್ (Storage): ನಮ್ಮ 90 ದಿನಗಳ ಡೇಟಾಸೆಟ್ 40 GB ಇದೆ. ಹಳೆಯ ವಿಧಾನದಲ್ಲಿ ಇದು 1.2 TB ವಾಗುತ್ತಿತ್ತು.
- ನಿಖರತೆ (Accuracy): ನಾವು ಸುಮಾರು 0.6% ರಿಂದ 1.4% ತಪ್ಪುಗಳನ್ನು (error) ಕಾಣುತ್ತಿದ್ದೇವೆ. ಒಂದು హీಟ್ಮ್ಯಾಪ್ಗೆ ಇದು ಪರಿಪೂರ್ಣವಾಗಿದೆ.
ಕಲಿತ ಪಾಠಗಳು:
- ಡೇಟಾಬೇಸ್ನ ಒಳಗಡೆಯೇ ಹ್ಯಾಶ್ (Hash) ಮಾಡಿ. ನಿಮ್ಮ ಅಪ್ಲಿಕೇಶನ್ನಲ್ಲಿ ಹ್ಯಾಶ್ ಮಾಡಬೇಡಿ.
- ಅಪ್ಡೇಟ್ಗಳನ್ನು ಬ್ಯಾಚ್ (Batch) ಮಾಡಿ. ಸ್ಕೆಚ್ಗಳನ್ನು ಒಂದೊಂದೇ ಸಾಲಿನಲ್ಲಿ ಅಪ್ಡೇಟ್ ಮಾಡಬೇಡಿ, ಇಲ್ಲದಿದ್ದರೆ ನೀವು ರೈಟ್ ಹಾಟ್ಸ್ಪಾಟ್ಗಳನ್ನು (write hotspots) ಸೃಷ್ಟಿಸುತ್ತೀರಿ.
- ಹ್ಯಾಸ್ಗಳಿಗಾಗಿ ದೈನಂದಿನ ಸಾಲ್ಟ್ (salt) ಬಳಸಿ. ಇದು ಗೌಪ್ಯತೆಯನ್ನು ರಕ್ಷಿಸುತ್ತದೆ ಮತ್ತು ನಿಮ್ಮ ದೈನಂದಿನ ಸ್ಕೆಚ್ಗಳನ್ನು ಸ್ವಚ್ಛವಾಗಿಡುತ್ತದೆ.
ನಿಮ್ಮ ಡ್ಯಾಶ್ಬೋರ್ಡ್ COUNT(DISTINCT) ಕಾರಣದಿಂದ ನಿಧಾನವಾಗಿದ್ದರೆ, ನಿಮಗೆ ಬೃಹತ್ ಡೇಟಾ ವೇರ್ಹೌಸ್ ಅಗತ್ಯವಿಲ್ಲ. ನಿಮಗೆ HLL ಬೇಕು.
