ಆರಂಭಿಕ ವೆಬ್ ಪ್ಲೇನ್ ಟೆಕ್ಸ್ಟ್ (plain text) ಮೇಲೆ ಚಾಲನೆಯಲ್ಲಿತ್ತು. ನೀವು ಫಾರ್ಮ್ನಲ್ಲಿ ಬಳಕೆದಾರರ ಹೆಸರು ಅಥವಾ ಕಾಮೆಂಟ್ ಅನ್ನು ಟೈಪ್ ಮಾಡಿ, ಸಬ್ಮಿಟ್ ಮಾಡಿದಾಗ, ಒಂದು ಚಿಕ್ಕ ಸ್ಟ್ರಿಂಗ್ (string) HTTP ಮೂಲಕ ಸರ್ವರ್ಗೆ ತಲುಪುತ್ತಿತ್ತು. ಆ ಸರಳ ರಿಕ್ವೆಸ್ಟ್-ರಿಸ್ಪಾನ್ಸ್ (request-response) ಚಕ್ರವೇ ಮೂಲಸೌಕರ್ಯವನ್ನು ನಿರ್ಧರಿಸುತ್ತಿತ್ತು. ಜನರು ಫೋಟೋಗಳು, ದಾಖಲೆಗಳು ಮತ್ತು ವೀಡಿಯೊಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲು ಬಯಸಲು ಪ್ರಾರಂಭಿಸಿದಾಗ, ಇಂಜಿನಿಯರ್ಗಳು ಸಂಪೂರ್ಣವಾಗಿ ಓದಬಲ್ಲ ಪಠ್ಯಕ್ಕಾಗಿ ನಿರ್ಮಿಸಲಾದ ವ್ಯವಸ್ಥೆಯ ಮೂಲಕ ರ (raw) ಬೈನರಿ ಡೇಟಾವನ್ನು ಹೇಗೆ ವರ್ಗಾಯಿಸಬೇಕೆಂದು ಕಂಡುಹಿಡಿಯಬೇಕಾಯಿತು.
ನೀವು ಒಂದು JPEG ಅನ್ನು JSON ಆಬ್ಜೆಕ್ಟ್ಗೆ ಹಾಕಲು ಪ್ರಯತ್ನಿಸಿದರೆ, ನೀವು ಒಂದು ಮೂಲಭೂತ ಅಡೆತಡೆ ಎದುರಿಸುತ್ತೀರಿ. JSON ಎಂಬುದು ಒಂದು ಟೆಕ್ಸ್ಟ್ ಪ್ರೋಟೋಕಾಲ್ (text protocol). ಇದು ಮಾನ್ಯವಾದ Unicode ಅಕ್ಷರಗಳು, ಕೊಟೇಶನ್ಗಳು, ಬ್ರೇಸ್ಗಳು ಮತ್ತು ಸರಿಯಾಗಿ ಎಸ್ಕೇಪ್ ಮಾಡಿದ ಸ್ಟ್ರಿಂಗ್ಗಳನ್ನು ನಿರೀಕ್ಷಿಸುತ್ತದೆ. ಬೈನರಿ ಫೈಲ್ ಎಂಬುದು ಕೇವಲ ಬೈಟ್ಗಳ (bytes) ಸುದೀರ್ಘ ಸರಣಿಯಾಗಿದೆ, ಅವುಗಳಲ್ಲಿ ಹೆಚ್ಚಿನವುಗಳಿಗೆ ಮುದ್ರಣ ಮಾಡಬಹುದಾದ (printable) ರೂಪವಿಲ್ಲ. ಆ ಬೈಟ್ಗಳನ್ನು JSON ಸ್ಟ್ರಿಂಗ್ಗೆ ಒತ್ತಿ ಹಾಕಿದರೆ ಪಾರ್ಸರ್ (parser) ದೋಷ ತೋರಿಸುತ್ತದೆ, ಎಸ್ಕೇಪ್ ಸೀಕ್ವೆನ್ಸ್ ಪೇಲೋಡ್ ಅನ್ನು ಹಾಳುಮಾಡುತ್ತದೆ ಮತ್ತು ಇಡೀ ಸಂದೇಶವು ಇನ್ನೊಂದು ಬದಿಯಲ್ಲಿ ಓದಲು ಅಸಾಧ್ಯವಾಗುತ್ತದೆ.
Base64 ಎನ್ಕೋಡಿಂಗ್ ಒಂದು ಸ್ಪಷ್ಟ ಪರಿಹಾರವಾಗಿ ಹೊರಹೊಮ್ಮಿತು. ಇದು ಬೈನರಿ ಡೇಟಾವನ್ನು ಅರವತ್ತನಾಲ್ಕು ಮುದ್ರಣ ಮಾಡಬಹುದಾದ ASCII ಅಕ್ಷರಗಳ ಸೀಮಿತ ಗುಂಪಿಗೆ ಮರು-ನಕ್ಷೆ ಮಾಡುತ್ತದೆ (re-maps). ಬೈನರಿಯ ಪ್ರತಿ ಮೂರು ಬೈಟ್ಗಳು ಪಠ್ಯದ ನಾಲ್ಕು ಅಕ್ಷರಗಳಾಗುತ್ತವೆ. ಈಗ ಪೇಲೋಡ್ ಕಾನೂನುಬದ್ಧ JSON ಆಗಿರುತ್ತದೆ, ಅಂದರೆ ಅದು ಯಾವುದೇ ಸ್ಟ್ಯಾಂಡರ್ಡ್ API ಮೂಲಕ ಸುಲಭವಾಗಿ ಚಲಿಸುತ್ತದೆ. ಆದರೆ ಇದರ ಬೆಲೆ ತಕ್ಷಣವೇ ತಿಳಿಯುತ್ತದೆ. ಆ ರೀ-ಎನ್ಕೋಡಿಂಗ್ ಫೈಲ್ ಗಾತ್ರವನ್ನು ಸುಮಾರು ಮೂವತ್ತಮೂರು ಪ್ರತಿಶತ ಹೆಚ್ಚಿಸುತ್ತದೆ. ಮೂರು ಮೆಗಾಬೈಟ್ ಇಮೇಜ್ ವೈರ್ನಲ್ಲಿ ನಾಲ್ಕು ಮೆಗಾಬೈಟ್ ಆಗುತ್ತದೆ. ಡೇಟಾವನ್ನು ಅತ್ತ ಇತ್ತ ಪರಿವರ್ತಿಸಲು ಕ್ಲೈಂಟ್ ಮತ್ತು ಸರ್ವರ್ ಎರಡೂ ಹೆಚ್ಚಿನ CPU ಸೈಕಲ್ಗಳನ್ನು ಬಳಸುತ್ತವೆ. ಮುಖ್ಯವಾಗಿ, ಅನೇಕ JSON ಸರ್ವರ್ ಫ್ರೇಮ್ವರ್ಕ್ಗಳು ಪಾರ್ಸ್ ಮಾಡುವ ಮೊದಲು ಇಡೀ ಬಾಡಿಯನ್ನು ಮೆಮೊರಿలోకి ಓದುತ್ತವೆ. ಏಕಕಾಲದಲ್ಲಿ ಕೆಲವು ದೊಡ್ಡ ಅಪ್ಲೋಡ್ಗಳು ಬಂದಾಗ ಸಾಧಾರಣ ಸರ್ವರ್ ಕೂಡ ಸ್ಲೋ ಆಗಬಹುದು ಅಥವಾ ಕುಸಿಯಬಹುದು, ಏಕೆಂದರೆ ಪ್ರತಿಯೊಂದನ್ನು ಡಿಸ್ಕ್ಗೆ ಉಳಿಸುವ ಮೊದಲು ಅತಿಯಾದ ಗಾತ್ರದ ಟೆಕ್ಸ್ಟ್ ಸ್ಟ್ರಿಂಗ್ ಆಗಿ RAM ನಲ್ಲಿ ಇರಿಸಲಾಗುತ್ತದೆ. Base64 ತುರ್ತು ಸಂದರ್ಭಗಳಲ್ಲಿ ಕೆಲಸ ಮಾಡುತ್ತದೆ, ಆದರೆ ಇದನ್ನು ದೊಡ್ಡ ಪ್ರಮಾಣದ ಉತ್ಪಾದನಾ ಮಟ್ಟದಲ್ಲಿ (production scale) ಭಾರೀ ಫೈಲ್ಗಳನ್ನು ಸಾಗಿಸಲು ವಿನ್ಯಾಸಗೊಳಿಸಲಾಗಿಲ್ಲ.
ಇದಕ್ಕೆ ಉತ್ತಮ ಉತ್ತರ multipart/form-data. ಈ ಫಾರ್ಮ್ಯಾಟ್ ಒಂದೇ HTTP ರಿಕ್ವೆಸ್ಟ್ ಅನ್ನು ಪ್ರತ್ಯೇಕ ಭಾಗಗಳ ಸಂಗ್ರಹವಾಗಿ ಪರಿಗಣಿಸುತ್ತದೆ, ಪ್ರತಿಯೊಂದು ಭಾಗವನ್ನು ವಿಶಿಷ್ಟವಾದ ಬೌಂಡರಿ ಸ್ಟ್ರಿಂಗ್ (boundary string) ಮೂಲಕ ವಿಂಗಡಿಸಲಾಗಿರುತ್ತದೆ. ಒಂದು ಭಾಗವು ಪ್ಲೇನ್ ಟೆಕ್ಸ್ಟ್ ಫೀಲ್ಡ್ ಅನ್ನು ಹೊಂದಿರಬಹುದು. ಮುಂದಿನ ಭಾಗವು ತನ್ನದೇ ಆದ Content-Type ಮತ್ತು Content-Disposition ಹೆಡರ್ಗಳೊಂದಿಗೆ ರ (raw) ಬೈನರಿ ಇಮೇಜ್ ಅನ್ನು ಹೊಂದಿರಬಹುದು. ಸರ್ವರ್ ಬರುವ ಸ್ಟ್ರೀಮ್ ಅನ್ನು ಅನುಕ್ರಮವಾಗಿ ಓದುತ್ತದೆ, ಬೌಂಡರಿ ಮಾರ್ಕರ್ಗಳಿಗಾಗಿ ಕಾಯುತ್ತದೆ ಮತ್ತು ಇಡೀ ಪೇಲೋಡ್ ಅನ್ನು ಒಂದೇ ಬ್ಲಾಕ್ ಆಗಿ ಪರಿಗಣಿಸುವ ಅಗತ್ಯವಿಲ್ಲದೆಯೇ ಪ್ರತಿಯೊಂದು ವಿಭಾಗವನ್ನು ಸೂಕ್ತವಾದ ಹ್ಯಾಂಡ್ಲರ್ಗೆ (handler) ಹಸ್ತಾಂತರಿಸುತ್ತದೆ.
Node.js ನಲ್ಲಿ, ಈ ವ್ಯತ್ಯಾಸವು ಬಹಳ ಮುಖ್ಯವಾಗಿದೆ. express.json() ಮಿಡ್ಲ್ವೇರ್ (middleware) JSON ಬಾಡಿಗಳನ್ನು ಪಾರ್ಸ್ ಮಾಡುವುದನ್ನು ತಿಳಿದಿದೆ, ಆದರೆ ಅದು ಫೈಲ್ ಸ್ಟ್ರೀಮ್ಗಳನ್ನು ನಿರ್ವಹಿಸುವುದಿಲ್ಲ. multipart ಅಪ್ಲೋಡ್ಗಳನ್ನು ಪ್ರೊಸೆಸ್ ಮಾಡಲು, ನಿಮಗೆ Multer ಅಥವಾ Busboy ನಂತಹ ಸ್ಟ್ರೀಮಿಂಗ್ ಪಾರ್ಸರ್ ಅಗತ್ಯವಿದೆ. ಈ ಪರಿಕರಗಳು ರ (raw) ರಿಕ್ವೆಸ್ಟ್ ಸ್ಟ್ರೀಮ್ಗೆ ಅಂಟಿಕೊಳ್ಳುತ್ತವೆ ಮತ್ತು ಅದನ್ನು ಚಂಕ್ಗಳ ಮೂಲಕ (chunk by chunk) ಓದುತ್ತವೆ. ಉದಾಹರಣೆಗೆ, Multer ಬರುವ ಫೈಲ್ಗಳನ್ನು ಡಿಸ್ಕ್ನಲ್ಲಿರುವ ತಾತ್ಕಾಲಿಕ ಫೋಲ್ಡರ್ಗೆ ಬರೆಯಬೇಕೆ ಅಥವಾ ಸಣ್ಣ ಫೈಲ್ಗಳನ್ನು ಮೆಮೊರಿಯಲ್ಲಿ ಇರಿಸಬೇಕೆ ಎಂಬುದನ್ನು ನೀವು ಆಯ್ಕೆ ಮಾಡಬಹುದು. ಈ ಕಾನ್ಫಿಗರೇಶನ್ ನಿರ್ಧಾರವು ಬಹಳ ಮುಖ್ಯವಾಗಿದೆ. ನೀವು ಎಲ್ಲವನ್ನೂ ಮೆಮೊರಿಯಲ್ಲಿ ಇರಿಸಿಕೊಂಡರೆ ಮತ್ತು ನಿಮ್ಮ ಅಪ್ಲಿಕೇಶನ್ಗೆ ಇದ್ದಕ್ಕಿದ್ದಂತೆ ಹಲವಾರು ದೊಡ್ಡ ಫೈಲ್ಗಳು ಬಂದರೆ, ನಿಮ್ಮ ಪ್ರೊಸೆಸ್ ಹೀಪ್ ಸ್ಪೇಸ್ (heap space) ಖಾಲಿಯಾಗಿ ಕ್ರ್ಯಾಶ್ ಆಗಬಹುದು. ಡಿಸ್ಕ್ಗೆ ಬರೆಯುವುದು ಸ್ಥಿರತೆಗಾಗಿ I/O ಅನ್ನು ಬಳಸಿಕೊಳ್ಳುತ್ತದೆ, ಆದರೆ ಇದು ಕ್ಲೀನಪ್ ಮತ್ತು ಪಾತ್ ಸೆಕ್ಯೂರಿಟಿ (path security) ಬಗ್ಗೆ ತನ್ನದೇ ಆದ ಪ್ರಶ್ನೆಗಳನ್ನು ಉಂಟುಮಾಡುತ್ತದೆ.
ಸಣ್ಣ ಅಪ್ಲಿಕೇಶನ್ಗೆ, ./uploads ನಂತಹ ಲೋಕಲ್ ಫೋಲ್ಡರ್ಗೆ ಫೈಲ್ಗಳನ್ನು ಉಳಿಸುವುದು ಸಹಜ ಮತ್ತು ವೇಗವಾಗಿ ಅನಿಸುತ್ತದೆ. ಫೈಲ್ ನಿಮ್ಮ ಕೋಡ್ ಚಲಾಯಿಸುತ್ತಿರುವ ಅದೇ ಮಷೀನ್ನಲ್ಲಿ ಇರುತ್ತದೆ ಮತ್ತು ಅದನ್ನು ಸರ್ವ್ ಮಾಡುವುದು ಕೇವಲ ಸರಿಯಾದ ಪಾತ್ ಅನ್ನು ತೋರಿಸುವ ವಿಷಯವಾಗಿರುತ್ತದೆ. ಇದು ಕೆಲಸ ಮಾಡುವವರೆಗೆ ಮಾತ್ರ ಸರಿ.
ನೀವು ಎರಡನೇ ಅಪ್ಲಿಕೇಶನ್ ಸರ್ವರ್ನ ಮುಂದೆ ಲೋಡ್ ಬ್ಯಾಲೆನ್ಸರ್ (load balancer) ಇರಿಸಿದ ಕ್ಷಣವೇ, ಲೋಕಲ್ ಸ್ಟೋರೇಜ್ ಒಂದು ಸಮಸ್ಯೆಯಾಗುತ್ತದೆ. ಬಳಕೆದಾರರು ಪ್ರೊಫೈಲ್ ಚಿತ್ರವನ್ನು ಅಪ್ಲೋಡ್ ಮಾಡುತ್ತಾರೆ. ಲೋಡ್ ಬ್ಯಾಲೆನ್ಸರ್ ಆ ರಿಕ್ವೆಸ್ಟ್ ಅನ್ನು ಸರ್ವರ್ A ಗೆ ಕಳುಹಿಸುತ್ತದೆ ಮತ್ತು ಫೈಲ್ ಸರ್ವರ್ A ನ ಡಿಸ್ಕ್ನಲ್ಲಿ ಬರೆಯಲ್ಪಡುತ್ತದೆ. ನಂತರ, ಅದೇ ಬಳಕೆದಾರರು ಚಿತ್ರವನ್ನು ನೋಡಲು ಕೇಳಿದಾಗ, ಲೋಡ್ ಬ್ಯಾಲೆನ್ಸರ್ ರಿಕ್ವೆಸ್ಟ್ ಅನ್ನು ಸರ್ವರ್ B ಗೆ ಕಳುಹಿಸುತ್ತದೆ. ಸರ್ವರ್ B ತನ್ನದೇ ಆದ ಫೈಲ್ಸಿಸ್ಟಮ್ ಅನ್ನು ಪರಿಶೀಲಿಸುತ್ತದೆ ಮತ್ತು ಅಲ್ಲಿ ಏನೂ ಇರುವುದಿಲ್ಲ. ಫೈಲ್ ಅಸ್ತಿತ್ವದಲ್ಲಿಲ್ಲ ಎಂದರ್ಥ. ಬಳಕೆದಾರರನ್ನು ಒಂದೇ ಮಷೀನ್ಗೆ ಕಟ್ಟಿಹಾಕಲು ನೀವು ಸ್ಟಿಕಿ ಸೆಷನ್ಗಳನ್ನು (sticky sessions) ಬಳಸಬಹುದು, ಆದರೆ ಅದು ಅಸ್ಥಿರವಾದ ಪರಿಹಾರವಾಗಿದೆ. ಸರ್ವರ್ A ಮರುಪ್ರಾರಂಭಗೊಂಡರೆ, ಮರು-ನಿಯೋಜನೆಗೊಂಡರೆ (redeployed) ಅಥವಾ ಆಟೋಸ್ಕೇಲಿಂಗ್ ಇನ್ಸ್ಟೆನ್ಸ್ನಿಂದ ಬದಲಾಯಿಸಲ್ಪಟ್ಟರೆ, ಡೇಟಾ ಮಾಯವಾಗುತ್ತದೆ. ಕಂಟೇನರೈಸ್ಡ್ (containerized) ಪರಿಸರಗಳಲ್ಲಿ, ಲೋಕಲ್ ಡಿಸ್ಕ್ಗಳು ಇನ್ನೂ ಹೆಚ್ಚು ಅಸ್ಥಿರವಾಗಿರುತ್ತವೆ. ಡಾಕರ್ (Docker) ಕಂಟೇನರ್ನ ಫೈಲ್ಸಿಸ್ಟಮ್ ಅನ್ನು ಬಿಸಾಡಬಹುದಾದ (disposable) ವಸ್ತುವಿನಂತೆ ಪರಿಗಣಿಸಲಾಗುತ್ತದೆ. ಅದನ್ನು ಶಾಶ್ವತ ಸ್ಟೋರೇಜ್ ಆಗಿ ಪರಿಗಣಿಸುವುದು ಬಳಕೆದಾರರ ಡೇಟಾವನ್ನು ಕಳೆದುಕೊಳ್ಳಲು ಒಂದು ಸುಲಭ ದಾರಿ.
ಇದಕ್ಕೆ ಪ್ರಮಾಣಿತ ಪರಿಹಾರವೆಂದರೆ ಕಂಪ್ಯೂಟ್ (compute) ಅನ್ನು ಸ್ಟೋರೇಜ್ನಿಂದ ಪ್ರತ್ಯೇಕಿಸುವುದು. ನಿಮ್ಮ ಅಪ್ಲಿಕೇಶನ್ ಸರ್ವರ್ಗಳನ್ನು ಸ್ಟೇಟ್ಲೆಸ್ (stateless) ಆಗಿ ಇರಿಸಿ ಮತ್ತು ಅಪ್ಲೋಡ್ ಮಾಡಿದ ಫೈಲ್ಗಳನ್ನು AWS S3 ಅಥವಾ Google Cloud Storage ನಂತಹ ಮೀಸಲಾದ ಆಬ್ಜೆಕ್ಟ್ ಸ್ಟೋರೇಜ್ಗೆ ಕಳುಹಿಸಿ. ಈ ಸೇವೆಗಳು ಬಾಳಿಕೆ (durability), ಭೌಗೋಳಿಕ ವಿತರಣೆ ಮತ್ತು ಬೃಹತ್ ಸಮಾನಾಂತರ ಪ್ರಕ್ರಿಯೆಗಳಿಗಾಗಿ (massive concurrency) ನಿರ್ಮಿಸಲಾಗಿದೆ. ಅಪ್ಲಿಕೇಶನ್ ಸರ್ವರ್ ರಿಕ್ವೆಸ್ಟ್ ಅನ್ನು ನಿರ್ವಹಿಸುತ್ತದೆ, ಮೆಟಾಡೇಟಾವನ್ನು ವ್ಯಾಲಿಡೇಟ್ ಮಾಡುತ್ತದೆ ಮತ್ತು ನಂತರ ಬೈಟ್ಗಳನ್ನು ಅವುಗಳನ್ನು ಹಿಡಿದಿಡಲು ವಿನ್ಯಾಸಗೊಳಿಸಲಾದ ಮೂಲಸೌಕರ್ಯಕ್ಕೆ ಹಸ್ತಾಂತರಿಸುತ್ತದೆ.
Yet even this pattern creates a bottleneck if you implement it carelessly. Many teams start by having the browser upload the file to the backend, and then having the backend forward every single byte to object storage. If a user uploads a five-hundred megabyte video, your server becomes a middleman. It consumes bandwidth pulling the file in, then consumes more bandwidth pushing it out to S3. The connection stays open for the entire duration of the transfer. Slow uploads from users with poor network conditions can tie up server connections for minutes. Memory usage stays elevated if the server buffers the stream, and if you are running on metered hosting, you are paying twice for the same data transfer. Horizontal scaling does not solve this, because every additional server you add still gets stuck ferrying bytes it does not need to see.
Modern systems solve the problem by removing the backend from the data path entirely. Instead of accepting the file, the backend only accepts a request for permission to upload. The flow looks like this:
- The browser asks the backend to initiate an upload, usually sending only the filename, file type, and intended purpose.
- The backend authenticates the user, validates the request against business rules, and uses an SDK to generate a temporary presigned URL from the object storage provider.
- The backend returns that URL to the browser. The URL is scoped to a specific bucket and key, valid for a short window such as five or fifteen minutes, and signed with a token that grants only the precise permissions needed.
- The browser uploads the file directly to S3 or GCS using a standard PUT or POST. The bytes travel straight from the user’s device to
