Openstatus www.openstatus.dev

post: first 48h being public (#131)

* wip: 48h

* wip: 48h

* fix: include assets in middleware

* fix: more middleware

* wip: revert back to img

* wip: 48h

* feat: include react-tweet

* fix: missing props

* wip: 48h

* chore: typo

* fix: typo

* wip: emoji

* chore: add create account

* chore: update title

authored by

Maximilian Kaske and committed by
GitHub
14fb70a1 84ded4f4

+158 -99
+1
apps/web/package.json
··· 57 57 "react-day-picker": "8.8.0", 58 58 "react-dom": "18.2.0", 59 59 "react-hook-form": "7.45.1", 60 + "react-tweet": "^3.0.3", 60 61 "reading-time": "1.5.0", 61 62 "rehype-pretty-code": "0.10.0", 62 63 "resend": "0.15.3",
apps/web/public/assets/posts/blog-post-1.png

This is a binary file and will not be displayed.

apps/web/public/assets/posts/the-first-48-hours/enough-gas-workspace-slug.png

This is a binary file and will not be displayed.

apps/web/public/assets/posts/the-first-48-hours/github-trending.png

This is a binary file and will not be displayed.

apps/web/public/assets/posts/the-first-48-hours/qstash-usage.png

This is a binary file and will not be displayed.

apps/web/public/assets/posts/the-first-48-hours/vercel-usage.png

This is a binary file and will not be displayed.

apps/web/public/assets/posts/the-first-48-hours/wet-grandmother-workspace-slug.png

This is a binary file and will not be displayed.

+10 -4
apps/web/src/app/blog/[slug]/page.tsx
··· 64 64 return ( 65 65 <> 66 66 <BackButton href="/blog" /> 67 - <Shell> 67 + <Shell className="sm:py-8 md:py-12"> 68 68 <article className="grid gap-8"> 69 - <div className="mx-auto grid max-w-prose gap-3"> 70 - <h1 className="font-cal text-3xl">{post.title}</h1> 69 + <div className="mx-auto grid w-full max-w-prose gap-3"> 70 + <h1 className="font-cal mb-5 text-3xl">{post.title}</h1> 71 71 <div className="border-border relative h-64 w-full overflow-hidden rounded-lg border"> 72 - <Image 72 + {/* <Image 73 73 src={post.image} 74 74 fill={true} 75 75 alt={post.title} 76 76 className="object-cover" 77 + /> */} 78 + {/* HOTFIX: plain `img` */} 79 + <img 80 + src={post.image} 81 + alt={post.title} 82 + className="h-full w-full object-cover" 77 83 /> 78 84 </div> 79 85 <p className="text-muted-foreground text-sm font-light">
+9
apps/web/src/components/content/mdx-components.tsx
··· 1 1 import * as React from "react"; 2 2 import Link from "next/link"; 3 + import type { TweetProps } from "react-tweet"; 4 + import { Tweet } from "react-tweet"; 3 5 4 6 export const components = { 5 7 a: ({ ··· 24 26 className="text-foreground underline underline-offset-4 hover:no-underline" 25 27 {...props} 26 28 /> 29 + ); 30 + }, 31 + Tweet: (props: TweetProps) => { 32 + return ( 33 + <div data-theme="light" className="not-prose [&>div]:mx-auto"> 34 + <Tweet {...props} /> 35 + </div> 27 36 ); 28 37 }, 29 38 };
+1 -1
apps/web/src/components/content/mdx.tsx
··· 11 11 12 12 return ( 13 13 // FIXME: weird behaviour when `prose-headings:font-cal` and on mouse movement font gets bigger 14 - <div className="prose prose-neutral dark:prose-invert prose-pre:border prose-pre:border-border prose-pre:rounded-lg"> 14 + <div className="prose prose-neutral dark:prose-invert prose-pre:border prose-pre:border-border prose-pre:rounded-lg prose-img:rounded-lg prose-img:border prose-img:border-border"> 15 15 <MDXComponent components={{ ...components }} /> 16 16 </div> 17 17 );
-92
apps/web/src/content/posts/hello-world.mdx
··· 1 - --- 2 - title: Understanding Hoisting and Promises in JavaScript 3 - description: 4 - Understanding two important concepts in JavaScript - hoisting and promises. 5 - author: 6 - name: Nikhil Mohite 7 - url: https://nikhilmohite.com 8 - publishedAt: 2023-07-16 9 - image: /assets/posts/blog-post-1.png 10 - --- 11 - 12 - ## Understanding Hoisting and Promises in JavaScript 13 - 14 - JavaScript is a versatile programming language that offers a wide range of 15 - features and functionalities. Two important concepts to grasp in JavaScript are 16 - hoisting and promises. Understanding these concepts can greatly enhance your 17 - ability to write efficient and reliable code. In this blog post, we will explore 18 - what hoisting and promises are, how they work, and provide some code examples to 19 - illustrate their usage. 20 - 21 - ## Hoisting: Bringing Declarations to the Top 22 - 23 - Hoisting is a behavior in JavaScript where variable and function declarations 24 - are moved to the top of their containing scope during the compilation phase. 25 - This means that you can access and use variables and functions before they are 26 - actually declared in your code. However, it's important to note that only the 27 - declarations are hoisted, not the initializations. 28 - 29 - For example, consider the following code snippet: 30 - 31 - ```js title="index.js" {} showLineNumbers 32 - console.log(name); 33 - ``` 34 - 35 - ## Promises: Managing Asynchronous Operations 36 - 37 - Promises are a powerful concept introduced in ECMAScript 6 (ES6) that enable 38 - better management of asynchronous operations in JavaScript. They provide a 39 - cleaner and more structured approach to handling asynchronous tasks, such as 40 - network requests or file operations. A promise represents a value that may not 41 - be available yet, but will be resolved at some point in the future. 42 - 43 - Promises have three states: pending, fulfilled, or rejected. When a promise is 44 - pending, it means the asynchronous operation is still in progress. Once the 45 - operation is completed successfully, the promise is fulfilled and returns a 46 - value. On the other hand, if an error occurs during the operation, the promise 47 - is rejected, and an error object is returned. 48 - 49 - This code fetches data asynchronously using a promise in TypeScript, resolves 50 - the data after a delay, and handles the received data or any potential errors 51 - using then and catch blocks. 52 - 53 - ```ts title="index.ts" {3, 16} showLineNumbers 54 - function getData(): Promise<string[]> { 55 - return new Promise<string[]>((resolve, reject) => { 56 - setTimeout(() => { 57 - const data: string[] = ["John", "Jane", "Bob"]; 58 - if (data.length > 0) { 59 - resolve(data); 60 - } else { 61 - reject(new Error("No data available.")); 62 - } 63 - }, 2000); 64 - }); 65 - } 66 - 67 - getData() 68 - .then((data) => { 69 - console.log("Received data:", data); 70 - }) 71 - .catch((error) => { 72 - console.error("Error:", error.message); 73 - }); 74 - ``` 75 - 76 - ## Advantages of Using Promises 77 - 78 - - Promises improve code readability and maintainability in JavaScript. 79 - - Promises simplify error handling in asynchronous code. 80 - - Promises help avoid <strong>"callback hell"</strong> by providing a more 81 - linear and sequential approach to asynchronous operations. 82 - 83 - ## Conclusion 84 - 85 - In conclusion, hoisting and promises are important concepts in JavaScript that 86 - greatly contribute to writing efficient and reliable code. Hoisting allows 87 - variable and function declarations to be moved to the top of their scope, 88 - enabling early access to these entities. Promises, on the other hand, provide a 89 - structured and readable approach to managing asynchronous operations, enhancing 90 - code maintainability and error handling. 91 - 92 - [test link](https://openstatus.dev)
+109
apps/web/src/content/posts/the-first-48-hours.mdx
··· 1 + --- 2 + title: 48 hours of public OpenStatus 3 + description: The numbers, limits we faced and consequences we have taken. 4 + author: 5 + name: Maximilian Kaske 6 + url: https://twitter.com/mxkaske 7 + publishedAt: 2023-08-02 8 + image: /assets/posts/first-48h/github-trending.png 9 + --- 10 + 11 + ## 48 hours of Rollercoaster 🎢 12 + 13 + The past two days following the launch have been incredibly hectic. The level of 14 + interest we received exceeded our expectations. We were even trending on GitHub. 15 + The number of users and created monitors far surpassed what we anticipated. We 16 + sincerely appreciate your support, as it further motivates us. The raw 17 + statistics are astonishing: 18 + 19 + - Monitors: 315 20 + - Users: 418 21 + - Status pages: 174 22 + 23 + With a large user base comes great responsibility. Thanks to the high volume of 24 + users, we discovered several flaws in our system. We promptly addressed the 25 + issues and took action. 26 + 27 + ## Limits we encountered 😱 28 + 29 + We conducted a DDOS attack on Vercel's firewalls from Upstash servers because we 30 + where pining every single vercel region for every monitor (18 \* 315 = 5.670) 31 + more or less at once. Before considering the removal of 32 + [QStash](http://upstash.com/qstash?ref=openstatus), we implemented a hotfix to 33 + introduce random delays of 0 to 180 seconds between each ping. This prevented us 34 + from overwhelming Vercel with simultaneous requests, significantly reducing the 35 + number of retries on QStash. As a result of these measures, we now have a 36 + randomized check interval of 0 to 90 seconds. 37 + 38 + For the time being, we will keep it to ensure that every check is successfully 39 + processed. 40 + 41 + ![Qstash Usage](/assets/posts/first-48h/qstash-usage.png) 42 + 43 + Moreover, our actions caused the `/monitor/[id]/data` data-table to break. We 44 + were storing the metadata `res.text()` in Tinybird and retrieving it with the 45 + query `SELECT * from monitors WHERE ....` This means that if the text is in HTML 46 + format, we store the HTML content of the page, which is acceptable. However, 47 + this led to quickly exceeding the 100MB limit for the result length when 48 + accessing monitor data. Now, we are only querying the necessary data from 49 + tinybird: `SELECT latency, timestamp, url, ... from monitors WHERE ...`. Despite 50 + this, we continue to ingest the data and will only request it in the future when 51 + you click on the "View metadata" action. 52 + 53 + And of course no need to mention all the little hotfixed we had to make here and 54 + there. 55 + 56 + ### What would it cost us? 💸 57 + 58 + Let's break the current numbers a bit more down if we would have kept pinging 59 + all the regions: 60 + 61 + - Number of checks every 10min: 315 \* 18 regions = 5.670 62 + - Number of checks every 1h: 5.670 \* 6 = 34.020 63 + - Number of checks every day: 34.020 \* 24 = 816.480 64 + 65 + ![Vercel Usage](/assets/posts/first-48h/vercel-usage.png) 66 + 67 + > We have resolved an issue with regex invocation in the edge middleware. 68 + > Previously, it was being called whenever an API endpoint was called. 69 + 70 + Under Vercel's Pro plan, we have 1 million edge function executions included, 71 + and for each additional 1 million, there is a 72 + $2 charge. Because the numbers are constant, we would have payed 73 + 1$/100k Qstash 74 + messages which would lead us to a approx. **10$/day** for the current free 75 + tiers. 76 + 77 + ### Actions taken 👷 78 + 79 + But due to the high cost of our current infrastructure, **we have downgraded all 80 + monitors to a single region** (`/regions/auto`, a which will take a random 81 + vercel region) in order to stay within a reasonable budget, by dividing it 82 + by 18. You can edit the region for each monitor. We apologize for any 83 + inconvenience caused and will update you on the new conditions once everything 84 + is normalized. 85 + 86 + A big appreciation to [@chronark\_](https://twitter.com/chronark_) who joined us 87 + on a call to discuss potential solutions and for sharing his knowledge about his 88 + own service, [planetfall.io](https://planetfall.io). 89 + 90 + Also, Guilherme had a very good comment: 91 + 92 + <Tweet id="1686482013685940224" /> 93 + 94 + As a result of this incident, we will be reevaluating the prices and plans 95 + displayed on our homepage. 96 + 97 + Join [Discord](https://openstatus.dev/discord) if you want to learn more or just 98 + have a chat! 99 + 100 + Again, thank you for all your support and your understanding. 🙏 101 + 102 + --- 103 + 104 + P.S. We are generating your workspace-slug by merging two random names. Create 105 + an account on [openstatus](https://openstatus.dev) and share yours on 106 + [Discord](https://openstatus.dev/discord) - it's fun to watch! 🍿 107 + 108 + ![wet-grandmother slug](/assets/posts/first-48h/wet-grandmother-workspace-slug.png) 109 + ![enough-gas slug](/assets/posts/first-48h/enough-gas-workspace-slug.png)
+1 -1
apps/web/src/middleware.ts
··· 104 104 105 105 export const config = { 106 106 matcher: [ 107 - "/((?!api|_next/static|_next/image|favicon.ico).*)", 107 + "/((?!api|assets|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)", 108 108 "/", 109 109 "/(api/webhook|api/trpc)(.*)", 110 110 "/(!api/checker/:path*|!api/og|!api/ping)",
+27 -1
pnpm-lock.yaml
··· 185 185 react-hook-form: 186 186 specifier: 7.45.1 187 187 version: 7.45.1(react@18.2.0) 188 + react-tweet: 189 + specifier: ^3.0.3 190 + version: 3.0.3(react-dom@18.2.0)(react@18.2.0) 188 191 reading-time: 189 192 specifier: 1.5.0 190 193 version: 1.5.0 ··· 2728 2731 '@peculiar/asn1-schema': 2.3.6 2729 2732 '@peculiar/json-schema': 1.1.12 2730 2733 pvtsutils: 1.3.2 2731 - tslib: 2.4.1 2734 + tslib: 2.6.1 2732 2735 webcrypto-core: 1.7.7 2733 2736 dev: false 2734 2737 ··· 10384 10387 tslib: 2.6.1 10385 10388 dev: false 10386 10389 10390 + /react-tweet@3.0.3(react-dom@18.2.0)(react@18.2.0): 10391 + resolution: {integrity: sha512-dKPpCCGGo07hV8QtOthAwVbG/VfDA22kg5ny53UHnYYMR18gv7hDYiT9ZjMS4hWLzEObz++e616Sf4qLo9hhag==} 10392 + peerDependencies: 10393 + react: '>= 18.0.0' 10394 + react-dom: '>= 18.0.0' 10395 + dependencies: 10396 + '@swc/helpers': 0.5.1 10397 + clsx: 1.2.1 10398 + date-fns: 2.30.0 10399 + react: 18.2.0 10400 + react-dom: 18.2.0(react@18.2.0) 10401 + swr: 2.2.0(react@18.2.0) 10402 + dev: false 10403 + 10387 10404 /react@18.2.0: 10388 10405 resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} 10389 10406 engines: {node: '>=0.10.0'} ··· 11131 11148 react: ^16.11.0 || ^17.0.0 || ^18.0.0 11132 11149 dependencies: 11133 11150 react: 18.2.0 11151 + dev: false 11152 + 11153 + /swr@2.2.0(react@18.2.0): 11154 + resolution: {integrity: sha512-AjqHOv2lAhkuUdIiBu9xbuettzAzWXmCEcLONNKJRba87WAefz8Ca9d6ds/SzrPc235n1IxWYdhJ2zF3MNUaoQ==} 11155 + peerDependencies: 11156 + react: ^16.11.0 || ^17.0.0 || ^18.0.0 11157 + dependencies: 11158 + react: 18.2.0 11159 + use-sync-external-store: 1.2.0(react@18.2.0) 11134 11160 dev: false 11135 11161 11136 11162 /synckit@0.8.5: