HTTP/3について

はじめに

2022年6月に標準化されたHTTP/3の内容把握になります

概要

gihyo.jp

の内容を自己流で噛み砕いたものです。
(間違い・違和感等あれば、やさしくご指摘頂けると幸いです)

Software DesignにあるHTTP/3の記事は
大変わかりやすくので、こちらを直接読んで頂く方が断然オススメです!!

HTTPの歴史

HTTP/0.9

1991年に仕様が公開される デフォルトのポート80番を使用し、TCP(Transmission Control Protocol)とIP(Internet Protocol)によって コネクションを確立し、HTTPリクエストを送り、レスポンスを受け取るという流れ

HTTP/0.9の問題点

  • 画像のやりとりができない
  • レスポンスがエラーかどうか判別できない

HTTP/1.0

1992年に仕様が公開される
HTTPヘッダーフィールドにより、HTML以外のコンテンツもHTTPでやりとり可
HTTPステータスコードにより、 レスポンスのエラー判定可

HTTP/1.0の追加要素

HTTP/1.0の問題点

-1つのリクエストとレスポンスのやりとりでTCPコネクションを閉じる - 3ウェイハンドシェイクするオーバーヘッドが大きい

HTTP/1.1

1997年にRFC2068が発行 1999年にRFC2616が発行

Keep-Aliveヘッダによって、TCPコネクションを使いわませるようになる
(再確立のオーバーヘッドが解消)

HTTP/1.1の追加要素

  • Connectionヘッダ
  • Keep-Aliveヘッダ

HTTP/1.1の問題点

  • Head-of-Line Blocking(レスポンスの順番待ち)
  • TLS通信の3ウェイハンドシェイクのオーバーヘッド

HTTP/2.0

2015年にRFC7540が発行 SPDYプロトコルを標準化したもの HTTP2よりバイナリ形式のフレームを利用(HTTP/1.1と非互換)

HTTPレイヤのHead-of-Line Blockingを解消するために、ストリーム多重化
ストリームIDをもつことで、レスポンス順不同で送信できるようになる
ヘッダ圧縮にはHPACKを利用する

HTTP/2.0の追加要素

  • ストリームのよる多重化
  • 優先度制御
  • フロー制御
  • 類似ヘッダフィールド
  • HTTPヘッダフィールド圧縮
  • サーバープッシュ

HTTP/2.0の問題点

  • TCPレイヤによるHead-of-Line Blocking
  • プロトコル硬直化(ミドルボックスが拡張フォーマット未対応によるパケット破棄や通信遮断)

HTTP/3.0

2022年6月にRFC9114を発行
もともとHTTP over QUICをいう名称

UDPプロトコルを採用することで、TCPレイヤによるHead-of-Line Blockingを解消させる
暗号化必須にすることで、プロトコル硬直化のリスクを軽減させる

https://raw.githubusercontent.com/rmarx/h3-protocol-stack/main/png/protocol-stack-h1-h2-h3.png

HTTP/3.0の新要素

  • ストリームのよる多重化
  • Head-of-Line Blocking が発生しない
  • 優先制御がシンプル
  • 暗号化が必須
  • 0-RTT(Round Trip Time)、1-RTTでハンドシェイクが完了
  • コネクションマイグレーションが可能
  • ヘッダ圧縮にはQPACKを利用

暗号化が必須

トランスポート層における諸問題を、従来のようなTCPを改善するアプローチではなく
UDP+QUICで改善するアプローチ
UDP+QUICにすることで、オーバーヘッドの問題を解消できる

TCPの通信の信頼性はUDPにはないが、QUICにより信頼性を担保させる

QUICのパケット構造
https://datatracker.ietf.org/doc/html/draft-ietf-quic-transport-27#section-17.2

ショートヘッダパケット

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+
|0|1|S|R|R|K|P P|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                Destination Connection ID (0..160)           ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                     Packet Number (8/16/24/32)              ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                     Protected Payload (*)                   ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

ロングヘッダパケット

+-+-+-+-+-+-+-+-+
|1|1|T T|X X X X|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                         Version (32)                          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| DCID Len (8)  |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|               Destination Connection ID (0..160)            ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SCID Len (8)  |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                 Source Connection ID (0..160)               ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • Connection IDはコネクションの維持、ロードバランサーのアクセス制御
  • QUICバージョンはクライアント - サーバー間で合わせるため

スケジュールされたクエリで、各テーブルのレコード件数を自動取集する

週次でテーブルのレコード件数の推移とテーブルの増減を調べたく
ストアド プロシージャの勉強も兼ねて
スケジュールされたSQLで、定期的に収集できるようにしました。

レコードを登録するテーブル名一覧を一時表に登録して
LOOPでメタ情報を順次登録しています。

DECLARE dataset STRING;
DECLARE current_row int64 DEFAULT 0;
DECLARE loc STRING;

SET loc = 'US';
CREATE temp table dataset_list AS (
  SELECT
    catalog_name || '.' || schema_name || '.__TABLES__' AS dataset_name,
    ROW_NUMBER() OVER (ORDER BY schema_name) AS row_num
  FROM
    INFORMATION_SCHEMA.SCHEMATA
  WHERE
    location = loc);
LOOP
  SET
    current_row = current_row + 1;
  SET
    dataset = (
    SELECT
      dataset_name
    FROM
      dataset_list
    WHERE
      row_num = current_row);
  IF dataset IS NULL THEN
    LEAVE;
  END IF;
  EXECUTE IMMEDIATE FORMAT("INSERT INTO ds.table_metas SELECT *, CURRENT_TIMESTAMP() AS check_timestamp FROM %s", dataset);
END LOOP;
-- データセット、テーブル登録件数を保持
INSERT INTO ds.records
SELECT DATE_TRUNC(CURRENT_DATE(), week(sunday)) AS recorded_on,
       COUNT(DISTINCT dataset_id) AS dataset_record,
       COUNT(DISTINCT dataset_id||table_id) AS table_record,
       SUM(row_count) AS total_record,
       SUM(size_bytes) AS total_size,
  FROM ds.table_metas
 WHERE TIMESTAMP_TRUNC(check_timestamp, week(sunday)) = TIMESTAMP_TRUNC(CURRENT_TIMESTAMP(), week(sunday))
;

参考にしたブログはこれ以外にもあったのですが。。 qiita.com

いろいろ探してみたけど
そのブログを見つけられなかった。。

どんなキーワードで検索して そのブログへ辿り着けたかも忘れてしまった。 多分、名の知れた方だったかな

もうちょい、ここを熟読しないとな

cloud.google.com

【備忘録】 __init__.pyの使い方

はじめに

Pythonで、ディレクトリの階層で分けてファイルを管理していますが 今まで、__init__.py ファイルを使っていませんでした...

Pythonチュートリアルを読む(一部抜粋)と

ファイルを含むディレクトリをパッケージとしてPython に扱わせるには、ファイル __init__.py が必要です。
これにより、 string のようなよくある名前のディレクトリにより、
モジュール検索パスの後の方で見つかる正しいモジュールが意図せず隠蔽されてしまうのを防ぐためです。
最も簡単なケースでは __init__.py はただの空ファイルで構いませんが、
__init__.py ではパッケージのための初期化コードを実行したり、
後述の __all__ 変数を設定してもかまいません。

とのことで、初期化コードを実行と all 変数を設定を試してみたいと

どう試した?

お試しのディレクトリ構成は以下です

├── main.py
└── lib
    ├── __init__.py
    └── logger.py

お試し版のソースは以下の通りです。

init.py

from .logger import Logger

logger: Logger = Logger()

__all__ = ["logger"]

logger.py.py

from logging import Formatter, getLevelName, getLogger, StreamHandler

class Logger():
    def __init__():
        self.logger = getLogger()
        self.logger.setLevel(getLevelName("debug"))

        handler = StreamHandler()
        handler_format = Formatter(
            '%(asctime)s : process(%(process)d) : thread(%(thread)d) : [%(levelname)s] : %(message)s')

        handler.setFormatter(handler_format)
        self.logger.addHandler(handler)

    def debug(self, msg: str):
        self.logger.debug(msg)

    def info(self, msg: str):
        self.logger.info(msg)

    def warning(self, msg: str):
        self.logger.warning(msg)

    def error(self, msg: str):
        self.logger.error(msg)

    def critical(self, msg: str):
        self.logger.critical(msg)

main.py

from lib import logger

logger.info("hello world")

実行結果

$ python main.py

2022-05-27 20:16:51,917 : process(56990) : thread(4490100224) : [INFO] : hello world

main.pyで、Loggerのインスタンス生成せず(logger = Logger()と書かず)とも、ログ出力ができた。

まとめ

自分に対して言うことは、 ドキュメントをサボらずに読め です。

リンク集まとめ

こちらは、個人的にためになったブログや資料等の単なるリンク集です

BigQueryのHASH パーティショニングもどきを利用する

BigQueryで
主キー当たる項目にUUID(Universally Unique Identifier)を生成し
こちらの項目に対してパーティション列として使いたい用途がありました。

パーティション分割テーブルについて 公式ドキュメントを読むと

取り込み時間 日付 数値範囲
テーブルの分割方法 データの取り込み時間や到着時間 TIMESTAMP または DATE列 整数列
分割単位 自動で生成される疑似列 列を指定 列を指定

となり、UUID(文字列)は対応しておりません。

公式ドキュメントにあるハッシュ関数を読んだところ farm_fingerprintを見つけました

文字列を数値に変換できますので
次にハッシュパーティションのように利用できるようにするため
UDFユーザ定義関数を定義します。

-- partition_division_sizeは分割する数を指定
CREATE OR REPLACE FUNCTION dataset.generate_partion_no(uuid STRING)
RETURNS INT64
AS (
    ABS(MOD(FARM_FINGERPRINT(uuid), partition_division_size))
)
;

パーティション列にデータを登録する際にUDF関数を利用します。

SELECTする際は 以下のようなSQLで取得できます。

SELECT *
FROM dataset.table
WHERE partition_column = dataset.generate_partion_no(@uuid)

ハッシュ関数を利用することで、
文字列をハッシュパーティションのように利用することができます。

argparseのchoicesで小数点を指定する方法

コマンドライン引数 argparseのchoicesで小数点を指定したいと考えて、よく調べず

parser.add_argument('rate', type=float, choices=range(0.1, 1.0))

こんなイメージでできると思って記述したところ、エラーになりました。。

何か方法がないかと思い、ググってみると   こんな感じの記事に遭遇しました。

この手段は、別に悪くはないのですが
別手段はないかなぁと、いろいろな方法を調べてみると
内包表記との組み合わせでイケるんではないかと思って試したらところ

parser.add_argument('rate', type=float, choices=[i / 10 for i in range(1, 11)])

こちらの書き方で無事できました。