ISUCON11 予選 参加しました(RUST)

ISUCON11 予選 参加しました(RUST)

チーム名sengine(エスエンジン)です。一人チームでした。

「ブログを書くまでがISUCONです!」なので書きます。

参加するまで

ISUCON8からISUCONのこと知りました。

普段の業務ではJAVAがメインの言語です。ISUCONではJAVAの実装はないため、GO,TypeScript,RUSTなどを
勉強しながらで参加しました。

  • ISUCON8予選 Golangを2週間の入門レベルで参加しました。
    初めてのISUCONだったので手も足も出ない状態でした。
    頭真っ白で終了したことだけ印象に残っています。
    結果はもちろん敗退です。

  • ISUCON9予選 知り合いと2人でチャレンジしました。
    フロントエンドのJavaScriptの開発経験があるので、NodeJSのJavaScript実装があることを期待していました。しかしNodeJSの参考実装はTypeScriptだったので、またお手上げ状態になりました。
    あれこれやってもスコア上がらないので途中で諦めモードになりました。
    また敗退です。

  • ISUCON10予選 申請が遅れ参加出来ませんでした。

  • ISUCON11予選 RUST言語を勉強し始めたので、RUSTでチャレンジ出来ました。

やったこと

RUSTの初期スコア4000ぐらいでした。

1. SQLを見ながらテーブルにindex追加。

isu_conditionテーブルにてjia_isu_uuidと(jia_isu_uuid,timestamp)のindexを追加しました。

2. #[actix_web::post(“/api/condition/{jia_isu_uuid}”)] にて複数のINSERT文があるため1つに纏める。

Spring JDBCのbatchUpdateのようにやりたかったが、RUSTでは似た実装が見つからず、試行錯誤で何とか実装できました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
let mut sql = String::from("INSERT INTO `isu_condition` (`jia_isu_uuid`, `timestamp`, `is_sitting`, `condition`, `message`) VALUES ");
let mut times = Vec::new();
for (idx, cond) in req.iter().enumerate() {
let timestamp: DateTime<chrono::FixedOffset> = DateTime::from_utc(
NaiveDateTime::from_timestamp(cond.timestamp, 0),
JST_OFFSET.fix(),
);
times.push(timestamp.naive_local());
if idx == req.len() - 1 {
sql.push_str("(?, ?, ?, ?, ?)");
} else {
sql.push_str("(?, ?, ?, ?, ?), ");
}
}

let mut query = sqlx::query(sql.as_str());

for (idx, cond) in req.iter().enumerate() {

if !is_valid_condition_format(&cond.condition) {
return Err(actix_web::error::ErrorBadRequest("bad request body"));
}
query = query
.bind(jia_isu_uuid.as_ref())
.bind(&times[idx])
.bind(&cond.is_sitting)
.bind(&cond.condition)
.bind(&cond.message);
}

query.execute(&mut tx)
.await.map_err(SqlxError)?;

3. 各所にCacheControl追加。

  • #[actix_web::get(“/api/isu/{jia_isu_uuid}/icon”)]
    にて86400秒

    1
    Ok(HttpResponse::Ok().insert_header(CacheControl(vec![CacheDirective::MaxAge(86400u32)])).body(image))
  • #[actix_web::get(“/api/isu/{jia_isu_uuid}/graph”)]
    にて1秒

    1
    Ok(HttpResponse::Ok().insert_header(CacheControl(vec![CacheDirective::MaxAge(1)])).json(res))
  • #[actix_web::get(“/api/condition/{jia_isu_uuid}”)]
    にて1秒

    1
    Ok(HttpResponse::Ok().insert_header(CacheControl(vec![CacheDirective::MaxAge(1)])).json(conditions_response))

4. isuテーブルに入れていた画像をファイルにする。

  • #[actix_web::post(“/initialize”)]
    にて
    1
    2
    3
    4
    5
    6
    7
    8
    let isu_list: Vec<Isu> = sqlx::query_as("SELECT * FROM `isu`")
    .fetch_all(pool.as_ref())
    .await
    .map_err(SqlxError)?;
    for isu in isu_list {
    let file = format!("{}/{}_{}.jpeg", IMG_PATH, isu.jia_isu_uuid, isu.jia_user_id);
    fs::write(file, isu.image).unwrap();
    }

また

#[actix_web::post(“/api/isu”)]

#[actix_web::get(“/api/isu/{jia_isu_uuid}/icon”)]
にて合わせた修正をしました。

大分時間かかった割にスコアが全く上がりませんでした。
椅子の数が少ないからだと思います。

5. SQL箇所でlimit 追加。

終了時間が迫ったので強引なやり方をしました。

  • get_isu_conditions_from_db関数にて

    1
    2
    3
    4
    5
    6
    7
    8
    9
             sqlx::query_as(
    - "SELECT * FROM `isu_condition` WHERE `jia_isu_uuid` = ? AND `timestamp` < ? AND ? <= `timestamp` ORDER BY `timestamp` DESC",
    + "SELECT * FROM `isu_condition` WHERE `jia_isu_uuid` = ? AND `timestamp` < ? AND ? <= `timestamp` ORDER BY `timestamp` DESC limit 25",
    )

    sqlx::query_as(
    - "SELECT * FROM `isu_condition` WHERE `jia_isu_uuid` = ? AND `timestamp` < ? ORDER BY `timestamp` DESC",
    + "SELECT * FROM `isu_condition` WHERE `jia_isu_uuid` = ? AND `timestamp` < ? ORDER BY `timestamp` DESC limit 25",
    )
  • #[actix_web::get(“/api/trend”)]

    1
    2
    3
    4
    5
                 let conditions: Vec<IsuCondition> = sqlx::query_as(
    - "SELECT * FROM `isu_condition` WHERE `jia_isu_uuid` = ? ORDER BY timestamp DESC",
    + "SELECT * FROM `isu_condition` WHERE `jia_isu_uuid` = ? ORDER BY timestamp DESC limit 1",
    )
    )
  • generate_isu_graph_response関数にて

    1
    2
    3
    4
    5
    6
    7
    8
         let mut rows = sqlx::query_as(
    - "SELECT * FROM `isu_condition` WHERE `jia_isu_uuid` = ? ORDER BY `timestamp` ASC",
    + "SELECT * FROM `isu_condition` WHERE `jia_isu_uuid` = ? AND `timestamp` < ? + interval 1 day AND ? - interval 1 hour <= `timestamp` ORDER BY `timestamp` ASC",
    )
    .bind(jia_isu_uuid)
    + .bind(graph_date.naive_local())
    + .bind(graph_date.naive_local())
    .fetch(tx);

やった、スコアが72315まで上がりました。

やり残したこと

mariaDBを2号サーバーにしたかったが、設定が上手くいかない。1号サーバーからログインできず、時間切れで断念しました。

RUSTの感想

RUSTはGCがなく、メモリをキッチリ制御できる言語です。
しかし他のスクリプト言語と比べるとコンパイルが遅く、ベンチマーク走らせるまでに時間かかります。
今回のシステムはコンパイルに50秒ぐらいかかった。

最後に

ISUCONは大変素晴らしいコンテストです。運営の方々に感謝します。
また、試合に集中させてくれた妻に感謝します。

追伸

結果敗れました。

最終順位は43でした。
悔しいが、初めて決勝進出に近づくことが出来ました。
RUSTとmysqlを磨いて、来年も頑張ります。

TODO

  1. actix_web のアクセスを計測するツール作る。
  2. mysqlの設定に関する勉強。