<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://s20055232.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://s20055232.github.io/" rel="alternate" type="text/html" /><updated>2025-11-29T15:55:21+00:00</updated><id>https://s20055232.github.io/feed.xml</id><title type="html">Lai Lee Han’s Blog</title><subtitle>一名後端軟體工程師，喜歡旅遊、美食跟capypara</subtitle><author><name>Han</name></author><entry><title type="html">Key Technologies - API Gateway</title><link href="https://s20055232.github.io/system%20design/api-gateway/" rel="alternate" type="text/html" title="Key Technologies - API Gateway" /><published>2024-12-05T00:00:00+00:00</published><updated>2024-12-05T00:00:00+00:00</updated><id>https://s20055232.github.io/system%20design/api-gateway</id><content type="html" xml:base="https://s20055232.github.io/system%20design/api-gateway/"><![CDATA[<h2 id="api-gateway">API Gateway</h2>

<p>API Gateway 跟 Load balancer 很容易會搞混，因為他們能做到的事有高度的重疊，這時候就必須知道他們的發展脈絡，才能比較好的區隔他們。</p>

<p>API Gateway 是在微服務架構盛行之後所出現的產物，目的是將外部呼叫的 request 可以導流到對應的微服務，而 load balancer 的目的則是將流量均勻的分配的服務，透過上述的文字，你就可以知道差異，一個重點是正確導流到特定服務，一個是均勻分配流量。</p>

<table>
  <thead>
    <tr>
      <th><strong>功能</strong></th>
      <th><strong>API Gateway</strong></th>
      <th><strong>Load Balancer</strong></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>導流和管理</td>
      <td>將外部呼叫的request導流到對應的微服務</td>
      <td>平衡流量，均勻分配給多個服務</td>
    </tr>
    <tr>
      <td>安全控制</td>
      <td>透過API管理、安全控制等功能</td>
      <td>無</td>
    </tr>
    <tr>
      <td>平衡流量</td>
      <td>無</td>
      <td>優化系統效能和可用性</td>
    </tr>
  </tbody>
</table>

<p>因為 API Gateway 是微服務的“警衛室”，所以通常會有 authentication（身份驗證）、authorization（權限管控）的功能，在現今的系統設計面試中，通常 API gateway 是不可或缺的，把它納入你的設計裡面八成沒錯，並且遇到身份管控相關的問題時，你都可以直接說「我的 API gateway 會處理」，可知 API gateway 的重要性</p>

<p>常見的選項有：AWS API Gateway、GCP Apigee、Kong</p>

<h2 id="api-gateway-vs-load-balancer-vs-reverse-proxy">API Gateway vs Load Balancer vs Reverse Proxy</h2>

<h3 id="reverse-proxy">Reverse Proxy</h3>

<p>Reverse Proxy 在做的事就是接受所有外部所有的 request，然後再發送給內部的 server 做處理，多一層 proxy 可以讓我們隱藏 server 的 IP、Port 等等資訊，讓使用者不會知道這些細節，這可以避免被針對特定主機的攻擊。</p>

<p>並且因為都會經過 reverse proxy，所以我們也可以快取一些常見資源在 reverse proxy 這邊，這樣甚至不需要經過我們的 server 就可以回應。</p>

<p>為了提高傳輸速度、降低網路頻寬的使用，reverse proxy 通常會對 server 回傳的資料進行壓縮。</p>

<p>Reverse Proxy 所隱藏的 server 可能不只一台，說不定是 10 台，如果這 10 台都要自己處理 TLS 的交互，就會很麻煩，這些 server 也必須同步更新最新的 TLS 憑證，更何況如果導流到不同的 server，那 TLS 就要重新處理，這一來一往就把我們搞死了，所以通常會 client 統一與 reverse proxy 進行 TLS 的交互。</p>

<h4 id="小結">小結</h4>

<p>Reverse proxy 的用途是</p>

<ul>
  <li>隱藏資訊確保內部 server 安全</li>
  <li>快取常見資源降低 server 負載</li>
  <li>壓縮回傳的資訊以提高傳輸速度、降低網路頻寬</li>
  <li>TLS 的加解密</li>
</ul>

<h3 id="load-balancer">Load Balancer</h3>

<p>剛剛有提到 Reverse Proxy 可以透過快取降低 server 負載，但這樣還不夠，我們還想要更加高效率的分配跟處理負載，此時， load balancer（LB）就可以派上用場。</p>

<p>LB 會依據策略將流量分流給不同 server 做處理，來高效的使用資源，並且發現 server 停止服務時會將流量導向給正常的 server，這稱為「「容錯移轉」」，這可以提高可用性、可靠性。</p>

<p>因為 LB 需要知道哪個 server 正常、哪個不正常，所以通常會有 health check 跟 monitoring 的功能。</p>

<h4 id="小結-1">小結</h4>

<ul>
  <li>Load Balancer 主要針對負載問題做出優化</li>
  <li>透過分流策略來高效使用資源，提高可用性</li>
  <li>監控、確認 server 的健康，並適時進行「容錯移轉」，確保可靠性</li>
</ul>

<h3 id="api-gateway-1">API Gateway</h3>

<p>上述的技術可以處理“過去”大部分的需求，但“現在”就有點不夠用了，主要是近幾年微服務架構的興起，如果用上述的技術處理與多個內部微服務的交互，複雜性會大大提升，會遇到的難題像是</p>

<ul>
  <li>每個 server 都執行著不同的微服務，LB 要怎麼導流？</li>
  <li>要怎麼管控權限？ 難道要每個微服務都實現一套各自的權限控管嗎？</li>
  <li>每個微服務有不同的 protocol，A 用 HTTP/1.1、B 用 gRPC、C 用 HTTP/2，client 根本不知道怎麼用</li>
  <li>每個微服務都有著不同的職責，我要去哪邊知道總共有哪些 API 可以使用？ 要怎麼測試？</li>
</ul>

<p>因應微服務時代，我們需要新的工具來處理上述需求，因此有了 API Gateway 這個新類型的工具</p>

<h4 id="小結-2">小結</h4>

<p>API Gateway 是因應微服務架構出現的產物，基於 Reverse Proxy、Load Balancer 之上，又添加了數項功能</p>

<ul>
  <li>微服務的流量管理</li>
  <li>協議轉換，將一種協議轉換成另一種協議</li>
  <li>Developer Portal，一個集合的平台讓開發者方便測試以及查看 API 文檔</li>
  <li>權限控管，這樣每個微服務就可以專注在自己的 scope，而不用擔心權限問題</li>
</ul>]]></content><author><name>Han</name></author><category term="System Design" /><summary type="html"><![CDATA[API Gateway]]></summary></entry><entry><title type="html">Key Technologies - Blob Storage</title><link href="https://s20055232.github.io/system%20design/blob/" rel="alternate" type="text/html" title="Key Technologies - Blob Storage" /><published>2024-12-02T00:00:00+00:00</published><updated>2024-12-02T00:00:00+00:00</updated><id>https://s20055232.github.io/system%20design/blob</id><content type="html" xml:base="https://s20055232.github.io/system%20design/blob/"><![CDATA[<h2 id="blob-storage">Blob Storage</h2>

<p>有時候你會需要儲存大型的二進制物件，像是影片、圖片、機器學習模型等等，這些資料不適合給 DB 來做儲存跟管理，既沒效率也昂貴，當前主流做法是使用 Amazon S3 或 Google Cloud Storage 這類專門的服務，如果是本地部署的話可以考慮 MinIO</p>

<p>Blob 儲存服務很簡單，就是讓你上傳數據，然後會回傳給你一個 URL，你之後可以透過 URL 來下載資料，通常會結合 CDN，你就可以上傳檔案然後透過 CDN 快取到各地讓別人快速讀取。</p>

<p>一般來說這類 Blob 儲存服務是次要的，你還是會需要一個主要的 DB 來處理資料，而 Blob 回傳的 URL 還可以儲存在 DB 中，讓我們可以查詢跟索引資料，混合兩者的好處。</p>

<p>這邊是幾個常見的作法：</p>

<ul>
  <li>設計 AutoML -&gt; 將模型、數據集儲存在 blob 儲存中，將 meta-data（數據集大小、模型大小、類型等等）儲存在資料庫中。</li>
  <li>設計 Youtube -&gt; 將影片儲存在 blob 儲存中，將 meta-data（影片長度、類型、上傳者等等）儲存在資料庫中。</li>
</ul>

<p><img src="/assets/2024-12-02-blob/image.png" alt="blob" /></p>

<p>這類服務的特點是：</p>

<ol>
  <li>
    <p>Durability：透過 replication、erasure coding（抹除碼將一個訊息由n個區塊變成一個訊息超過n個區塊，原本的訊息可以由新的訊息的區塊子集合所重建。） 等技術來確保你的資料會安全的保留</p>
  </li>
  <li>
    <p>Scalability：像 S3 這類儲存方案可以視為無限擴展，當然不是真的無限，但你理論上用不完，而在面試時你也可以直接不考慮 blob 服務的可擴展性</p>
  </li>
  <li>
    <p>Cost：blob 服務便宜很多，例如：S3 前 50TB 的每月每 GB 收費 $0.023，在 DynamoDB 中收費是前 10TB 每月每 GB $1.25 元，試想我們都儲存 50TB 的資料，在 S3 中我們一個月要付出 $50000 * 0.023 = 1150$，而 DynamoDB 要 $50000 * 1.25 = 62500$，這還不包含 DynamoDB 在 10TB 後的費率調整，相當驚人</p>
  </li>
  <li>
    <p>Security：Blob 服務提供像是傳輸中加密、靜態檔案加密，還有訪問控制</p>
  </li>
  <li>
    <p>直接從客戶端下載或上傳：Blob 允許直接從客戶端上傳和下載 blob，這些檔案就不用先經過我們的服務才傳輸出去，省去一道工序，這部分需要了解預先簽署（presigned）URL 跟如何授予他們臨時訪問權限</p>
  </li>
  <li>
    <p>Chunking：在傳輸大型檔案時，通常使用 chunking 將文件切分成一個個小區塊（chunks），這讓我們可以失敗恢復上傳、並行上傳，可以參考 S3 的 multipart upload API  來得知更多細節</p>
  </li>
</ol>

<p>常見的有 Amazon S3 或 Google Cloud Storage，作者推薦 S3，因為最多人使用。</p>]]></content><author><name>Han</name></author><category term="System Design" /><summary type="html"><![CDATA[Blob Storage]]></summary></entry><entry><title type="html">Key Technologies - Search Optimized Database</title><link href="https://s20055232.github.io/system%20design/search-optimized-db/" rel="alternate" type="text/html" title="Key Technologies - Search Optimized Database" /><published>2024-12-02T00:00:00+00:00</published><updated>2024-12-02T00:00:00+00:00</updated><id>https://s20055232.github.io/system%20design/search-optimized-db</id><content type="html" xml:base="https://s20055232.github.io/system%20design/search-optimized-db/"><![CDATA[<h2 id="search-optimized-database">Search Optimized Database</h2>

<p>當你今天有特殊場景時，一般傳統 DB 提供給你的搜尋可能沒辦法滿足，像是全文搜尋、向量資料、日誌分析，以前你可能需要這樣子下指令，但這樣是 full table scan，既沒效率又沒辦法 scale</p>

<pre><code class="language-SQL">SELECT * FROM documents WHERE document_text LIKE '%search_term%'
</code></pre>

<p>你需要一個專門的工具來處理，而這個工具就是 Search Oprimized Database，他內部會對文字進行 indexing、tokenization、stemming（詞幹提取），來加速文字的搜尋跟效率，而這種作法被稱為「Inverted Indexes」（倒排索引）。</p>

<p>使用場景很簡單，當你遇到需要各種文字搜尋的場合，你大概率會需要這類工具來協助你，雖然說 Postgres 也有提供 GIN 這個 indexing 的 type 來給你對該欄位進行倒排索引，不過查詢速度會隨著資料的成長而跟著下滑（不過億級以內的應該感受不大），而且擴展性也沒那麼好，但反過來說，如果你的資料量沒有很大，擴展性需求也不高，你直接 Postgres + GIN 就可以搞定了。</p>

<p>這類 Search Oprimized Database 有以下特性：</p>

<ul>
  <li>
    <p>倒排索引（Inverted Indexes）：倒排索引是一種從單字映射到包含它們的文件的資料結構。這使您可以快速查找包含給定單字的文檔。</p>
  </li>
  <li>
    <p>標記化（Tokenization）：標記化是將一段文字分解為單字的過程，將單字對應到倒排索引中的文件。</p>
  </li>
  <li>
    <p>詞幹提取（Stemming）：詞幹提取是將單字還原為詞根形式的過程，可以匹配同一單字的不同形式。例如，「running」和「runs」都將簡化為「run」。</p>
  </li>
  <li>
    <p>模糊搜尋（Fuzzy Search）：模糊搜尋是找到與給定搜尋字詞相似的結果的能力。大多數搜尋優化資料庫都支援開箱即用的模糊搜尋作為配置選項。簡而言之，這是透過使用可以容忍搜尋字詞中輕微拼字錯誤或變化的演算法來實現的。這是透過編輯距離計算等技術來實現的，編輯距離計算可以測量需要更改、添加或刪除多少個字母才能將一個單字轉換為另一個單字。</p>
  </li>
  <li>
    <p>擴展（Scaling）：就像傳統資料庫一樣，搜尋優化資料庫透過向叢集添加更多節點並跨這些節點分片資料來擴展。</p>
  </li>
</ul>

<p>最常見的選項就是 Elasticsearch 沒有之一，他是基於 Apache Lucene 之上，提供 RESTful API，簡單好上手讓他成為大家的首選。</p>]]></content><author><name>Han</name></author><category term="System Design" /><summary type="html"><![CDATA[Search Optimized Database]]></summary></entry><entry><title type="html">JSON vs JSONB</title><link href="https://s20055232.github.io/database/json-jsonb/" rel="alternate" type="text/html" title="JSON vs JSONB" /><published>2024-12-01T00:00:00+00:00</published><updated>2024-12-01T00:00:00+00:00</updated><id>https://s20055232.github.io/database/json-jsonb</id><content type="html" xml:base="https://s20055232.github.io/database/json-jsonb/"><![CDATA[<p>總是考慮 JSONB 如果你不用考慮以下問題：</p>

<ol>
  <li>
    <p>JSON 是純文字的完整複製，不會進行預先解析，不會添加索引，如果你可以接受解析較慢，且你使用 JSON 時都是使用整份文字，不需要檢索特定資訊</p>
  </li>
  <li>
    <p>可以接受空間的浪費，JSONB 會幫你將 JSON 做處理，去掉空格、重複的 key，格式會比較緊湊</p>
  </li>
  <li>
    <p>你的 key 可以接受跟原本順序不符，JSONB 不會幫你保留原本 JSON 的 key 順序</p>
  </li>
  <li>
    <p>你的 JSON 非常小，JSONB 幫你做的額外操作顯得沒有必要，直接讀取做使用反而更簡單更快</p>
  </li>
</ol>

<p>雖然 JSONB 聽起來很完美，但對於超出數字精度的部分，JSONB 會犧牲其資料精度，而 JSON 不會，這一點取決於系統需求需要被納入考慮。</p>]]></content><author><name>Han</name></author><category term="Database" /><summary type="html"><![CDATA[總是考慮 JSONB 如果你不用考慮以下問題：]]></summary></entry><entry><title type="html">Key Technologies - Database</title><link href="https://s20055232.github.io/system%20design/key-tech-db/" rel="alternate" type="text/html" title="Key Technologies - Database" /><published>2024-11-29T00:00:00+00:00</published><updated>2024-11-29T00:00:00+00:00</updated><id>https://s20055232.github.io/system%20design/key-tech-db</id><content type="html" xml:base="https://s20055232.github.io/system%20design/key-tech-db/"><![CDATA[<h2 id="key-technologies">Key Technologies</h2>

<p>系統設計面試會要求你像堆積木一樣層層架構出你的產品設計架構，這代表你需要知道每個組件（積木）能夠做什麼，但通常不會要求你知道某個具體的技術解決方案，只要能夠選擇一個合適的就好，不過你就必須對市面上常見的技術跟工具有所了解，知道它們可以用在什麼場景，才能應對大多數系統設計的挑戰。</p>

<p>系統設計面試，深度是與你面試的職等成正比的，而前期你應該關注的是廣度，知道每個組件可以做什麼，然後再向下探討它們背後是怎麼做到的。</p>

<h3 id="core-database">Core Database</h3>

<p>照理來說，你一定會需要一個資料庫來儲存你的資料，如果你面試的職位專注在產品設計的話，你會需要強一致性，關聯式資料庫（RDBMS）大概率可以滿足你的需求，如果你面試的職位專注在基礎設施的話，那高可用性跟可應對海量資料的 NoSQL 會是你的不二考量。</p>

<p>在當前 NoSQL 跟 RDBMS 已經有高度的功能重疊，而且大多數場景兩者都能夠辦到，因此去比較這兩者在大多數情況都是不必要的，而且這類比較很可能會因為不精確導致你透露出自己的不熟悉，如果面試官要求你去比較，一個好的技巧是，談論你熟悉的資料庫、比較它們的差異、說明它們如何影響你的設計，舉例來說，當你選擇 PostgreSQL 時，可以強調其 ACID 屬性，如何幫助你維護數據的一致性與完整性</p>

<h4 id="relational-databases">Relational Databases</h4>

<p>RDBMS，是常見的資料庫選項，使用 SQL 作為查詢語言，具有 ACID 的特性，在大多數情況下你都可以無腦選擇它作為你的資料儲存方案。</p>

<p>而 RDBMS 最著名的就是其 Transaction、Index、SQL Join，常見的選擇有 MySQL、Postgres，而 CMU 教授最愛 Postgres，此文章也推薦 Postgres</p>

<h4 id="nosql">NoSQL</h4>

<p>不是傳統 RDBMS 的可以稱為 NoSQL，包含 key-value、document、column-family、graph formats，NoSQL 給予更高的彈性、擴展性、可用性，通常選擇 NoSQL 的場景是在面對海量資料時，你需要快速的讀寫，並且這些資料的結構還沒有定案，需要可以隨著需求進行動態調整，同時這些 NoSQL 都有提供各式的一致性選擇，從最終一致性到強一致性都有，聽起來很完美，不過缺乏了 SQL 提供的靈活查詢語法、RDBMS 複雜的關聯操作、表示一對一、一對多的關聯性。</p>

<p>關於 NoSQL，你需要知道</p>

<ol>
  <li>資料模型：針對不同資料類型需要選擇不同的 NoSQL，每個資料庫都有針對該類型的資料有特殊的優化</li>
  <li>一致性模型：提供各式資料模型，從最終一致性到強一致性都有，</li>
  <li>Indexing：NoSQL 也有提供 indexes</li>
  <li>Scalability：NoSQL 提供 一致雜湊、sharding 來讓你將資料分發做到水平擴展</li>
</ol>

<p>常見的選項有 MongoDB 和 DynamoDB，作者推薦 DynamoDB</p>]]></content><author><name>Han</name></author><category term="System Design" /><summary type="html"><![CDATA[Key Technologies]]></summary></entry><entry><title type="html">系統設計核心觀念 (3) - 安全性</title><link href="https://s20055232.github.io/system%20design/security/" rel="alternate" type="text/html" title="系統設計核心觀念 (3) - 安全性" /><published>2024-11-28T00:00:00+00:00</published><updated>2024-11-28T00:00:00+00:00</updated><id>https://s20055232.github.io/system%20design/security</id><content type="html" xml:base="https://s20055232.github.io/system%20design/security/"><![CDATA[<h2 id="security">Security</h2>

<p>雖然系統設計中不會討論到太細節，但還是會進行討論，所以知道一些基本的會有很大的幫助</p>

<h3 id="authentication--authorization">Authentication / Authorization</h3>

<p>Authentication 是指身份驗證，這一步的目的是查看“你是誰”，而 Authorization 是權限，這一步的目的是看你“能做什麼”，通常我們會用 API Gateway 或是專門的服務像是 Auth0 來處理，雖然面試官可能會希望討論更詳細的細節，但通常回答「我的 API Gateway 會處理身分驗證和授權」就足夠了</p>

<p>當前社群常見的選項有 Traefik、Kong、AWS API Gateway、Apigee</p>

<h2 id="encryption">Encryption</h2>

<p>簡單來說，就是確保資料在各個環節都是加密的，不論是在傳輸上、儲存時都確保是加密的，HTTP(s) 本身就有加密，而 gRPC 也有提供 SSL/TLS 的選項，對於敏感資料來說，讓每個用戶擁有各自的專屬密鑰來加密這些資料是必要的。</p>

<p>安全相關的議題可以延伸很多，可以參考<a href="https://roadmap.sh/best-practices/api-security">這一篇</a></p>

<h2 id="data-protection">Data Protection</h2>

<p>確保數據免受未經授權的訪問、使用或洩露的過程，在某些系統中，可能會有敏感數據暴露的風險，即使這些數據不在授權路徑內。</p>

<p>這類數據暴露通常是通過端點抓取（scraping）發現的。端點抓取是一種自動化技術，黑客或惡意用戶通過大量發送請求來嘗試從 API 中提取信息。因此，建議對這些端點實施某種形式的速率限制或請求限流（rate limiting 或 request throttling），以防止濫用和暴露敏感數據。</p>

<p>像是過去 Instagram 就曾被大量撈取隱私資料（<a href="https://www.twingate.com/blog/tips/instagram-data-breach">來源</a>），這都跟 data protection 沒做好有關。</p>

<p>當初赫赫有名的劍橋分析事件似乎也算是 data protetction 一個失敗的案例（<a href="https://appworks.tw/facebook-api/">來源</a>）</p>

<h2 id="monitoring">Monitoring</h2>

<p>隨著系統越來越龐大跟成熟，你會需要維運跟監控，監控一般來說有三個層級</p>

<ul>
  <li>
    <p>Infrastructure Monitoring</p>

    <p>基礎設施的運作狀況，像是 CPU、記憶體、硬碟空間、網路使用率</p>
  </li>
  <li>
    <p>Service-Level Monitoring</p>

    <p>監控服務的健康狀況跟效能，包括 request latency、error rates、throughput</p>
  </li>
  <li>
    <p>Application-Level Monitoring</p>

    <p>監控 APP 的健康狀況跟效能，包括使用者在線人數、active session 數量、active connections 數量，以及業務指標，通常也是系統設計面試最重要的討論層級</p>
  </li>
</ul>

<blockquote>
  <p>Service 是指以 API 提供服務的服務，而 Application 是調用 API 的服務，你可以視為</p>

  <ul>
    <li>service = server-side, application = client-side</li>
    <li>application 為使用者服務，servivce 為機器服務</li>
    <li>提供資料方、使用資料方
<a href="https://stackoverflow.com/questions/9111243/web-service-vs-web-application">參考資料</a></li>
  </ul>
</blockquote>]]></content><author><name>Han</name></author><category term="System Design" /><summary type="html"><![CDATA[Security]]></summary></entry><entry><title type="html">系統設計核心觀念 (2)</title><link href="https://s20055232.github.io/system%20design/indexing/" rel="alternate" type="text/html" title="系統設計核心觀念 (2)" /><published>2024-11-27T00:00:00+00:00</published><updated>2024-11-27T00:00:00+00:00</updated><id>https://s20055232.github.io/system%20design/indexing</id><content type="html" xml:base="https://s20055232.github.io/system%20design/indexing/"><![CDATA[<h2 id="indexing">Indexing</h2>

<p>索引，就像字典會有索引目錄一樣，是一種用來幫助你快速找到想要的資料的資料結構，在大多數系統中，我們可以接受寫入慢一點，但我們不能接受讀取很久，所以良好設計的 index 相當重要。</p>

<p>一個簡單粗暴的方式是使用 hash map，$O(1)$ 就可以找到想要的資料，但當資料海量時，維護一個這麼大的 hash table 可能佔用相當多記憶體。</p>

<p>另一個方式是將資料排序儲存，這讓我們可以使用 binary search 來 $O(log\ n)$ 找到資料，而這也是最常見的方式。</p>

<p>還有很多不同方式，但概念是透過一些前置步驟來大幅加快之後搜尋資料的速度。</p>

<p>大多數討論 index 的時候還是跟資料庫比較相關，根據資料庫的不同，我們有不同的 indexing 策略，大多數關聯式資料庫可以讓我們針對一個欄位或一組（多個）欄位來建立索引，這對搜尋速度會造成相當顯著的差異。</p>

<p>儘管有些 DB 會提供你客制 index ，但如果 DB 本身有提供，建議是直接使用現成的，這些現成的策略都經過大量的實戰驗證，絕對會比你自己從零打造來得好。</p>

<h2 id="specialized-indexes">Specialized Indexes</h2>

<p>除了一些常見的 index，像是 B-tree、Hash，也有比較特別的像是</p>

<ul>
  <li>geospatial indexes: 專門用來搜尋地理位置的索引，像是：最近的餐廳、最近的加油站等等的</li>
  <li>Vector databases: 專門用來搜尋高維度的資料，像是：找類似的圖片、文件</li>
  <li>full-text indexes: 專門用來搜尋文字資料，像是：搜尋文章、推文</li>
</ul>

<p>上述大多的 index 現有的 DB 就有支援，根據 CMU 教授的說法「與其考慮新穎、特殊的資料庫來解決上述的需求，不如先考慮用既有成熟的 DB 然後附加插件去解決」。</p>

<p>儘管如此，但對於上述的需求，作者推薦 ElasticSearch 作為二級索引的解決方案，上述三者都有支援，我們可以透過 Change Data Capture (CDC) 來讓 ElasticSearch 集群根據 DB 的變化隨之更新，聽起來很美好，但電腦科學一切都是取捨，加上一個新的 component 同時也增加了一個可能 failure 的點以及延遲，並且從搜索索引中讀取的數據可能不是最新的，不過，如果你需要強大的搜尋功能以及可以容忍輕微延遲，那 ElasticSearch 是一個很好的選擇</p>

<blockquote>
  <p>補充: 除了主鍵之外的索引都稱作二級索引</p>
</blockquote>

<h2 id="communication-protocols">Communication Protocols</h2>

<p>溝通的方式主要就兩種：對內 or 對外，對內通常比較單純，使用 RESTful HTTP 跟 gRPC 可以處理大多數的場景，對外就必須考慮使用者會怎麼使用你的服務、誰先發起連線、延遲的程度、有多少資料要傳輸，儘管如此，但大多數情況用以下 4 種可以解決</p>

<h3 id="http">HTTP</h3>

<p>簡單的一個 request 一個 response，如果有 follow RESTful API 設計，你的 API 應該是 stateless 的，我們可以使用一個 load balancer 後面部署多個服務，簡單的做到水平擴張</p>

<h3 id="sse">SSE</h3>

<p>Server Send Events 在應對 server 單方向推送更新至 client 的場景很好用，就像 WebSocket 一樣，透過維持一個持續的 HTTP 連線，當有更新時就主動推送至 client-side，不過 client-side 是不能推送資訊到 server 這邊的，這使得 SSE 更容易實現並整合到現有的 HTTP 基礎架構中，例如負載平衡器和防火牆，而不需要特殊處理</p>

<p>跟 WebSocket 最大的差異就是通訊方向，SSE 是單向而 WebSocket 是雙向，適合單方面傳輸資料的場景，像是: 股價更新、即時通知</p>

<h3 id="long-polling">Long-Polling</h3>

<p>要認識 Long-Polling，就要先知道 polling，polling 在做的事就是每隔一段時間送出一個 request，很簡單但這樣很浪費資源，因為你要求資料的時候可能後台根本還沒更新，Long-Polling 針對這一點做出改善，當今天 client-side 送出請求時，如果 server-side 沒有可用的更新資料，我們就將連接保留著，直到 server-side 有更新時再回傳或是逾時回傳，此時這個連接功成身退，我們就可以將它關閉</p>

<p>比起 SSE，Long-Polling 需要消費更多資源，因為每次請求都還是需要建立連接，相當不適合大量即時資料傳輸的場景，不過對於一些低頻的請求，或是兼容舊系統，Long-Polling 還是有他應用的空間</p>

<h3 id="websocket">WebSocket</h3>

<p>在當前實時相關的需求中，常見的一個選項是 WebSocket，透過在一條 TCP 連線上提供雙向、full duplex（數據可以同時在兩個方向上傳輸）的資料傳輸，這讓我們可以做到即時的資料交換，但天底下沒有白吃的午餐，除了 server 需要支持外，有些 firewall 跟 proxy server 可能會阻止 WebSocket 連接，如何維護許多連線也可能是一個挑戰。</p>

<p>一個常見的做法是使用 message broker 來處理客戶端和伺服器之間的通信，然後後端服務都與 message broker 進行通訊，如圖下</p>

<pre class="mermaid">
graph TD
    C1[WebSocket 客戶端 1] --&gt;|WebSocket| WS[WebSocket 服務]
    C2[WebSocket 客戶端 2] --&gt;|WebSocket| WS
    C3[WebSocket 客戶端 3] --&gt;|WebSocket| WS
    WS --&gt; MB[Message Broker]
    MB --&gt; WS
    MB --&gt;|訂閱| S1[後端服務 1]
    MB --&gt;|訂閱| S2[後端服務 2]
    S1 --&gt;|發布| MB
    S2 --&gt;|發布| MB

    style C1 fill:#f9f,stroke:#333,stroke-width:2px
    style C2 fill:#f9f,stroke:#333,stroke-width:2px
    style C3 fill:#f9f,stroke:#333,stroke-width:2px
    style MB fill:#ff9,stroke:#333,stroke-width:4px
    style S1 fill:#9ff,stroke:#333,stroke-width:2px
    style S2 fill:#9ff,stroke:#333,stroke-width:2px
</pre>

<p>在系統面試中，通常不會要求你客制一個傳輸協定，我們用既有的即可。</p>

<p><img src="/assets/2024-11-27-indexing/rest%20&amp;%20sse%20&amp;%20websocket.png" alt="compare" /></p>

<h3 id="比較-sse--long-polling--websocket">比較 SSE &amp; long polling &amp; WebSocket</h3>

<table>
  <thead>
    <tr>
      <th>特性</th>
      <th>SSE (Server-Sent Events)</th>
      <th>Long-Polling</th>
      <th>WebSocket</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>通訊方向</td>
      <td>單向（伺服器 → 客戶端）</td>
      <td>單向（伺服器 → 客戶端）</td>
      <td>雙向</td>
    </tr>
    <tr>
      <td>連接模式</td>
      <td>持續連接</td>
      <td>斷續連接</td>
      <td>全雙工持續連接</td>
    </tr>
    <tr>
      <td>協議</td>
      <td>HTTP/HTTPS</td>
      <td>HTTP/HTTPS</td>
      <td>WebSocket 協議 (ws/wss)</td>
    </tr>
    <tr>
      <td>延遲</td>
      <td>低</td>
      <td>中等</td>
      <td>最低</td>
    </tr>
    <tr>
      <td>數據效率</td>
      <td>高</td>
      <td>低</td>
      <td>最高</td>
    </tr>
    <tr>
      <td>伺服器負載</td>
      <td>低</td>
      <td>中</td>
      <td>中等</td>
    </tr>
    <tr>
      <td>瀏覽器支持</td>
      <td>大多數現代瀏覽器</td>
      <td>所有瀏覽器</td>
      <td>大多數現代瀏覽器</td>
    </tr>
    <tr>
      <td>適用場景</td>
      <td>單向即時更新</td>
      <td>簡單即時通訊</td>
      <td>雙向、即時互動</td>
    </tr>
    <tr>
      <td>典型應用</td>
      <td>新聞推送、股票更新</td>
      <td>聊天應用（早期）</td>
      <td>即時聊天、遊戲、協作工具</td>
    </tr>
  </tbody>
</table>

<h2 id="狀態">狀態</h2>

<p>狀態 (state) 是系統複雜性的主要來源。如果可能的話，將狀態存放在像消息代理（message broker）或資料庫這類外部系統中，能夠簡化系統設計。這樣可以使你的服務保持無狀態 (stateless)，並且可以水平擴展 (horizontal scaling)，同時仍然可以與客戶端保持狀態化的通訊。</p>

<p>具體來說，我們可以將狀態儲存在外部系統中，像是 Message broker（RabbitMQ、Kafka）或 Database，來解耦合資料與服務邏輯本身，以下是一個例子：</p>

<p>假設你有一個電商系統，客戶在瀏覽商品時可能會將商品添加到購物車。傳統的方式可能是將購物車狀態保存在應用服務器內存中，這會導致服務器重啟或增加更多實例時出現問題。為了解決這個問題：</p>

<ul>
  <li><strong>狀態外部化</strong>：將購物車的狀態存儲在 Redis 這樣的快取數據庫中或消息代理中。這樣，每次請求都可以獨立處理，不同的服務實例只需要讀取 Redis 或消息代理中的狀態即可處理客戶的購物車操作。</li>
  <li><strong>保持無狀態服務</strong>：服務本身不需要存儲購物車的狀態，因此可以自由擴展，隨著流量增加自動擴展實例數量，而不需要考慮狀態同步問題。</li>
</ul>

<p>這種架構使系統既簡單又可擴展，適合高並發、分佈式系統的場景。</p>]]></content><author><name>Han</name></author><category term="System Design" /><summary type="html"><![CDATA[Indexing]]></summary></entry><entry><title type="html">系統設計核心觀念 (1)</title><link href="https://s20055232.github.io/system%20design/scaling/" rel="alternate" type="text/html" title="系統設計核心觀念 (1)" /><published>2024-11-27T00:00:00+00:00</published><updated>2024-11-27T00:00:00+00:00</updated><id>https://s20055232.github.io/system%20design/scaling</id><content type="html" xml:base="https://s20055232.github.io/system%20design/scaling/"><![CDATA[<h2 id="scaling">Scaling</h2>

<p>有兩種</p>

<ul>
  <li>水平擴張（horizontal）: 串連多台伺服器來進行擴張</li>
  <li>垂直擴張（vertical）: 針對伺服器添加更多資源</li>
</ul>

<p>垂直擴張相對簡單，不過系統面試通常不在乎垂直擴張，系統面試在乎的是水平擴張，也就是透過串連多台伺服器來進行擴張。</p>

<p>雖然說大多數面試官所想要考驗的是面試者對於水平擴張的理解，不過如果你能計算出具體數字來證實垂直擴張就能夠處理的話，回答垂直擴張就會是更好的選擇，因為 scale out 所需要考慮的事情，像是工作分配、資料一致性、狀態分享等等都很困難，一個常見的誤區是對於任何效能問題不考慮需不需要都直接選擇水平擴張來解決，並且沒有考慮到水平擴張對系統的影響。</p>

<h2 id="work-distribution">Work distribution</h2>

<p>工作如何正確、平均的分配流量是分布式系統的一個大難題，通常會使用 <strong>load balancer</strong> 來滿足這個需求，對於非同步的工作則通常會用 <strong>message queue</strong> 來實現。</p>

<p>我們在使用這類 work distribution 相關的技術跟工具時，想要解決的關鍵問題是如何確保工作平均地分流，畢竟如果你水平擴張，但工作仍然都由特定的節點來完成的話，那就沒有意義。</p>

<h2 id="data-distribution">Data Distribution</h2>

<p>除了工作分配以外，還有就是資料，資料要怎麼同步、儲存在哪</p>

<p>有些人會把資料儲存在記憶體，但資料會隨著服務關閉而跟著消失</p>

<p>有些則儲存在 DB 然後共享給所有的節點，這方式相對簡單，但隨著需求增加，擴張時容易遇到單點故障、併發的難題</p>

<p>DB 也透過分區（partition）來劃分節點可使用的資料，讓節點可以不必與其他節點溝通，減少延遲跟依賴，如果你的系統跟地理區域有關，一個好的作法是使用類似於 REGION_ID 的 key 來作為 partition 的依據</p>

<p>也可以透過溝通來得到所需的資料（也稱 fan-out），但要保持相互溝通的節點盡可能少，以避免任一節點故障、延遲導致的連鎖反應</p>

<p>對資料來說，水平擴張帶來最大的難題是資料同步的挑戰，你有以下選擇</p>

<ol>
  <li>讀寫一個通過網路躍點（當封包從一個網路轉到另一個網路時，這稱為「躍點」。）的共享資料庫（理想情況下 ≈ 1-10ms）</li>
  <li>在每個伺服器上保持多個冗餘副本，這意味著會有競態條件和一致性問題！大多數資料庫系統是為了解決這些問題而設計的（例如，使用事務）。</li>
</ol>

<p>在其他情況下，你可能需要使用分散式鎖。無論如何，你都需要準備討論如何保持資料的一致性。</p>

<h2 id="consistency">Consistency</h2>

<p>一致性，是數一數二重要的問題，從高層次來看，是探討你的用戶可以容忍陳舊資料的程度，一個有著強一致性的系統會確保當資料寫入時，所有後續的讀取都會是最新的資料</p>

<p>弱一致性或常見的最終一致性則反過來，他可以確保在某段時間內會更新（最終會一致），但可能沒有那麼快</p>

<p>選擇何種一致性，就像前面說的，重點是你的用戶可以容忍陳舊資料的程度，對於社群媒體來說，晚一點跟早一點對使用者來說可能沒那麼重要，我們大可選擇最終一致性，但對於銀行系統來說，強一致性就非常重要了</p>

<p>當然，更多時候你的系統可能會部分需要強一致性，部分可以接受最終一致性，就像線上購物，計算商品數量需要確保正確，而商品描述則晚一點更新也沒關係</p>

<p>一致性的概念適用於設計的每一層。即使你使用的是一個強一致性的資料庫，如果你插入了一個快取並使用 TTL（存活時間）來維護資料，通過該快取進行的讀取將會是最終一致性的。</p>

<h2 id="locking">Locking</h2>

<p>有些資源我們必須互斥，也就是確保同一時間只能有一個人在修改跟讀取，像是商品數量，如果已經沒有庫存但卻沒有互斥導致有人讀取到錯誤資訊，那可能就會導致錯誤下單，對客戶的體驗是大扣分的</p>

<p>鎖會出現在系統的各個層級，OS Kernel、App、DB、分佈鎖等等到處都有，這對於正確性的確保是必須的，但累積下來對效能來說可能會有重大的影響。</p>

<p>有鎖的地方就有 race condition，我們需要考慮以下：</p>

<ul>
  <li><strong>鎖的顆粒度（Granularity of the lock）</strong>
  我們希望鎖能夠越精確越好，能夠鎖在真正需要的地方，盡可能不要影響其他東西，你不會希望使用者更換名稱然後將整張 table 鎖住導致其他使用者都登入不了</li>
  <li><strong>鎖的持續時間（Duration of the lock）</strong>
  鎖的持續時間越短越好，這代表我們使用鎖時要留意互斥的邏輯是否很耗時以及鎖的時機點是否剛好，你可能會想要使用者更換名稱時簡單上個鎖，但不用整個 request 都上鎖</li>
  <li>
    <p><strong>是否可以不用鎖</strong>
  在很多時候，我們可以不用那麼“悲觀”，可以使用“樂觀鎖”，特別是遇到 read-only 或是可以 retry 的工作，在樂觀鎖的情況下，我們可以假設不用鎖也可以完成工作，事後檢查正確與否就好，在大多數系統中，我們可以使用 ”compare and swap“ 來達成</p>

    <p>樂觀鎖建立在我們覺得大多數的情況下是“沒有衝突的”，當然，很多系統可以“樂觀”，但反過來說，有些系統你必須“悲觀”，像是銀行帳戶，你可不能犯錯，就算真的沒有人會同時寫入，你也必須謹慎一點乖乖上鎖</p>

    <blockquote>
      <p>補充：樂觀鎖的事後檢查機制通常是透過一個數值來代表 version，並且在每次更新時確認寫入後的版本是否跟寫入前獲得的數值 + 更新條件（可能加上 1 之類的）一致，如果不一致，代表中間有人有操作，我們可以重新嘗試，反之則天下太平，我們可以直接修改</p>
    </blockquote>
  </li>
</ul>]]></content><author><name>Han</name></author><category term="System Design" /><summary type="html"><![CDATA[Scaling]]></summary></entry><entry><title type="html">Facts to know</title><link href="https://s20055232.github.io/system%20design/facts-to-know/" rel="alternate" type="text/html" title="Facts to know" /><published>2024-11-26T00:00:00+00:00</published><updated>2024-11-26T00:00:00+00:00</updated><id>https://s20055232.github.io/system%20design/facts-to-know</id><content type="html" xml:base="https://s20055232.github.io/system%20design/facts-to-know/"><![CDATA[<p>在進行估算時，我們需要一些數字做為起頭，而使用越合理的數字，你得出的結果就越讓人信服，我們可以在得出粗估結果之後再尋求回饋，以下是你應該知道的數字：</p>

<table>
  <thead>
    <tr>
      <th><strong>Power of 1000 (1000^x)</strong></th>
      <th><strong>Number</strong></th>
      <th><strong>Prefix</strong></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>0</td>
      <td>Unit</td>
      <td> </td>
    </tr>
    <tr>
      <td>1</td>
      <td>Thousand</td>
      <td>Kilo</td>
    </tr>
    <tr>
      <td>2</td>
      <td>Million</td>
      <td>Mega</td>
    </tr>
    <tr>
      <td>3</td>
      <td>Billion</td>
      <td>Giga</td>
    </tr>
    <tr>
      <td>4</td>
      <td>Trillion</td>
      <td>Tera</td>
    </tr>
    <tr>
      <td>5</td>
      <td>Quadrillion</td>
      <td>Peta</td>
    </tr>
  </tbody>
</table>

<h2 id="latencies">Latencies</h2>

<p>以下是一張可愛的圖，闡述電腦的運行時間與我們的認知時間之間的差異</p>

<p><img src="/assets/2024-11-26-facts-to-know/the%20scale%20of%20computing%20latencies.png" alt="the scale of computing latencies" data-fancybox="gallery" /></p>

<p>以下是我們常用的操作所耗費的時間</p>

<table>
  <thead>
    <tr>
      <th>操作</th>
      <th>延遲</th>
      <th>延遲（μs）</th>
      <th>延遲（ms）</th>
      <th>比較</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>L1 緩存引用</td>
      <td>0.5 ns</td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>分支預測錯誤</td>
      <td>5 ns</td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>L2 緩存引用</td>
      <td>7 ns</td>
      <td> </td>
      <td> </td>
      <td>14x L1 緩存</td>
    </tr>
    <tr>
      <td>互斥鎖定/解鎖</td>
      <td>25 ns</td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>主內存引用</td>
      <td>100 ns</td>
      <td> </td>
      <td> </td>
      <td>20x L2 緩存, 200x L1 緩存</td>
    </tr>
    <tr>
      <td>用 Zippy 壓縮 1K 字節</td>
      <td>3,000 ns</td>
      <td>3 μs</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>通過 1 Gbps 網絡發送 1K 字節</td>
      <td>10,000 ns</td>
      <td>10 μs</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>從 SSD 隨機讀取 4K</td>
      <td>150,000 ns</td>
      <td>150 μs</td>
      <td> </td>
      <td>~1GB/秒 SSD</td>
    </tr>
    <tr>
      <td>從內存順序讀取 1 MB</td>
      <td>250,000 ns</td>
      <td>250 μs</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>同一數據中心的往返</td>
      <td>500,000 ns</td>
      <td>500 μs</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>從 SSD 順序讀取 1 MB</td>
      <td>1,000,000 ns</td>
      <td>1,000 μs</td>
      <td>1 ms</td>
      <td>~1GB/秒 SSD, 4X 內存</td>
    </tr>
    <tr>
      <td>磁盤尋道</td>
      <td>10,000,000 ns</td>
      <td>10,000 μs</td>
      <td>10 ms</td>
      <td>20x 數據中心往返</td>
    </tr>
    <tr>
      <td>從磁盤順序讀取 1 MB</td>
      <td>20,000,000 ns</td>
      <td>20,000 μs</td>
      <td>20 ms</td>
      <td>80x 內存, 20X SSD</td>
    </tr>
    <tr>
      <td>加州到荷蘭再到加州的數據包往返</td>
      <td>150,000,000 ns</td>
      <td>150,000 μs</td>
      <td>150 ms</td>
      <td> </td>
    </tr>
  </tbody>
</table>

<p>注意：</p>

<ul>
  <li>1 ns = 10^-9 秒</li>
  <li>1 μs = 10^-6 秒 = 1,000 ns</li>
  <li>1 ms = 10^-3 秒 = 1,000 μs = 1,000,000 ns</li>
</ul>

<p>SSD 超高速的讀寫速度顛覆了傳統 HDD 的效能瓶頸，一台 server 加上一堆 SSD 可以做到過去一個伺服器集群可以做到的事，你的考官可能沒有意識到這點，你可以適時的提醒他</p>

<h2 id="storage">Storage</h2>

<p>一些常見的檔案所佔用的儲存空間大概如下：</p>

<table>
  <thead>
    <tr>
      <th><strong>Item</strong></th>
      <th><strong>Size</strong></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>A two-hour movie</td>
      <td>7 GB</td>
    </tr>
    <tr>
      <td>A 15 mins movie</td>
      <td>1 GB</td>
    </tr>
    <tr>
      <td>A small book of plain text</td>
      <td>1 MB</td>
    </tr>
    <tr>
      <td>A high-resolution photo</td>
      <td>1 MB</td>
    </tr>
    <tr>
      <td>A medium-resolution image (or a site layout graphic)</td>
      <td>100 KB</td>
    </tr>
  </tbody>
</table>

<p>我們這邊用 movie 來展開，雖然你可以直接背下來，但還是知道怎麼算出來的比較好</p>

<ul>
  <li>影片長度：2 hours = 120 mins</li>
  <li>解析度：1080p</li>
  <li>幀數：24fps ~ 60fps，我們可以使用落於中間的常見格式 30fps（<a href="https://www.hitpaw.tw/video-resources/what-is-the-best-frame-rate-for-youtube.html">參考</a>）</li>
  <li>壓縮格式：目前主流是 H.264，後起之秀有 H.266、VP9、AV1，但還沒有普及（編碼解碼吃效能，<a href="https://jacksonlin.net/20221230-how-to-choose-format/">參考</a>），所以這邊使用 H.264（<a href="https://support.google.com/youtube/answer/1722171?hl=zh-Hant#zippy=%2C%E5%AE%B9%E5%99%A8mp%2C%E9%9F%B3%E8%A8%8A%E8%BD%89%E7%A2%BC%E5%99%A8aac-lc%2C%E5%BD%B1%E6%A0%BC%E9%80%9F%E7%8E%87%2C%E4%BD%8D%E5%85%83%E7%8E%87%2C%E5%BD%B1%E7%89%87%E8%A7%A3%E6%9E%90%E5%BA%A6%E5%92%8C%E9%95%B7%E5%AF%AC%E6%AF%94%2C%E8%89%B2%E5%9F%9F%2C%E5%BD%B1%E7%89%87%E8%BD%89%E7%A2%BC%E5%99%A8h">參考</a>）</li>
</ul>

<p>未壓縮的 HD 影片，1920x1080像素、10-bit 色彩深度（有三個 color channel，每個 channel 有 10bits，<a href="https://gist.github.com/YamashitaRen/2dcea6fd5830ecd53236">參考</a>）</p>

\[1920 \times 1080 \times 30\ bits \times\ 30\ frames \div 8\ bits = \ 233280000\ bytes \approx 233\ MB\]

<p>每秒會產生 233 MB，不過通常為了確保觀看畫質與下載速率，透過 H.264 壓縮後，1080p 的影片的 bitrate（位元速率）需要控制在  4 ~ 8 Mbps（1 Mbps 等於 125KB/秒） 左右（<a href="https://simular.co/blog/post/54-%E5%A6%82%E4%BD%95%E5%A3%93%E7%B8%AE%E5%BD%B1%E9%9F%B3%E8%87%B3%E9%81%A9%E5%90%88%E6%92%AD%E6%94%BE%E7%9A%84%E5%A4%A7%E5%B0%8F">參考</a>），我們取平均為 6 Mbps（750KB/s），重新計算後得知</p>

\[750\ KB/s \times 60\ secs \times 120\ mins = 5400000\ KB \approx 5.15\ GB\]

<h2 id="business">Business</h2>

<p>有些關於領域知識的數字，考官照理來說會提供給你，畢竟工程師可能對這些數字沒有概念，因此你也不用擔心這個數字你抓錯會被扣分</p>

<table>
  <thead>
    <tr>
      <th><strong>Metric</strong></th>
      <th><strong>Order of Magnitude</strong></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Daily active users of major social networks</td>
      <td>O(1b)</td>
    </tr>
    <tr>
      <td>Hours of video streamed on Netflix per day</td>
      <td>O(100m)</td>
    </tr>
    <tr>
      <td>Google searches per second</td>
      <td>O(100k)</td>
    </tr>
    <tr>
      <td>Size of Wikipedia</td>
      <td>O(100gb)</td>
    </tr>
  </tbody>
</table>]]></content><author><name>Han</name></author><category term="System Design" /><summary type="html"><![CDATA[在進行估算時，我們需要一些數字做為起頭，而使用越合理的數字，你得出的結果就越讓人信服，我們可以在得出粗估結果之後再尋求回饋，以下是你應該知道的數字：]]></summary></entry><entry><title type="html">系統設計面試架構圖</title><link href="https://s20055232.github.io/system%20design/sdi-flow/" rel="alternate" type="text/html" title="系統設計面試架構圖" /><published>2024-11-26T00:00:00+00:00</published><updated>2024-11-26T00:00:00+00:00</updated><id>https://s20055232.github.io/system%20design/sdi-flow</id><content type="html" xml:base="https://s20055232.github.io/system%20design/sdi-flow/"><![CDATA[<p>照著架構練習跟回答通常表現會比較好</p>

<p><img src="/assets/2024-11-26-sdi-flow/flow.png" alt="flow" data-fancybox="gallery" /></p>

<h2 id="requirements-5-minutes">Requirements (~5 minutes)</h2>

<p>釐清問題是重要的第一步</p>

<h3 id="functional-requirements">Functional Requirements</h3>

<p>簡單來說就是「這個產品有什麼功能」，要找出功能性需求，就必須反覆跟面試官討論，最後討論的結果可能會像是：</p>

<ul>
  <li>使用者可以發布推文</li>
  <li>使用者可以追蹤其他用戶</li>
  <li>使用者可以看到自己追蹤的人的更新內容</li>
</ul>

<p>要記住，這階段所找出的需求是你等等系統設計時要解決的，所以不能太發散、太廣，找出核心重點的功能來解決就好。</p>

<h3 id="non-functional-requirements">Non-functional Requirements</h3>

<p>非功能性需求應該要量化，像是：</p>

<ul>
  <li>延遲應該要 &lt; 500 ms</li>
</ul>

<p>而不是</p>

<ul>
  <li>延遲要低</li>
</ul>

<p>這邊的需求不是能做到什麼，而是「有什麼期望」，像是：</p>

<ul>
  <li>系統應該高可用且可用性大於一致性</li>
  <li>系統要能夠承受 1 億每日活躍使用者（DAUs）</li>
  <li>低延遲，渲染速度低於 200 ms</li>
</ul>

<p>非功能需求會比較難想，我們可以利用以下清單來發想：</p>

<ul>
  <li><strong>CAP Threorem</strong>：Consistency 與 Availability 權衡取捨，Fault Tolerance 在分布式系統中是默認具備的，這點要留意。</li>
  <li><strong>Environment Constraints</strong>: 環境有任合條件跟限制嗎？邊緣端？手機？網路或記憶體有限制？</li>
  <li><strong>Scalability</strong>：系統會需要在特定條件下擴張嗎？黑色星期五？對於系統來說讀跟寫哪種需求更大？</li>
  <li><strong>Latency</strong>：能夠接受多大的延遲？對於有意義的需求，要特別考慮，像是 google 搜尋結果的輸出</li>
  <li><strong>Durability</strong>：資料遺失對系統來說重不重要？社群媒體可能可以接受部分資料遺失，但對於銀行系統來說就不行</li>
  <li><strong>Security</strong>：系統要多安全，這要考慮資料保護、存取控制、規範</li>
  <li><strong>Fault Tolerance</strong>：系統的容錯性應該多好？冗余、容錯移轉、復原這些都是可以考慮的選項</li>
  <li><strong>Compliance</strong>：是否有業界規範、法律需要遵守？</li>
</ul>

<h3 id="capacity-estimation">Capacity Estimation</h3>

<p>雖然很多教學可能會說要進行 back-of-the-envelope calculations（粗略計算），但通常這是不必要的，做計算的時機只有在那些計算對系統至關重要時才做，像是：</p>

<blockquote>
  <p>計算出在不同雲服務提供商下存儲 100TB/天 的成本</p>

  <p>以及長期儲存這些視頻所需的總成本</p>

  <p>並考慮是否需要設計更高效的壓縮和刪除策略來降低成本。</p>
</blockquote>

<p>假設流量大部分來自北美和亞洲。你可以計算每個地區的高峰時段流量，以此來決定 CDN 節點的部署策略。</p>

<p>在面試環節時可以先跳過此環節，並說明當有需求時再回來計算。</p>

<h2 id="core-entities-2-minutes">Core Entities (~2 minutes)</h2>

<p>找出系統中核心的實體，這些實體是主要用來交換、儲存的資料模型，面試過程時簡單的記下這些實體即可，隨著我們的設計進行，我們可以快速迭代並添加新的實體到清單中。</p>

<p>以 Twitter 來說，核心的實體可能會是：</p>

<ul>
  <li>User</li>
  <li>Tweet</li>
  <li>Follow</li>
</ul>

<h2 id="api-or-system-interface-5-minutes">API or System Interface (~5 minutes)</h2>

<p>通常在這個環節，我們會依照我們前面功能需求所定義的，去設計一組供使用的接口。</p>

<p>有幾種常見的選擇：</p>

<ul>
  <li>RESTful API</li>
  <li>GraphQL API</li>
  <li>Wire Protocol（自定義）</li>
</ul>

<p>通常 RESTful API 就已經可以滿足大多數的需求了，GraphQL 只發生在讓客戶端自行搜尋所需的資料以避免 over-fetching or under-fetching，而如果需要雙向互動諸如 websocket 的話，資料傳遞的格式就必須自行定義了。</p>

<h2 id="optional-data-flow-5-minutes">[Optional] Data Flow (~5 minutes)</h2>

<p>如果系統會執行繁雜的資料操作，像是資料處理系統，那麼透過簡單的列表來說明資料大致會經過什麼流程是有幫助的，但如果相反，你的資料處理相對單純，那直接跳過此步驟沒關係。</p>

<p>用網頁爬蟲舉例：</p>

<ol>
  <li>獲取 URLs</li>
  <li>解析 HTML</li>
  <li>萃取 URLs</li>
  <li>儲存資料</li>
  <li>重複</li>
</ol>

<h2 id="high-level-design-10-15-minutes">High Level Design (~10-15 minutes)</h2>

<p>在這個環節，我們可以將常見的 components 用線、方塊來表示並連接在一起來滿足我們前面提到的功能、非功能性需求，注意，重點是滿足前面所討論的需求，不要過度思考跟複雜化問題。</p>

<p>通常我們會一個一個 API 檢視，從功能性需求開始著手，然後是非功能性需求，一個個建立相對應的設計來滿足需求，你的系統設計會從一個很簡單的樣貌開始逐步添加直到滿足需求。</p>

<p>在繪製系統時，要與面試官討論思考過程，明確表示資料如何流經系統以及狀態如何變化，從 request 開始並結束在回傳 response，當資料流到持久層時，是一個好時機紀錄該實體可能會有什麼欄位，不用太具體，紀錄有相關的欄位就好，但具體是什麼資料類別並不重要。</p>

<p>用 Twitter 的 4 個 API 來說明，可以看見我們一個一個的進行 API 設計，並逐步迭代我們的設計</p>

<p><img src="/assets/2024-11-26-sdi-flow/twitter%20api.png" alt="twitter api" data-fancybox="gallery" /></p>

<h2 id="deep-dives-10-minutes">Deep Dives (~10 minutes)</h2>

<p>在上述的環節，一定有很粗糙跟不夠好的地方，我們要做的就是在這個環節優化他們，但要留意以下幾點</p>

<ol>
  <li>確保有滿足非功能需求</li>
  <li>處理邊緣情況</li>
  <li>識別 &amp; 解決問題、瓶頸</li>
  <li>根據面試官的反饋優化答案</li>
</ol>

<p>較為初階的面試者會等待面試官的回饋，但高階的面試者應該會自己找到可能的問題並引導討論。</p>

<p>舉例來說，Twitter 的範例接下來可能會討論要如何滿足 &gt; 100M DAU，那可能討論會聚焦於水平擴張、快取、db sharding，並隨著討論逐步更新。</p>]]></content><author><name>Han</name></author><category term="System Design" /><summary type="html"><![CDATA[照著架構練習跟回答通常表現會比較好]]></summary></entry></feed>