Web ComponentとReact

以下の記事は、自分の知識整理のための記事です。間違っていたらご指摘ください。

Web Componentとは

ウェブの再利用可能なウィジェットコンポーネントを作成するための標準技術の集合。

これにより、HTML, CSS, JSをカプセル化した上で再利用することが可能になる。

1. カスタム要素

カスタム要素と、その動作に関するJavaScript API。以下のようなコードをJavaScriptに書くことで、HTMLからpopup-infoタグを使用できるようになる。

customElements.define("popup-info", PopupInfo);

class PopupInfo extends HTMLElement {
  constructor() {
    // コンストラクターでは常に super を最初に呼び出してください
    super();

    // ここに要素の機能を記述します
  }
}
<popup-info></popup-info>

2. Shadow DOM

Shadow DOMツリーを要素に紐づけて関連する機能を制御するための、一連のJava Script APIカプセル化している。つまり、シャドウDOM外のJavaScript, CSSが、シャドウDOM内の要素に影響を与えることは無い。

1のカスタム要素単体だと、カプセル化されていないので、Shadow DOMと組み合わせることで、外部と隔離された形でタグが使用できるようになる。Shadow DOMは、HTML templateと違って、attachされたらすぐにレンダリングされる。

3. HTML template

template要素と、slot要素によって、レンダリングされたページに表示されないマークアップのtemplateを書くことができる。JavaScriptを介してインスタンス化される。

HTML templateは、templateの内容をカスタム要素としてカプセル化した上でShadow DOMに追加することで真価を発揮する。

customElements.define(
  "my-paragraph",
  class extends HTMLElement {
    constructor() {
      super();
      let template = document.getElementById("my-paragraph");
      let templateContent = template.content;

      const shadowRoot = this.attachShadow({ mode: "open" });
      shadowRoot.appendChild(templateContent.cloneNode(true));
    }
  },
);
<template id="my-paragraph">
  <style>
    p {
      color: white;
      background-color: #666;
      padding: 5px;
    }
  </style>
  <p>My paragraph</p>
</template>

こうすると、以下のような形で、my-paragraphタグを利用できる。my-paragraphは、HTML templateなので、ページ読み込みと同時にレンダリングされるわけではないので、パフォーマンス向上に貢献できる。

<my-paragraph></my-paragraph>

Web ComponentとReact

メルカリのWeb Componentsに関する記事によると、メルカリ Core teamはWeb ComoponentsからReactに移行中であるとのこと(2022年末)。 そもそもWeb Componentsを導入した背景としては、Web Componentsを使えば、React, Vueなど、様々なフレームワークで利用できるというメリットがあったためらしい。

しかし、導入後、以下のような課題が出てきた。

  • Web Componentsを使ってくれる・contributeしてくれる人が少ない
  • Web Componentsでサーバーサイドレンダリング(SSR)を実行するのは難しい
  • Web Componentsのその他の技術的な制約

そこで、Web ComponentsからReactへの移行を実施しているらしい。

所感

Web Components自体は面白い技術だが、導入した場合、メルカリの記事にあるような技術課題に直面しそう。つまり、Web Componentsは「世間的な認知がそこまで高くない・SSRなどの技術的制約が存在する」。Web Componentsを導入するのであれば、組織全体でWeb Components体制をサポートする必要がある。ただ、そこまでの企業努力をしてまで導入するメリットは、一般的な会社には無さそう。エンジニア採用しやすくて、使いやすい技術スタックという観点で考えると、Reactに軍配が上がる。

参考

https://developer.mozilla.org/ja/docs/Web/API/Web_components

https://engineering.mercari.com/en/blog/entry/20221207-web-design-system-migrating-web-components-to-react/

https://engineering.mercari.com/blog/entry/20210823-8128e0d987/

Youkiの基礎知識とcontribution環境構築

Youkiとは

runc同様、OCI runtime specである。Rustで書かれている。日本語の容器が名前の由来。

なお、OCI runtime specは、1. 隔離環境の作成, 2. コンテナ実行, 3. プロセスのKill, 4. コンテナの削除、に関する明文化された仕様を指す。

メリット

Rustで実装するメリットは、Goよりsystem callを扱いやすい(Cを呼び出すオーバーヘッドが少ない、ゼロコスト抽象化などの仕様のおかげ?)、Cと比べてメモリ安全、が挙げられていた。そのおかげか、Youkiは、runcより高速に動作するらしい。

仕様

現在はLinux環境しかサポートしていない。Linux以外の環境で動かしたい場合、VagrantVMを利用する必要がある。

Youkiは低レベル container runtimeなので、Docker, Podmanなどの高レベル container runtimeと組み合わせて使うことになる。

Youkiには、ルート権限が必要なrootful modeと、不要なrootless modeがある。ルート権限とは、コンテナ内のプロセス権限がルートユーザーと同等かどうか。ルート権限がある場合、セキュリティ面での問題が懸念される。このあたりはコンテナセキュリティ本を読むと、雰囲気がわかるはず。

Dockerで動かす方法

etc/docker/daemon.jsonを書き換えることで、Dockerでもyoukiもruntimeとして使える。

{
"default-runtime": "runc",
"runtimes": {
"youki": {
"path": "/path/to/youki/youki",
"runtimeArgs": [
"--debug",
"--systemd-log"
]
}
}
}

レポジトリの説明

libcgroups

Linux cgroupとやり取りしたりするための機能を担っている。common, stats, systemd, test_manager, v1, v2といったmoduleを持つ。例えば、cgroup fileの読み書きを簡単にしてくれるuser interfaceなどが提供されている。

libcontainer

containerの作成・管理を担う。apparmor, container, hook, namespacesなどの様々なmoduleがある。

liboci-cli

OCI container runtimeのためのCLIを提供している。

libseccomp

libseccompへのRust FFI bindingを提供する。FFIとは、あるプログラミング言語から他プログラミング言語を利用するための機構。この文脈でいうと、RustからC言語で書かれたlibseccompを扱うということだと思う。

Webasssembly

runwasiのように、WebAssembly moduleをYoukiで実行することが可能。

control flow

以下のような流れでYoukiは動作するらしい。

Youki flow

Contribution環境を作るときに試行錯誤したメモ

Rustに関するメモ

コードリーディング・contributionをする際に、分からなかったところのメモ。

Builder

以下のstruct Linuxの場合、ビルダーパターンを利用して初期値を設定することができる。ビルダーパターンを使うと、オブジェクトの構築をstep by stepで行えるようになり、オブジェクトの初期化を扱いやすくしてくれる。コード内でLinuxBuilderが出てきたが、LinuxBuilderというオブジェクトを定義している箇所はなく、LinuxオブジェクトにBuilderがderiveされているだけ。

#[derive(Builder, Clone, Debug, Deserialize, Eq, Getters, Setters, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
#[builder(
    default,
    pattern = "owned",
    setter(into, strip_option),
    build_fn(error = "OciSpecError")
)]
#[getset(get = "pub", set = "pub")]
/// Linux contains platform-specific configuration for Linux based
/// containers.
pub struct Linux {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    /// UIDMappings specifies user mappings for supporting user namespaces.
    uid_mappings: Option<Vec<LinuxIdMapping>>,

Linux Builderの初期化例

// Linux構造体のビルダーを使用して、uid_mappingsに値を設定
let linux = Linux::builder()
    .uid_mappings(vec![uid_mapping]) // ここでuid_mappingsにベクターを設定
    .build() // ビルダーパターンを完成させ、Linuxインスタンスを生成
    .expect("Failed to build Linux"); // build()メソッドがエラーを返す可能性があるため、unwrap()やexpect()で処理

enum

以下のようなenum型があるとする。

#[derive(Debug, thiserror::Error)]
pub enum InitProcessError {
    #[error("failed to set sysctl")]
    Sysctl(#[source] std::io::Error),
    #[error("failed to mount path as readonly")]
    MountPathReadonly(#[source] SyscallError),

enum型は、以下のように、InitProcessError::Sysctl (各enumの値)、のように指定して使う。

fn set_sysctl() -> Result<(), InitProcessError> {
    Err(InitProcessError::Sysctl(io::Error::new(io::ErrorKind::Other, "oh no!")))
}

contribution環境の構築

私はLinux環境 PCでは無いので、CodeSpaceを使用した。EC2(amazon Linux)も試したが、CodeSpaceの方がラクだった。

just

justとは、makeに影響を受けたtask runner。Makefileと違って、.PHONYが不要なので簡潔に書ける。

install方法はdocumentに書いてある(makeで良いのでは?と思った)

curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to ~/bin
export PATH="$PATH:$HOME/bin"

golang

runtime-toolsを動かすために、go 1.19が必要。apt-getだとversion指定できないのでwgetでversion指定してインストールした。

wget https://go.dev/dl/go1.19.13.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.19.13.linux-amd64.tar.gz
export PATH="$PATH:/usr/local/go/bin"

Rustc

CodeSpaceではすでにインストールされていて不要。EC2 amazon Linuxで動かすときはインストールが必要。

curl https://sh.rustup.rs -sSf | sh

GitHubとの接続

CodeSpaceでは不要。EC2 amazon Linuxで動かすとき必要だった。

sudo yum install -y git
ssh-keygen -t rsa -b 4096
cat ~/.ssh/id_rsa.pub
# 結果をgithub settingsに貼り付ける。
ssh -T git@github.com
git clone git@github.com:hogehoge/youki.git

その他のライブラリ

CodeSpaceでは不要。EC2 amazon Linuxで動かすとき必要だった。このあたりはYoukiのREADMEに書いてある。

sudo dnf install          \
      pkg-config            \
      systemd-devel         \
      elfutils-libelf-devel \
      libseccomp-devel      \
      clang-devel           \
      openssl-devel

# 以下のコマンドを実行してリンカーを入れる。
sudo dnf install glibc-static libstdc++-static

参考

github.com

containers.github.io

GraphQLの基礎知識

GraphQLを使っているエンジニアの人と話していて、自分がGraphQLをよくわかってないことに気づいたので、基本的な情報を調べてみた。

GraphQL

Metaが開発したOSSAPIを効率よく呼び出すために作られたクエリ言語。

SQLRDBを使うように、GraphQLはGraphQL用の特殊なDBを使うのかと思いがちだが、そういうわけでは無い。GraphQLもRDBなどを使う。なので、DB内の計算などを効率化するわけでは無い。backend側のコードを見通し良く効率的に書ける、というモチベーションが強い印象。

GraphQLの特徴

必要なデータを過不足なく、一度で取得できる(アンダーフェッチ、オーバーフェッチが無い)。そのため、データフェッチの効率性が高い。

基本的に、1つのデータを取得する際1つのエンドポイントを呼ぶだけで完結するので、APIの設計をシンプルにすることができる。クエリ言語を柔軟に記載することができる。

色々なサポート機能がある。例えば、Subscriptionsという機能を使って、リアルタイムデータを効率的に扱うことができる。

向いているサービス

ソシャゲ、eコマースなどの複雑なデータ構造を持つサービス、リアルタイムデータを扱うアプリケーションなどで真価を発揮する。

一方で、データ構造がシンプルなサービス、キャッシュが重要なサービスなどには向いていない。

データベース

特定のデータベースに依存していない。RDBでも、NoSqlでもOK。必ずしも、GraphQLを使うとREST APIよりもデータベースからfetchする回数が減るとは限らない。実装次第。

Resolver

スキーマとデータソースを結びつける役割を持つ。スキーマはあくまで定義のみで、Resolverが実際の操作を行なってくれる。

Subscriptions

GraphQLサーバーにデータ変化などの特定のイベントが生じるたびに、クライアントにデータ送信するPubSubのような仕組み。クライアントがサーバーにGraphQLのクエリ、クエリ変数を送信する。サーバーは、イベントがトリガーされたらクエリを実行する。

DataLoader

データ取得に使用される汎用的なツール。データ取得をbatch処理する機能と、結果をcacheする2つの機能がある。

batchは、データ取得のリクエストを一定時間待って、その間に行われたリクエストを統合してbatch処理してくれる。

cacheは、batchに登録されるkeyと、それに対応するvalueの組をメモ化する。

これらの機能を使うと、データアクセスの回数が減少する。

DataLoader導入前

SELECT item_id, amount FROM orders WHERE customer_id = "foobar";

SELECT name, description, price FROM items WHERE id = "hoge";
SELECT name, description, price FROM items WHERE id = "fuga";
SELECT name, description, price FROM items WHERE id = "piyo";

DataLoader導入後

SELECT
  name, description, price
FROM
  items
WHERE
  id IN ("hoge", "fuga", "piyo");

N+1問題

N件のデータレコードを取得した時、関連レコードの取得にN回のfetchを行なって、N+1回のfetchをしてしまうこと。DataLoaderで解決できる。

Relay

Metaが開発しているGraphQLクライアント。

  • 全てのObjectに対して、一意のGlobal IDを要求するので、データの再利用性が高い
  • リスト、ページネーションなどの際、Connectionという仕様を使うことで効果的に処理できる
  • Mutationを行いやすい
  • Fragment colocationによって、データフェッチが最適化される

Fragment colocation

コンポーネントとGraphQLフラグメントを密接に配置するデザインパターン

以下のような利点がある。

  • 明確な依存関係を示すことができる
  • 再利用性の向上
  • 効率的なデータフェッチ
  • 保守性の向上

GraphQLのメリット

クライアントはスキーマをもとに、欲しいフィールドだけをリクエストできる。

リレーションがある値は、一度のリクエストで取得できる。例えば、twitterのフォロー数、フォロワー数を取得する際、REST APIだと2回のリクエストが必要だが、GraphQLであれば一度で取得できる。

スキーマをもとに開発するので型の不一致が起きない。強い型付けが採用されている。

GraphQLのデメリット

クエリが複雑になる可能性・学習の難しさが挙げられる。

また、GraphQLのメリットを享受するのが難しい。GraphQLはMetaがReact・Relayと組み合わせて使い始めた。「RelayスタイルでないGraphQLに価値はない」と言うGraphQL有識者もいる。Relayを使わないにせよ、Fragment Colocationがわかってないとメリットを享受しづらい。

RESTでやっていたことが直感的にやり辛くなる側面もある。

gqlgen

GraphQLサーバーを構築するGoライブラリ。GraphQLスキーマ定義言語を使用してAPIを自動生成してくれる。

参考文献

https://sizu.me/adwd/posts/34mkeimfb06x

https://lyohe.github.io/blog/2021/12/16/reading-dataloader/#:~:text=graphql%2Fdataloader

runwasi関連の基礎知識

runwasiとは

ContainerdでWasmを動かすための共通platformを提供する。WASIをターゲットにcompileされたプログラムであれば、WASIに準拠したランタイム(wasmtimeなど)で実行できる。Rustで記述されている。

runcの代わりに、WebAssemblyランタイムを可能にするshim(異なる環境での互換性を保つソフトウェア層)であるrunwasiをcontainerdで動かすことが出来る。

Container毎に1つのshim processが存在するnormal modeと、すべてのshimを実行する単一のprocessで実行されるshared modeがある。

Wasm containerはコンパイルされたWasmバイトコードのみなので、Linuxコンテナよりも非常に軽量で起動が速く可搬性も高い。

Component

containerd-shim-[ wasmedge | wasmtime | wasmer ]-v1

wasmedge, wasmtime, wasmerでwasmを実行するためのcontainer shimである。このshimはPod毎に1つ実行される。

containerd-shim-[ wasmedge | wasmtime | wasmer ]d-v1

containerdをcontainerd-[wasmedge | wasmtime | wasmer]d sandbox daemonに接続するために使われるCLI。containerdがcontainer作成を要求すると、このshimバイナリが起動され、ホスト上で実行されているcontainerd-[wasmedge | wasmtime | wasmer]dサービスに接続する。このバイナリ自体がリクエストを処理することはなく、サンドボックスの作成・破壊のリクエストをcontainerd-[wasmedge | wasmtime | wasmer]d daemonに送信する。

containerd-[ wasmedge | wasmtime | wasmer ]d

Pod毎、Container毎ではなく、Node全体で1つのwasmホストを実行できるようにするsandbox manager。Containerが作成されると、このサービスはsandboxを作成するリクエストを受け取る。

Wasmedge | wasmtime | wasmer engineは、全てのsandbox間で共有化される。

Runtime

あるソフトウェアを実行するために必要な環境のこと。Javaプログラムを実行するにはJava Runtime環境(JRE)が必要、といった意味。Containerを実行するには、Container実行環境が必要である、ということ。Container runtimeが担っている役割は、以下のcontainerdの項目で記載している。

containerd

コンテナのライフサイクルを管理するコンテナランタイムで、Linuxwindowsのデーモンとして動作する。コンテナイメージの管理、実行中のコンテナの管理・監視、ストレージからのコンテナ実行、低レベルのストレージ、ネットワークアタッチメントなど、ホストシステムのコンテナライフサイクルを管理する(厳密にはruncがサポートする部分もある)。

元々containerdはDockerの一部だったが、DockerがOCIの仕様に基づいてDockerが分割された。その一部がcontainerdとしてCNCFに寄贈された。そのため、containerdはシステムの一部として動くことを想定してデザインされている。

コンテナ実行レイヤのデフォルトのランタイムはruncを利用している。containerdが高レベルランタイム、runcが低レベルランタイムである。他の高レイヤランタイムにはcri-o, rktなどがあり、低レイヤランタイムには、gVisor, Nabla containersなどがある。

ctrコマンドで操作する。

runc

runcがカーネル機能を使ってコンテナ作成を行う。cgroups, namespaces, pivot_rootなどを操って、プロセスの隔離環境を作成して、コンテナを実行する。OCI runtime specificationに準拠している。

OCI Runtime specification

以下のライフサイクルに従う。runcは準拠している。

1 隔離環境の作成

2 コンテナ実行

3 プロセスのKill

4 コンテナの削除

CRI

criはkubernetesでcontainerdを動かすためのpluginである。cri pluginはcontainerdを介してコンテナライフサイクルなどを管理する。一方で、cri pluginがCNIを介してpod networkも管理する。

WebAssembly

Webブラウザ上でネイティブコード(CPUが直接実行可能な命令セット)に近い実行速度で高速に実行できるバイナリフォーマット。従来のJavaScriptソースコード形式で提供されていたのと違って、低レベルのバイナリで提供されるので、高速に実行できる。特定のプロセッサに依存しない。

厳密には、stack virtual machineのためのバイナリ形式の命令セット。

ちなみに、WebAssembly単体ではメモリ確保ができないため、外部プログラムが確保したメモリを渡す必要がある。このメモリは線形メモリと呼ばれる。

WASI (WebAssembly System interface)

OSのAPIを抽象化するための業界標準仕様のこと。Webブラウザだけでなく、WindowsLinuxなどのOS上にWebAssembly runtimeを配置して実行できるようになった。WebAssemblyがファイルやネットワーク、メモリなどのシステムリソースへ安全にアクセスするためのAPI標準仕様。

WASIは、特定のOSに依存した設計にしないために、System call (OS操作)に依存せず、User Land(CPU・メモリーの直接計算)上での処理だけが可能になっている。その欠点を補うために、他のプログラム(runtime)と連携して動くようになっている。WebAssemblyをwasmtimeでファイル操作をしようとすると、ファイル操作のみwasmtimeが実行する、といった仕組み。

WASIは、外部プログラム(runtime)がWebAssemblyを実行する標準仕様を定義している。WebAssemblyをどう実行するか、WebAssemblyにExportする関数の仕様などである。

WASI preview1でファイル操作規格が決められ、WASI preview2でネットワークに関する規格が追加される予定。

参考にした記事

https://qiita.com/wbcchsyn/items/153327b0946358694061

https://github.com/containerd/runwasi

https://bokuweb.github.io/undefined/articles/20230224.html

OpenTelemetry 101

OpenTelemetryとは

テレメトリデータを収集してバックエンドプラットフォームに転送するための方法を標準化してくれるフレームワーク。OpenCensus・OpenTracingという2つのソフトウェアがマージされて誕生した。OpenTelemetryは、SDKAPI・ツールの総称であり、バックエンドを担うものでは無い。

分散システム上のアプリケーション・ホスト環境のトラブルシューティングデバッグに必要なデータを収集することが可能。複雑なシステムのトラブルシューティングを容易にしてくれる。

CNCFでk8sに次いで、2番目に活発なProjectだとか。

OpenTelemetryのメリット

  • 一貫性
    • ベンダー(AWS, GCPなど)・言語に依存せず共通化した方法が提供されている
    • 様々なバックエンドをサポートしている(OTLPに対応していることが前提)
      • Jaeger・Zipkin・Prometheusなど
  • 互換性
    • OpenTracing, OpenCensusのどちらとも互換性がある
  • 拡張性
    • 用途に応じた拡張性能が高い

OpenTracing

OpenTracingは、トレーシングを標準化するAPIを提供することを目指していた。トレーシングに特化しており、用途が限られているという課題があった。

OpenCensus

OpenCensusはGoogleが社内のトレーシングプラットフォームをベースに開発した。収集したメトリクスを任意のバックエンドに転送することをサポートしていた。しかし、OpenCensusをコードに組み込むAPIが提供されていなかった。

可観測性(オブザーバビリティ)とは

「監視」と比較されることが多い。監視には積極的な意味合いが強く、システムに設定された閾値を超えたらアラートを鳴らす。一方で可観測性は、システム内の状態を常に包括的に把握することを指す。システムの出力からシステム状態を測定するために利用する。従来の監視は、既存の問題にしか対応できないが、分散システムにおいては、どのような問題が発生するか、事前に予測するのがとても難しい。可観測性は、予測不可能性にアプローチする最適解となり得る。

可観測性が誕生した背景

前述したように、分散システムを適切に監視できる手法が求められていた。従来の監視だと以下のような問題に対処できなかった。 - マイクロサービスに分散したログをリクエスト単位で追いづらい - エラー原因箇所の同定の難しさ - レスポンス遅延の原因把握

テレメトリーデータとは

テレメトリーデータは、Log, Metrics, Tracesの3つに分類される。それに加えて、OpenTelemetry特有のBaggageというテレメトリーが存在する。

Log

  • 発生したイベントを各時点で記録するテキスト。プレーンテキスト・構造化・非構造化データがある。
I, [2021-02-23T13:26:23.505892 #22473]  INFO -- : [6459ffe1-ea53-4044-aaa3-bf902868f730] Started GET "/" for ::1 at 2021-02-23 13:26:23 -0800

Metrics

  • 一定の期間にわたって測定された値を指す。タイムスタンプ・イベント名・イベント値などの属性が記録される。構造化データであることが普通
    • CPU使用率、トランザクション量など
    • よりメタ的には、Counter, Gauge, Histogramとして計測する
      • Counter
        • リクエスト数など、測定可能かつ常に増加するMetrics
      • Gauge
        • キューイング数など、時間によって増減するMetrics
      • Histogram
        • 一定期間における値の分布に関するMetrics

Traces

  • 分散システムでのリクエスト処理経路をエンドツーエンドで表すデータ
    • 例:リクエストの処理フローに関するデータ
      • システムがリクエストを処理した場合に、トレースID、スパンID、操作名、開始・終了タイムスタンプ、などの重要情報がエンコードされる
  • トレースを利用してMetricsを作成することも可能。RED(ratio, error, time)
{
  "name": "hello",
  "context": {
    "trace_id": "0x5b8aa5a2d2c872e8321cf37308d69df2",
    "span_id": "0x051581bf3cb55c13"
  },
  "parent_id": null,
  "start_time": "2022-04-29T18:52:58.114201Z",
  "end_time": "2022-04-29T18:52:58.114687Z",
  "attributes": {
    "http.route": "some_route1"
  },
  "events": [
    {
      "name": "Guten Tag!",
      "timestamp": "2022-04-29T18:52:58.114561Z",
      "attributes": {
        "event_attributes": 1
      }
    }
  ]
}

Baggages

  • spanを介して伝達されるcontext情報のこと。key-value storeとして機能する
  • context, propagationという仕組みで、分散トレーシングを効率的に実現する
    • Context
      • service間でのシグナルの連携を把握することができるObject
      • serviceAが serviceBを呼んだ場合に、contextに存在するtrace IDをもとにsignalを後から分析しやすくする
    • Propagation

スクリーンショット 2024-02-04 14.29.40.png

OpenTelemetryの機能

システム構成

スクリーンショット 2024-02-04 10.45.55.png

言語別のOpenTelemetry API

言語別のOpenTelemetry SDK

  • APIとエクスポーターの間の橋渡しとなる

OpenTelemetry Protocol (OTLP)

OpenTelemetry Collector (OTLC)

  • ベンダーに依存することなくテレメトリーデータをアプリケーション、インフラから受信して、監視バックエンドにデータ連携する。Receiver, Processor, Exporterの3要素から構成される
    • Receiver
      • OpenTelemetryを受け取る。いろいろなデータを受け取ることができる
    • Processor
      • 属性に応じて送信先を変えたり、指標を集積してカウンタ値を作成することが可能
        • production・staging, host, serviceなど
    • Exporter
      • HTTP, gRPCなどで送信。あくまでOpenTelemetryはバックエンドに送信するだけで、データ保存はPrometheusなどの下流が担う

Auto instrumentation (kubernetes operator)

  • 従来のトレーシングでは、観測データを出力する部分をコーディングする必要があったが、OpenTelemetryでは、コードを記載しなくてもinstrumentationを行うことができる
    • OpenTelemetry Operatorをk8s上にdeployした上でPodにannotationを追加すると、そのPod sidecarからトレーシングデータを自動取得できるようになる
  • Auto instrumentationでカバーできないデータは、自分で実装する必要がある
    • どのデータをauto instrumentationするのかは、yaml fileで指定可能

FaaS

AWS LambdaのようなFunctions as a Service (FaaS)でも、instrumentationは機能する。

まとめ

OpenTelemetryの各種仕様がfixしてきているので、今後さらに盛り上がることが期待される。

promptfooをkubernetesで動かすツールを作った

LLMの勢いがすごいです。そろそろエンジニアも廃業かと思いきや、なかなか僕の仕事は無くなりません。

昨今LLM自体の開発が進む一方で、LLMOpsのツールはそんなに発展しないなぁと思っていましたが、Gunosyさんのブログで、promptfooというすごいツールを知りました。

promptfooの出来栄えに感動したので、promptfooをkubernetesで動かせるツールを作りました。

要約

kubernetes上でpromptfooを定期実行(cronjob)するツールを作りました。PromptFooConfigというCRDに、prompt, openai_key, scheduleを設定すれば、cronjobが実行されます。

github.com

この記事で、promptfoo自体の紹介はしないので、気になる方はGunosyさんのblogを読んでください。

モチベーション

LLMをproductionで使う時の課題として、時間が経つことで、当初想定していたoutputを得られなくなってしまうことが挙げられます。例えば、以下のようなケースがあると思います。

  • コード、promptの変更によって、想定されるoutputを入手できなくなる
  • OpenAI APIのsilent updateによって、想定されるoutputがいつの間にか手に入らなくなっていた

前者の課題については、promptfooをCI/CDに組み込むことで解決できます。

しかし、後者の課題は、promptfoo単体では解決できません。定期実行するための機能を備えていないからです。

promptfooはCLIツールなので、定期実行できる環境さえあればどこでも実行できますが、kubernetes上でcronjobとして実行することができれば、1つの解決策になるだろうと考えました。

使い方

このsampleを参考にしていただければ、問題なく使えるはずです。

github.com

spec fieldに、実行したいyamlの内容をprompt, cronjobの設定をschedule, open_ai_api_keyをopenaiapikeyを書きます。

spec:
  prompt: |

 .....
  schedule: "*/10 * * * *"
  openaiapikey: "sk-*****"

このCRDを作成すると、対応するconfigmap, cronjobが作成されます。openAI APIによってpromptのoutputが変化していないか、定時観測することができます。

課題

あくまでただの趣味で作成したツールなので、課題はたくさんあります。例えば、以下のような課題があります。

  • secretでkeyを渡せない
  • promptfooのファイルをstring型で渡す必要がある
  • pod containerの内容が洗練されていない
  • cronjobの結果を管理する方法がない

また、そもそも論として「自分でcronjobを書けばいいのでは?CRD要らなくない?」「サービス全体の整合性をチェックしたいが、promptfooだと複雑なことができなくない?」という点も、課題として挙げられます。その通りだと思います。趣味開発なのでいいか、と開き直っていますが、次はもっと洗練されたOSSを作りたいですね。

終わり

Pensieve: An embedding feature platformの要約

要約

近年では、特徴量自体もmanualではなく、機械学習手法で作成されることが主流になってきている。

LinkedInにおいて、embedding feature platformとして使われているPensieveについて紹介する。Pensieveは教師あり機械学習として学習され、潜在表現を利用したランクモデルに使われる。Talent solution・Careerで利用されている。

Introduction

LinkedIn Talent solution and career teamの目標は、仕事を探している人にマッチするpostを見つけさせることである。

我々は、embedding featureの計算に時間がかかることを解決するために、Pensieveというembedding feature platformを導入した。

Pensieve platform

1. Offline training pipeline

特徴量を作成する(MLは使わない?)。

2. Pensieve modeling

1の特徴量から、低次元のembedding表現を出力するモデルをtrainingする。

3. Embedding serving framework

学習済みのモデルをembedding servingとして提供する。

Pensieve model

model input

LinkedIn knowledge gparhがInputに用いられる。TItle, skill, company, geolocations, resumeなどがsparse categorical featuresとして利用される。

weighted bipartite graphにおいて、u(user), v(job), e(co-occurrence)を使う。

network architecture

Pensieve modelはDeep structured semantic modelにinspireされたDNNである。

member embedding、job embeddingを利用して、eを学習させる。

MLPにおいて、前の層で使った特徴量を使うことで性能を上げている(skip connection)。diagram-depicting-skip-connections-in-the-architecture

model deployment

学習が終わったら、member側のモデル、job側のモデルとして、それぞれdeployされる。

Nearline embedding serving framework

system architecture

効率的に出力できることと、実験を高速に回すことを意識して設計されている。

Apache BeamとSamzaを使っている。

job postingが作成・更新されると、Beam pipelineが動いて、modelが特徴量を生成して、Key value storeとKafka topicにpublishされる。

 

flowchart-showing-the-stages-of-feature-standardization-processors

system optimization

heap sizeを増やしたり、並列処理数を増やすことで対応している。

multi-data-center strategy

計算を全データセンターで行うことで単一障害点となることを防ぐ。

diagram-showing-the-improved-multi-data-center-strategy