カレーの恩返し

おいしいのでオススメ。

ISUCON8で予選敗退してきました

昨日、ISUCON8のオンライン予選があり予選敗退してきたのでその様子を報告します。

牛久大仏「う〜ん、顔採用w!」 というチーム名で筑波に編入した同期の@k5342, @chigichan24とISUCON8オンライン予選に参加してきました。

↓↓チームメンバーの記事はこちら↓↓

k5342.hatenablog.com

chigichan24.hatenablog.com

Rubyで参加しBest Scoreは5000点、最終Scoreは4000点と学生枠ボーダーには全然届かない点数でフィニッシュしました。

サーバはserver1: DB, server2: app1, server3: app2として利用し、
app1にベンチに来てもらい、ラウンドロビンでapp1とapp2にロードバランシングするといった構成になっていました。

スコアが目に見えて上がったのは複数台構成に変更したときくらいでした。

github.com

もちろんやってないこともたくさんあったけど、それなりに高得点者の歩いていた道を辿っていた気がするのでもう少し点数伸びてもよかったなぁというのが正直な感想です。


以下詳細です。

また、自分が関係するムーブだけ書いていきます(他のメンバーのムーブはブログに書いてくれるはず)

時刻 作業内容
前日
16:00 Redisの使い方をブログにまとめる→ これ
22:00 1台構成→複数台構成時に変えたときに起こりうる問題を色々考える
今まで1台構成でばかり解いていたので色々考えることがあることが分かって、色々調べた
当日
8:00
10:05 DBのschemaを確認する
10:30 自分のポートでwebサーバを立ち上げようとするが失敗する、ここで時間を多少消費する(firewalld周り)
11:30 reservationsのreserved_atにindexを貼ってみる
11:45 kataribe, myprofierを入れる(nginxに置き換え作業が発生していたため待ち時間が発生する)
←DBめちゃ重との連絡を受けたのでN+1を潰していくことにした
15:00 get_eventsから呼ばれてるget_eventのみ修正しN*N+1をN+1に改善した #6 
サクッと書いたクエリを「ほんまに合っとんかこれ?」と3人で悩む時間が発生していた(合っていた)。ここでかなりのタイムロスをした感がある
15:20 get_eventsのtransaction要らなくね?となったので外した #7
16:30 get_eventをN+1→1にした #8
クエリ自体は1になったけど処理方法がよくなかった(Array#selectでぐるぐる回しまくっていた)
17:00 開発に使っていたサーバからの移動を命じられ、いそいそと移動する
17:15 app serverのメモリがやべえという連絡を受け、メモリを節約できそうな場所を探すがよくわからん(残り1時間を切っていたのでget_eventを複数回呼んでいるところの修正は諦めた)
17:30 メモリ使用を減らすためにとりあえずSELECT *を消した、が終了直前だったのでマージは見送られた #10
17:45 app serverの再起動確認を行った&ベンチにenqueueされる様子を眺めていた
17:55 やることがなくなったので反省会開始
ISUCON終了
18:30 同じ場所でISUCONに参加していたluvtechno(カニオムライス), qnighy(カニオムライス), Altech(カニオムライス), gedorinku(winjisucon)と一緒に反省会会場(焼肉)へ移動
f:id:euglena1215:20180917115632j:plain:w450
↑反省し終わった(肉を撮り忘れていた)
Wantedly社員の人が反省会費用を出してくれた(特にCTO)!ありがとうございます!
22:30 つくばへ帰り反省会2次会(ラーメン)へ移動
ISUCON第2ラウンド開始
23:45 色々とやってないことはあったのでおもむろにPRを作り始める
f:id:euglena1215:20180917115924p:plain:w500
24:00 get_eventsでpublicなイベントだけ欲しい場合も一度全てのイベントを取得してからフィルタリングしていたのでSQLで行うように変更 #11
24:30 get_eventの修正後の処理がよくなかったので最適化した #12
25:20 get_eventをループ内で複数回呼んでるところ用にget_events_from_idsを作った、これが動けばN+1は全滅のはず #13
26:00 メモリが足りないとのことだったので不必要な変数の取り回しをなくした #14

おまけ

また、今回は自分のインターン先のWantedlyオフィスを会場として使わせてもらいました。

f:id:euglena1215:20180917123610j:plain

チームメンバーのうち1人は元インターン生、1人はWantedly全然関係ないという状況でしたが快諾してもらってありがとうございました!

f:id:euglena1215:20180917124431p:plain

所感

  • 初動が遅かった
    コードを書き始めたのが13:00ごろだった、このころには1万点弱に到達していたチームもちらほらいた気がする
  • 例年よりも作り込まれていてすごいと感じた
    フロントエンドはvue.jsで書かれていて時代の波を感じた
  • p debugよりもbinding.pryの方がやはり優秀
  • 帰ってからlocalで開発してみた結果、明らかにlocalで開発した方が実装速度が早いことが判明した(vim難しい)
  • 来年からは社会人として出るのでもっと力をつけていきたい

【ISUCON用】Redis Cheat Sheet

ISUCON予選が明日に迫ってきました。

Redisの操作が全く覚えられないのでまとめておきます。

セッティング

インストール

qiita.com

この辺とか見たらいい感じにできるのではないでしょうか(未確認)

使い方

# デフォルトは127.0.0.1:6379で繋がるはずなのでとりあえずこれで
def redis
   @redis ||= (Thread.current[:isu8_redis] ||= Redis.new(host: "127.0.0.1", port: 6379))
end

# keyはこんな感じでメソッドで隠してあげるとtypoが減ってよさそう
def redis_key_unread_count(channel_id)
  "isu7:unread_count:#{channel_id}"
end

# redisの操作もこんな感じでメソッドで隠してあげよう
def redis_increment_unread_count(channel_id)
  user_ids = redis_get_user_ids
  user_ids.each do |user_id|
    redis.hincrby(redis_key_unread_count(channel_id), user_id, 1)
  end
end

Redis豆知識

格納されているフォーマットについて

Redisには数値という概念がなく、一度全て文字列として格納されている。 (incrとかは文字列を数値に変換してから+1して文字列に戻すなどの操作をしているっぽい)

そのため、redis.get ...とかで取得した数値は毎回to_iする必要がある。

redis-cliでのデバッグ

  • とりあえず格納されているkeyを全部見たい
keys *
  • 一旦格納されてるデータ全部消したい
flushall

データ型

つらつら書いていこうかと思ったけどドキュメント見ればわかると思ったのでひとことに留めておきます。

String

標準的なkey value get, set, mget, mset, incr, incrby とかをよく使う気がします。

文字列型 — redis 2.0.3 documentation

# 使う可能性のあるコマンド一覧
SET(key, value)
GET(key) # なければnilを返す
GETSET(key, value)
MGET(key1, key2, ..., keyN)
SETNX(key, value)
MSET(key1, value1, key2, value2, ..., keyN, valueN)
MSETNX(key1, value1, key2, value2, ..., keyN, valueN)
INCR(key)
INCRBY(key, integer)
DECR(key, integer)
DECRBY(key, integer)

List

Listです。
両端からアクセスできる(rpush, lpush)けどisuconだとListは帯に短し襷に長し感があります。
Hashを使った方がいい場合の方が多そう。
idを保存しておいてllenとかでSQLのcountの擬似実装とかには使えるかも?

リスト型 — redis 2.0.3 documentation

# 使う可能性のあるコマンド一覧
RPUSH(key, string)
LPUSH(key, string)
LLEN(key)
LRANGE(key, start, end) # key, 0, -1 で全取得
LINDEX(key, index)
LSET(key, index, value)
LPOP(key)
RPOP(key)

Set

sorted setを使おう。setを使いたい場面ってisuconであんまり思いつかない。

セット型 — redis 2.0.3 documentation

Sorted Set

score付きのset。コマンド名に癖があるけどなんかすごい高機能。
DBのtableをそのままredisに載せようと思ったら

  1. Hashとしてそのままデータをredisに突っ込む
  2. Sorted Setでkeyをテーブル名、memberをhashのkey, scoreをDBのindexとして使う

みたいな感じになるんじゃないかな。(やったことない)
scoreの範囲を指定して取得とかできるので本当にtableと置き換えられる。
ただ、データの変形をせずにそのままテーブルの置き換えてパフォーマンスがどのくらい変わるのかはよく分かっていない。

ソート済みセット型 — redis 2.0.3 documentation

# 使う可能性のあるコマンド一覧
ZADD(key, score, member)
ZCARD(key) # memberの数を返す
ZSCORE(key, member) # 対応するmemberのscoreを返す
ZREM(key, member) # memberを削除
ZINCRBY(key, increment, member)
ZRANK(key, member) # 指定したmemberのスコア順にソートされた場合のindexが取得できる
ZREVRANK(key, member) # ZRANKの降順版
# indexで範囲指定してmemberを取得できる、WITHSCORESをつけると対応するscoreも取得できる
ZRANGE(key, start, end, [WITHSCORES])
ZRANGE(key, start, end, [WITHSCORES]) # ZRANGEの降順版
# scoreで範囲指定してmemberを取得できる。LIMIT, offsetも指定できるので実質SQL
ZRANGEBYSCORE(key, min, max, [LIMIT, offset, count, [WITHSCORES]])
ZCOUNT(key, min, max) # ZRANGEBYSCOREのCOUNT版

# 複数のsorted setから和集合、積集合を作る。共通するmemberのscoreは和をとる。
# ランキング計算とかするときにめちゃくちゃ便利なんだけど、複雑なので時間が余らないと使わない気がする?
ZUNIONSTORE(dstkey, N, k1, ..., kN, [WEIGHTS, w1, ..., wN], [AGGREGATE, SUM|MIN|MAX])
ZINTERSTORE(dstkey, N, k1, ..., kN, [WEIGHTS, w1, ..., wN], [AGGREGATE, SUM|MIN|MAX])

Hash

Hash。tableをそのままredisに持ってこようとするような荒技を使うときに使う。 操作方法自体はStringと大差ない。

ハッシュ型 — redis 2.0.3 documentation

# 使う可能性のあるコマンド一覧
HSET(key, field, value)
HGET(key, field)
HMSET(key, field1, value1, ..., fieldN, valueN)
HMGET(key, field1, ..., fieldN)
HINCRBY(key, field, value) # 負数を指定すればデクリメントもできる
HEXISTS(key, field)
HLEN(key)
HKEYS(key)
HVALS(key)
HGETALL(key)

雑にaxiosの便利ラッパー書いた

フロント書くときにaxiosが便利なのでよく使うんだけどHTTPメソッドごとで引数の型が若干違うので毎回忘れてググってる。

なので引数が同じになるようなラッパーに雑に書いてgistにあげておくことにした。
特に変なことはしてないのでコピペで使えるはず。

RailsCSRF対応済み

gista6599bb16d9999ecfccf457a7513e04d

RubyKaigi 2018に行ってきた【Day1】

まだ途中ですが、公開してから考えようと思います。

Keynote

f:id:euglena1215:20180605102252j:plain

ことわざの話

「名は体を表す」= 名前重要

ソフトウェアの世界には物理的制約がなく、概念で構成されているため名前はとても重要。
なのでソフトウェアエンジニアは概念に名前をつける必要がある。

1. 振る舞いに名前をつける

クラス、メソッド、変数に名前をつけるのがこれに該当
よく「メソッドの命名が難しい」と言うが、名前をつけるのが難しいのではなく十分に概念を理解できていないだけ
使う場面、機能を十分に理解していれば難しくない
なのでいい名前がついている機能は自然と良い機能・実装となる

Rubyにはyield_selfというメソッドがある

class Object
  def yield_self(*args)
    yield(self, *args)
  end
end

何がしたいかという情報が入っていない
なので昨日コミットして新しいaliasを作った(matzがCRubyに機能面でコミットしたのは5年ぶりらしい!)
object.c: Add a new alias then to Kernel#yield_self · GitHub
thenという表現はmatzが推しているだけで他のRubyコミッターはみんな反対しているとのこと

2. プロジェクトに名前をつける

良い名前は求心力になる
例えばRuby
Rubyという名前をつけなかったら仙台に1000人以上集まってくれるような言語になってなかったんじゃないか
Rubyは良い名前付けだったなぁと思うが名前を付けたのは1993年の話
今つけるとしたらもっと違う名前をつけるかもしれない
今はググラビリティ(=Google検索時の単語のユニークさ)を意識した方がいい
悪い例としてGoやSwiftが挙げられる(www)
現代のプロジェクトの名前付けのトレンドとしては

  • 単語を組み合わせる(Ruby on Rails, tensorflowなど)
  • 既存の単語から1文字変えてみる
  • 変な単語を選ぶ(kaminari, hanami, kibaなど) ← 何をやっているのか分からないという弊害もある

名前はとても重要なので、良いプログラマーは名前つけにsensitiveであるべき

「時は金なり」= Time is Value

時間をどのように使うかが自分の人生を決定づける
プログラミングにおける時間は2つあると思っている

開発時間 ≒ エンジニアがPCに向かってる時間


Rubyは生産性が高い

  • 便利なメソッドがたくさんあるのでやりたいことはだいたい探せばある
  • 色んなフレームワークがある
  • Rubyを使っている人がたくさんいる
    • 個人の言語の選択基準は「近くに詳しい人がいる言語」を選ぶべき
  • (見かけ上)シンプルな構文

実行時間 ≒ コンピュータが処理する時間

パフォーマンスの話
Ruby 1.8以前は確かに遅かった
Ruby 2.6からはJIT compilerが入ったりとかなり改善されてきた
今まで言われ続けてきたconcurrency(=並列実行)もどんどん現実のものとなってきている
Ruby 3.0ではRuby 2.0の3倍速くなる
実行時間が短くなればサーバーのリソースを減らすことができて、その分が全て利益になる

「塞翁が馬」

人生の幸、不幸は予測できない=なにが起こるか分からない
1995-2004 Rubyを知らない人が多かった
2005-2012 普及し始め「Ruby is Great!」と言われるように
2013-2018 一時期の興奮が冷め安定期に入った
Rubyは死んだのか?という記事を目にするようになった
Rubyは毎年死んで毎年生まれ変わっている
Ruby is Dead Every Year
互換性は大事だが変化は止めてはいけない

RubyKaigiが「Rubyがもっとこうなったらいいなぁ」と考える機会になればいいなあと締めた。

Analyzing and Reducing Ruby Memory Usage

TODO: スライドを見つけたらまとめる

草生えるの人

feature cache Direct Isen - メモリをどのくらい使われているか調べる方法 - Reading code - Mall stack tracing - rubyは2つのヒープに分けられる - object space - allocation tracer - malloc stack logging :love: - プログラムがどのくらいメモリを使っているか - どこがメモリを食っているか - 誰がmallocしたのかが分からない - メモリ使用量を減少するパッチについて - feature cacheの最適化 - shared strings - 存在する文字列の添え字の調整で表現できるならそれで - 同じrequireは1度だけ - どうやって同じファイルとするのか? - 毎回requireされたかどうかチェックすると遅い - setupに時間がかかるようになる - key generation - ruby objectを消す - cacheを参照するときに生成していたruby objectを作成しないでよくする - rails アプリケーションのメモリ使用量4.2%減らせる - パッチを書く前にすでにないか調べてみよう - Stack VM - コード - AST - Linked List - Byte Code - Byte CodeはVMによって実行される - Linked List(T_NODE) -> ByteCode

puts “Hello World”

Hijacking Ruby Syntax in Ruby

www.slideshare.net Rubyの黒魔術を使ってfinal override abstract with deferといった他の言語にある機能を作ってみたよ、という発表。

使った黒魔術たち

Binding#local_variable_setはbindingした箇所でlocal変数を宣言できる
存在しない変数を定義したときはbindingのblockのスコープを抜けるとその変数定義は消える
存在する変数に対して定義したときはoriginalの変数定義を上書きする ← 🤔🤔🤔

ということは好きなところで変数の中身をいじれる!

TracePoint

ほぼ全てのイベントをhookに処理を走らせることができる

  • 行を移動したとき
  • raiseが発生したとき
  • classを定義したとき
  • returnしたとき…
  • bindingしたとき

これらの情報を合わせると コードの任意の箇所の変数を上書きできる!

refinements 前の記事を参照

euglena1215.hatenablog.jp

github.com

github.com

github.com

finalist, overrider, abstrikerで使われているMethod Hook

  • Module#method_added
  • Module#method_removed
  • Module#method_undefined
  • BasicObject#singleton_method_added
  • BasicObject#singleton_method_removed
  • BasicObject#singleton_method_undefined

Rubyはmethod定義の方法がたくさんあるため、このくらいMethod Hookをつける必要がある。 同じようなことをしようと思ったときは気をつけてねという話でした

TODO: 途中

All About RuboCop

TODO: スライドを見つけたらまとめる

英語が分からんぞ、、、

Fast Numerical Computing and Deep Learning in Ruby with Cumo

TODO: スライドを見つけたらまとめる

chainerの人 PFN Cumo

CUDA programming GPUはやい コア数の桁が違う 行列演算早い

CUDA GPUのメモリをallocate CPU メモリ -> GPU メモリ GPU側で計算 特徴 非同期になる GPU likes a job queue

Numo -> Cumoの文字列置換で動く element-wise operation 要素ごとの足し算 並列的に処理がしやすい reduction operation 和をとったりするやつ 並列かさせるのむずかしい 行列の積にも対応 cuBLAS

cudamallocが遅くなりやすい memory poolを使おう 一番簡単な実装は配列 best-fit hashのchain法かな? 基本はchain法、ほしいメモリサイズよりも大きいものが余ってたら切り出して利用する

rubyでは文字列評価によって行う

NumoよりCumoの方がめっちゃ早い 75倍早い

GPUGCと相性がよくないかも? mkmfの機能が足りない 第三のコンパイラの指定ができない broadcast operationが遅かった

すごそう +=のoverrideができない

A parser based syntax highlighter

speakerdeck.com

TODO: あとでまとめる

既存のhighlighterは正規表現で実装されている atomはcsonという形式で書かれてる

それの問題点は?

  • コードを読むのが辛い
    • 正規表現は難しい
      • 問題にぶつかったとき多くの人は正規表現で解決しようとするけど、問題が2つにふえる
  • highlightが完璧じゃない
    • トリッキーなコードを書いているとsyntax highlighterを倒すことができて
  • Iro
    • ripper based syntax highlighter
    • iro vs iro vim
      • iro vimがiroにデータを渡してiroで処理する
    • advantage iro
      • easy to read
        • parseはripperに任せている
      • ローカル変数をhighlightできる
      • parseがeditor依存じゃなくなる
    • implementation
      • Ripper.lex
        • tokenizeしていい感じに返す
          • 行番号と列番号
          • keyword, space, identify
          • その文字
          • lex state
      • Ripper.sexp
      • event driven

        - performanceのため

RailsのService層とうまく付き合うにはどうすればいいのか調べてみた

RailsのService層ってどう使っていくのがいいんだろうね?」って聞かれたときにすぐ答えられなかったのでまとめておきたいと思います。

※ Fat Modelの解決策としてTrailblazerが最近よく話題に上がりますが、私がまだ使ったことがないので触れない方向で行きます。

情報を漁る

まずは「Rails service」とググって検索して引っかかった記事を読みました。

 

techracho.bpsinc.jp

  • アクションが複雑になる場合 (決算期の終わりに帳簿をクローズする、など)
    → 複雑な処理をmodelから分離させたい
  • アクションが複数のモデルにわたって動作する場合 (eコマースの購入でOrder, CreditCard, Customer を使用する、など)
    → どのmodelに書けばいいのかよく分からないのでとりあえずserviceに書いとけ感ある
  • アクションから外部サービスとやりとりする場合 (SNSに投稿する、など)
    → 外部サービスだしServiceっぽい!
  • アクションが背後のモデルの中核をなすものではない場合 (一定期間ごとに古くなったデータを消去する、など)
    → 複雑な処理をmodelから分離させたい
  • アクションの実行方法が多岐にわたる場合 (認証をアクセストークンやパスワードで行なう、など)。これはGoF (Gang of Four) のStrategyパターンです。
    → 複雑な処理をmodelから分離させたい

 

qiita.com

  • 一つのModelで複数のミドルウェアと通信する場合はService層に書く
    → 複雑なロジックをmodelから分離したい
  • 複数のModelが絡み合う処理はService層に書く
    → どのmodelに書けばいいのかよく分からないのでとりあえずserviceに書いとけ感ある
  • Service層では振る舞いだけを定義する(状態を持たないためModuleで設計する)
    → テストをしやすいようにする、1serviceに責務を負わせすぎないようにする

 

qiita.com

  • クラス名には動詞と目的語と「Service」を付ける
    → ソースを読んだときにすぐServiceだと気づけるようにするため、命名を悩まないようにするため
  • 引数は出来る限りnewで渡してインスタンス化する
    → 1serviceに1つの役割だけを持たせるため、
  • 1つのサービスにpublicなメソッドは、原則1つにする
    → 1serviceに1つの機能だけを持たせるため、上と一緒
  • 初期化したインスタンスはprivateのattr_readerで呼ぶ   → なるべく隠蔽したい、attr_readerにまとめることで可読性が上がる
  • 切り分けたメソッドは全てprivateなgetterメソッドとして実装する
    → 可読性、隠蔽の面から

 

qiita.com

  • クラス名は「動詞 (+ 目的語)」にする
    命名を悩まないようにするため、機能を明確化させるため
  • 外部に公開するメソッドは call という名前のクラスメソッドのみ
    → 1つの機能しか実装できないようにするため、命名しやすく、

 

techracho.bpsinc.jp

  • 命名規則を1つに定める 記事では名詞+動詞orだと動詞に違和感がある場合があるので動詞+名詞の方がいいかもって言ってる
    → 可読性
  • Service Objectを直接インスタンス化しないようにする
    callをClass Methodとして実装することでステートレスなメソッドとして実装したい
  • Service Objectの呼び出し方法を1つに定める
    call , run, executeなどの意味を持たないメソッドを使うよう義務付けてクラス名でどんな機能なのか表現できるようにする、serviceを利用しようと思ったときに調べなくて済むようになる
  • Service Objectの責務を1つに絞り込む
    Manageなどの責務が曖昧な単語をserviceクラスの命名として使わないよう気をつける
    → 細かい粒度で作って疎にするため
  • Service Objectのコンストラクタを複雑にしない
    → 複雑なコンストラクタは責務が複雑な証拠。できるだけシンプルなserviceになるように心がける。
  • callメソッドの引数をシンプルにする 利用しやすいように、引数が2つ以上あるときはキーワード引数を使うとよい
  • 結果はステートリーダー経由で返す callによる副作用を期待するもの(DBに対する更新、メール/Slack送信など)だけではなく処理の結果を受け取りたいときはcallの返り値をservice objectそのものにすると柔軟な処理がかけて良い。
  • callメソッドの可読性を下げないようにする
    メソッドを分割して可読性を維持する
  • callメソッドをトランザクションでラップすることを検討する
    → 処理の中断によるバグをなくす
  • Service Objectが増えたら名前空間でグループ化する
    → 可読性向上

ServiceをCommandパターンで作るのをいい感じにサポートしてくれるGemが紹介されていた。

github.com

  • commandパターンの為のレールを敷いてくれる
  • 引数のvalidationができる
  • runrun!をいい感じに使い分けることができる
  • services/ではなくinteractions/として別のディレクトリを切ることを推奨している
  • そもそもserviceという曖昧な名前を使うのがよくないのではと思っていたのでこれはよさそう

 

記事で主張していることが被ってきたのでまとめてみると

Railsでよく使われているServiceの役割

ARを継承したクラス(≒テーブル)を利用しないロジックをまとめる

  • 便利ツール群みたいなイメージ
  • Gemが提供している機能を使いやすいようにwrapする、外部サービスとの連携など

複雑なロジックをまとめておきcontroller側で簡潔に呼ぶ

  • 複雑なロジックなのでCommandパターンを使ってできるだけ分割して書くと色々メリットがある
    • 可読性の向上
    • ステートレス(callのclass method化)にすることにより、テストをしやすくなる
  • 複数リソースを扱うために使うのもここに該当
  • starategyパターンもここに該当

オープンソースを眺めてみる。

理想はだいたい分かったので実際どんな感じになっているか確かめるためにオープンなRailsアプリケーションはどうなっているのか眺めてみた。

github.com

  • #{動詞}#{目的語}Serviceという命名規則
  • 基本的にServiceクラスのpublic methodはcallのみ
    • 綺麗なCommandパターン
  • callの見通しがよくなるように処理をprivateメソッドを切り出している
    • privateメソッド内で他serviceを呼んでいて綺麗に分離できていてすごい
  • 便利ツール群はlib/にあった

github.com

  • #{動詞}#{目的語}Serviceという命名規則
  • 機能によってnamaspaceが切られている
  • 基本的にServiceクラスのpublic metodはexecuteのみ
  • レコードオブジェクトを操作するタイプのServiceはレコードオブジェクトをそのまま返していることが多い
  • google-apiのwrapはlib/でしていた
    • 便利ツール群はlib/に書いてあるみたい

github.com

  • #{名詞}#{動詞}er/orという命名規則
  • 色々なメソッドが生えている
  • たまにCommandパターンのserviceがあったりする
  • 正直よくわからない
  • 便利ツール群はlib/にあった
  • discourseって意外とイケてない?

まとめ

  • Serviceという言葉は曖昧なのでプロジェクトごとでしっかりとルールを決めよう。
    • Commandパターンで実装するのか、全部のせServiceクラスを作って実装するのか
    • 多くの記事がCommandパターンを用いた実装を推奨
    • でも、ある程度の経験がないとどのくらいの粒度で作ればいいのか悩むかも?
  • 複雑なロジックを整理するためにServiceを使うときはCommandパターン使って上手に分割しよう。
    • Serviceクラスを作るときはManageなどの責務の広そうな単語を使わないよう注意する。
    • Commandパターンのような一貫したルールがあるのとないのとでは初見でServiceのコードを見たときのインパクトが全然違う。
  • 今まで便利ツール群はServiceに置いてたけどlib/に置くのがベターっぽい。

追記

色々書きましたが Service層とうまく付き合うには texta.fm の 3. Low-Code Developmentを聞いてもらうのが一番です。聞いたことない人はぜひ聞いてみてください。Service層に対する解像度がぐっと高まると思います。

呉高専HPをスクレイピングしてワードクラウドを作った

これは呉高専エンジニア勉強会Advent Calendar 2017の15日目の記事です。

こんにちは。呉高専OB1年目の@euglena1215です。

AdventCalendar 4記事目になります。
あと6,7記事書かないと埋まらないだろうと思っていたのですが、色々な人に手伝ってもらって気付いたら全部埋まっていました!
もう感謝感激です。

f:id:euglena1215:20171215031355p:plain

こんな感じになりました。
高専はものづくりと国際交流を頑張ってるみたいです。

 

 

で、終わってもいいんですがちょこっと作り方をまとめてみます。

ソースコードはこちらになります。
GitHub - kure-kosen/kurekosen_wordcloud

基本的にはこのページを参考に(ほぼパクリ)しました。 qiita.com

!!!注意!!!

今回はrobots.txtを目視で確認したところ404が返ってきたのでスクレイピングしても問題ないと判断し、プログラム上ではrobots.txtのチェック処理を入れていません。
他のサイトへスクレイピングを行う際はrobots.txtを確認した上で実行してください。
また、リクエストの間隔を1秒以上空けるのがマナーなので気をつけましょう。

処理の流れ

  1. python get_url.pyhttps://www.kure-nct.ac.jp/ からアクセスできる呉高専ドメインのhtmlファイルのURLを全て取得し、url.txtに書き出します。
    イメージとしては、アクセスしていないURLのスタックとアクセス済みのURLのスタックを用意してアクセスしていないURLのスタックから済みのスタックへと移していきアクセスしていないURLのスタックが空になった時点で済みのスタックを書き出す感じです。

  2. python get_wordlist.py でurl.txtを元にhtmlファイル内の必要なテキストに形態素解析をかけて単語ずつに切り分けURLごとのファイルに書き出します。

  3. python wordcloud.pyで2. で書き出された単語を元にワードクラウドを生成します。
    2.で書き出されたファイルのファイル名はURLとほぼ同じものにしてあるので、読み込むファイル名を正規表現で絞ってやれば今年の高専日誌の情報のみなど任意の条件からワードクラウドを生成することができます。


最後に面白いと思った結果を紹介します。

f:id:euglena1215:20171215031802p:plain 2015年の高専日誌から抽出したワードクラウド

f:id:euglena1215:20171215031816p:plain 2016年の高専日誌から抽出したワードクラウド

f:id:euglena1215:20171215031832p:plain 2017年の高専日誌から抽出したワードクラウド

 

例年高専日誌は部活動やコンテストの結果を載せることが多いので「大会」や「チーム」といった単語が多いのですが、今年は「大会」と同じくらいの文字サイズで「イベント」がありました。

これはインキュベーションワークやその他の活動によって色々なところでイベントがたくさん開催されるようになった裏付けなのではないでしょうか。

ワードクラウドを使って1年を振り返るのもなかなかいいですね。

Podcast配信機能をRailsに組み込んでみた

これは呉高専エンジニア勉強会 Advent Calendar 2017の9日目の記事です。

高専OB1年目の@euglena1215です。
今年の12月はすごい勢いで記事書いてます。

とあるラジオのサイトでPodcastを配信したくなったのでどう実現するべきなのかを考えた過程を書いていこうと思います。

コードだけ見たい人はこちら

podcastを配信する仕組みについて

AppleのQ&Aから引用させてもらうと

Can I host my podcast on Apple Podcasts?

No, Apple Podcasts does not host podcasts. You must host media files and RSS feeds on your own web servers or use a third-party host. You can then submit your podcast feed for inclusion in the iTunes Store podcast directory.

What is a podcast feed?

A podcast feed is a URL to an RSS (rich site summary) in XML format. RSS is a technology for announcing updates to a website, and XML is a format for creating content on the Internet.

To publish a podcast on Apple Podcasts, use an Apple Podcast Hosting Partner or create a website that generates a feed conforming to RSS 2.0. When you update the site that hosts your podcast, the RSS feed notifies Apple Podcasts that a new episode is available.
iTunes Connect Resources and Help

とのことです。

ざっくり説明すると Appleではホスティングはしてないよ、何らかの方法でRSSを公開してURLを登録してくれればPodcastとして登録してあげるよ。ってことですね。

ふわっと理解するにはこのページが分かりやすいです。
haruthanatos.wixsite.com

ちなみにTech系Podcastとして有名なrebuildはRSSはこんな感じです。
http://feeds.rebuild.fm/rebuildfm

調査してみる

まずPodcastを配信する方法はなにがあるのかを調べたところ以下を発見しました。

Wordpressプラグイン

wordpressでぽちぽちすればPodcast配信機能を追加できる。

参考:Podcast番組の作り方・配信方法【WordPressで30分でラジオを始める方法】 – ヒーローズジャーニー

Octopod

Octopod is a set of Jekyll templates, helpers and extensions to deliver your podcasts the cool text file lover's way.
https://github.com/pattex/octopod

設定ファイルをYAMLで書くだけでpodcastRSSと良い感じのpodcastのページが表示してくれるwebサーバを立てられる

rebuild.fm のrssには <generator>Jekyll</generator> と書かれてあるのでこれを利用しているっぽい?

dropcaster

With Dropcaster, you simply put the mp3 files into the Public folder of your Dropbox. Then run the Dropcaster script that generates the feed, writing it to a file in your Dropbox, e.g. index.rss. All mp3 files in the Public folder of your Dropbox are already accessible via HTTP, and so will the RSS file. You can then take the RSS file's URL and publish it (again, this is because any file in the Public folder of a Dropbox automatically gets a public, HTTP-accessible URL).
https://github.com/nerab/dropcaster

dropboxにmp3ファイルをアップロードして設定ファイルを作るとrssを自動生成してくれるGem 生成されたrssレンタルサーバーやS3にアップロードすればpodcastをすぐ配信できるようになる

rss(Ruby標準ライブラリ)

生成されるRSSpodcast配信に必要なnamespaceに対応していたので頑張れば書けそう

選択してみる

今回の要件としては

  1. podcastが配信できる
  2. podcast配信以外にも他の機能も組み込みやすい
  3. なるべく簡単に作れる
  4. Rails wayに外れすぎない

wordpressとoctopodはアプリケーションとして完結してしまっていて機能を組み込む余地が少ないのでパス、
dropcasterでdropboxを使わずにRSSを生成することも考えましたがコード読むより作った方が早そうだったのでRuby rssを使って実装することにしました。

設計してみる

擬似コードでやりたいことを表現したらこんな感じです。
日本語で説明するのは苦手なのでやりたいことを察してほしい。。。

# routes.rb
Rails.application.routes.draw do
  # /podcast にGETでアクセスすると
  # PodcastControllerのindexメソッドの結果を返す
  get "/podcast", to: "podcast#index"
end

# podcast_controller.rb
class PodcastController < ApplicationController
  def index
    rss = if RSSがキャッシュに存在する
            cached_rss
         else
            radios = Radio.published # 公開されているラジオ全件取得
            Podcast.Feed.new(radios).generate # podcast用RSSを文字列として出力
         end
    render xml: rss # xml形式のデータとしてrssを出力
  end
end

# radio.rb
class Radio < ApplicationRecord
  # レコードが追加/更新/削除された後実行される
  after_save :update_rss_cache

  def update_rss_cache
    # キャッシュを更新
    cached_rss = Podcast.Feed.new(radios).generate
  end
end

RSSを文字列として出力できるモジュールを実装してしまえばpodcastを配信できるイメージが湧いたので実装に移っていきます。

実装してみる

rssのドキュメントとrebuildのrssをにらめっこします。
そしてできたのが feed.rb, config.rb です。

Configにプロジェクトに依存しているデータを集め、FeedではRSSを生成する処理に専念できるようにしてます。

initializeの引数に取っているradioのschemaはこんな感じです。

# == Schema Information
#
# Table name: radios
#
#  id                     :integer          not null, primary key
#  title                  :string(255)      not null
#  description            :text(65535)      not null
#  mp3(ファイルパス)        :string(255)      not null
#  created_at             :datetime         not null
#  updated_at             :datetime         not null
#  published_at           :datetime
#  duration(再生時間[sec]) :integer          not null 
#  size(バイト数)          :integer          not null
#

複雑なことはしてないのでなんとなく処理の流れはつかめると思います。

PRはこんな感じになりました。
https://github.com/kure-kosen/cho_kure_web/pull/26

テストしてみる

いつか絶対にテストを書くという強い意志は持って日々を過ごしています。

まとめ

少しの実装でPodcast配信機能を実装することができました。
テストはきちんと書きましょう。