E2E Webサーバー構築
文章でばかり書いていたので練習も兼ねてやっていきます
7/20 ユーザのアイコンの色をランダムに割り当てる
要件
- アイコンの文字は名前の1文字目を表示する
- 色は5色からランダムで設定する
- 色はユーザーに固有のもので、再読み込みなどで変更されない。
やり方
- 登録日時を文字コードにし、ランダムのhsl( 色相(Hue)、彩度(Saturation)、輝度(Lightness))で色を割り当てる、濃淡は調整可能なのでデザイナーと擦り合わせる(今回は hを登録日時の文字コード sが30 lが60)
参考
Compute an arbitrary color for user avatar starting from their username (with Javascript).
// Import import React, { VFC, useMemo } from 'react'; import styled from 'styled-components'; import { managementColors } from '../../../../themes/colors'; // Types type ContainerProps = { item: MemberListItem }; type Props = { item: MemberListItem; icon: string; iconColor: string }; export interface MemberListItem { id: string; icon?: string; name: string; email: string; createdAt: string; } interface IconProps { color: string; } // Style // 省略 const Icon = styled('div')<IconProps>` width: 32px; height: 32px; border-radius: 50%; margin-right: 12px; background-color: ${(props) => props.color}; color: white; display: flex; justify-content: center; align-items: center; font-size: 14px; font-weight: 700; `; // DOM const Component: VFC<Props> = ({ item, icon, iconColor }) => { return ( <> <Icon color={iconColor}>{icon}</Icon> <div> {item.name} <br /> <Email>{item.email}</Email> </div> </> ); }; // Container const Container: VFC<ContainerProps> = ({ item }) => { const dateToHslColor = (dateStr: string, s: number, l: number): string => { let hash = 0; Array.from(dateStr).forEach((char) => { // eslint-disable-next-line no-bitwise hash = char.charCodeAt(0) + ((hash << 5) - hash); }); const h = hash % 360; return `hsl(${h}, ${s}%, ${l}%)`; }; const getInitial = (name: string) => { return name[0]; }; const getRandomColor = useMemo(() => { return dateToHslColor(item.createdAt, 30, 60); }, [item.createdAt]); const icon = getInitial(item.name); const iconColor = getRandomColor; return <Component {...{ item, icon, iconColor }} />; }; export { Container as MemberListCell };
dateToHslColor(dateStr: string, s: number, l: number): string
この関数は、渡された日付文字列をベースに一意の色を生成します。その色はHSL(Hue, Saturation, Lightness)形式で表されます。
具体的には、日付文字列からハッシュ値を計算し、そのハッシュ値を360で割った余りを色相(Hue)として使用しています。なぜ360で割るのかというと、色相は通常0から360の範囲で表されるからです。関数の2つ目と3つ目の引数は彩度(Saturation)と輝度(Lightness)を指定するために使われます。
getInitial(name: string)
この関数は、名前の最初の文字(イニシャル)を返します。ここでは、アイコン上に表示する文字として使用されます。
getRandomColor
この関数は、useMemoフックを使って生成された色の値をメモ化(キャッシュ)します。ここでのキーは
item.createdAt
で、この値が変わると新しい色が生成されます。
これらの関数を組み合わせることで、各ユーザーが作成された日時に基づいて異なる色のアイコンを持つようになります。この方法はユーザーが視覚的に区別しやすくなり、特定のユーザーを一目で認識できるようになります。
コメント
元々、私が考えた実装では、ユーザ名を文字コードにして合計値を指定された色の数で割り、その余りから色を選択するロジックにしていました。
ですが、実装後に、これでは同姓同名では一緒の色になってしまうのと、当初の色は5色だったんですが、5色では被る人が多く出てきてしまうので、登録日時にし、色も5色ではなく、hslのslのみを固定して、hをcreateAtの文字コードによって変化するようにすることで、対応しました。
7/15 TypeScript デコレータ
デコレータ
メタプログラミングに役にたつ
他の開発者が使いやすい道具を提供することに向いている
使うための準備
tsconfig.jsonの設定
"target": "es6" "experimentalDecorators": true
- 上記の二つを設定する必要がある
クラスデコレータ
function Logger(constructor: Function) { console.log("ログ出力中…"); console.log(constructor); } @Logger class Person { name = "Max"; constructor() { console.log("Personオブジェクトを作成中…"); } } const pers = new Person(); console.log(pers);
- Loggerというデコレータを定義している
- 大文字から始めなければいけないわけではないが、大文字から定義されていることが多い
- Personクラスの前に@Loggerとしてデコレータを定義
- 大文字から始めなければいけないわけではないが、大文字から定義されていることが多い
重要なこととして、デコレータが呼び出されるのはインスタンス化された時ではなく、@Loggerのタイミングで呼び出されるということ
出力結果
ログ出力中… app.js:10 class Person { constructor() { this.name = "Max"; console.log("Personオブジェクトを作成中…"); } } app.js:15 Personオブジェクトを作成中… app.js:22 Person {name: 'Max'}
- ログ出力中とデコレータで呼び出されているコンストラクタ関数が、Personがインスタンス化された際に呼び出される出力(Personオブジェクトを作成中、Person {name: 'Max'})より前に呼び出されている
デコレータファクトリ
デコレータ関数を何かに割り当てるときにデコレータをカスタマイズできるようにするもの
function Logger() { return function (constructor: Function) { console.log("ログ出力中…"); console.log(constructor); }; } @Logger() class Person { name = "Max"; constructor() { console.log("Personオブジェクトを作成中…"); } } const pers = new Person(); console.log(pers);
- Loggerのデコレータの中で無名関数を定義する
- これで新しい関数を返す関数を定義することができた
- これをデコレータとして適応するために、Logger()と関数として実行する必要がある
使い方の例
function Logger(logString: string) { return function (constructor: Function) { console.log(logString); console.log(constructor); }; } @Logger("ログ出力中 - PERSON") class Person { name = "Max"; constructor() { console.log("Personオブジェクトを作成中…"); } } const pers = new Person(); console.log(pers);
- ファクトリ関数を実行したときに、その中の無名関数内で使う値をカスタマイズできるようになった
- @Logger("ログ出力中 - PERSON")では関数を実行しているのではなく、デコレータ関数を返す関数を実行している
複数のデコレータを実行順序
function Logger(logString: string) { console.log("LOGGER ファクトリ"); return function (constructor: Function) { console.log(logString); console.log(constructor); }; } function Template(template: string) { console.log("TEMPLATE ファクトリ"); return function (constructor: Function) { console.log(template); }; } @Logger("ログ出力中 - PERSON") @Template("テンプレートを出力中") class Person { name = "Max"; constructor() { console.log("Personオブジェクトを作成中…"); } } const pers = new Person(); console.log(pers);
- Templateファクトリ関数を追加して、@Templateデコレータを@Loggerデコレータの後に実行した
- この時の実行順序を確認します
LOGGER ファクトリ app.js:16 TEMPLATE ファクトリ app.js:18 テンプレートを出力中 app.js:11 ログ出力中 - PERSON
- ファクトリ関数としては、基本通りに上から順番に実行されている
- 注目すべきは、ファクトリ関数の内部の無名関数の実行順序は、テンプレートのほうが先に実行されていることです
デコレータが複数定義されていたら、デコレータの内部の実行順序としては、したから順番に出力されます。
コメント
NestJSでデコレータをなんとなく触っていて、よくわかっていなかったので、少しずつ紐解いていけてる感じです。
7/14 PostgreSQLが接続できなくなった対処
どのタイミングで発生したか
- 普段dockerで開発しており、dockerを使用せずに、npm run devで立ち上げて動作確認したいものがあったため、久しぶりにnpm run dev をしたところ、PostgreSQLに接続できないエラーで立ち上げられなかった
なぜ発生したか
- PostgreSQLは「共有メモリ」を使用する
- 共有メモリは複数のプロセス(ここではPostgreSQLの異なる部分や、別のソフトウェア)が同時にアクセスできるメモリ領域です
- PostgreSQLは、データベースの操作を効率的に行うため、この共有メモリを必要とします。
- 共有メモリの総量は限られており、その上限はオペレーティングシステムによって設定されます
- その設定値を「SHMALL」パラメータと言います
- 今回のエラーは、PostgreSQLが必要とする共有メモリの量が、オペレーティングシステムが設定しているSHMALLパラメータの上限を超えてしまったことを示しています
- PostgreSQLが必要とするメモリがOSに割り当てられていないため、データベースの初期化ができていないということ
- その設定値を「SHMALL」パラメータと言います
- 共有メモリの総量は限られており、その上限はオペレーティングシステムによって設定されます
- PostgreSQLは、データベースの操作を効率的に行うため、この共有メモリを必要とします。
- 共有メモリは複数のプロセス(ここではPostgreSQLの異なる部分や、別のソフトウェア)が同時にアクセスできるメモリ領域です
用語の整理
- カーネルパラメータ: カーネルとは、コンピュータのオペレーティングシステムの中心部分で、ハードウェアとソフトウェアの間で仲介役を果たします。カーネルパラメータは、このカーネルの動作を制御するための設定値のことを指します。
- 共有メモリ: コンピュータには、複数のプロセス(基本的には個々のプログラムの実行単位)があります。これらのプロセスがデータを共有するために使うメモリ領域のことを共有メモリと呼びます。共有メモリを使うと、プロセス間で大量のデータを効率的にやりとりできます。
- SHMALL: これは、システム全体で使用可能な共有メモリの最大値を制御するカーネルパラメータです。SHMALLの値は、通常はシステムの物理メモリの一部として設定されます。この値を増やすと、プロセスが共有メモリをより多く使えるようになります。しかし、その分、他のプロセスが使えるメモリは減少するため、この値を適切に設定することが重要です。
使用コマンド
現在のSHMALL値を確認
sysctl -a | grep shm
私の設定されていた値
kern.sysv.shmmax: 4194304 kern.sysv.shmmin: 1 kern.sysv.shmmni: 32 kern.sysv.shmseg: 8 kern.sysv.shmall: 1024 security.mac.posixshm_enforce: 1 security.mac.sysvshm_enforce: 1
このファイルはなければ新規作成する必要があります
sudo nano /etc/sysctl.conf
以下2行を設定します
kern.sysv.shmmax=設定したい値 kern.sysv.shmall=設定したい値
- 値は自分の設定したい値を入力
編集が完了したら Ctrl+X
を押し、 Y
を押してファイルを保存し、最後に Enter
を押して終了します
今回のセッション中のみ、こちらのコマンドで上記の設定を反映させられます
sudo sysctl -w kern.sysv.shmmax=1073741824 sudo sysctl -w kern.sysv.shmall=2097152
こちらのコマンドで反映されているかの確認
sysctl -a | grep kern.sysv.shm
再起動してもこの設定が反映されるようにする
なぜ反映されないか
セキュリティ上の制約から、macOSは /etc/sysctl.conf
の値を起動時に読み込むことが許されていないから
解決法
macOSのlaunchdを使用して、起動時にsysctlコマンドを実行する.plistファイルを作成する
手順
/Library/LaunchDaemons
ディレクトリに sysctl.plist
ファイルを作成
sudo nano /Library/LaunchDaemons/sysctl.plist
sysctl.plist
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>local.sysctl</string> <key>ProgramArguments</key> <array> <string>/usr/sbin/sysctl</string> <string>-w</string> <string>kern.sysv.shmmax=1073741824</string> <string>kern.sysv.shmall=2097152</string> </array> <key>RunAtLoad</key> <true/> <key>KeepAlive</key> <false/> </dict> </plist>
このXMLファイルは、macOSの起動時に特定のコマンドを実行するための設定を持つ「プロパティリスト」(plist)ファイルです。具体的には次のような動作を指定しています
Label
キー: このジョブの一意の識別子を指定します。ここではlocal.sysctl
となっています。ProgramArguments
キー: 実行するコマンドとその引数を指定します。ここでは、/usr/sbin/sysctl
コマンドを、w kern.sysv.shmmax=1073741824
およびkern.sysv.shmall=2097152
という引数で実行するように指定しています。これにより、システムの共有メモリに関する設定を変更しています。RunAtLoad
キー: このジョブをシステム起動時に実行するかどうかを指定します。true
と指定されているので、システム起動時に上述のコマンドが実行されます。KeepAlive
キー: ジョブが何らかの原因で終了した場合に自動的に再起動するかどうかを指定します。ここではfalse
と指定されているので、一度実行されたジョブは再起動されません。
このplistファイルを/Library/LaunchDaemons/
ディレクトリに保存し、適切な権限を設定することで、システム起動時にこれらの設定が自動的に適用されるようになります。
作成したファイルのパーミッションを設定
sudo chown root /Library/LaunchDaemons/sysctl.plist sudo chmod 644 /Library/LaunchDaemons/sysctl.plist
sudo chown root /Library/LaunchDaemons/sysctl.plist
:このコマンドは/Library/LaunchDaemons/sysctl.plist
ファイルの所有者をrootに変更します。chown
はUNIX系OSでファイルの所有者やグループを変更するためのコマンドです。root
は最高権限を持つユーザーで、このファイルを安全に管理するためにはrootユーザーの所有とすることが一般的です。sudo chmod 644 /Library/LaunchDaemons/sysctl.plist
:このコマンドは/Library/LaunchDaemons/sysctl.plist
ファイルのパーミッション(許可設定)を変更します。具体的には、所有者(この場合はroot)に対して読み書きのパーミッションを、それ以外のユーザーに対しては読み取りのみのパーミッションを与えます。chmod
はUNIX系OSでファイルのパーミッションを変更するためのコマンドで、644
は一般的なパーミッション設定の一つです。
plistをロード
sudo launchctl load -w /Library/LaunchDaemons/sysctl.plist
- このコマンドは
launchctl
ツールを使用して、/Library/LaunchDaemons/sysctl.plist
というLaunchDaemonをロード(起動)します。-w
オプションは、plistファイルの設定に関わらず、このDaemonを永続的に有効化することを指示します。これにより、このDaemonはシステム起動時に自動的にロードされるようになります。
再起動して設定が反映されているか確認
sysctl -a | grep kern.sysv.shm
解決しました
7/10 AzureFunctionsが複数exportされた関数を特定するためのentryPointを明示的に指定する
なぜやるか
Azure Functionsでは、JavaScriptやTypeScriptで書かれた関数をエクスポートするとき、その関数が実行されるべきものであることを明示的に示す必要があります。これは、Azure Functions ホストが関数アプリ内のファイルからどの関数を実行するかを判断するための情報です。 もしエクスポートされた関数が一つだけであれば、Azure Functions ホストはその関数をデフォルトで実行します。しかし、複数の関数がエクスポートされている場合、ホストはどの関数を実行すべきか判断できません。そのため、特定の関数を実行するように指示するために、entryPoint プロパティを使用して関数名を指定します。
- AzureFunctionsのentryPointを指定していないことで、どの関数を実行すればいいのか、特定できずに実行されなかった
- これまでは、同時刻の定期実行タスクがなかったので、問題なかった
- 複数の関数を同時刻に定期実行する場合は、entryPointを明示的に指定する必要がある
Testディレクトリにfunction.jsonとindex.tsがあると仮定します
function.json
{ "bindings": [ { "name": "Test", "type": "timerTrigger", "direction": "in", "schedule": "0 30 8 * * *" } ], "scriptFile": "../built/functions/Test/index.js", "entryPoint": "default" }
- entryPointを指定
index.ts
// eslint-disable-next-line import/no-unresolved import { AzureFunction } from '@azure/functions'; const fn: AzureFunction = async function 関数名( context ) { try { await 定期実行したい関数() ); } catch (err) { context.log.error(err); } }; export default fn;
azure-functionsディレクトリに、function.jsonとindex.tsがあります
function.json
{ "bindings": [ { "name": "__function__", "type": "timerTrigger", "direction": "in", "schedule": "0 */1 * * * *" // fix this } ], "scriptFile": "../built/functions/__function__/index.js", "entryPoint": "default" }
- entryPointを指定
index.ts
import { AzureFunction } from "@azure/functions"; const fn: AzureFunction = async function __function__(context) { try { // run task here } catch (err) { context.log.error(err); } }; // Do not change this line. Azure Functions need to one default export function. export default fn;
7/6 GA 主要指標の定義と意味
Googleアナリティクスでは、多くの意表があるが、中でも、「ユーザー>概要」に表示される指標が重量指標である。
- 重要指標は多くのレポートで使われる
- 重要指標は設定によって多少変化する
- ユーザー分析のオンオフで変化する
- 切り替えはいつでもできるので適宜変更すれば良い
- ユーザー分析のオンオフで変化する
主要指標
ユーザー
ユニークなブラウザの訪問者数。
一定期間中の同一訪問者の訪問はマージされ、ダブルカウントされない。
セッション
前提として、訪問=セッション。
ユーザーのひと続きのページの閲覧。
ユーザーがページ閲覧開始した時点から訪問が開始する。
ユーザーの操作がないまま30分が経過したら、訪問は終了する。
30分経過しなくても以下が発生すると新しい訪問が開始する。
新規ユーザー
サイトに初めて訪問したユーザーのセッションが「新規ユーザー」です。
ページビュー数
ページが表示された延べ回数です。
セッション中に同じページが表示された場合、ブラウザのリロード等で同じページが連続して閲覧された場合でも構わずカウントする。
ユーザーあたりのセッション
「セッション」を「ユーザー」で割ったものが「ユーザーあたりのセッション」です。
新規セッション率
全セッションにおける「新規ユーザー(=新規セッション)」の割合をパーセンテージであらわしたもの。
ページ/セッション
ページビュー数をセッションで割ったもの。
平均セッション時間
セッション毎のサイト滞在時間の平均。
セッション開始時点のタイムスタンプと、セッションの最終ヒットのタイムスタンプを差し引いて求める。
Aさん10分 + Bさん20分 / 2(セッション数) = 15分(平均セッション率)
直帰率
全セッションに対する「直帰セッション」の割合。
「直帰セッション」とは、1セッションで1ページビューしか閲覧されなかったセッションのこと。
コンバージョン率
「ユーザー>概要」にはないが、主要指標である。
全セッションにおける、コンバージョンが発生したセッションの割合で計算される。
eコマースのコンバージョン率
「ユーザー>概要」にはないが、主要指標である。
全セッションにおける、トランザクションが発生した回数の割合で計算される。