SQLiteでは集計とグルーピングの対象外のカラムでも、集計関数がMAXとMINの場合はそれらの行由来の値を返すことが保証されている(例外あり)

AndroidSQLiteがバックエンドのJetpack Roomを使っていた時の話題。

SELECT column_a, column_b, column_c, MAX(column_d)
FROM foo
GROUP BY column_a

このようなSQLにおいて GROUP BY に使われる column_a と、集計関数(この場合は MAX )に使われる column_d 以外の column_b, column_c の2つのカラムは bare columns と呼ばれる。SQLiteでは集計関数が MAXMIN の場合には、これらbare columnsの値が、その集計関数で返される行に由来することが保証されている(いくつかの例外はある)。

Special processing occurs when the aggregate function is either min() or max(). Example:

SELECT a, b, max(c) FROM tab1 GROUP BY a;

If there is exactly one min() or max() aggregate in the query, then all bare columns in the result set take values from an input row which also contains the minimum or maximum. So in the query above, the value of the "b" column in the output will be the value of the "b" column in the input row that has the largest "c" value. There are limitations on this special behavior of min() and max():

1. If the same minimum or maximum value occurs on two or more rows, then bare values might be selected from any of those rows. The choice is arbitrary. There is no way to predict from which row the bare values will be choosen. The choice might be different for different bare columns within the same query.
2. If there are two or more min() or max() aggregates in the query, then bare column values will be taken from one of the rows on which one of the aggregates has their minimum or maximum value. The choice of which min() or max() aggregate determines the selection of bare column values is arbitrary. The choice might be different for different bare columns within the same query.
3. This special processing for min() or max() aggregates only works for the built-in implementation of those aggregates. If an application overrides the built-in min() or max() aggregates with application-defined alternatives, then the values selected for bare columns will be taken from an arbitrary row.

SQLite以外のRDBではこのような動作は保証されないのでポータブルではなく避けた方がよさそうではあるが、AndroidのRoomの場合はSQLiteを前提として使ってもいいんじゃなかろうか。

GitHub Actionsのサードパーティーマネージドランナーの紹介

この記事は はてなエンジニア Advent Calendar 2023 の 2024年1月4日 の記事です。


GitHub Actionsの実行環境であるランナーには、GitHubが提供するGitHub ホステッド ランナーと、自分でランナーを用意・管理するセルフホステッド ランナーの大きく二種類があります。

最近はGitHub ホステッド ランナーにもラージランナーが用意されるようになり、ある程度ランナーのスペックを選べるようにもなりましたが、他のCIサービスと比べてもスペックの割にコストが高めである感じは否めません。一方でセルフホステッド ランナーにはスペックを自分で調整できる自由度がありつつも、管理する手間とコストが掛かってきます。

こうした隙間を突くように、サードパーティーによるマネージドなセルフホステッド ランナーを提供するサービスが増えつつあります。基本的には runs-on: に各サービスが提供するランナーの名前を指定するだけで、その名前に応じた環境(CPUアーキテクチャーやスペック、OS)を選べる、という形になっています。

自分が見かけたり調べたり実際に使ってみたものをいくつか紹介してみようと思います。

BuildJet

はてなでも昨年から一部のチームで使用していて、CI/CDの速度向上の恩恵を受けています。管理画面はあまりいけていなくて、vCPU数上限がある割にはどれだけ消費されているかのリアルタイムの数値やグラフでの確認ができなかったりするので、キャパシティプランニングが難しいですね(上限に引っ掛かるとジョブの起動待ち状態になるので、悲鳴ベースで対処していくしかない)。

BuildJetのPricing

Namespace

  • 単なるGitHub Actionsのマネージドランナーというだけではなくて、Dockerイメージのリモートビルダーや、開発環境としてのKubernetesクラスターの作成もできたりする
    • Webアプリケーションのプレビュー環境機能なども
  • キャッシュのアップロード・ダウンロードではなく、キャッシュ用のボリュームをアタッチすることでの高速化が最近サポートされたようでとても良さそう
  • Pricingはプラン毎にシート数やコミコミのリソースが決まっている(超過分は別途)、という形なので若干複雑

NamespaceのPricing

GitRunners

  • 雰囲気としてはBuildJetに近そう
  • コスト的にはGitHub ホステッド ランナーとBuildJetの中間
    • 並列数の制限はなさそうなので、BuildJetのvCPU上限の拡張に課金するよりもいいケースもありそう
  • 1vCPUを選べるのが特徴的(GitHub ホステッド ランナーやBuildJetは最低2vCPU)
  • 個人で少し触ってみた程度だが、管理画面はBuildJetよりもいけていそう
  • Upcoming featuresのアピールが気になる
    • Persistent storage for all ephemeral runners and flamegraph visualizations to analize workflows runtimes.

    • 前者はNamespaceのCache Volumesに相当するものっぽい
    • 後者はボトルネックを見つけるのに便利そうで、やはり管理系の機能には力を入れていそう

GitRunnersのPricing

WarpBuild

  • Argonautというクラウドサービスのデプロイツールからピボットしたサービスらしい
  • こちらもBuildJetと近しい雰囲気
    • CPUパフォーマンスがいい
    • x86_64もARMもサポート
  • 並列数の制限もなし!
  • SSHデバッグ可能というのは面白そう・便利そう
    • この時の課金がどうなるのかは気になる
  • PricingもGitHub ホステッド ランナーの半額なので、BuildJetと同様
    • vCPUの選択肢が2X、4X、16Xとなっていて、8Xがないのが悩ましいかもしれない
  • Coming soonにも期待したい
    1. Faster distributed caching
    2. Windows and MacOS runners
    3. Custom runner images ... and more!

WarpBuildのPricing

Cirrus Runners

まとめ

ということで、自分の知る範囲でざっくりとGitHub Actionsのマネージドランナーについて紹介してみました。改めて調べてみると、新サービスが増えたり、各サービスで機能追加の予定も出ていたりして楽しみが増えました。逆にBuildJetは最近あまりアナウンスやブログの投稿がなかったりするのは気がかりですね。他にも同種のサービスをご存知の方がいたらぜひ教えてください!

iOSDC Japan 2023 day2でLT登壇します #iosdc

すでに始まってしまったiOSDC Japan 2023。今年ははてな勢は4人でお邪魔しております。自分も2019年以来、4年振りのオフライン参加をとても楽しんでいます!

X(Twitter)ではお知らせしていたものの、ブログには書いていなかったので改めて共有ですが、最終日の明日9/3 (日) day2 ラストのLT大会にて登壇させていただきます!(最後から4番目)

fortee.jp

Renovate芸人・またiOSDC JapanのLT芸人として頑張りたいと思います⚡️

カンファレンスの最後までワイワイ楽しみましょう!😆

UISearchBarのデフォルトの背景色は `UIColor.tertiarySystemFill`

Use system fill colors for items situated on top of an existing background color. System fill colors incorporate transparency to allow the background color to show through.

Use this color to fill large shapes, such as input fields, search bars, or buttons.

と説明されているとおりだった。

ライトでの cgColor の出力はこんな感じ:

<CGColor 0x60000260e1c0> [<CGColorSpace 0x60000260cf00> (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB IEC61966-2.1; extended range)] ( 0.462745 0.462745 0.501961 0.12 )

XcodeGenのpackagesをRenovateで更新するプリセットを用意しました

ikesyo.hatenablog.com

この時の発表でXcodeGenの packagesをRenovateのregex managerで更新する方法を紹介してから、複数回言及・参照されることがありました。

これはこの方法を一般化しておいた方がよかろう、と思って hatena/renovate-config にXcodeGen用のプリセットを用意してみました。XcodeGenxcodegen ディレクトリにファイルを分割しているケースにも対応しています。

fileMatch が合致する場合はこのプリセットをextendsするだけで活用できるので、どうぞご利用ください。

{
  "extends": ["github>hatena/renovate-config:xcodegenSwiftPackages.json5"]
}

あわせて読みたい

Mojoのパラメータ(コンパイル時メタプログラミング)を使ったFizzBuzzを書いてみる

Mojoが発表されて早速盛り上がっていますね。

LLVMやSwiftの作者であるChris Lattner率いるチームが開発した新しい言語ということで、Swift好きな自分としてももちろん興味があります。現時点ではPlaygroundにアクセスするのにウェイトリストに並ぶ必要がありますが、自分もアクセスできるようになったので早速ちょっと遊んでみます。

Mojoにはコンパイルメタプログラミングを可能とするパラメータという機能があって、これを遊ぶにのにFizzBuzzを書いてみましょう。

早速コードを書いてみるとこんな感じです:

fn parameterizedFizzBuzz[n: Int, counter: Int]():
    @parameter
    if counter > n:
        return
    else:
        if counter % 15 == 0:
            print("FizzBuzz")
        elif counter % 3 == 0:
            print("Fizz")
        elif counter % 5 == 0:
            print("Buzz")
        else:
            print(counter)
        parameterizedFizzBuzz[n, counter + 1]()

通常の引数(argument)は () に引数リストを定義しますが、パラメータ(parameter)は [] に定義します。また @parameter if を使うことで、コンパイル時にif文を実行することができます。

This makes use of the @parameter if feature, which is an if statement that runs at compile time. It requires that its condition be a valid parameter expression, and ensures that only the live branch of the if is compiled into the program.

これを実際に実行してみると正しく動作します。

parameterizedFizzBuzz[60, 1]()

ちなみに2023年5月7日現在のPlayground環境のトップレベルコードとしては60回が限界でした。61にすると elaborator expansion is 129 levels deep - infinite recursion? というエラーになりました。

error: Expression [6]:21:1: no viable expansions found
fn __lldb_expr__(__mojo_repl_arg&: __mojo_repl_context__):
^

Expression [6]:23:28:   call expansion failed - no concrete specializations
    __mojo_repl_expr_impl__(__mojo_repl_arg)
                           ^

Expression [6]:34:26:     call expansion failed - no concrete specializations
  __mojo_repl_expr_body__()
                         ^

Expression [6]:31:33:       call expansion failed - no concrete specializations
    parameterizedFizzBuzz[61, 1]()
                                ^

Expression [6]:18:46:         call expansion failed - no concrete specializations
        parameterizedFizzBuzz[n, counter + 1]()
                                             ^

Expression [6]:5:1:                                                                                                                                 elaborator expansion is 129 levels deep - infinite recursion?
fn parameterizedFizzBuzz[n: Int, counter: Int]():
^

この elaborator というのは、ロードマップの「Protocols / Traits」というセクションで少し触れられていました。コンパイルのパイプライン中でパラメータ化されたコードをインスタンス化する役割のようですね。ここの再帰に制限があるのは納得できます。

Unlike C++, Mojo does not “instantiate templates” in its parser. Instead, it has a separate phase that works later in the compilation pipeline (the “Elaborator”) that instantiates parametric code, which is aware of autotuning and caching. This means that the parser has to perform full type checking and IR generation without instantiating algorithms.

パラメータではなく引数を使った場合は1万回や10万回でも問題なかったので、普通に末尾再帰最適化もされているということでいいのかな:

fn fizzBuzz(n: Int, counter: Int):
    if counter > n:
        return
    else:
        if counter % 15 == 0:
            print("FizzBuzz")
        elif counter % 3 == 0:
            print("Fizz")
        elif counter % 5 == 0:
            print("Buzz")
        else:
            print(counter)
        fizzBuzz(n, counter + 1)

ということでMojoのパラメータで少し遊んでみました。

Mojoは現時点ではPlaygroundが公開されているだけで、手元で動かすこともできませんし、オープンソースでもありませんが、すでにGitHubリポジトリは用意されてバグレポートやフィーチャーリクエストも受け付けています。

正式リリースまではクローズドな形で開発が進み、OSS化まではしばらく時間が掛かりそうではありますが、ロードマップやChangelogもチェックしながら今後を楽しみにしていきたいですね。

なぜ人はソースコードの複数行コメントで文が分かれているのに、改行を文区切りとしてしまって句点(。)を使わないのか?

改行されているときに、文が終わっている時もあれば、文が続いている時もあって、これは前の行からの続きなのか、別の文なのかを毎回意識する必要があって読みにくいなと思ってしまうパーソンです。皆さんはいかがですか?