Cách tôi xây dựng một ứng dụng với 500.000 người dùng trong 5 ngày trên máy chủ 100 đô la

Dường như có một sự đồng thuận chung trong thế giới khởi nghiệp rằng bạn nên xây dựng một MVP (sản phẩm khả thi tối thiểu) mà không cần quan tâm quá nhiều đến khả năng mở rộng kỹ thuật. Tôi đã nghe nhiều lần rằng ưu tiên duy nhất là chỉ cần đưa sản phẩm ra khỏi đó. Miễn là mô hình kinh doanh của bạn sẽ hoạt động ở quy mô, thì bạn rất tốt. Bạn không nên lãng phí thời gian và tiền bạc để tạo ra một sản phẩm có thể mở rộng về mặt kỹ thuật. Tất cả những gì bạn lo lắng là kiểm tra các giả định của bạn, xác nhận thị trường và đạt được lực kéo. Khả năng mở rộng là một mối quan tâm cho sau này. Thật không may, niềm tin có phần mù quáng này đã dẫn đến một số thất bại khủng khiếp. Và Pokémon GO đã nhắc nhở chúng tôi về điều đó.

Một người đã chiến thắng lại mắc lỗi này là Jonathan Zarra, người tạo ra GoChat cho Pokémon GO. Anh chàng đã đạt được 1 triệu người dùng sau 5 ngày bằng cách tạo một ứng dụng trò chuyện cho người hâm mộ Pokémon GO. Tuần trước, như bạn có thể đọc trong bài báo, anh ấy đã nói chuyện với các VC để xem làm thế nào anh ấy có thể phát triển và kiếm tiền từ ứng dụng của mình. Ngay sau đó, GoChat đi xuống. Rất nhiều người dùng bị mất và rất nhiều tiền đã bỏ ra. Một sự xấu hổ thực sự cho một động thái thiên tài.

Bài báo nói rằng Zarra đã có một thời gian khó khăn để trả tiền cho các máy chủ cần thiết để lưu trữ người dùng hoạt động 1M. Ông không bao giờ nghĩ để có được nhiều người dùng này. Ông đã xây dựng ứng dụng này như một MVP, quan tâm đến khả năng mở rộng sau này. Ông đã xây dựng nó để thất bại. Zarra đã thuê một nhà thầu trên Upwork để khắc phục rất nhiều vấn đề về hiệu suất. Nhà thầu tuyên bố rằng chi phí máy chủ là khoảng 4.000 đô la. Vì lịch của tôi có tên là 2016, tôi giả sử anh ấy nói về 4000 phần cứng, nhưng $ 4000 về chi phí lưu lượng và máy chủ ảo hàng tháng hoặc hàng năm.

Tôi đã thiết kế và xây dựng các nền tảng web cho hàng trăm triệu người dùng tích cực trong phần lớn sự nghiệp của tôi. Tôi có thể nói 4.000 đô la là một khoản tiền hoàn toàn không cần thiết cho người dùng 1 triệu trong một ứng dụng trò chuyện. Ngay cả đối với một MVP. Nó có nghĩa là công nghệ máy chủ ứng dụng được thiết kế kém. Nó không dễ để xây dựng một hệ thống có thể mở rộng, tiết kiệm chi phí cho hàng triệu người dùng hàng tháng. Nhưng nó cũng không quá phức tạp để có một số loại thiết lập có thể xử lý ít nhất một lượng người dùng kha khá trên một số máy chủ giá rẻ trong đám mây. Bạn chỉ cần tính đến nó khi xây dựng MVP, bằng cách lựa chọn đúng.

GoSnaps: 500.000 người dùng trong 5 ngày trên máy chủ $ 100 / tháng

Tương tự như GoChat, tôi cũng đã ra mắt ứng dụng dành cho người hâm mộ Pokémon GO vào tuần trước, được gọi là GoSnaps. GoSnaps là một ứng dụng để chia sẻ ảnh chụp màn hình và hình ảnh Pokémon GO trên bản đồ. Instagram / Snapchat cho Pokémon GO. GoSnaps đã tăng lên 60 nghìn người dùng trong ngày đầu tiên, 160 nghìn người dùng vào ngày thứ hai và 500 nghìn người dùng duy nhất sau 5 ngày (hiện tại). Nó có 150 sn200200k tải lên snaps bây giờ. Nó có khoảng 1000 người dùng đồng thời tại bất kỳ thời điểm nào. Tôi đã xây dựng phần mềm nhận dạng hình ảnh để tự động kiểm tra xem hình ảnh được tải lên có liên quan đến Pokémon GO hay không và thay đổi kích thước công cụ cho hình ảnh được tải lên. Chúng tôi chạy toàn bộ thiết lập này trên một máy chủ Google Cloud trung bình 100 đô la / tháng, cộng với (giá rẻ) Google Cloud Storage để lưu trữ hình ảnh. Vâng, 100 đô la. Và nó thực hiện tốt.

So sánh kỹ thuật của GoChat và GoSnaps

Hãy cùng so sánh với GoChat và GoSnaps. Cả hai ứng dụng có thể kích hoạt rất nhiều yêu cầu mỗi giây để tìm nạp các cuộc trò chuyện / hình ảnh trong một khu vực nhất định trên bản đồ. Đây là một tra cứu không gian địa lý trong cơ sở dữ liệu (hoặc công cụ tìm kiếm), bằng một đa giác của các vị trí vĩ độ / kinh độ hoặc bởi một điểm cụ thể. Chúng tôi sử dụng một đa giác và chúng tôi thực hiện yêu cầu này mỗi khi ai đó di chuyển bản đồ. Các loại truy vấn này là các hoạt động nặng trên cơ sở dữ liệu, đặc biệt là kết hợp với sắp xếp hoặc lọc. Chúng tôi nhận được loại yêu cầu tìm kiếm này hàng trăm lần mỗi giây. GoChat có lẽ cũng vậy.

Điểm độc đáo của GoChat là nó phải tìm nạp và đăng rất nhiều tin nhắn trò chuyện mỗi giây. Bài viết về GoChat nói về 600 yêu cầu mỗi giây cho toàn bộ ứng dụng. 600 yêu cầu đó là sự kết hợp của các yêu cầu bản đồ và tin nhắn trò chuyện. Các tin nhắn trò chuyện này nhỏ và có thể / nên được thực hiện qua một kết nối ổ cắm đơn giản, nhưng xảy ra thường xuyên và phải được phân phối cho các cuộc trò chuyện khác. Điều này có thể quản lý được với thiết lập đúng, nhưng tai hại với thiết lập kém, giống như MVP.

GoSnaps, mặt khác, có rất nhiều hình ảnh được tải xuống và ’thích trên mỗi giây. Các snaps chồng chất trên máy chủ, vì các snaps cũ vẫn có liên quan. Trò chuyện cũ thì không. Vì các tệp hình ảnh thực tế được lưu trữ trong Google Cloud Storage, nên số lượng tệp hình ảnh được yêu cầu không phải là mối quan tâm đối với tôi với tư cách là nhà phát triển. Google Cloud xử lý việc này và tôi tin tưởng Google. Nhưng các snaps được yêu cầu trên bản đồ là mối quan tâm của tôi. GoSnaps có phần mềm nhận dạng hình ảnh tìm kiếm các mẫu trên tất cả các video tải lên để xem hình ảnh có liên quan đến Pokémon hay không. Nó cũng thay đổi kích thước hình ảnh và gửi chúng đến Cloud Storage. Đây là tất cả các hoạt động nặng về CPU và băng thông. Cách nặng hơn so với việc phân phối một số tin nhắn trò chuyện nhỏ, nhưng ít thường xuyên hơn.

Kết luận của tôi là cả hai ứng dụng đều rất giống nhau về độ phức tạp về khả năng mở rộng. GoChat xử lý nhiều tin nhắn nhỏ hơn trong khi GoSnaps xử lý hình ảnh lớn hơn và hoạt động máy chủ nặng hơn. Thiết kế một kiến ​​trúc cho hai ứng dụng này đòi hỏi một cách tiếp cận hơi khác nhau, nhưng phức tạp tương tự nhau.

Cách tôi xây dựng MVP có thể mở rộng trong 24h

GoSnaps được xây dựng dưới dạng MVP, không phải là một sản phẩm kinh doanh chuyên nghiệp. Nó được xây dựng hoàn toàn trong 24 giờ. Tôi đã lấy một dự án soạn sẵn NodeJS cho hackathons và sử dụng cơ sở dữ liệu MongoDB mà không có bất kỳ hình thức lưu trữ nào. Không Redis, không Varnish, không có cài đặt Nginx ưa thích, không có gì. Ứng dụng iOS thực tế được xây dựng bằng mã Objective-C gốc, với một số mã liên quan đến Apple Maps mượn từ Unboxd, ứng dụng chính của chúng tôi. Vì vậy, làm thế nào tôi làm cho nó có thể mở rộng? Bởi không được lười biếng.

Hãy nói rằng tôi sẽ coi MVP chỉ là một cuộc đua với đồng hồ để xây dựng một ứng dụng chức năng nhanh nhất có thể, bất kể chất lượng phụ trợ kỹ thuật. Tôi sẽ đặt hình ảnh của mình ở đâu? Trong cơ sở dữ liệu: MongoDB. Nó sẽ không yêu cầu cấu hình và hầu như không có mã. Dễ dàng. MVP. Làm thế nào tôi có thể truy vấn các snaps trong một khu vực nhất định có nhiều lượt thích nhất? Bằng cách chỉ chạy một truy vấn MongoDB đơn giản trên toàn bộ đống snaps đã tải lên. Chỉ cần một truy vấn cơ sở dữ liệu trên một bộ sưu tập cơ sở dữ liệu. MVP. Tất cả điều này sẽ phá hủy ứng dụng của tôi và tính năng ứng dụng.

Nhìn vào truy vấn tôi sẽ phải chạy để có được những snaps này: Nhận thấy tất cả các snaps trong đa giác vị trí [A, B, C, D], ngoại trừ snaps được đánh dấu là lạm dụng, ngoại trừ snaps vẫn đang được xử lý, được sắp xếp theo số lượt thích, được đặt hàng bởi Pokémon GO hợp lệ trước tiên và sau đó được đặt hàng bởi lần đầu tiên mới nhất. Điều này hoạt động tuyệt vời trên một tập dữ liệu nhỏ, tuyệt vời, MVP. Nhưng điều này sẽ hoàn toàn thảm họa dưới bất kỳ loại tải trọng nghiêm trọng nào. Ngay cả khi tôi đã đơn giản hóa truy vấn trên chỉ bao gồm ba điều kiện / thao tác sắp xếp, thì nó cũng sẽ là thảm họa. Tại sao? Bởi vì đây không phải là cách cơ sở dữ liệu được sử dụng. Một cơ sở dữ liệu chỉ nên truy vấn trên một chỉ mục tại một thời điểm, điều này là không thể đối với các truy vấn không gian địa lý này. Bạn sẽ tránh xa nó nếu bạn không có nhiều người dùng, nhưng bạn sẽ đi xuống sau khi thành công. Giống như GoChat.

Tôi đã làm gì thay thế? Sau khi áp dụng nhận dạng hình ảnh đắt tiền CPU và thực hiện thay đổi kích thước, hình ảnh đã thay đổi kích thước được tải lên Google Cloud Storage. Bằng cách này, máy chủ và cơ sở dữ liệu don Nhận được yêu cầu hình ảnh. Cơ sở dữ liệu nên lo lắng về dữ liệu, không phải hình ảnh. Điều này tiết kiệm nhiều máy chủ của chính nó. Về phía cơ sở dữ liệu, tôi tách các snaps thành một vài bộ sưu tập khác nhau: tất cả snaps, snaps thích nhất, snaps mới nhất, snaps hợp lệ mới nhất và vv. Bất cứ khi nào một snap được thêm, thích hoặc đánh dấu là lạm dụng, mã sẽ kiểm tra nếu nó (vẫn) thuộc về một trong những bộ sưu tập và hành động tương ứng. Bằng cách này, mã có thể truy vấn từ các bộ sưu tập đã chuẩn bị thay vì chạy các truy vấn phức tạp trên một đống hỗn độn lớn. Nó chỉ đơn giản là tách dữ liệu một cách hợp lý vào một số nhóm đơn giản. Không có gì phức tạp. Nhưng nó cho phép tôi truy vấn duy nhất trên tọa độ không gian địa lý với một thao tác sắp xếp, thay vì truy vấn phức tạp như mô tả ở trên. Nói một cách đơn giản: nó giúp bạn dễ dàng chọn dữ liệu.

Tôi đã dành bao nhiêu thời gian cho tất cả những điều này? Có thể 2 đến 3 giờ. Tại sao tôi làm điều này ở nơi đầu tiên? Bởi vì đó chỉ là cách tôi thiết lập mọi thứ. Tôi cho rằng ứng dụng của tôi sẽ thành công. Không có điểm nào trong việc xây dựng một ứng dụng giả định rằng nó đã thắng thành công. Tôi sẽ không thể ngủ nếu ứng dụng của tôi có lực kéo và sau đó chết do công nghệ xấu. Tôi nướng các nguyên tắc khả năng mở rộng tối thiểu vào ứng dụng của tôi. Nó khác biệt giữa hạnh phúc và hoảng loạn. Nó là những gì tôi nghĩ nên là một phần của MVP ứng dụng.

Chọn công cụ phù hợp cho MVP của bạn

Nếu tôi đã xây dựng GoSnaps với ngôn ngữ lập trình chậm hơn hoặc với khung lớn, tôi sẽ cần nhiều máy chủ hơn. Nếu tôi đã sử dụng một cái gì đó như PHP với Symfony hoặc Python với Django hoặc Ruby on Rails, thì tôi đã dành nhiều ngày để sửa các phần chậm của ứng dụng hoặc thêm máy chủ. Tin tôi đi, tôi đã làm điều đó nhiều lần rồi. Các ngôn ngữ và khung này rất tuyệt vời trong nhiều tình huống, nhưng không dành cho MVP với ngân sách máy chủ thấp. Điều này chủ yếu là do nhiều lớp mã thường được sử dụng để ánh xạ các bản ghi cơ sở dữ liệu thành logic và mã khung không cần thiết. Nó chỉ đơn giản là chạm CPU quá mạnh. Hãy để tôi cho bạn một ví dụ về mức độ thực sự của vấn đề này.

Như đã nói, GoSnaps sử dụng NodeJS làm ngôn ngữ / nền tảng phụ trợ, thường nhanh và hiệu quả. Tôi sử dụng Mongoose làm ORM để làm cho MongoDB hoạt động đơn giản như một lập trình viên. Tôi không phải là một chuyên gia Mongoose bằng bất kỳ phương tiện nào và tôi biết rằng thư viện tự nó có một cơ sở mã lớn. Do đó Mongoose là một lá cờ đỏ. Nhưng vâng, MVP. Vào một thời điểm cuối tuần trước, các quy trình NodeJS trên máy chủ của chúng tôi đã chạy với 90% CPU mỗi CPU, điều này không thể chấp nhận được đối với 800 người dùng đồng thời 800 10001000. Tôi nhận ra rằng đó phải là Mongoose làm mọi thứ với dữ liệu được tìm nạp của tôi. Rõ ràng tôi chỉ đơn giản là phải kích hoạt hàm Mongoose trong trò chơi lean () để có được các đối tượng JSON đơn giản thay vì các đối tượng Mongoose ma thuật. Sau thay đổi đó, các quy trình NodeJS đã giảm xuống còn khoảng 5 mức sử dụng CPU10%. Chỉ cần logic đơn giản để biết những gì mã của bạn thực sự làm là rất quan trọng. Nó giảm tải 90%. Hãy tưởng tượng có một thư viện thực sự nặng, như Symfony với Doctrine. Nó sẽ yêu cầu một vài máy chủ có nhiều lõi CPU chỉ thực thi mã một mình, mặc dù cơ sở dữ liệu được coi là nút cổ chai chứ không phải mã.

Chọn một ngôn ngữ nạc và nhanh rất quan trọng đối với khả năng mở rộng, trừ khi bạn có nhiều tiền cho máy chủ. Chọn một ngôn ngữ có nhiều thư viện có sẵn hữu ích thậm chí còn quan trọng hơn, vì bạn muốn xây dựng MVP của mình một cách nhanh chóng. NodeJS, Scala và Go là những ngôn ngữ tốt đáp ứng cả hai yêu cầu này. Họ cung cấp rất nhiều công cụ tốt với nhiều hiệu suất tốt. Bản thân một ngôn ngữ như PHP hoặc Java không nhất thiết phải chậm, nhưng thường được sử dụng cùng với các khung và cơ sở mã lớn làm cho ứng dụng trở nên nặng nề. Các ngôn ngữ này là tuyệt vời để phát triển hướng đối tượng sạch và mã được kiểm tra tốt, nhưng không cho khả năng mở rộng nhanh chóng và rẻ tiền. Tôi không muốn bắt đầu một cuộc tranh luận ngôn ngữ lập trình lớn, vì vậy hãy để tôi nói rằng điều này là chủ quan và không đầy đủ. Cá nhân tôi yêu Erlang và sẽ không bao giờ sử dụng nó cho MVP, vì vậy tất cả các đối số của bạn đều không hợp lệ.

Khởi động trước đây của tôi Cloud Games

Vài năm trước, tôi đồng sáng lập Cloud Games, một nhà xuất bản trò chơi HTML5. Khi chúng tôi bắt đầu, chúng tôi là một trang web chơi game B2C tập trung vào khu vực MENA. Chúng tôi đã dành rất nhiều nỗ lực để có được người dùng và đạt được 1 triệu người dùng hoạt động hàng tháng (MAU) sau một vài tháng. Vào thời điểm đó, tôi đã sử dụng PHP, Symfony2, Doctrine và MongoDB trong một thiết lập khá đơn giản và gọn gàng. Tôi đã từng làm việc cho Spil Games với 200 triệu MAU, lúc đó đã sử dụng PHP và sau đó chuyển sang Erlang. Sau khi Cloud Games đạt xấp xỉ 100.000 MAU, chúng tôi bắt đầu thấy nỗi đau máy chủ thực sự với Doctrine và MongoDB do chi phí rất lớn của các thư viện PHP này. Tôi đã thiết lập MongoDB, lập chỉ mục và truy vấn đúng cách, nhưng các máy chủ đã gặp khó khăn khi xử lý tất cả mã. Và vâng, tôi đã sử dụng bộ nhớ cache APC của PHP và v.v.

Vì cloudgames.com vẫn rất tĩnh, tôi đã có thể di chuyển MVP sang NodeJS bằng Redis trong vài ngày. Thiết lập tương tự, ngôn ngữ khác nhau. Điều này dẫn đến giảm tải ngay lập tức khoảng 95%. Cấp, điều này có liên quan nhiều đến việc tránh các thư viện PHP hơn là ngôn ngữ thực tế. Nhưng một thiết lập NodeJS tối giản có ý nghĩa hơn một thiết lập PHP tối giản. Đặc biệt là vì MongoDB và mã frontend cũng là 100% JavaScript, giống như NodeJS. PHP không có khung và thư viện của nó chỉ là một ngôn ngữ khác.

Chúng tôi cần thiết lập giá rẻ này, vì chúng tôi là một công ty khởi nghiệp giai đoạn đầu tự tài trợ. Cloud Games hiện đang hoạt động tốt và vẫn dựa trên kiến ​​trúc NodeJS tiết kiệm chi phí. Chúng tôi có thể đã không thành công với một thiết lập công nghệ tốn kém hơn, vì thực tế là chúng tôi đã trải qua một số thời điểm thực sự khó khăn khi khởi nghiệp. Thiết kế một kiến ​​trúc chi phí thấp, có thể mở rộng là rất cần thiết để thành công.

MVP và khả năng mở rộng có thể cùng tồn tại

Nếu có một cơ hội cho ứng dụng của bạn phát triển theo cấp số nhân do sự cường điệu hoặc khả năng phủ sóng truyền thông, hãy đảm bảo xem xét khả năng mở rộng như một phần của MVP của bạn. Các nguyên tắc của các sản phẩm khả thi tối thiểu và công nghệ có thể mở rộng có thể cùng tồn tại. Không có gì buồn hơn là xây dựng một ứng dụng thành công và thấy nó thất bại vì các vấn đề kỹ thuật. Bản thân Pokémon GO đã có rất nhiều vấn đề, nhưng rất độc đáo và được thổi phồng đến nỗi nó không thành vấn đề. Các công ty khởi nghiệp nhỏ không có sự sang trọng này. Thời gian là tất cả. Một triệu người dùng GoChat và nửa triệu người dùng GoSnaps có thể đồng ý với tôi.

Chỉnh sửa vào ngày 21 tháng 7 năm 2016

Tôi đã chỉnh sửa một chút bài viết, vì GoChat vẫn còn tồn tại trong cửa hàng Google Play. Trang Google Play cho biết, đó là một phần mềm khác với 100% người dùng, với hơn 2 triệu người dùng. Một phiên bản iOS được cho là sẽ sớm xuất hiện trở lại.

Hãy thích và làm theo!

Nếu bạn thích bài viết này, xin vui lòng thích nó ở đây dưới đây trên Medium. Điều này có ý nghĩa rất lớn với tôi. Hãy bình luận để được tư vấn về khả năng mở rộng. Tại Unboxd, chúng tôi luôn vui mừng khi thấy các ứng dụng khác phát triển! Và người New York: hãy xem ứng dụng Ẩm thực của chúng tôi! Cảm ơn bạn!