だいたい死んでる

名古屋で働いているWEBプログラマーです。

isucon11予選に出場して敗退した

isucon11に予選に出場して敗退したのでやったことのまとめ。 スコア的には本戦出場かしないかくらいのところにいる気がするのですごく悔しい。 チーム名は「牡蠣に当たるときの効果音→カッキーン」

当日のリポジトリはこちら 

github.com

メンバー

当日一緒に出てくれたメンバーの紹介

pinkumohikan

我らがリーダー

常に冷静沈着でいい感じに指示を出してくれます

インフラアプリ何でもできるすごい人

cureseven

アプリガシガシ直していく担当

isucon公式ソングの歌ってみたを一番早く出した人

自分の担当

サーバー分離とかミドルウェア周りの整備とかをする人

なんとか調べてなんとかしてます

当日やったこと

github.com

このissueを見るとだいたい流れがわかるので、大きなところだけ。

10:00~11:00

いつも設定するやつを設定していました。

ここは練習したので、30分程度でいつも終わっています。

pincmohikanがindex貼ったり、curesevenがDBのconnection数や動的プレースホルダなどを行いました。

僕はDBの負荷が結構あったのでアプリとDBの分離を行いました。

この時点でスコア21701で6位を取って興奮してました。

11:00~12:00

pincmohikanがバルクインサートを設定してくれていました。

github.com

12:00~13:00

mysql8にして「ORDER BY timestamp DESC」となっていた箇所にindexを効くように変更。

insertが重かったのでmysqlinnodb_autoinc_lock_mode =2を設定して少しだけスコアが伸びる。

13:00~15:00(ご飯もこのあたり)

varnishと格闘していた思い出しかなく、何もできていない感・・・

設定への理解不足が辛かった。

ここは設定できなくて挫折。

15:00~16:00

Web2台DB1台構成に変更。

半々の割合くらいでリクエストを各サーバーへ飛ばすように変更。

16:00~終わりまで

ほそぼそとチューニングをしていました。

再起動に問題がないかを最終チェック。

あとはなにかSQL書いていた気がします。

まとめ

f:id:MikaE:20210822151328p:plain

最終スコア31位にいて悔しい気持ちでいっぱいです。

今年はどのチームよりも練習した自信があるだけすごく悔しいです。

16回近く練習に付き合ってくれたチームメンバーには感謝しかないです。ありがとうございます!

来年こそは、本戦に出たいなと思います。

運営の皆さんお疲れさまでした。

メンバーのブログ

shiningcureseven.hatenablog.com

blog.pinkumohikan.com

ISUCON10に出て予選突破しました

isucon10に「牡蠣の鋭利な殻が指に突き刺さり利き手を負傷」チームで出場し予選を突破したようです。

f:id:MikaE:20200913004306p:plain

簡単に当日までにやったことと、当日やったことを書いていきます。

当日までにやったこと

今年は予選通過したいねとチームで話し合い、練習を多く行ってきました。9回ほどれ過去問を解いたと思います。

チームの練習がない時はインフラの設定項目を見たり、デフォルトで設定することをまとめたりしていました。 普段業務では全くインフラをやらないのでいい勉強になりました。(予選で知識不足は痛感しました)

チームで何回も練習を行ったことで当日いい感じに動けた気がします。 特に何かあったときの判断軸が良かった気がします。

当日やったこと

チームの担当としては

  • アプリと全体指揮: pinkumohikan
  • アプリ全般: キュアセブン
  • インフラ(アプリも少し):僕

で行いました。

12:00

競技が開始したのでサーバーに入り、インフラの構成をまとめたりバックアップをとったりしていました。 この時点でベンチを回すことができませんでした。 負荷が見えないと設定変更ができないのでレギュレーションを熟読していました。

13:00

ベンチを回しスロークエリを解析してキュアセブンにインデックスを貼ってもらうお願いをしました。

その間にベンチ中の負荷を見てくれていたpinkumohikanがDBのマスター/スレーブ構成を提案してくれていたのでDBのサーバ分離に取り掛かりました。

練習では一回も取り組んでいませんでしたが、初めての構成でしたができてよかったです。

16:00

マスター /スレーブができたので検索系のクエリを全てスレーブを見るように書き換えました。

裏でインデックスを色々貼ってくれていたのでもう一度スロークエリの解析をしました。

また、mysqlとnginxの設定変更をしました。

(スコアはあまり上がらなかったです)

18:00

以下クエリがN+1として発生していたので指定された範囲に座標があるかの計算ロジックをプログラム側で行うように改修をはじめました。

SELECT * FROM estate WHERE id = ? AND ST_Contains(ST_PolygonFromText(%s), ST_GeomFromText(%s))`, coordinates.coordinatesToText(), point)

ここで1つのクエリでできるのではとは思ったのですが、やり方が思いつかず。(DBに計算させることで負荷が上がることを避けたかった)

論文を読んで座標計算方法を勉強しました。

各点の内角の総和が360になるのが学びです。(実装ミスったので違うアルゴリズムを使いました)

※実装をミスった原因はifの条件を反転してしまって範囲外の座標を取得していたことのようです。

20:00

N+1を改善していい感じにしたつもりだったのですが、ベンチ結果がFailしてしまったので運営に一旦お問い合わせをしました。

その間に再起動試験をしたところmysqlが再起動しなかったので、焦りました(10分前)

間違えてリブートかけてしまったことで気が付けました。

まとめ

5年目にして初めて本戦に出ることができるのでいい結果を出せるように練習していきたいです。

あと今回インフラ面の改修を入れていなく知識不足を痛感しました。勉強しておこうと思います。

予選に出た方のブログや感想戦を読んでみます。

余談

ここ最近ISUCONは若手ものづくり集団 Oysters - oystersjpのメンバーで出ています。

みんながブログ書いてくれるはずなのでまとめていきます。

blog.okashoi.net

blog.pinkumohikan.com

4年間お世話になった会社を辞めた

2020/05/22最終出社日だった。コロナの渦中リモートワークが始まり、今までお世話になった人たちとほとんど会わないまま退職しました。

このブログはいつもの通勤電車に揺られながら書いています。

電車の中で僕は、リモートワークで家から仕事をしていた影響なのか、本当に僕は辞めるのだろうか、もしかしたら来週も同じように起きてパソコンを開くのだろうか、そんな気持ちでいます。

 

でもそんなことはなく、いじってくれる先輩も、仲良くした同期も、可愛い後輩たちももう一緒に仕事をしない。そんな寂しさが心を一杯にする。最後にバグ見つけてごめんなさい。

 

f:id:MikaE:20200522191614j:image

 

せっかくなのでこの4年間を振り返ってみる。

 

1年目

暮らしニスタというサービスの開発者としてアサインされた。新規事業ということもあり、いろいろな挑戦をさせてもらいました。

この時期に、お世話になった方々は、長い時間、すごく長い時間一緒にキャリアを考えてくださって、だめだめな僕を少しでも良くしてくれました。人生の中ですごくいい財産になりました。まるで家族のような関係な気がしてます。

 

2年目

仕事にも慣れてきて、ちょっとずつやりたいことも見えてきた感じでした。

1年目にお世話になった方と二人三脚で仕事を進める中で、得意不得意をちゃんと補完しながら進めていく大切さを知れた気がします。めちゃくちゃご迷惑をおかけしました。

この時期に入った新卒の子はとても優秀で悔しさをすごく感じていました。安定感がすごくて、良い子でした。

 

3年目

Web制作にだいぶ慣れ、アプリも作りたいと感じていました。そんなこんなで、当時の上司に、アプリを書ける場を与えていただきました。アプリのリニューアルを担当し、自分で調べて自走する力を身に付けられたかなと思います。ここで設計力も上がった気がします。

ここでもまた、優秀な後輩が入ってきて自分が成長していないかのような、焦りをすごく感じました。いやほんと凄かった。

 

4年目

4年目に入る前に本当に転職を考えていました。会社にいても思うような成果が出ず、逃げるような気持ちでいました。その時に、新規事業のサーバーサイド側の設計の見直しについての仕事を振っていただき、取り組ませていただきました。この時の上司は、できると信じて任せてくれていると感じさせてくれて、本当にお世話になったと感じています。

チームメンバーも自走していて、チーム感もあって最高でした。

 

退職日当日

f:id:MikaE:20200522235932j:plain

物悲しいオフィスで仕事をして、後輩が弁当を作ってきてくれて、最後のあいさつを関連部署にしました。ただ挨拶をするつもりが、チームメンバーからお便りが読まれたり、コンテンツがあったりと、ここで初めて退職の実感が湧いた。

そして冒頭に続く。

 

最後に

そもそもやめる理由は会社のせいでも、コロナのせいでもなんでもなく、ただ自分の都合で転職することになりました。4年間辛かったこともありましたが、楽しかったことの方がいっぱいあります。

コロナが開けたら改めて挨拶に伺おう。その時少しでも成長した姿を見せられるようにしよう。そんな気持ちでいっぱいです。

 

プログラミング言語Rust触ってみる

Rustでコマンドラインアプリを作るためのドキュメントがあったので試してみる。

rust-cli.github.io

インストール

インストールしていない方はこちらからどうぞ。

www.rust-lang.org

作っていく

プロジェクトセットアップ

$ cargo new grrs
     Created binary (application) `grrs` package
$ cd grrs/
$ cargo run
   Compiling grrs v0.1.0 (/grrs)
    Finished dev [unoptimized + debuginfo] target(s) in 3.73s
     Running `target/debug/grrs`
Hello, world!

Hello, world!が表示されれば完了

作るCLIアプリについて

引数で与えたテキストをtext.txtに入力するコマンドを作る。

 grrs foobar test.txt

引数を取得できるようにする

第一引数でパターンを取得し、第2引数でパスを与える。

--- a/src/main.rs
+++ b/src/main.rs
+    let pattern = std::env::args().nth(1).expect("no pattern given");
+    let path = std::env::args().nth(2).expect("no path given");

引数をデータ型にする

Rustでは、扱うデータを中心にプログラムを構成するのが非常に一般的らしいので、データ型を定義します。

--- a/src/main.rs
+++ b/src/main.rs
     let pattern = std::env::args().nth(1).expect("no pattern given");
     let path = std::env::args().nth(2).expect("no path given");

+    struct Cli {
+        pattern: String,
+        path: std::path::PathBuf,
+    }
 }

StructOptを使用したCLI引数の解析

コマンドライン引数を解析するためのライブラリにclapがあります。サブコマンド、シェル補完、ヘルプメッセージのサポートがされている。

+++ b/Cargo.toml
 [dependencies]
+structopt = "0.2.10"

structoptライブラリはclap上に構築され、派生マクロを生成するためのstructの定義を提供しています。 構造体に注釈を加えることで、引数をフィールドに解析するコードが生成されます。

--- a/src/main.rs
+++ b/src/main.rs
+use structopt::StructOpt;
+
+/// Search for a pattern in a file and display the lines that contain it.
+#[derive(StructOpt)]
+struct Cli {
+    /// The pattern to look for
+    pattern: String,
+    /// The path to the file to read
+    #[structopt(parse(from_os_str))]
+    path: std::path::PathBuf,
+}

構造体を呼び出すときは以下のコードからできます。

--- a/src/main.rs
+++ b/src/main.rs
 fn main() {
-    println!("Hello, world!");
+    let args = Cli::from_args();
 }

実行してみる

$ cargo run
error: The following required arguments were not provided:
    <pattern>
    <path>

USAGE:
    grrs <pattern> <path>

For more information try --help

引数を与えてみる。

$ cargo run -- some-pattern some-file
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/grrs some-pattern some-file`

引数を処理する

引数を取得しファイルを習得するコードを追加する。

--- a/src/main.rs
+++ b/src/main.rs
 fn main() {
     let args = Cli::from_args();
+    let content = std::fs::read_to_string(&args.path)
+        .expect("could not read file");
 }

行を反復して印字する処理を追加します。

--- a/src/main.rs
+++ b/src/main.rs
     let args = Cli::from_args();
     let content = std::fs::read_to_string(&args.path)
         .expect("could not read file");
+
+    for line in content.lines() {
+        if line.contains(&args.pattern) {
+            println!("{}", line);
+        }
+    }
 }

実行してみる

$ cargo run -- main src/main.rs
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/grrs main src/main.rs`
fn main() {

ファイル内から引数で与えた文字を探し出し印字している。

エラーハンドリングを追加する

エラーを戻り値として処理できるようにするライブラリを追加する。

--- a/Cargo.toml
+++ b/Cargo.toml
 [dependencies]
 structopt = "0.2.10"
+failure = "0.1.5"
+exitfailure = "0.5.1"

ファイルが読めなかった場合のエラーと正常な場合の出力を記述する。

--- a/src/main.rs
+++ b/src/main.rs

+use failure::ResultExt;
+use exitfailure::ExitFailure;

 /// Search for a pattern in a file and display the lines that contain it.
 #[derive(StructOpt)]
@@ -10,14 +12,14 @@ struct Cli {
     path: std::path::PathBuf,
 }

-fn main() {
+fn main() -> Result<(), ExitFailure>  {
     let args = Cli::from_args();
     let content = std::fs::read_to_string(&args.path)
-        .expect("could not read file");
-
-    for line in content.lines() {
+            .with_context(|_| format!("could not read file `{}`", args.path.display()))?;
+
+    Ok(for line in content.lines() {
         if line.contains(&args.pattern) {
             println!("{}", line);
         }
-    }
+    })
 }

実行してみる

# ファイルが有る場合
    Finished dev [unoptimized + debuginfo] target(s) in 0.03s
     Running `target/debug/grrs main src/main.rs`
fn main() -> Result<(), ExitFailure>  {

# ファイルがない場合
$ cargo  run -- main src/main.r
    Finished dev [unoptimized + debuginfo] target(s) in 0.03s
     Running `target/debug/grrs main src/main.r`
Error: could not read file `src/main.r`
Info: caused by No such file or directory (os error 2)

まとめ

Rustを初めて触りましたがドキュメントが充実していた。また、サンプルプログラムの説明も詳しく、初学者に優しい印象を感じました。 今回はテストを省いているので、テストをちゃんと書けるようになっていきたいです。

第1章 アプリケーションアーキテクチャとは1

この記事は「アプリケーションアーキテクチャ設計パターン」を読んだまとめ記事です。 今回は第1章についてまとめます。読書のメモになるためより詳しくは本を読んでください。

www.amazon.co.jp

アーキテクチャとは 

システム開発現場でのアーキテクチャは以下のように多数の意味をもつ。

この本はアプリケーションアーキテクチャを主題に書かれている。 アプリケーションアーキテクチャはアプリケーションアーキテクチャを設計する上で基本的な設計方針であり制約である。

アプリケーションアーキテクチャの目標

2つの目標がある

アプリケーションの構造

規模の大きなシステムを小さな部品に分けて行く。部品は「コンポーネント」と呼ぶ。コンポーネントを適切に分けることで管理しやすくする。分割の粒度とコンポーネント同士の連携をどのようにするかを考える必要がある。

アプリケーションの処理方式

どのように処理を記述するかを考える。

アプリケーションアーキテクチャが必要な理由

アプリケーションの整合性確保

アプリケーションアーキテクチャを適切に設計しないと、全体的な整合性が崩れてしまう事があり、これは結合テストなどの後工程で顕在化する傾向にある。

設計品質の向上

アプリケーションアーキテクチャは設計上の制約であるため十分に検証する必要がある。アプリケーションアーキテクチャの設計が不十分であると各ユースケース担当者が設計する余地が残ってしまう。その結果、検証が不十分なアーキテクチャを採用してしまったり、機能要件に対して不十分もしくは過剰なアーキテクチャになってしまう可能性があります。 適切に設計することにより、良い意味で自由が奪われるため、各ユースケース開発における設計品質を一定に保つことができます。

拡張性・保守性の向上

リリースして終了ではなく投資を回収する必要があり、長期間運用されることを考慮しなければいけない。運用期間の間に機能拡張や仕様変更を行う必要が出てくる。 アプリケーションアーキテクチャが適切に設計されていないと、機能拡張や仕様変更での影響範囲が大きくなってしまい、場合によってはアプリケーションを作り直す必要が出てくるかもしれない。 このような事態を回避するためにはコンポーネントを適切に分割する必要がある。コンポーネント同士はお互いに疎結合にする。

アプリケーションアーキテクチャ設計のポイント

アプリケーションアーキテクチャは非機能要件と密接な関係がある。 非機能要件も充足できるように設計する必要がある。 また、目的が複数ある中で全てを達成しようとするとビジネス的に成功と言えなくなる場合や学習コストが高すぎるとチームメンバーへの負荷が高まる。現実的な妥協点を見つけ出すことも大切。

まとめ

文章の量が増えてきたので、別記事で続きを書いていきます。

SwiftUIでMVVM

この記事は ウィルゲート Advent Calendar 2019 の22日目の記事です。

ConbainとSwiftUIが登場したことで、データバインディングの仕組みを公式がサポートしました。 サンプルとしてMVVMでアプリを作ってみようと思います。

作るアプリ

天気を一覧で見れるアプリを作ってみる。 天気情報取得には以下のAPIを使用しました。

openweathermap.org

ディレクトリ構成について

app
- View
- ViewModel
- Model

上表のように各役割によってディレクトリを分ける。 役割ごとに依存の方向は下図のようにしていきます。

f:id:MikaE:20191216000004p:plain

Viewについて

ユーザーへ機能を提供するために描画を担当します。また、ユーザーからの入力を受け取りViewModelへ渡します。 またViewModelのデータとデータバインディングすることでデータの変更と描画の変更を同期させます。

ViewModelについて

Viewを描画するための状態の保持と、Viewから受け取った入力を適切な形に変換してModelに伝達する役目を持っています。 Viewとの通信はデータバインディングの機構を使って行われます。

Modelについて

アプリケーションのドメインを持つのがModelです。ビジネスロジックを書くところになります。また、データの永続化を行う箇所でもあります。

コードについての説明

Model

APIのレスポンス値からCodableのコードを作成してくれる、quicktypeを使用して自動生成できます。 自動生成したので基本的に何も変えずに使用します。

// MARK: - Weathers
struct Weathers: Codable {
    let coord: Coord
    let weather: [Weather]
    let base: String
    let main: Main
    let visibility: Int
    let wind: Wind
    let clouds: Clouds
    let dt: Int
    let sys: Sys
    let id: Int
    let name: String
    let cod: Int
}

// MARK: - Clouds
struct Clouds: Codable {
    let all: Int
}

// MARK: - Coord
struct Coord: Codable {
    let lon, lat: Double
}

// MARK: - Main
struct Main: Codable {
    let temp: Double
    let pressure, humidity: Int
    let tempMin, tempMax: Double

    enum CodingKeys: String, CodingKey {
        case temp, pressure, humidity
        case tempMin = "temp_min"
        case tempMax = "temp_max"
    }
}

// MARK: - Sys
struct Sys: Codable {
    let type, id: Int
    let message: Double
    let country: String
    let sunrise, sunset: Int
}

// MARK: - Weather
struct Weather: Codable {
    let id: Int
    let main, weatherDescription, icon: String

    enum CodingKeys: String, CodingKey {
        case id, main
        case weatherDescription = "description"
        case icon
    }
}

// MARK: - Wind
struct Wind: Codable {
    let speed: Double
    let deg: Int
}

ViewModel

データをAPIから取得する処理とModelの型に合わせて変数へ格納する処理を書いていきます。 また、Viewへデータバインドしたいので、PassthroughSubjectとObservableObjectを使用してバインドするための準備もしています。

class WeatherViewModel: Identifiable {
    
    let id = UUID()
    
    let weather: Weather
    
    init(weather: Weather) {
        self.weather = weather
    }
    
    var main: String {
        return self.weather.main
    }
}

class WeathersViewModel: ObservableObject {
    
   let didChange = PassthroughSubject<MemosViewModel,Never>()
    
    init() {
        fetchTopHeadlines()
    }
    
    var weathers = [MemoViewModel]() {
        didSet {
            didChange.send(self)
        }
    }
    
    private func fetchTopHeadlines() {
        let apKey = ""
        let baseURL = "https://samples.openweathermap.org/data/2.5/weather?q=London,uk&appid="
        guard let url = URL(string: baseURL + apKey) else {
            fatalError("URL is not correct!")
        }
        
        ApiCommon().loadTopHeadlines(url: url) { weathers in
            
            if let weathers = weathers {
                self.weathers = weathers.map(MemoViewModel.init)
            }
            
        }
        
    }
}

ViewModelをからデータをバインドするためObservedObjectを使用してオブザーバーを使用できるようにします。 こうすることでデータが変更されると自動的にViewが更新されるようになります。

View

import SwiftUI

struct WeathersView: View {
    @ObservedObject var model = WeathersViewModel()
    
    var body: some View {
        List(model.weathers) { weather in
            Text(weather.main)
        }
    }
}

struct MemoView_Previews: PreviewProvider {
    static var previews: some View {
        MemoView()
    }
}

まとめ

SwiftUIを使うことでこれまでよりも簡単にMVVMの実装をすることが出来ました。 その他のアーキテクチャについても実装をしてみます。

明日はcuresevenさんで、『アドベントカレンダー戦略』です。 お楽しみに!

netdataのメモ

isuconのとき毎回調べるので、netdataのメモを自分用に貼っておきます。

netdataとは

github.com

Netdata is distributed, real-time, performance and health monitoring for systems and applications. It is a highly optimized monitoring agent you install on all your systems and containers.

Netdataはシステムの監視を行うためのアプリケーションです。すべてのシステムのコンテナにインストールすることができます。

Netdata provides unparalleled insights, in real-time, of everything happening on the systems it runs (including web servers, databases, applications), using highly interactive web dashboards. It can run autonomously, without any third party components, or it can be integrated to existing monitoring tool chains (Prometheus, Graphite, OpenTSDB, Kafka, Grafana, etc).

Netdataは、インタラクティブなWebダッシュボードを使用して、実行中のシステムで発生するすべてのリアルタイムで監視することが出来ます。 サードパーティコンポーネントなしで実行され、既存の監視ツール(Prometheus、Graphite、OpenTSDB、Kafka、Grafanaなど)に統合することもできます。

 以下みたいな画面を作れる。 f:id:MikaE:20191217105034p:plain

使い方

ドキュメントを見たほうがいいのでドキュメントを貼ります。

GitHub - netdata/netdata

 導入方法

bash <(curl -Ss https://my-netdata.io/kickstart.sh)
systemctl status netdata.service

1999ポートを開ける http://Host:1999で見ることができる

止める

systemctl stop netdata.service

動かす

systemctl start netdata.service