Stripe 對於控制 API 使用量的作法

在「Scaling your API with rate limiters」這篇 StripePaul Tarjan 提到了四種如何保護 API 的作法。

前兩種都是 rate limit。第一種是最標準的「你一分鐘可以用幾次」的方式,這是最容易理解的方式。第二種是「你同時間可以用幾個 API request」,這通常會用在大量消耗資源的 API 上,避免短時間內被打爆。

第三種是拉到整體來看,把 API 分成重要與不重要的,然後直接保留確保重要的 API 有一定的 capacity 可以用:

We always reserve a fraction of our infrastructure for critical requests. If our reservation number is 20%, then any non-critical request over their 80% allocation would be rejected with status code 503.

第四種方式是當過載時的自動化處理,平常就把各種工作排優先順序,當超量的時候自動先將低優先權的拿掉:

Only 100 requests were rejected this month from this rate limiter, but in the past it’s done a lot to help us recover more quickly when we have had load problems. This load shedder limits the impact of incidents that are already happening and provides damage control, while the first three are more preventative.

不過還是有點怪,Stripe 應該是全部都建在 AWS 上面 (AWS Case Study: Stripe),跟 auto scaling 的配合好像都沒提到?

歸類的方式還蠻有條理的,可以學這個方法來規劃...

Google Cloud Platform 的終身免費方案

Google 在這次 Google Cloud Next '17 公佈了 Google Cloud Platform Free TierAlways Free Usage Limits,也就是終身免費的方案。

這次宣佈的包括了許多服務。掃了一遍應該會去玩 GCE 的部份,包括了 0.6GB RAM 的機器以及 1GB 的對外流量 (不過到中國與澳洲要另外計費,不包含在這個範圍內)。

領先者的 AWS 不知道會不會也跟進...

Let's Encrypt 宣佈脫離 Beta

Let's Encrypt 宣佈脫離 beta,正式開放:「Leaving Beta, New Sponsors」。

翻資料的時候發現在今年 3/26 的時候,限制已經放寬了:「Rate Limits for Let’s Encrypt」。

首先一張證書只能包括 100 個 hostname,跟原來相同:

Names/Certificate is the limit on how many domain names you can include in a single certificate. This is currently limited to 100 names, or websites, per certificate issued.

再來是每個禮拜可以申請的數量從 5 個 hostname 變成 20 個,另外本來 renew 也算 quota,現在變成不會吃到 quota:

Certificates/Domain limits how many certificates can be issued that contain a single registered domain*.
This is limited to 20 certificates per domain per week. Exception: When you request a certificate with the same exact set of FQDNs as previously-issued certificate, this rate limit does not apply, but the one below does.

不知道會不會再放寬限制...

避免用 SQL 的 OFFSET 實做 Pager

發現以前沒有提到過...?

WordPress.com Developer Resources 寫的這篇「An efficient alternative to paging with SQL OFFSETs」提到了用 OFFSET 實做 Pager 的效率問題。

因為 RDBMS 是人寫的,所以人想不到的方法,程式也做不到,像是這樣的 SQL query 效率不會好:

SELECT * FROM my_table ORDER BY pk LIMIT 8000000, 100;

如果這是一般以 B+tree 儲存的 RDBMS (或是類似的資料結構),會先把 pk 拿出來,從最小的開始計算,掃過八百萬次後再去抓一百筆 pk 的值,最後再回到 my_table 內把所有資料拉出來。

這個方法當 offset 小的時候不會有感覺,但大了以後就會爆炸。就算 primary key 都在記憶體內,仍然是狂操 CPU L2/L3 以及記憶體的存取速度。

目前常見的分頁作法是在 url 上妥協,只提供「下一頁」或「下 n 頁」的功能。如此一來,url 變成:

https://www.example.com/blog?page=10&started_at=12345678
https://www.example.com/blog?page=11&started_at=12345690
https://www.example.com/blog?page=12&started_at=12345710

started_at 變成 unix timestamp,而資料庫對時間欄位 index。如此一來,資料庫在查詢的時候就可以先 B+tree 找到 started_at 的節點 (或是小於他最近的節點),然後連續抽出一百筆。

另外一種則是在產品面上「妥協」,也就是方法保持不變,但限制「一個 thread 最多只能回 1000 篇」,這在 2ch 或是論壇上很常看到。

Percona XtraDB Cluster 5.5.33-23-7.6...

Percona XtraDB Cluster (Galera Cluster) 出新版:「Percona XtraDB Cluster 5.5.33-23.7.6 is now available」。

看到了幾個比較特別的功能:

Desync functionality has now been exposed to the client. This can be done either via /*! WSREP_DESYNC */ comment on the query or by setting the global wsrep_desync variable to 1.

這個功能感覺上是打算為了在 Percona Toolkit 裡面配合 pt-table-sync 而準備的?

另外一個重要的功能是限速,這可以避免在伺服器最忙碌的時候加重負擔造成伺服器撐不住:

Percona XtraDB Cluster has implemented new rate limiting, rlimit, option for XtraBackup SST that can be used to avoid saturating the donor node.

以往我是自己 patch 一個 shell script 出來用,現在則變成是原生支援,那麼本來的 patch 方式就要轉換到原生支援上...

然後文末有建議 Debian 使用者在升級前要先安裝 socat,避免升級發生問題 :o

MySQL subquery 的限制...

官方文件參考 MySQL 5.5 的「Restrictions on Subqueries」這篇。

直接拿官方的範例:

SELECT * FROM t1 WHERE s1 IN (SELECT s2 FROM t2 ORDER BY s1 LIMIT 1);

這會出現 subquery 不支援 LIMIT 的錯誤訊息:

ERROR 1235 (42000): This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'

如果試著用 Google 找解法,會找到用 temporary table 解決的方法:

SELECT * FROM t1 WHERE s1 IN (SELECT * FROM (SELECT s2 FROM t2 ORDER BY s1 LIMIT 1) AS t);

這個方法是可以用啦,但還是讓人很囧啊 XD

另外一個 subquery 對效能的影響是 SQL-92 定義 WHERE 裡 subquery 的行為:(參考 sql1992.txt)

2) Each <subquery> in the <search condition> is effectively executed for each row of T and the results used in the application of the <search condition> to the given row of T. If any executed <subquery> contains an outer reference to a column of T, then the reference is to the value of that column in the given row of T.

因為如此,並不是想像中「先算完 subquery 再拿結果算前面」...

在「MySQL Limitations Part 3: Subqueries」也有一些討論可以看 (在 comment 裡),雖然文章有點舊了... 把這部份 subquery 的結果自己拉出來組 SQL query 可能會比較快。