AllIsHackedOff

Just a memo, just a progress

Rails5のAPIModeつかってるけどRailsAdminもheroku上でサクッと使いたいよねというお話(失敗談)

API Mode

Rails 5 をAPI modeで動かしている。 * RailsAdminは使いたいよな(管理画面のフロントをSPAで書くのは工数の無駄) * herokuは使いたい(今更deploy周りとかサーバのセットアップとかだるい)

ということで Client -> rack app (routing) -> API @ port 3000 -> Admin (RailsAdmin) @ port 19998

構成

Gemfile
Gemfile.lock
app.rb      
config.ru   
vendor      
web1        
web2
ADMIN_PATH = '/admin'
WEB_SERVER_PORT = 3000
ADMIN_SERVER_PORT = 19899

class SimpleApp
  def call(env)
    request = Rack::Request.new(env)
    request_path = env['REQUEST_PATH']

    if request_path.start_with?(ADMIN_PATH)
      [ 301, {'Location' => "http://localhost:#{ADMIN_SERVER_PORT}#{request_path}" }, self ]
    else
      [ 301, {'Location' => "http://localhost:#{WEB_SERVER_PORT}#{request_path}" }, self ]
    end
  end
  def each(&block)
  end
end

こんな感じでheroku上で動かそうとしたが、そもそもlocalhostヘリダイレクトはクライアント側のローカルホストにリダイレクトされてしまうのでだめですよね。という当然の結論にheroku上で立ち上がっているappサーバにうまく通信しに行く方法はないんだろうか

2017年にもなってHerokuでハマるクラスタ (heroku MySQL)

初期データ投入のため

heroku run rake db:seed_fu
ActiveRecord::StatementInvalid: Mysql2::Error: User 'ba2df6a9fed0b0' has exceeded the 'max_questions' resource (current value: 3600): INSERT INTO `item_resources` (`item_id`, `resource_id`, `created_at`, `updated_at`) VALUES (1741, 2, '2017-05-01 04:53:46', '2017-05-01 04:53:46')

max_questionsてなんやねんと思ったら、MySQL側でユーザごとのクエリ数とかを制限する仕組みらしい。 そういった制限のあるところでMySQLを使ったことがなかったので初めて遭遇するエラーである。 Heroku上にはQPHの制限について書いてなかったので、ググっていると下記のサイトを見つけた。

getsatisfaction.com なるほど、関連テーブルもあるからクエリ数が足りなすぎる問題。どうしたものか。

そもそも放置気味になっているN+1問題を引き起こすエンドポイントとかあるので…直さないと一瞬で食いつぶされてしまう…orz

MacのxargsのJ option (replacerを使う記法)

command  | xargs -i cp {} target_dir

的なことがやりたかったがどうもMacのxargsだとだめらしい(これだからMacは…)

manを開くと下記が記載されていたので事なきを得た。

     -J replstr
             If this option is specified, xargs will use the data read from standard input to replace the first occurrence of replstr instead of appending that data after all other arguments.  This option will not
             affect how many arguments will be read from input (-n), or the size of the command(s) xargs will generate (-s).  The option just moves where those arguments will be placed in the command(s) that are
             executed.  The replstr must show up as a distinct argument to xargs.  It will not be recognized if, for instance, it is in the middle of a quoted string.  Furthermore, only the first occurrence of the
             replstr will be replaced.  For example, the following command will copy the list of files and directories which start with an uppercase letter in the current directory to destdir:

                   /bin/ls -1d [A-Z]* | xargs -J % cp -rp % destdir

エレガントに削る / エレガントに実装するということ

BtoBにしろCtoCにしろBtoCにしろ、プロダクトを開発するエンジニアに求められるエレガントさについてふと思うことがあったので書いておく。

言われた仕様や要件をそのまま作ってしまうエンジニアはどんなにコードを書く速度が早かったり難しい実装ができたり、 難しいアルゴリズムを考案することができたとしてもプロダクト開発には向かないと思う。 前提としてリスペクトがあることには留意しておくが、 単純に尖ったエンジニアリングがしたい / その能力が高いならばOSSへコミットしたり言語を作ったり、 研究をやっていたほうが人類を前に進められると思う (どのみち、それらの分野においても要求を「そのまま作る」ことしかできなければ役に立たないと思うが…)

エンジニアに求められるのはある種のエレガントさだ。 ここでのエレガントさは

  1. エレガントに要求を削れること=問題を適切にハンドルできること.
     ・必要なもの/解決すべき問題に対して適切に技術を用いることができること.
  2. エレガントに実装できること.
     ・解決すべき問題を別の問題を誘発せずに解決することができること.
      ・負債を生まない.
      ・運用上の手間を増やさない.
        ・手作業.
        ・コミュニケーション

の2つに大別されると思う。 A.に関しては、下手に要求を削ってしまうと骨抜きになってしまうし、要求を課題解釈して問題に適切に対処できないと巨大な城を建築することになってしまう。どちらの場合も、リソースを使ってゴミを生生していることにほかならない。チームのスループットを確実に低下させる。 ゴミを作らないためにはAのエレガントさが必要だ。Aのエレガントさを高めるためには、技術的な勘所とビジネスドメイン特有のお作法(何をするとユーザに喜ばれるか)の両方を体得していなければいけない。技術的な勘所は削る判断をするために必要なコストパフォーマンスについての示唆をもたらしてくれる。ビジネスドメインの知見に関しては要求が本質的かどうかを見極めるのに使うことができる。
これらの感覚は意識して磨くことができると思う。磨くための方法論に関しては僕も整理がしきれていない(故に記載はしない)。

B.が何故必要かに関しては、大抵の場合は本番でフルスクラッチでコードを書くことなく、エレガントな実装ができなければリリースまでに必要な時間が膨らむし、変なコードを既存コードに混ぜ込んでしまうことで負債を生んだり、別の問題を生んだりしてしあうことがその理由と言える。

AとBどちらに関しても、ユーザ / チームとのコミュニケーションに長けていることは必要条件だと思う。
エンジニアはコミュ障でもいいみたいな意見もあると思うのですが、実際問題無理ゲーだと思う。

このエレガントはプランナーが別に居て企画を立てる場合でもそうでない場合も成り立つかなと。

Crystalの配列(0)

配列

[1,2,3] [1, "aaa", 'ks']

文字列配列はRubyと同じ表記が可能

%w(one two three)

シンボルの配列

%i(one two three)

配列ライクな型

https://crystal-lang.org/api/0.20.3/Array.html

You can use a special array literal syntax with other types too, as long as they define an argless .new method and a #<< method. Set is one such type:

set = Set(typeof(1, 2, 3)).new
set << 1
set << 2

引数なしのnewと << が定義されていれば Array特有の文法を使うことができる。

Arrayはgenericな型、複数の型を含む配列の方はUNION型と呼ばれる [1, "aaa", 'ks']など

structural subtyping的になっているので、呼び出されるメソッドに応答するればその型の 条件を満たしているとみなされ、コンパイルが通る

=> 不用意に型制約をすると柔軟性が減る

crystalでyamlなど触ってみた

crystal-langが少し流行りだしそうなので触ってみた。

timeの扱い

t = "2011-10-11 11:00:00"
t = Time.parse(t_str, "%F %T")
puts t

yaml(読み込んだyamlの特定のキーに対応する部分を別の変数に代入したい RubyならばHashでよしなにできるのだけれど、crystal-langには空ハッシュがないので空のYAML::Anyを定義してあげる必要がある。

file_path = "hoge.yaml"
y_data = YAML.parse_all(File.read(file_path))
y_data = banner_yaml[0]
empty_yaml = YAML::Any.new("")

net/http触り直し(1)

GolangでWAFを使わなくても問題ない的な話が年末上がっていたのと(advent calendar界隈で)自分的にもそう思ったので RequestのBodyやらHeaderやらを適当にParseする方法を復習がてらメモ。

package main

import (
    "fmt"
    "log"
    "net/http"

    "github.com/davecgh/go-spew/spew"
    "github.com/julienschmidt/httprouter"
)

func main() {
    router := httprouter.New()
    router.GET("/test", testFuncGET)
    router.POST("/test", testFuncPost)
    log.Fatal(http.ListenAndServe(":9999", router))
}

func testFuncGET(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
    h := r.Header
    spew.Dump(h)
    val := h.Get("Hoge")
    fmt.Println("=======")
    fmt.Println(val)
    fmt.Println("=======")

    q := r.URL.Query()
    spew.Dump(q)
}

func testFuncPost(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
    r.ParseForm()
    f := r.Form
    spew.Dump(f)
}

httprouterくらいは使わないとさすがにアレなコードになる気はしている。

curl "http://localhost:9999/test" -H "Hoge:Fugaaa" -XPOST -d "a=1" -d "a=2" -d "a=3" -d "vals=[{'key':'value1'},{'key':'value2']"
 (string) (len=1) "v": ([]string) (len=1 cap=1) {
  (string) (len=5) "1,2,3"
 }
}
(url.Values) (len=2) {
 (string) (len=4) "vals": ([]string) (len=1 cap=1) {
  (string) (len=34) "[{'key':'value1'},{'key':'value2']"
 },
 (string) (len=1) "a": ([]string) (len=3 cap=4) {
  (string) (len=1) "1",
  (string) (len=1) "2",
  (string) (len=1) "3"
 }
}
-d "a=1" -d "a=2" -d "a=3"

この送り方で配列になるのは意外だった。RFCにでもそう書いてあるのだろうか(調べるか)