Shogo's Blog

Jul 16, 2024 - 3 minute read - mysql

MySQL8.0で近傍検索

「Redis、PostgreSQL、MySQLで近傍検索」を公開した当時は、 MySQL 5.7 で検証を行いました。 「MySQL 8.0 ではGIS関連も強化されているぞ!」という話を聞いていたので、MySQL 8.0でも検証してみます。 (MySQL 8.0リリースから何年経ってるんだよというツッコミは置いておく) 検証環境 検証環境はDocker上で起動しました。 バージョンは2024-07-16時点で8.0系列最新の8.0.38です。 mysql> SELECT VERSION(); +-----------+ | VERSION() | +-----------+ | 8.0.38 | +-----------+ 1 row in set (0.01 sec) 最新LTSの8.4.1がリリースされていますが、Amazon RDSでは未サポートです。 マネージドサービスを利用したいので、ひとまず8.0で検証を行います。 テーブルの準備 スキーマ定義でSRIDを指定できるようになりました。 何が嬉しいかというと MySQL が「地球は丸い」ということを理解してくれます! CREATE DATABASE test; USE test; CREATE TABLE IF NOT EXISTS `geotable` ( `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, `name` VARCHAR (255) NOT NULL, `geom` POINT NOT NULL SRID 4326, PRIMARY KEY (`id`), SPATIAL KEY `geom` (`geom`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; SRIDは省略可能ですが、インデックスを利用するためには必須です。 忘れずに指定しましょう。

Jun 30, 2024 - 2 minute read - github

GitHub Artifact Attestations を試してみた

GitHub Artifact Attestations が正式リリースされました。 GitHub Artifact Attestations is generally available ベータ公開 から2カ月足らずでの正式リリースです。 早いですね。それだけ力を入れているということでしょうか。 なんか難しい単語(attestations とか provenanceとか)が並んでいて正直良くわからんのですが、とりあえず試してみましょう。 署名してみる 「S3からファイルを落とすだけのツールを作った」で作成した、 s3cli-mini で試してみます。 s3cli-miniのリリース処理は GitHub Actions 上で実行しています。 ビルドしたバイナリーは以下のリンクからダウンロード可能です。 v0.0.15 - GitHub Releases しかしこのバイナリーは本当に GitHub Actions 上でビルドしたものでしょうか? リリースバイナリーはあとから差し替えも可能です。僕が悪意のあるコードを密かに忍ばせ、再ビルドし、上書きしているかもしれません。 僕に悪意がなくとも、僕がうっかりクレデンシャルを流出させてしまい、悪意のある第三者によって上書きされるかもしれません。 そんな心配を解決するのが Artifact Attestations です。 使い方は簡単で、リリースのワークフローに actions/attest-build-provenance を呼ぶステップを追加するだけです。 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b5b65f4..31f2d6c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,6 +10,7 @@ jobs: permissions: contents: write id-token: write + attestations: write runs-on: ubuntu-latest steps: @@ -35,3 +36,14 @@ jobs: args: release --clean env: GITHUB_TOKEN: ${{ secrets.

Jun 25, 2024 - 1 minute read - go golang

go-retry v2 リリースのお知らせ

shogo82148/go-retry は指数的バックオフを行ってくれるライブラリです。 Goで指数的バックオフをやってくれるgo-retryを書いた v2へメジャーバージョンアップをリリースしました。 破壊的な変更が入っているのでお知らせします。 エラーのTemporaryメソッドをチェックしなくなりました DoメソッドやDoValue関数のエラーハンドリングの挙動が変更になります。 v1 までの挙動 v1のDoメソッドは戻り値のエラーが Temporary メソッドを実装している場合、 その実行結果によってリトライの挙動を変えていました。 Temporary メソッドが true を返した場合はリトライを続行します。 false を返した場合はそれ以上のリトライは無意味と判断し、リトライ処理を中断します。 この挙動は net.Error を参考に実装したものでした。 ネットワーク処理中に回復不可能なエラーが発生した場合に、リトライが自動的に中断されることを期待して実装しました。 v2 からの挙動 しかし、残念ながら Go 1.18 から Temporary メソッドは非推奨になってしまいました。 Temporaryの意味はあいまいで、利用者の意図した通りの結果を得られるとは限りません。 実際に「リトライして欲しいときにリトライしてくれない」という報告を受けました。 そういうわけで、エラーのTemporaryメソッドの戻り値は信用せず、呼ばないよう変更しました。 v1からv2への移行 多くのケースでは、インポートパスを github.com/shogo82148/go-retry から github.com/shogo82148/go-retry/v2 に変更するだけでOKなはずです。 万が一v1と同じ挙動を維持したい場合は、以下のように書き換えてください。 // v1 code policy.Do(func() error { return DoSomething() }) // v2 code policy.Do(func() error { err := DoSomething() interface temporary { Temporary() bool } var tmp temporary if errors.As(err, &tmp) && !

Jun 23, 2024 - 3 minute read - typescript

npmとjsrにパッケージを公開してみた (認証バッジ付)

先日、同時実行数を制限しながら並行実行する関数を書きました。 TypeScriptで同時実行数を制限しながら並行実行する 便利関数を作ったら他のプロジェクトから参照したいですよね。 そこでパッケージレジストリに登録してみました。 正直コピペで実装で十分なのでは?という分量ですが、パッケージ公開の練習です。 ソースコードを準備する まずは公開するソースコードを準備していきましょう。 ソースコードはGitHubで公開しました。 shogo82148/limit-concurrency - GitHub Denoの開発環境を整える TypeScriptの開発環境を整えたいのですが、Node+TypeScriptの組み合わせはプロジェクトの立ち上げは意外と面倒です。 そこで今回は Deno を使ってみることにしました。 DenoはTypeScriptの実行ランタイムとして開発されており、特別な設定なしでTypeScriptを実行できます。 Brewでインストールしました。 brew install deno 僕は最近エディターには VS Code を使っているので、Deno用の拡張機能をインストールしました。 denoland/vscode_deno インストールしただけでは有効化されません。 ワークスペースの設定ファイル .vscode/settings.json を編集して、明示的に有効化します。 { "deno.enable": true, "deno.lint": true, "editor.formatOnSave": true, "editor.defaultFormatter": "denoland.vscode-deno", "[typescript]": { "editor.defaultFormatter": "denoland.vscode-deno" }, } 開発環境が整ったらパッケージ本体のソースコードを書いていきます。 GitHub Actionsでテストを実行する パッケージとして公開するのであれば、テストを書いて、CIを回しておきたいですよね。 Deno公式がセットアップするためのアクションを公開しているので、これを利用します。 denoland/setup-deno あとは deno test コマンドを実行するだけです。 on: push: pull_request: jobs: deno: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: denoland/setup-deno@v1 with: deno-version: v1.

Jun 22, 2024 - 2 minute read - typescript

TypeScriptで同時実行数を制限しながら並行実行する

タスクがたくさんあって並行実行することを考えます。 何も考えずにすべてのタスクを並行実行すると負荷が高すぎるので、 同時実行数を制限したいことがありました。 ググってみるといくつか実装例が見つかりますが、その多くは配列を受け入れるものです。 AsyncIterator を受け入れるバージョンが欲しいなと思い、 他の人の記事を参考に実装してみました。 IteratorまたはIterableを受け入れる版 いきなりAsyncIterator版を実装するのは大変なので、Iterator版で練習してみました。 以下の関数 limitConcurrency は、タスクのIteratorまたはIterableを受け取って、並行実行します。 async function limitConcurrency<T>( iter: Iterator<() => Promise<T>> | Iterable<() => Promise<T>>, limit: number ) { const iterator = Symbol.iterator in iter ? iter[Symbol.iterator]() : iter; async function runNext(): Promise<void> { for (;;) { const { value: task, done } = iterator.next(); if (done) { return; } await task(); } } try { const initialTasks: Promise<void>[] = []; for (let i = 0; i < limit; i++) { initialTasks.

Mar 27, 2024 - 2 minute read -

鍵生成には暗号論的に安全な乱数を使おう

SSHの鍵生成には暗号論的に安全な疑似乱数を使おうという話。 暗号論的に安全ではない疑似乱数がどれだけ危険かというのを、簡単なCTFを解くことで検証してみました。 背景 SSH公開鍵に自分の好きな文字列を入れる、という記事を読みました。 かっこいいSSH鍵が欲しい 例えばこのSSH公開鍵、末尾に私の名前(akiym)が入っています。 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFC90x6FIu8iKzJzvGOYOn2WIrCPTbUYOE+eGi/akiym そんなかっこいいssh鍵が欲しいと思いませんか? かっこいい!真似してみたい! そこまではいいんですが、問題は実装です。 秘密鍵を生成する際の乱数生成には高速化のために Goのmath/randを使っていますが、乱数が用いられるのは公開しない秘密鍵自体であり、このアルゴリズム自体はLagged Fibonacci generatorのようなので変に乱数に偏りがない限りは大丈夫だろうと思います(追記: これは乱数予測を主とした話)。 (強調 は筆者によるもの) おっとそれはまずい。 Goの math/rand パッケージは暗号論的に安全な疑似乱数 ではありません 。 SSH秘密鍵のようなセキュリティーが重要な場面では使用してはいけません。 Capture the Flag Challenge ブログ記事の筆者も math/rand を使ったらまずいことは認識したようで、 かっこいいSSH鍵生成プログラムのREADMEに以下のような追記がありました。 A Small CTF Challenge This is a joke software, but there was a serious bug in commit 7db96da05684a86bdbea18319ecc39097d0320d4. Can you recover the private key generated by the following command? Of course, it can be recovered in about an hour.

Mar 20, 2024 - 2 minute read - mysql go golang

GoでMySQLを使ったテストを書くときにつかうユーティリティーライブラリを作った

GoでMySQLを使ったテストを書く場合、MySQLのデータベースを初期化する処理や、使い終わったデータベースを削除する処理が必要になります。 毎回似たような処理を書いているので、そろそろライブラリとして切り出せそうだなと思って書いてみました。 shogo82148/go-mysql-pool 背景 弊社ではデータベースに関連したテストを書く場合、ローカルでMySQLを起動し、実際にMySQLへ接続する手法を取っています。 SQLの文法エラーを検知するには、実際にMySQLで処理するのが手っ取り早いからです。 この方法を採用する場合、次の問題は「いつMySQLのデータベースを初期化するか」です。 Goでは TestMain 関数を用意することで、テストの開始前の処理、テストの終了後の処理を書けます。 初期化しているコードは CREATE DATABASE するだけの単純なものです。 そんな分量もないので、プロジェクト毎にコピー&ペーストして使っていました。 // こんな感じのイメージ。 // 実際に使っているコードとは異なります。 package example_test import ( "database/sql" "testing" ) // 各ユニットテストで使い回す var db *sql.DB func TestMain(m *testing.M) { var cleanup func() db, cleanup = setup() defer cleanup() m.Run() } func setup() (*sql.DB, func()) { // MySQLに接続して新しいデータベースを作る db0, err := sql.Open("mysql", "user:password@/") if err != nil { panic(err) } _, err = db0.Exec("CREATE DATABASE dbname") if err !

Mar 12, 2024 - 1 minute read - mysql go golang

GoのMySQLドライバーを使うときはmysql.NewConnectorとsql.OpenDBを使おう

これから新規に書くコードでは mysql.NewConnector と sql.OpenDBを使ったほうが良さそう、という話。 昔からある書き方 go-sql-driver/mysql のDSNを文字列結合で実現するのは意外と大変なので、 某所ではDSN(Data Source Name)の生成を mysql.Config を使って行っています。 // DSNの生成 cfg := mysql.NewConfig() cfg.User = "user" cfg.Passwd = "password" cfg.DBName = "dbname" dsn := cfg.FormatDSN() // 接続 db, err := sql.Open("mysql", dsn) if err != nil { panic(err) } 新しい書き方 実はDSNを生成しなくとも、 mysql.Configから *sql.DB を取得できます。 cfg := mysql.NewConfig() cfg.User = "user" cfg.Passwd = "password" cfg.DBName = "dbname" // *mysql.Config を直接渡す conn, err := mysql.NewConnector(cfg) if err != nil { panic(err) } db := sql.

Mar 11, 2024 - 2 minute read - aws mysql go golang

GoのMySQLドライバーにBeforeConnectが追加されました

先日 go-sql-driver/mysql v1.8.0 がリリースされ、 いくつかのオプションが追加されました。 その中からひとつ BeforeConnect を紹介したいと思います。 Add BeforeConnect callback to configuration object #1469 何が嬉しいの? パスワード以外の方法で MySQL にログインするのが簡単になります。 BeforeConnect を使わない従来の方法 たとえば、AWS では IAM 認証を使ってログインする方法を提供しています。 IAM の情報を使って短期間だけ有効なトークンを発行し、そのトークンを使ってログインします。 MariaDB、MySQL、および PostgreSQL の IAM データベース認証 トークンの有効期限は短いので、接続を開始する直前にトークンを発行し接続設定を書き換えなければいけません。 しかし、Go はコネクションプールを採用しているため、実際に接続を開始するタイミングを知るのは意外と難しいです。 頑張ってそれを実現するためにわざわざドライバーを書いたこともありました。 IAM 認証で AWS RDS へ接続する MySQL ドライバを作った shogo82148/rdsmysql BeforeConnect を使った方法 BeforeConnect は、接続を開始する直前に接続設定を書き換える機能です。 shogo82148/rdsmysql を使用せずとも、簡単に IAM 認証を実現できます。 package main import ( "context" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/feature/rds/auth" "github.com/go-sql-driver/mysql" ) func main() { mycnf := mysql.NewConfig() mycnf.TLSConfig = "true" mycnf.

Feb 6, 2024 - 2 minute read - aws aws-lambda go golang

AWS Lambda Function URLsのイベントをnet/httpで扱えるridgenativeとlambtripを書いた

AWS Lambda関数をGoの標準ライブラリのインターフェイスに変換するライブラリを作りました。 http.Handler 実装: shogo82148/ridgenative http.RoundTripper 実装: shogo82148/lambtrip ridgenative Lambda Function URLsではリクエストとレスポンスがJSON形式になって渡ってきます。 これらをGo標準ライブラリの net/http.Request と net/http.ResponseWriter で扱えるように書いたアダプターが shogo82148/ridgenativeです。 同じコードをHTTPサーバーやAWS Lambda上で動かす これのうれしいポイントは net/http.Handler の実装をひとつ用意すれば、 普通のHTTPサーバーとしても、AWS Lambda上でも動かすことができるという点です。 たとえば以下のコードは go run main.go のように実行すると、 8080ポート上で動く普通のHTTPサーバーになります。 package main import ( "fmt" "net/http" "github.com/shogo82148/ridgenative" ) func main() { http.HandleFunc("/hello", handleRoot) // httpの代わりにridgenativeを呼び出す ridgenative.ListenAndServe(":8080", nil) } func handleRoot(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain") fmt.Fprintln(w, "Hello World") } bootstrap という名前でコンパイルして、zipで固めてアップロードすれば、 Lambda Function URLsやAPI Gatewayで動くHTTP APIの完成です。 GOOS=linux GOARCH=arm64 CGOENABLED=0 go build -o bootstrap main.