S3にホストされているMaven RepositoryをMaven (pom.xml) から参照する
S3にホストされているprivateなMaven Repositoryというものがあり、それをMaven (pom.xml) から参照する必要があったのでの方法についてのメモ。Gradleとかsbtとかからは割と使ったことはあったのだけれど、Mavenからは無かったので……
<project> ... <repositories> <repository> <id>your-repository-name</id> <url>s3://path/to/your/repository</url> </repository> </repositories> ... <build> ... <extensions> <extension> <groupId>com.github.seahen</groupId> <artifactId>maven-s3-wagon</artifactId> <version>1.3.3</version> </extension> </extensions> ... </build> </project>
というふうに pom.xml
書いてあげると s3://path/to/your/repository
でホストされているpackagesを dependencies
から参照できるようになります。 maven-s3-wagon
のバージョンについては適宜ドキュメントやリリースノートなどを見て調整しましょう。
AWSのアクセストークンについては READMEのAuthenticationのセクション の通りに解決されるので、そこを読むと良いでしょう。
簡単でしたね。良かった良かった。
Zigのビットシフト演算がちょっと面白い
なんとなくZigを触っているのですが、ビットシフト演算が独特の挙動で面白かったです。
const n: u8 = 0b00000001; const shifted: u8 = n << 1; std.debug.print("{b}\n", .{shifted}); // => 0b00000010
これは直感的なコードでしょう。n
が1バイト左にシフトしています。
さて以下のコードはどうでしょうか。
const n: u8 = 0b00000001; const shifted: u8 = n << 8; std.debug.print("{b}\n", .{shifted}); // expects 0, but...
このコードは
./main.zig:5:30: error: integer value 8 cannot be coerced to type 'u3' const shifted: u8 = n << 8;
というコンパイルエラーが返却されます。u8
型の値に対しては u3
型すなわち高々 7
までの整数値でのみシフトできるということです。たしかに8 bitの値なんだから最大7 bitシフトできれば良いでしょう、ということのようです。なるほど。
これはシフト対象の型 T
に対し、シフトbit数の型が Log2T
と定義されているからです。Log2T
型って面白いですね。u3
みたいな型を今まであまり見かけたことが無かったので斬新でした。まあこれが便利かどうかでいうと賛否ありそうな感じはしますね。
// generates the u3 random number var rnd = std.rand.DefaultPrng.init(@intCast(u64, std.time.milliTimestamp())); const random_num: u3 = rnd.random().int(u3); std.debug.print("[debug] rand: {}\n", .{random_num}); const n: u8 = 0b00000001; const shift_amout: u3 = 7; const shifted: u8 = n << (shift_amout + random_num); std.debug.print("{b}\n", .{shifted});
ではこのような、実行するまでシフトするbit数が不定の場合はどうなるでしょうか。
生成された乱数が 0
だった場合は単に7 bitシフトして 0b10000000
が無事得られますが、1
以上だった場合は……
[debug] rand: 2 thread 431510 panic: integer overflow /private/tmp/main.zig:10:39: 0x1005e6727 in main (main) const shifted: u8 = n << (shift_amout + random_num); ^ /usr/local/Cellar/zig/0.9.1_1/lib/zig/std/start.zig:551:22: 0x1005e9b7c in std.start.callMain (main) root.main(); ^ /usr/local/Cellar/zig/0.9.1_1/lib/zig/std/start.zig:495:12: 0x1005e6de7 in std.start.callMainWithArgs (main) return @call(.{ .modifier = .always_inline }, callMain, .{}); ^ /usr/local/Cellar/zig/0.9.1_1/lib/zig/std/start.zig:460:12: 0x1005e6d25 in std.start.main (main) return @call(.{ .modifier = .always_inline }, callMainWithArgs, .{ @intCast(usize, c_argc), c_argv, envp }); ^ ???:?:?: 0x10302351d in ??? (???) ???:?:?: 0x0 in ??? (???) Abort trap: 6
このようなruntime panicが発生します。キャー
とはいえ、そうは言っても様々な理由で Log2T
型 以上のbit数でshiftしたい時もあるじゃないですか、どうすれば……
const n: u8 = 0b00000001; const shift_amout: u3 = 7; var shifted: u8 = n << (shift_amout); var additional_shift_amount: u3 = 1; var i: u3 = 0; while (i < additional_shift_amount) { shifted = shifted << 1; i += 1; } std.debug.print("{b}\n", .{shifted}); // => 0
というわけでこうです。追加でシフトしたいbit数 (上記の場合 additional_shift_amount
) ぶんだけループで1 bitずつシフトしてあげることでコンパイルエラーとruntime panicを回避することができます。良かった良かった。
ちなみに左シフトをしつつその操作によるオーバーフロー・アンダーフローの発生場合を検出したい時には @shlWithOverflow()
という組み込み関数が利用できます。
[追記]
なるほどな。これでもいか。
— mattn (@mattn_jp) 2022年8月13日
const shifted: u8 = @truncate(u8, @as(u64, n) << 10);
一旦整数のbit数を拡張して、十分な空間でシフトしてから元の型にtruncateするという方法。これも (むしろこれで) 良さそうですね。ありがとうございます。
[追記ここまで]
なお、なんとなくZigを触った結果としてはこのようなライブラリができつつあります。
書評: ISUCONを「ゴール」で終わらせない。『達人が教えるWebパフォーマンスチューニング ~ISUCONから学ぶ高速化の実践』
著者のid:catatsuyさんよりご恵投いただきました。ありがとうございます。実は著者の方から本を頂戴するのってはじめてです。
さて、この書籍のタイトルをはじめて見たときは「オッ、ついにISUCONの攻略本が来ましたね、これでワシも優勝間違いなしや!!」と思ったものですが実際に手に取ってみると必ずしもそうではないことに気付きました。むしろ「ISUCONで勝つための小手先のテクニック」のような話題は極力排除されており、高速かつ高可用なWebアプリケーションをどのように構築・運用していくか、というような実戦的な内容がその多くを占めています。
まず書籍の冒頭では「『Webアプリケーションのパフォーマンス』の定義」から始まり、「なぜWebアプリケーションが高速かつ安定して動作する必要があるのか」という目指すべき方向に対する動機付けが成されています。そういったハイパフォーマンスWebアプリケーションの構築・運用を模したコンテストがISUCONであり、「そのISUCONにどのようなアプローチを以って攻略していくか」というところから逆説的に「ハイパフォーマンスWebアプリケーションの構築・運用」を学ぶという筋書になっています。
「パフォーマンスに問題がある際は必ず計測をし、正しくデータを比較し、そこで明かになったボトルネックにのみ対応していく。勘や憶測で直す部分を決めるのは無駄なのでやらない」といったパフォーマンスチューニングの王道を下敷きとし、パフォーマンスを計測するための負荷試験のプランニングと実施方法、負荷に対するモニタリングの手法、その計測したデータをファクトとしたボトルネックの特定、そしてそのボトルネックに対する具体的な対応、といった内容が体系的に書かれています。
この中でもモニタリングの項が象徴的で、「ログのモニタリング」や「エージェントを使ったモニタリング」などといった継続して長期的にモニタリングを行なうための実践的な内容がおさえられており、ぶっちゃけこのへんってISUCONではやらないというか個人的にはISUCON競技中は目視でdstatの内容とかhtopの内容とか見て済ませがちなんですけど、こういった「実務で使える『ちゃんとした』モニタリング」に多くの紙面が割かれているのはISUCONに使えるテクニックの紹介だけを目的としているのではなく、その先にある「実際のWebアプリケーションを良くしていく」という部分をしかりと見据えているように感じました。
また、負荷試験のプランニングや実施については案外体系的・網羅的な情報が巷に無いように感じていたところ、本書籍ではそのへんが上手く言語化されていて非常に参考になりました。一方で、この書籍でもそうだったんですが負荷試験の項目ってアプリケーションの内容とアクセス特性を知った上でホワイトボックス的に作られがちですよね。ただ実際にアプリケーションを運用していると予期しないアクセスパターンがあったり意図せぬパフォーマンスの秘孔みたいなものがあったりするというのがままあると思っていて、そういうのをこう、探索的に負荷試験のプランニングをしていく方法みたいなのってあるんでしょうか? 毎度頭を悩ませております……
あと特筆すべきはデータベースの章で、著者のid:kazeburoさんのDBのパフォーマンス問題に対する試行・思考が文章としてトレースされている大変貴重な内容だと思います。常日頃から
my.conf kazeburo 最強
などとGoogleで検索してはkazeburoさんの秘伝のタレをパクってきてカスタムして使っている身としては非常にありがたいものです、いつもお世話になっております。この章は声に出して読みましょう。
それはそうとしてこの章では言及されていあんかったのですが、ISUCONでよくボトルネック爆弾として設置されているさまじい多重JOINだとか、恐るべき量のサブクエリとかってどうやってほどいて (解決して) いますか? ああいうのを見ると本能的に脳が理解を拒んでしまうのですが、やはり気合と根性でやるしかないのでしょうか……あとこの手のやつってDBレベルでなんとか解決するべきなのか、あるいは別の方法 (例えばRedisとか) でやるべきなのか、といった見切りを付けるのもなかなか難しいところですよね。
キャッシュの章については拙スライドを多く引用・参照していただいてありがとうございました。こういった書籍に自分が書いた内容が載っているというのはありがたいというか、面映ゆいというか、なんだか不思議な感情になりますね。スライドはコレです、もう5年前になるのかこれ……:
また、CDNを活用するパターンの話があったり (これもISUCONというよりは実戦寄りの内容という印象)、Linux Kernel・Kernel Parameterの基礎的な内容があったりと内容が多岐に渡っており面白かったです。ISUCONのベンチマークの作り方については惜しげもなく大公開されていたため技術的にベンチマーカーは作れそうな一方で、実際の問題を作るにあたってはどうすれば? (パフォーマンスが終わっている、しかし筋道立てた解決方法があるアプリケーションを故意に作っるには?) という部分には触れられていなかったので、これは続編の書籍に期待したいと思います!
というわけで、ISUCONはゴールではなく、ISUCONで得た知識・経験を実際のアプリケーションに還元し、ワンランク上を行くことこそが真のGOAL……
『達人が教えるWebパフォーマンスチューニング ~ISUCONから学ぶ高速化の実践』はISUCONを題材としながら、実際のWebアプリケーションをどのように高可用なハイパフォーマンスWebアプリケーションとして構築・運用していくかを体系的に解説している大変良い書籍でした。すなわちこれはISUCONで勝つための単なるTips集ではなく、本チャンのWebアプリケーションを地道に良くしていくための知識の集合であり、つまり本チャンのWebアプリケーションを常日頃からハイパフォーマンスにしていればISUCONにも勝てるはず……この本を買って、毎日実践して、ISUCON勝ちましょう!!!
しかしこんな体系的かつ網羅的なISUCONの本があるなんて本当に良い時代ですね、僕もISUCON 3とかの時代にこれを読んでサクセスしたかった……これからはパフォーマンスチューニングに悩んでいる人がいたら恵比須顔でこの本をおすすめしたいと思います。あとぜんぜん知らなかった内容が普通にポロポロあったんで勉強になりました、それがどういう内容か、ここから先は君の目で確かめてくれ!
protoc-gen-java-dynamodb書いた / protoc pluginはじめて書いた
protobufでスキーマを書いて protoc-gen-java-dynamodb
に食わせると、そのスキーマのJavaの生成コードに「生成されたDynamoDBのエンティティクラスのコード」を良い感じにねじ込んでくれるというprotocプラグインを書きました。
syntax = "proto3"; package com.example.dynamodb; import "path/to/protoc-gen-java-dynamodb/protos/options.proto"; option java_package = "com.example.dynamodb"; option java_outer_classname = "ExampleEntityProto"; option (net.moznion.protoc.plugin.dynamodb.fileopt).java_dynamodb_table_name = "example-entity-table"; message ExampleEntity { string hash_key = 1 [(net.moznion.protoc.plugin.dynamodb.fieldopt).java_dynamodb_hash_key = true]; int64 range_key = 2 [(net.moznion.protoc.plugin.dynamodb.fieldopt).java_dynamodb_range_key = true]; bool bool_var = 3; }
例えばこのようなproto3を書いてプラグインに食わせると、 ExampleEntityProto.ExampleEntity.DynamoDBEntity
というinner classとして以下のようなコードが生成されます。
// protoc-gen-java-dynamodb plugin generated ((( @com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable(tableName = "example-entity-table") public static class DynamoDBEntity { private String hashKey; @com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute() @com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey() public String getHashKey() { return this.hashKey; } public void setHashKey(final String v) { this.hashKey = v; } private Long rangeKey; @com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute() @com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBRangeKey() public Long getRangeKey() { return this.rangeKey; } public void setRangeKey(final Long v) { this.rangeKey = v; } private Boolean boolVar; @com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute() public Boolean getBoolVar() { return this.boolVar; } public void setBoolVar(final Boolean v) { this.boolVar = v; } public DynamoDBEntity() {} public DynamoDBEntity(final String hashKey, final Long rangeKey, final Boolean boolVar) { this.hashKey = hashKey; this.rangeKey = rangeKey; this.boolVar = boolVar; } public ExampleEntity toExampleEntity() { return new ExampleEntity.Builder() .setHashKey(this.hashKey) .setRangeKey(this.rangeKey) .setBoolVar(this.boolVar) .build(); } } public DynamoDBEntity toDynamoDBEntity() { return new DynamoDBEntity(this.getHashKey(), this.getRangeKey(), this.getBoolVar()); } // ))) protoc-gen-java-dynamodb plugin generated
なのでprotobufでdeserializeしたオブジェクトに対して例えば obj.toDynamoDBEntity()
としてやると生成された DynamoDBEntity
のインスタンスに変換ができ、それをDynamoDBMapper
等に食わせることが可能です。逆に DynamoDBEntity
のインスタンスにも toSomethingActualClass()
(SomethingActualClass
の部分は実際のクラスによって変わります) という変換コードが生えているので、実際のクラスのオブジェクトに変換することも容易です。
くわしい使い方についてはREADMEをご参照ください。
モチベーションとしては、例えばDynamoDB Streamsを使っている時なんかに良くありがちですが「データをインサートするコンポーネント」と「ストリームに乗ってきたデータを処理するコンポーネント」とが別々の言語で記述されていると、それぞれのコンポーネントでテーブル定義を二重に定義しなければならない、みたいな状況になることがあってそれが嫌。それであればprotobufで定義を書いておいてそれを各言語向けにコード生成して使えば良いのではないか、ということで作られたというのがこのプラグインです。
同じような悩みがある方、ぜひご利用ください。
今回protoc pluginを書いたのははじめてで、正直なところまったくのnewbieなので世界観を掴むところからのスタートだったのですが、最初の一歩としては@yuguiさんの記事が大変参考になりました。
この記事で基本的なことを把握しつつ、あとは既存のプラグイン実装 (e.g. https://github.com/Fadelis/protoc-gen-java-optional) を見ながら公式ドキュメントを読んで実装していく……というふうに進めたところなんとか形になりました。
Javaでprotocプラグインを書くときは素手でやると結構泥臭い部分 (例えばJavaのbuiltin typeのハンドリングなど) があるので、そのあたりは既にあるフレームワークを部分的に使うと楽で良いです。
例えばSalesforceが出しているコレなどを活用すると面倒な部分を自分で手書きしなくて大変便利。
あと犯した勘違いとしては「protocのプラグイン (つまりexecutableなjar) だけを配信すれば使えるから、ライブラリとしてMaven Centralとかにアップロードしなくても良い」というものがあり、確かに自前でprotoのoptionを提供しない場合それは正なのですが、仮に独自のoptionを提供している場合はそのoptionの情報をJavaのコードとして提供する必要があります。従ってMaven Centralなりなんなりでライブラリコードも配信する必要があります。なのでこのプラグインのライブラリもMaven Centralにアップロードされています。
なおGoで似たようなことをしたい時はsrikrsna/protoc-gen-gotagという便利なやつがあって、これはprotoファイルの中にコメントで書いておくと良い感じで生成コード中のGoのカスタムタグに値を埋め込んでくれるというナイスなやつです。
当初はJavaでもこういうことをやろうと意気込んでいたのですが、Javaのprotocのライブラリはprotoファイル中のコメントを読む機能が無かったり *1、Plugin Insertion Point (つまり生成コードを捻じ込める箇所) が限られていたり *2、仮に protoc-gen-gotag と同じアプローチを採ってprotocによって生成されたコードを構文解析してそこに値をブチ込むにしてもJavaだと色々と大変そうだなあ……などなど色々と制限があったので今回書いたプラグインの形に落ち着いた次第です。
一定のインターバル期間中に stdin に何かが来る、あるいは来ない時に任意のコマンドを実行するツール "conk" を書いてた
これ去年書いたツールなのでいつの話をしておるのかという感じですけれども、一定のインターバルの間に stdin 経由で何かデータが来た時あるいは来なかった時に任意のコマンドを実行するツールを書いていました。
使い方の例としては
$ ./do_something | conk --interval-sec 5 \ --on-notified-cmd '["echo", "notified"]' \ --on-not-notified-cmd '["echo", "not notified"]' \ --on-ticked-cmd '["echo", "ticked"]'
という風にしてあげると、5秒周期で評価が走り、その周期内に do_something
からパイプ経由で何かデータを渡された時には echo notified
が、何も来なかった時は echo "not notified"
が実行され、そして来ようが来まいが echo ticked
は毎度実行されるという挙動をするようになっております。
README のビデオを見てもらうとまさにそのような挙動を確認することができます。
たまにこういうのが欲しくなるので書いた、みたいな感じでした。
openapi-generator を使って go のファイルを生成した時に出てくる `var _ context.Context` の謎
openapi-generator を使って go のコード生成をすると、
// Linger please var ( _ context.Context )
という一見不要そうな変数宣言がコード中に出てきます。これはなんなのでしょうか? 「残しておいてね」とは書いてありますが……
TL;DR
たぶんこれ過去のワークアラウンドがそのまま残っている感じだと思われます。まあ特に気にする必要もなさそう……
<追記>
このワークアラウンドの残滓を消すパッチを送ってみた。
github.com
^ 送ってみたところマージされたのでこの謎は過去のものになりつつあります。
</追記>
詳しく見てみる
というわけでコードを掘ってみましょう。リビジョンについては今日 (2022-02-16) 現在の最新である 986446c1d5e7b2c16c667ed18b6c95b2679268f5 から遡っていきます。
なるほど、ここに該当するコードがありますね。blameしていきましょう。
openapi-generator/modules/openapi-generator/src/main/resources/go/api.mustache
の一番古いコミットを見てみるとここになるようです:
ここにもこの変数宣言はまだありますね。つまりこれが最古のコミットで、そこに「ある」ってことは「ある」んだよ、ガタガタ言うな、ということでしょうか……
とまあそうではなく、というのもこのファイルは openapi-generator/modules/swagger-codegen/src/main/resources/go/api.mustache
から改名されているので一見すると歴史が途絶えてしまっているようですが、これよりも古いコミットはちゃんとあります。更にこのファイルの歴史を辿ってみましょう。
しかし swagger から openapi に名前が変わっているのは歴史を感じますね。
で、掘っていくとこのコミットに行き当たります: 3ed1aa8e79687bed63dfa064de27317273080471
この diff を見ると、件の変数宣言はここが始祖のようです。それはそうとして golang.org/x/net/context
が使われていたりしてなんだか懐しいですね。
で、このテンプレートファイルを読んでみると {{#hasAuthMethods}}ctx context.Context, {{/hasAuthMethods}}
という条件分岐があるのがわかります。
つまり、この条件が false
になるとこの部分が欠落し、そうなった時に import "golang.org/x/net/context"
があるとコンパイラに未使用の import があると怒られるので、そのワークアラウンドに無名変数 (_
って無名変数って名前で良いんでしたっけ) として context.Context
を変数宣言することで import 未使用コンパイルエラーを回避しているように読みとれます。なるほど〜。 *1
さて翻って最新のコードを再び見てみましょう。
このコードを読むと context.Context
は常に使われているように見えるし、わざわざ var _ context.Context
を付けておく必要もないように見えます。これパッと見不要そうに思えるんですが、今でも残しておく必要あるんですかね? 後でパッチでも投げてみようかしら……
*1:とはいえコミットログにその旨は書いていない。コミットログの issue 番号も恐らく前の repository のものなのでどれと紐付いているかわからない、という感じなのであくまでコードを読んだ上での推察です
API越しでタイムスタンプをやりとりする時のフォーマットをどうするべきか
APIのリクエストにせよレスポンスにせよ、タイムスタンプを利用するというのはよくある話です。
この時、そのタイムスタンプのフォーマットをどうするのが良いのかという話題です。IDLを使って縛るというというのは良い考えだと思いますが、IDLを使うにせよフォーマットについては決めなくてはならないので。
1. 文字列を使う
これあんま良くないと思うんですよね……というのも、とあるAPIを触っている時に「タイムスタンプはRFC3339です」というフィールドがあったんですけれどRFC3339ではないフォーマットで返却されたり受け入れられたりしたのであまり信用ができない……
まあフォーマットが不正というのは極端な例かもしれないですが、仮にフォーマットが不正だと多くの場合 strptime()
や time.Parse()
なんかの時刻文字列のparserが正しく動かず (良いケースだとエラーが上がる、悪いケースだと本来意図しない時刻として扱われる。後者が大きな問題) システムが壊れると思うのでunix timeを使ったほうが良いと考えています。あといまだにstrptime/strftimeの変換指定が満足に覚えられない!
あとタイムスタンプ文字列だと無邪気にタイムゾーン情報が欠落してたりすることがあるんで、それもunix timeを使ったほうが良いと考える一因です。まあちゃんとタイムゾーン情報を付けてくれれば良いんですけど。
グローバル社会になって久しい一方で無邪気にタイムゾーンを省いたタイムスタンプ文字列がAPI越しに飛んできて俺の頭は爆発寸前だ……
— moznion (@moznion) 2021年12月9日
2. unix timeを使う
まあこれが穏当なんじゃないですかね、UTCであることも明確だし……と思いきやこういった落し穴が。
精度は秒の整数か、millisec単位の整数(秒の1000倍)か、subsecが入った浮動小数点のどちらがよろしいでしょうか…
— fujiwara (@fujiwara) 2021年12月9日
これについては2つあると思っていて、時間の解像度 (単位) についてはフィールドに _sec
とか _ms
のような接尾辞を付けることで明確にするように心がけています。本質的にミスを防げるかというと怪しいですが、無いよりは良いでしょう、という考えです。
unixtime返したり受けたりするAPI、最近はパラメータ名に単位付けるように心掛けています。`received_time_epoch_sec` とか `created_time_epoch_ms` とか……冗長だけど。
— moznion (@moznion) 2021年12月9日
もう一つの「整数か小数か」については型として縛るというのが方法じゃないでしょうか……IDLでやるのが良いのでしょうね。
3. TAI64を使う
https://cr.yp.to/libtai/tai64.html
やった、一意なフォーマットだ!!!
とはいえ人間が基本的には読めない、うるう秒の扱いが特殊、など色々ありこれはこれで困りそうですね。
やっぱtai64nしか救いはないな、djbを信じろ
— moznion (@moznion) 2021年12月9日
果たしてそうだろうか?
現時点での個人的な見解
- unix timeを使う
- 分解能は何らかの方法で明示する
- IDLを使う
というあたりが現実的な落しどころじゃないでしょうか。他になにか、APIのリクエスト・レスポンスに使えそうな先鋭的な時間表現ってあるんでしょうか?
[追記]
unix timeだと結局タイムゾーンを別途送るべきって話になりません?
— FUJI Goro (@__gfx__) 2022年1月11日
つまり、あるアクションが行われたとき、それがローカルタイムの朝なのか夜なのかはマーケ的には重要な情報だったりするわけで。
それって、そのAPIをcallしたユーザーの属性情報 (どこに住んでいるか) で突き合わせることができませんかね? あるいはGEO IPとか……いちいちTZ情報含んで送ってきてもらうのは大変な感じが。
— moznion (@moznion) 2022年1月11日
分析チームが「ノイズがあってもよい」というのに了承してくれるのであればそれでいいんじゃないですか。
— FUJI Goro (@__gfx__) 2022年1月11日
ただしノイズが含まれている確率は不明ですが…。
APIの特性に依ると思っていて、TZが必要なものであれば付ける、というのは同意です。ブラウザを使っているようなものだったらTZ取るのも難しくないでしょうし。
— moznion (@moznion) 2022年1月11日
一方でそういうのが必要ないAPIもあると思っていて、僕がよく扱うのはそういうAPIだったという話だと思います。
それは全くそのとおりです。一方でログは取り直しできないので、「時間には思考停止でtimezone情報はつけておけ」とも思います。
— FUJI Goro (@__gfx__) 2022年1月11日
プロトコルバッファーっていう大統一シリアライゼーションフォーマットに身を任せて、公式に出しているTimestampかタイムゾーンが必要ならCivil Timestampを使うってやつもありますね…… https://t.co/V6ylWGxffS
— ネコへの態度を悔い改めよ (@draftcode) 2022年1月11日
ちなみにUNIX timeでdoubleの型をつけておけば「あ、これは"UNIX time"は定義上sec単位であるという派閥に乗っ取りつつ、それより細かい精度は小数で示せということなんだな」ということを人類の忖度能力を持ってすれば察せないですかね。
— ネコへの態度を悔い改めよ (@draftcode) 2022年1月11日
[追記ここまで]