2019 年 Percona 對 UUID 當作 Primary Key 的看法

前陣子的「為資料庫提案新的 UUID 格式」這邊提到了有人提案要增加新的 UUID 格式,Percona 的老大 Peter ZaitsevTwitter 上貼了「UUIDs are Popular, but Bad for Performance — Let’s Discuss」這篇在 2019 年時他們家的文章,題到了 MySQL 使用 UUID 當作 Primary Key 的事情:

要注意的是這篇文章沒有要從頭解釋 UUID 對於 Primary Key 的壞處,如果你想要先了解的話,在這篇文章的開頭給了一堆其他文章的連結,裡面就有討論過了。

這篇主要是在討論,如果硬要用 UUID 當 Primary Key 時,可以有什麼方法降低對 InnoDB 的衝擊,剛好回應最近的提案。

開頭還是先花了一些篇幅大概講一下 UUID 的種類,然後在「What is so Wrong with UUID Values?」這邊提到了字串比較的差異,如果 UUID 是到最後一碼才不同的話 (這邊是跑 df878007-80da-11e9-93dd-00163e000002 與 df878007-80da-11e9-93dd-00163e000003 與比較一億次):

1 row in set (27.67 sec)

但如果是一開始就不同的話 (這邊是選擇 df878007-80da-11e9-93dd-00163e000002ef878007-80da-11e9-93dd-00163e000003) 會快很多:

1 row in set (2.45 sec)

但如果與數字相比的話 (這邊是 2=3 這樣的條件去比):

1 row in set (0.96 sec)

可以看數字在這邊的優勢,另外也是在說明,如果你用的是 time-based ordering 的 UUID,要考慮會遇到這個可能會發生的效能問題。

再來是玩 UUID 的三種不同的儲存方式對於寫入效能的差異,分別是 CHAR(36) (32 bytes 的 hex 加上四個 -)、base64 (用 CHAR(22) 存) 與 BINARY(16),可以看出來 BINARY(16) 因為佔用空間比較小的關係,是可以高速寫入持續最久的,再來是 base64,最差的是 CHAR(36)

後面給了兩個 workaround,第一個算是定義了另外一種產生 128 bits 的方式,第二個則是想辦法把 UUID 對應到數字。

這在 MySQL 的環境裡面算是被討論的很久的主題了。(我猜在 PostgreSQL 應該也是,不過 PostgreSQL 的社群沒跟那麼久...)

超快速的 Base64 encoding/decoding 實做

看到「Base64 encoding and decoding at almost the speed of a memory copy」這個,可以超級快速編解碼 Base64 的資料。

實做上是透過 IntelAVX-512 加速,在資料夠大的情況下 (超過 L1 cache 的大小),可以達到接近字串複製的速度 (這邊提到的 memcpy()):

We show how we can encode and decode base64 data at nearly the speed of a memory copy (memcpy) on recent Intel processors, as long as the data does not fit in the first-level (L1) cache. We use the SIMD (Single Instruction Multiple Data) instruction set AVX-512 available on commodity processors. Our implementation generates several times fewer instructions than previous SIMD-accelerated base64 codecs.

不過這樣 AMD 暫時要哭哭...

用 Google Docs 惡搞的方式...

看到「UDS : Unlimited Drive Storage」這個專案,利用 Google Docs 存放資料。主要的原因是因為 Google Docs 不計入 Google Drive 所使用的空間:

Google Docs take up 0 bytes of quota in your Google Drive

用這個方法可以存放不少大檔案 (像是各種 ISO image),讓人想起當年 Love Machine 的玩法 (不知道的人可以參考「愛的機器 Love machine」這篇),切割檔案後傳到某些空間以提供下載?只是這邊是用 base64 放到 Google Docs 上...

base64 的資料會比原始資料大 33%,而 Google Docs 單篇的上限大約是 710KB:

Size of the encoded file is always larger than the original. Base64 encodes binary data to a ratio of about 4:3.

A single google doc can store about a million characters. This is around 710KB of base64 encoded data.

方法不是太新鮮,但是讓人頗懷念的... XD

Command Line 下把 Hex 轉成 Base64...

每次都忘記,寫一篇之後查比較方便... 重點在對 xxd 的變化應用,而 xxd 被包在 Vim 裡,所以應該都會裝... 吧...

xxd 預設是把 binary 轉成 hex,但你可以用 -r 參數變成反向,也就是 hex 轉 binary。

所以剩下的就很簡單了,先把 hex 轉成 binary 再轉成 base64:

echo 0123456789ABCDEF | xxd -r -p | base64

這邊有裝 Base64 所以可以直接用,如果沒有的話,可以用 OpenSSL 替代:

echo 0123456789ABCDEF | xxd -r -p | openssl enc -base64

Amazon CloudFront 上 Protected Content 的 URL Sign...

Amazon CloudFront 也可以設定要簽名才能抓檔案,只是 URL Sign 設計的觀念跟 Amazon S3 完全不一樣,這不一致的調調很... 詭異...

大致上有這些差異:

  • Amazon S3 用的是 HMAC-SHA1 的機制簽名 (shared secret,也就是 Amazon S3 與你都有同一把 key),而 Amazon CloudFront 則是用 RSA key 簽名 (public key,也就是 Amazon CloudFront 存放 public key,你自己存放 private key)。
  • 也因為是使用 RSA key,有人會誤解跟 Amazon EC2 用的 RSA key 相同。但實際上需要在 Security Credentials 頁裡設,有專門對 Amazon CloudFront 用的 RSA key 的段落。
  • 自己對 Base64 編碼再處理,避開使用 += 以及 /。但不是有標準可以用嗎,為什麼要自己發明呢...
  • 引入 Policy 的彈性機制,不僅可以對時間控制,也可以對 IP address 控制,但 Policy 這是帶在 URL 裡傳進去的... 你可以看到我的程式碼內產生出 $json_str 後簽完名帶到 URL 內了。

官方的 CloudFront Signed URLs in PHP 這篇的範例程式碼其實很清楚了,要直接拿去用其實也沒麼問題。我自己整理後是這樣:

<?php

$key_pair_id = 'APKA...';
$pem_file = '';
$resource = 'http://test2-cdn.gslin.org/test.txt';

$expires = time() + 3600;

$json_str = json_encode(
    array(
        'Statement' => array(
            array(
                'Resource' => $resource,
                'Condition' => array(
                    'DateLessThan' => array(
                        'AWS:EpochTime' => $expires
                    )
                )
            )
        )
    ),
    JSON_UNESCAPED_SLASHES
);

$buf = file_get_contents($pem_file);
$key = openssl_get_privatekey($buf);

openssl_sign($json_str, $signed_policy, $key, OPENSSL_ALGO_SHA1);

openssl_free_key($key);

$signature = str_replace(
    array('+', '=', '/'),
    array('-', '_', '~'),
    base64_encode($signed_policy)
);

echo "${resource}?",
    "Expires=${expires}&",
    "Signature=${signature}&",
    "Key-Pair-Id=${key_pair_id}\n";

反正你搞不太懂 Amazon 為什麼要這樣設計的... =_=