花びし

天神から友楽が無くなって何年経っただろうか。ポスト友楽となる店がなかなか見つからない。 今回は Retty で評判良さそうだった、国体道路沿いに店を構える『花びし』に行ってみた。

店内はそんなに広くない。 なので席数も多くない。 てっきり2階もあると思っていたんだけどな。 ちょうど入れ替わりのタイミングだったので、すぐに席に座れたのはラッキーだった。 席に着いたら即、カツ丼とミニうどんのセットを注文。

甘くて濃いめの出汁で、しっかりとした味付け。 カツはその出汁が染みて柔らかい。 どことなく記憶の中の友楽を彷彿とさせるかも。 懐かしい感じがした。

天神で食べた中ではコスパ悪くない。 うどんもセットだし。 ただ、友楽のコスパには及ばない。 カツ丼探しの旅はまだ続きそうだ。

クボカリー大名店

南区大楠にある人気カレー店『クボカリー』が大名にも店を出していた。大名なら仕事帰りに寄りやすい。というわけで夕食食べに行ってみた。

注文した『クボカリープレート』は、スパイシーチキンカレー・とり軟骨ネギ炙りキーマ・サンバルの3種類のカレーを楽しめる一皿。チキンカレーは鶏肉がホロホロで、カレーもいい具合のスパイシーさで絶品だった。とり軟骨ネギ炙りキーマは肉肉しくてお酒が飲みたくなる味。サンバルはマイルドで良い舌休めになった。

カレーの具材や付け合わせの形で野菜もちゃんと摂取できて、食べるのに罪悪感が少ないカレーだった。ヘルシー。場所も大名だからまだ行きやすい方。カレーが食べたくなったときの候補に入れておこう。

関連ランキング:カレー(その他) | 赤坂駅薬院大通駅西鉄福岡駅(天神)

WEB+DB PRESS Vol.112

毎号購読している WEB+DB PRESS の感想メモ。

特集1:コンポーネント設計

アプリケーションのドメインを抽出して、ドメインオブジェクトコンポーネントを作り、 ドメインオブジェクトコンポーネントを構成する要素から、ドメインエレメントコンポーネントを切り出す。 確かにこのやり方だと Atomic Design と比べて余計なコンポーネントを作ってしまうことは減りそうだ。 筆者の提唱するコンポーネント設計手法は、ドメイン駆動設計を彷彿とさせる。 ユビキタス言語の作成と通じるものがあるし、ドメイン駆動設計と相性が良さそうだ。

特集2:RDBMS 徹底比較

主要な RDBMS の違いがまとまった記事で OracleMySQL は触ったことがなかったので勉強になった。本特集で得られる知識は RDBMS の選定に役立つのはもちろん、MySQL から PostgreSQL に移行といった RDBMS の載せ替えにも役立ちそう。

特集3:実践 Scala

メインで使っている C# は着実に進化していて不満はないけど、たまに Scala の表現力が羨ましくなる。その表現に寄与しているのが implicit parameter なのかな。暗黙的に引数を渡せるおかげで簡潔に書けるが、ぱっと見どこで渡されているのか分かりづらくて、Scala が難しいと思われる一因になってそう。

自作キーボードのススメ

自作キーボードはエンジニアにとって身近な沼だろうな。筆者は何個も組み立てていて、こだわり出したらキリが無さそうなのが十分伝わってきた。手を出したが最後、引きずり込まれて、抜け出せなくなりそう。

WEB+DB PRESS Vol.112

WEB+DB PRESS Vol.112

  • 作者: 樋口剛,篠田典良,谷口慶一郎,大沼由弥,豊島正規,三村益隆,笹田耕一,牧大輔,大原壯太,門松宏明,鈴木恭介,新倉涼太,末永恭正,久保田祐史,池田拓司,竹馬光太郎,はまちや2,竹原,粕谷大輔,泉征冶
  • 出版社/メーカー: 技術評論社
  • 発売日: 2019/08/24
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログを見る

Bistro O

愛読している福岡のグルメ情報誌ソワニエ』の肉特集で、 ハンバーグを売りにしたビストロが舞鶴にオープンしていたことを知った。 どうやら ONO グループが手掛けているらしい。 ONO グループの店で今のところハズレには出会っていないから期待できそう。 会社帰りに行ってみた。

注文したのは博多舞鶴ハンバーグ。 ソースは6種類から選べたけど、最初はデミグラスでしょ。 やっぱ。 ゴロっと丸くて大きいハンバーグは見るからに「肉汁閉じ込めています」って姿をしていて、 ジューシーそう。

やはりというべきか、箸を入れると肉汁があふれ出してきた。 それにデミグラスソースが上品で大人の味なのは、舞鶴という場所柄か。 他のソースも食べてみたいと思わせる。 ちょっと高級感あるビストロのハンバーグって感じ。 肉の旨味を堪能できて、ご飯が進む進む。 お代わり無料なので、もちろんお代わりした。

〆に牛テールスープのお茶漬けもあるんだけど、既に腹いっぱいで味わうことはできなかったのが心残り。 でもこのハンバーグとご飯の組み合わせだと、お代わりしない選択肢はない。 次回リベンジしたところで、同じ轍を踏みそう。どうすればいいんだ。

関連ランキング:洋食 | 赤坂駅天神駅西鉄福岡駅(天神)

メモの魔力

ゲームプランナーに憧れた幼少期にアイデアノートを書き始め、ソフトウェアエンジニアになってからも個人開発用にアプリやサービスのアイデアをメモしてきたけど、著者みたいに日常で気になったことまでは流石にメモしていないなぁ。気になったことを抽象化して転用するなんて発想すらなかった。 自分はまだまだメモ魔には程遠い。

スタートアップで解決するべき問題を見つけるには、それくらいアンテナの感度を高めておく必要があるのだと思う。 手段として、手始めになんでもメモするところから入ってみるのはアリかもしれないな。

メモの魔力 The Magic of Memos (NewsPicks Book)

メモの魔力 The Magic of Memos (NewsPicks Book)

Ulid vs. Guid

はじめに

Guid を主キーに使っているせいで、現在進行中でインデックスの断片化に悩まされている。 ソート可能な ULID を使えば、この悩みが軽減できたりするんだろうか。 気になったので実験してみた。

以下は実験に使ったサンプル

using System;
using System.Data.SqlClient;
using System.Threading.Tasks;
using System.Linq;
using Dapper;

namespace UlidVsGuid
{
    class Program
    {
        const int Count = 1000;

        const int DefaultIntervalMilliseconds = 10;

        static readonly string MasterConnectionString = new SqlConnectionStringBuilder()
        {
            DataSource = "(local)",
            InitialCatalog = "master",
            IntegratedSecurity = true,
        }.ToString();

        static readonly string ConnectionString = new SqlConnectionStringBuilder()
        {
            DataSource = "(local)",
            InitialCatalog = "ulid_vs_guid",
            IntegratedSecurity = true,
        }.ToString();

        static async Task Main(string[] args)
        {
            DefaultTypeMap.MatchNamesWithUnderscores = true;

            var intervalMilliseconds = int.TryParse(args.FirstOrDefault(), out var value)
                ? value
                : DefaultIntervalMilliseconds;

            await CleanUpAsync();
            await SetUpAsync();

            await Task.WhenAll(
                UlidTestAsync(intervalMilliseconds),
                GuidTestAsync(intervalMilliseconds),
                UlidToStringTestAsync(intervalMilliseconds),
                GuidToStringTestAsync(intervalMilliseconds));

            await AverageFragmentationInPercentAsync();

            Console.ReadLine();
        }

        static async Task CleanUpAsync()
        {
            using (var connection = new SqlConnection(MasterConnectionString))
            {
                await connection.OpenAsync();
                await connection.ExecuteAsync(
                    @"IF (DB_ID('ulid_vs_guid') IS NOT NULL)
                      BEGIN
                          DROP DATABASE ulid_vs_guid
                      END");
            }
        }

        static async Task SetUpAsync()
        {
            using (var connection = new SqlConnection(MasterConnectionString))
            {
                await connection.OpenAsync();
                await connection.ExecuteAsync(
                    @"CREATE DATABASE [ulid_vs_guid];");
            }
            using (var connection = new SqlConnection(ConnectionString))
            {
                await connection.OpenAsync();
                await connection.ExecuteAsync(
                    @"CREATE TABLE [dbo].[ulid_test] (
                          [id] [uniqueidentifier] NOT NULL,
                          [name] [nvarchar](50) NOT NULL,
                          CONSTRAINT [PK_ulid_test] PRIMARY KEY CLUSTERED
                          (
                              [id] ASC
                          )
                      );
                      CREATE TABLE [dbo].[guid_test] (
                          [id] [uniqueidentifier] NOT NULL,
                          [name] [nvarchar](50) NOT NULL,
                          CONSTRAINT [PK_guid_test] PRIMARY KEY CLUSTERED
                          (
                              [id] ASC
                          )
                      );
                      CREATE TABLE [dbo].[ulid_str_test] (
                          [id] [nvarchar](50) NOT NULL,
                          [name] [nvarchar](50) NOT NULL,
                          CONSTRAINT [PK_ulid_str_test] PRIMARY KEY CLUSTERED
                          (
                              [id] ASC
                          )
                      );
                      CREATE TABLE [dbo].[guid_str_test] (
                          [id] [nvarchar](50) NOT NULL,
                          [name] [nvarchar](50) NOT NULL,
                          CONSTRAINT [PK_guid_str_test] PRIMARY KEY CLUSTERED
                          (
                              [id] ASC
                          )
                      );
                ");
            }
        }

        static async Task UlidTestAsync(int intervalMilliseconds)
        {
            using (var connection = new SqlConnection(ConnectionString))
            {
                await connection.OpenAsync();
                for (var i = 0; i < Count; i++)
                {
                    var id = new Guid(Ulid.NewUlid().ToByteArray());
                    var name = $"No.{i}";
                    await connection.ExecuteAsync(
                        @"INSERT INTO ulid_test
                              (id, name)
                          VALUES
                              (@id, @name)",
                        new
                        {
                            id,
                            name,
                        });
                    await Task.Delay(intervalMilliseconds);
                }
            }
        }

        static async Task GuidTestAsync(int intervalMilliseconds)
        {
            using (var connection = new SqlConnection(ConnectionString))
            {
                await connection.OpenAsync();
                for (var i = 0; i < Count; i++)
                {
                    var id = Guid.NewGuid();
                    var name = $"No.{i}";
                    await connection.ExecuteAsync(
                        @"INSERT INTO guid_test
                              (id, name)
                          VALUES
                              (@id, @name)",
                        new
                        {
                            id,
                            name,
                        });
                    await Task.Delay(intervalMilliseconds);
                }
            }
        }

        static async Task UlidToStringTestAsync(int intervalMilliseconds)
        {
            using (var connection = new SqlConnection(ConnectionString))
            {
                await connection.OpenAsync();
                for (var i = 0; i < Count; i++)
                {
                    var id = Ulid.NewUlid().ToString();
                    var name = $"No.{i}";
                    await connection.ExecuteAsync(
                        @"INSERT INTO ulid_str_test
                              (id, name)
                          VALUES
                              (@id, @name)",
                        new
                        {
                            id,
                            name,
                        });
                    await Task.Delay(intervalMilliseconds);
                }
            }
        }

        static async Task GuidToStringTestAsync(int intervalMilliseconds)
        {
            using (var connection = new SqlConnection(ConnectionString))
            {
                await connection.OpenAsync();
                for (var i = 0; i < Count; i++)
                {
                    var id = Guid.NewGuid().ToString();
                    var name = $"No.{i}";
                    await connection.ExecuteAsync(
                        @"INSERT INTO guid_str_test
                              (id, name)
                          VALUES
                              (@id, @name)",
                        new
                        {
                            id,
                            name,
                        });
                    await Task.Delay(intervalMilliseconds);
                }
            }
        }

        // インデックスの断片化具合を取得
        static async Task AverageFragmentationInPercentAsync()
        {
            using (var connection = new SqlConnection(ConnectionString))
            {
                await connection.OpenAsync();

                var results = await connection.QueryAsync<Fragmentation>(
                    @"SELECT
                        t2.name AS table_name,
                        t3.name AS index_name,
                        t1.avg_fragmentation_in_percent
                      FROM
                        sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'LIMITED') AS t1
                      JOIN
                        sys.all_objects AS t2
                          ON t1.object_id = t2.object_id
                      JOIN
                        sys.indexes AS t3
                          ON t1.object_id = t3.object_id AND
                             t1.index_id = t3.index_id
                      WHERE
                        t2.is_ms_shipped = 0
                      ORDER BY
                        t1.avg_fragmentation_in_percent DESC");

                foreach (var result in results)
                {
                    Console.WriteLine(
                        $"{result.IndexName} = {result.AvgFragmentationInPercent}%");
                }
            }
        }
    }

    class Fragmentation
    {
        public string TableName { get; set; }
        public string IndexName { get; set; }
        public double AvgFragmentationInPercent { get; set; }
    }
}

下記の4パターンで、1000件登録したときのインデックスの断片化の具合を調べてみた。

  • uniqueidentifier 型の主キー列に Guid を格納
  • uniqueidentifier 型の主キー列に、Ulid を Guid に変換した値を格納
  • nvarchar(50) 型の主キー列に、Guid を string に変換した値を格納
  • nvarchar(50) 型の主キー列に、Ulid を string に変換した値を格納

1件登録するたびにインターバルをとっているが、その間隔も 1ms・10ms・100ms・1000ms の4パターンで試してみた。

実験結果は下記の通り

インターバル 1ms のとき

f:id:griefworker:20190808150520p:plain

インターバル 10 ms のとき

f:id:griefworker:20190808150531p:plain

インターバル 100ms のとき

f:id:griefworker:20190808150554p:plain

インターバル 1000ms のとき

f:id:griefworker:20190808150607p:plain

まとめ

Ulid は ToString して nvarchar 型の列に格納する必要がある。 互換性あるからといって、Guid に変換して uniqueidentifier 型の列に格納しても効果ない。

ごはんや飯すけ

ミシュランに掲載されたばかりの、地下鉄大濠公園駅の近くにある『ごはんや飯すけ』へ行ってみることに。この店には、ミシュランの基準を満たした『ミシュランプレート』のマークが付いていた。

魚料理に力を入れているとのことなので、刺身とか煮付けとか色々食べてみたいが、ちょっと手持ちが…。一番安い定食メニューの塩サバ(900円)を注文した。

塩サバは大きくて肉厚でジューシー。皮もパリッパリ。期待通りの逸品だった。ご飯が進むのでおかわりは不可避。 ミシュランに掲載された実力の片鱗を見ることができたが、本当の実力を見るには、やはり他のメニューも試さないといけないだろうな。時間とお金の都合がついたら再び行ってみたい。

ごはんや 飯すけ
〒810-0074 福岡県福岡市中央区大手門3-3-24 小金丸ビル1F南側