おいしい健康 開発者ブログ

株式会社おいしい健康で働くエンジニア・デザイナーが社内の様子をお伝えします。

Linuxでプロセスごとに開いているファイルディスクリプタの数を調べる

こんにちは 山下(@tomorrowkey) です。

Linuxをセットアップする時にすぐに数を大きくするfile descriptorの数ですが、どのプロセスがどのくらいファイルを開いているか確認する方法を知らなかったので調べました。 早速最終的なコマンドから。例えばunicornが開いているファイルの数を調べるならこんな感じ。

for i in $(ps aux | grep "[u]nicorn" | awk '{print $2}'); do  ls /proc/$i/fd | wc -l; done

開いているファイルディスクリプタ/proc/$PID/fdにあるので、数を数えればOKです。
調べたことなど書きます。

dockerでfile descriptorの数を設定する

検証にはdockerを使います。 起動時に--ulimitというオプションを渡すだけで制限が可能です。 検証用なので極端に小さく設定します。

$ docker run --ulimit="nofile=64" --rm -it ruby:2.4.2-jessie /bin/bash
root@ea6e45c79d10:/# ulimit -n
64

irbを起動しただけのfile descriptorの数を調べる

プロセスIDを調べます。

# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.1  20244  3208 pts/0    Ss   00:35   0:00 /bin/bash
root         7  0.1  0.1  20244  3196 pts/1    Ss   00:35   0:00 /bin/bash
root        14  1.6  0.4  47672  9632 pts/0    Sl+  00:35   0:00 irb
root        16  0.0  0.1  17500  2072 pts/1    R+   00:35   0:00 ps aux

file descriptorの数を調べます。

# ls /proc/14/fd | wc -l
9

9個でした。

今度はlsofで数を確認します。 rubyのdockerイメージにはlsofがインストールされていないので、インストールします。

# apt-get update && apt-get install -y lsof
# lsof -p 14
COMMAND PID USER   FD   TYPE DEVICE SIZE/OFF    NODE NAME
irb      14 root  cwd    DIR   0,46     4096   52302 /
irb      14 root  rtd    DIR   0,46     4096   52302 /
irb      14 root  txt    REG  254,1   146248 3145942 /usr/local/bin/ruby
irb      14 root  mem    REG  254,1   171800 1444564 /lib/x86_64-linux-gnu/libtinfo.so.5.9
irb      14 root  mem    REG  254,1   151120 1444515 /lib/x86_64-linux-gnu/libncurses.so.5.9
irb      14 root  mem    REG  254,1   297040 1444546 /lib/x86_64-linux-gnu/libreadline.so.6.3
irb      14 root  mem    REG  254,1   260648 3146915 /usr/local/lib/ruby/2.4.0/x86_64-linux/readline.so
irb      14 root  mem    REG  254,1   292976 3146919 /usr/local/lib/ruby/2.4.0/x86_64-linux/stringio.so
irb      14 root  mem    REG  254,1    33400 3146874 /usr/local/lib/ruby/2.4.0/x86_64-linux/enc/trans/transdb.so
irb      14 root  mem    REG  254,1   183168 3146831 /usr/local/lib/ruby/2.4.0/x86_64-linux/enc/encdb.so
irb      14 root  mem    REG  254,1  1738176 1444479 /lib/x86_64-linux-gnu/libc-2.19.so
irb      14 root  mem    REG  254,1  1051056 1444509 /lib/x86_64-linux-gnu/libm-2.19.so
irb      14 root  mem    REG  254,1    35176 1444487 /lib/x86_64-linux-gnu/libcrypt-2.19.so
irb      14 root  mem    REG  254,1    14664 1444492 /lib/x86_64-linux-gnu/libdl-2.19.so
irb      14 root  mem    REG  254,1   137384 1444543 /lib/x86_64-linux-gnu/libpthread-2.19.so
irb      14 root  mem    REG  254,1 15803280 3145980 /usr/local/lib/libruby.so.2.4.2
irb      14 root  mem    REG  254,1   140928 1444461 /lib/x86_64-linux-gnu/ld-2.19.so
irb      14 root    0u   CHR  136,0      0t0       3 /dev/pts/0
irb      14 root    1u   CHR  136,0      0t0       3 /dev/pts/0
irb      14 root    2u   CHR  136,0      0t0       3 /dev/pts/0
irb      14 root    3r  FIFO   0,10      0t0   54293 pipe
irb      14 root    4w  FIFO   0,10      0t0   54293 pipe
irb      14 root    5r  FIFO   0,10      0t0   54294 pipe
irb      14 root    6w  FIFO   0,10      0t0   54294 pipe
irb      14 root    7u   CHR  136,0      0t0       3 /dev/pts/0
irb      14 root    8u   CHR  136,0      0t0       3 /dev/pts/0

FDカラムに注目すると…

  • cwd -> current working directory
  • rtd -> root directory
  • txt -> program text
  • mem -> memory-mapped device
  • \[0-9]+r\ -> read-onlyで開いているファイル
  • \[0-9]+w\ -> write-onlyで開いているファイル
  • \[0-9]+u\ -> read-writeで開いてるファイル

lsofでfile descriptorの数を調べるにはFDが数字から始まるものを数えるとよさそう。 その他の値についてはこちら https://linux.die.net/man/8/lsof

lsofを使えばどのファイルを開いているかまで分かります。

たくさんのファイルを開いてfile descriporの数を調べる

スクリプトでファイルをたくさん開きます。

files = []
(1..(2 ** 10)).each do |i|
  puts i
  files << File.open("#{i}", "w")
  sleep 0.5
end
1
2
3
# ...
54
55
56
Errno::EMFILE: Too many open files @ rb_sysopen - 56
    from (irb):4:in `initialize'
  from (irb):4:in `open'
  from (irb):4:in `block in irb_binding'
    from (irb):2:in `each'
  from (irb):2
  from /usr/local/bin/irb:11:in `<main>'

無事ファイルの開き過ぎでエラーを吐きました。 irbをそのままに、違うターミナルでfile descriptorの数を調べます。

# for i in $(ps aux | grep "[i]rb" | awk '{print $2}'); do  ls /proc/$i/fd | wc -l; done
64

lsofで確認します。

# lsof -p $(ps aux | grep "[i]rb" | awk '{print $2}' | tr -d " ")
COMMAND PID USER   FD   TYPE DEVICE SIZE/OFF    NODE NAME
irb      14 root  cwd    DIR   0,46     4096   52302 /
irb      14 root  rtd    DIR   0,46     4096   52302 /
irb      14 root  txt    REG  254,1   146248 3145942 /usr/local/bin/ruby
irb      14 root  mem    REG  254,1   171800 1444564 /lib/x86_64-linux-gnu/libtinfo.so.5.9
irb      14 root  mem    REG  254,1   151120 1444515 /lib/x86_64-linux-gnu/libncurses.so.5.9
irb      14 root  mem    REG  254,1   297040 1444546 /lib/x86_64-linux-gnu/libreadline.so.6.3
irb      14 root  mem    REG  254,1   260648 3146915 /usr/local/lib/ruby/2.4.0/x86_64-linux/readline.so
irb      14 root  mem    REG  254,1   292976 3146919 /usr/local/lib/ruby/2.4.0/x86_64-linux/stringio.so
irb      14 root  mem    REG  254,1    33400 3146874 /usr/local/lib/ruby/2.4.0/x86_64-linux/enc/trans/transdb.so
irb      14 root  mem    REG  254,1   183168 3146831 /usr/local/lib/ruby/2.4.0/x86_64-linux/enc/encdb.so
irb      14 root  mem    REG  254,1  1738176 1444479 /lib/x86_64-linux-gnu/libc-2.19.so
irb      14 root  mem    REG  254,1  1051056 1444509 /lib/x86_64-linux-gnu/libm-2.19.so
irb      14 root  mem    REG  254,1    35176 1444487 /lib/x86_64-linux-gnu/libcrypt-2.19.so
irb      14 root  mem    REG  254,1    14664 1444492 /lib/x86_64-linux-gnu/libdl-2.19.so
irb      14 root  mem    REG  254,1   137384 1444543 /lib/x86_64-linux-gnu/libpthread-2.19.so
irb      14 root  mem    REG  254,1 15803280 3145980 /usr/local/lib/libruby.so.2.4.2
irb      14 root  mem    REG  254,1   140928 1444461 /lib/x86_64-linux-gnu/ld-2.19.so
irb      14 root    0u   CHR  136,0      0t0       3 /dev/pts/0
irb      14 root    1u   CHR  136,0      0t0       3 /dev/pts/0
irb      14 root    2u   CHR  136,0      0t0       3 /dev/pts/0
irb      14 root    3r  FIFO   0,10      0t0   54293 pipe
irb      14 root    4w  FIFO   0,10      0t0   54293 pipe
irb      14 root    5r  FIFO   0,10      0t0   54294 pipe
irb      14 root    6w  FIFO   0,10      0t0   54294 pipe
irb      14 root    7u   CHR  136,0      0t0       3 /dev/pts/0
irb      14 root    8u   CHR  136,0      0t0       3 /dev/pts/0
irb      14 root    9w   REG  254,1        0 3149355 /1
irb      14 root   10w   REG  254,1        0 3149365 /2
irb      14 root   11w   REG  254,1        0 3149369 /3
irb      14 root   12w   REG  254,1        0 3149372 /4
irb      14 root   13w   REG  254,1        0 3149422 /5
irb      14 root   14w   REG  254,1        0 3149423 /6
irb      14 root   15w   REG  254,1        0 3149425 /7
irb      14 root   16w   REG  254,1        0 3149455 /8
irb      14 root   17w   REG  254,1        0 3149457 /9
irb      14 root   18w   REG  254,1        0 3149458 /10
irb      14 root   19w   REG  254,1        0 3149459 /11
irb      14 root   20w   REG  254,1        0 3149460 /12
irb      14 root   21w   REG  254,1        0 3149461 /13
irb      14 root   22w   REG  254,1        0 3149462 /14
irb      14 root   23w   REG  254,1        0 3149463 /15
irb      14 root   24w   REG  254,1        0 3149464 /16
irb      14 root   25w   REG  254,1        0 3149465 /17
irb      14 root   26w   REG  254,1        0 3149466 /18
irb      14 root   27w   REG  254,1        0 3149467 /19
irb      14 root   28w   REG  254,1        0 3149468 /20
irb      14 root   29w   REG  254,1        0 3149469 /21
irb      14 root   30w   REG  254,1        0 3149470 /22
irb      14 root   31w   REG  254,1        0 3149471 /23
irb      14 root   32w   REG  254,1        0 3149472 /24
irb      14 root   33w   REG  254,1        0 3149473 /25
irb      14 root   34w   REG  254,1        0 3149474 /26
irb      14 root   35w   REG  254,1        0 3149475 /27
irb      14 root   36w   REG  254,1        0 3149476 /28
irb      14 root   37w   REG  254,1        0 3149477 /29
irb      14 root   38w   REG  254,1        0 3149478 /30
irb      14 root   39w   REG  254,1        0 3149479 /31
irb      14 root   40w   REG  254,1        0 3149480 /32
irb      14 root   41w   REG  254,1        0 3149481 /33
irb      14 root   42w   REG  254,1        0 3149482 /34
irb      14 root   43w   REG  254,1        0 3149483 /35
irb      14 root   44w   REG  254,1        0 3149484 /36
irb      14 root   45w   REG  254,1        0 3149485 /37
irb      14 root   46w   REG  254,1        0 3149486 /38
irb      14 root   47w   REG  254,1        0 3149487 /39
irb      14 root   48w   REG  254,1        0 3149488 /40
irb      14 root   49w   REG  254,1        0 3149489 /41
irb      14 root   50w   REG  254,1        0 3149490 /42
irb      14 root   51w   REG  254,1        0 3149491 /43
irb      14 root   52w   REG  254,1        0 3149492 /44
irb      14 root   53w   REG  254,1        0 3149493 /45
irb      14 root   54w   REG  254,1        0 3149494 /46
irb      14 root   55w   REG  254,1        0 3149495 /47
irb      14 root   56w   REG  254,1        0 3149496 /48
irb      14 root   57w   REG  254,1        0 3149497 /49
irb      14 root   58w   REG  254,1        0 3149498 /50
irb      14 root   59w   REG  254,1        0 3149499 /51
irb      14 root   60w   REG  254,1        0 3149500 /52
irb      14 root   61w   REG  254,1        0 3149501 /53
irb      14 root   62w   REG  254,1        0 3149502 /54
irb      14 root   63w   REG  254,1        0 3149503 /55

64個ファイルを開いていることが確認できました。

以上です。

Swift でドロップキャップな TextView を作る

こんにちは、関口@tanukiti1987 です。

今、おいしい健康では iOS アプリを開発しています。 そこで、ドロップキャップを使った表現が何箇所か登場する(予定)なのですが、 UITextView を純粋に使っただけでは、そういった表現が出来ずに、ちょっとした実装が必要になってきます。

今回は、そのちょっとした実装方法をご紹介していきます。

ドロップキャップ(drop-cap) とは

そもそも、ドロップキャップがなんなんだ。という話があると思います。 僕自身も実装を担当することになり、この言葉を知りました。

ドロップキャップは、こういうやつです。

f:id:oishi-kenko:20171115223923p:plain

1文字目が大きく表示され、以降1文字目を回り込むように文章が展開されていく例のやつです。

css の世界では

p:first-letter{
    font-size: 2em;
}

という指定をしてあげることで、このドロップキャップの表現が可能です。 いつもは嫌いだけど、この時ばかりは最高じゃないか、CSS

Swift ではどうやるのか

ここまで紹介しましたが、さきにも述べたように swift では :first-letter みたいなベンリ attribute はないので、自前でがんばっていくことになります。

使っていくのは、 UITextView UIImageView の2つです。

  • 一文字目を UIImageView としてコードで生成する
  • TextKit の exclusion path を指定することで 作った UIImageView の周りに文字を回り込ませる

この2点を行うことで、ドロップキャップを実現していきます。

文字を UIImageView として生成する

文字を画像として生成するには UIGraphics を使って、描画していきます。

今回は、以下のような関数を作ってみました。

@IBOutlet weak var dropCapImageView: UIImageView!

func createDropCapImage(text: String) -> UIImage {
    let size = CGSize(width: dropCapImageView.frame.size.width, height: dropCapImageView.frame.size.height)
    UIGraphicsBeginImageContext(size)

    let fontSize: UIFont = UIFont.systemFont(ofSize: 42.0)

    let style: NSMutableParagraphStyle = NSMutableParagraphStyle()
    style.alignment = NSTextAlignment.center
    style.lineBreakMode = NSLineBreakMode.byClipping
    style.minimumLineHeight = 30

    let attributes = [
        NSAttributedStringKey.font: fontSize,
        NSAttributedStringKey.paragraphStyle: style,
        NSAttributedStringKey.foregroundColor: UIColor.black,
        NSAttributedStringKey.backgroundColor: UIColor.clear
    ]

    text.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height), withAttributes: attributes)
    let image: UIImage = UIGraphicsGetImageFromCurrentImageContext() as UIImage!
    UIGraphicsEndImageContext()

    return image
}

font 情報を諸々設定したあとで text.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height), withAttributes: attributes) で文字を書き、 UIGraphicsGetImageFromCurrentImageContext() as UIImage! で画像を生成しています。

TextKit の exclusion path で文字を回り込ませる

先ほど作った画像を、UITextView の上に載せ、文字を回り込ませていきます。

こんな感じにviewを重ねます。

f:id:oishi-kenko:20171115230254p:plain

constraint などはよしなに調整してください。 今回のアプローチの少しイケてないところは、ドロップキャップの大きさを変えたり、テキストの attributes を変えると、レイアウトの微調整も必要になってくるところですね。。

ともあれ、他の方法もないので、この方法で突き進みます。

xibファイルが用意できたら、文字を回り込ませるような処理を書いていきます。

@IBOutlet weak var textView: UITextView!
@IBOutlet weak var dropCapImageView: UIImageView!
let text: String = "吾輩(わがはい)は猫である。名前はまだ無い。どこで生れたかとんと見当(けんとう)がつかぬ。"

textView.text = String(text[text.index(text.startIndex, offsetBy: 1)...])
setTextAttributes()

dropCapImageView.image = createDropCapImage(text: String(text[text.startIndex]))
let exclusionRect = CGRect(x: 0, y: 0, width: dropCapImageView.frame.size.width, height: dropCapImageView.frame.size.height)
let path: UIBezierPath = UIBezierPath(rect: exclusionRect)
textView.textContainer.exclusionPaths = [path]

また、ここで忘れてはいけないのは、1文字目は既に画像としてViewに含ませているので、2文字目以降の文字を UITextView に乗せていくというところです。

UITextView に2文字目以降の文字を代入し、 UIImageView に1文字目の画像を載せていきます。 最後に画像の周辺にテキストを回り込ませるための exclution path を生成し、 UITextView.textContainer.exclusionPaths に登録します。

ここまでいけば、完成です!

このあたりのコードを実装していけば、冒頭ご紹介したような

f:id:oishi-kenko:20171115232122p:plain

このような表示ができるはずです。

おしまいに

今回ご紹介したコードはこちらにおいておきました。

github.com

Xcode9.1 で作っていますので、古くて動かなくなったらご愛嬌ということで。 是非、みなさまのご参考になればと思います。

おしまいのおしまいに

おいしい健康では、一緒に Swift で iOS アプリを作ってくれる仲間を募集しています! 新しいプロジェクトであり、会社のコアプロダクトを作っているところですので、自由度も高く、もりもり開発していけます。

何より、この記事の内容を見て、思うところがある方とはぜひお話してみたいところです ( ̄ー ̄)

Swift初心者の関口がお送りしました〜。

Apache Solrでサジェスト機能を実装する

こんにちは。おいしい健康に入社して2ヶ月目の44akiです。

今回、おいしい健康のアプリで検索窓の入力サジェスト機能を導入する機会があり、 Apache SolrのSpellCheckComponentを使って実現しました。 おいしい健康のサービス全体でいえば小さな機能ではありますが、前職では検索エンジンベンダーでエンジニアをやっていたので、こういうところには無駄に拘ってしまいます。

この記事の内容は、Apache Solrのバージョン4.10.4で動作確認をしています。

f:id:oishi-kenko:20171107173455p:plain

候補語のデータについて

入力サジェスト機能を実現する場合、その候補語となるデータの取得元は大きく分けて2つあり、それぞれにメリット、デメリットがあります。

  1. 検索対象のデータから形態素解析で取得する
    検索対象のデータをmecab等の形態素解析エンジンで分かち書きして、候補語のデータとして使用する方法です。おいしい健康の場合はレシピ情報がこのデータにあたります。検索対象データを形態素解析して取得した候補語のため、必ず検索にヒットする候補語が取得できるというメリットがありますが、形態素解析エンジンの辞書データをもとに分かち書きされるため、辞書データの精度が低いと、候補語が名詞の途中で途切れたり、意図しない候補語が表示される可能性があります。

  2. 検索ログから取得する
    ユーザが実際に検索したキーワードをログとして保存しておき、候補語のデータとして使用する方法です。検索ログから候補語を取得しているため、ユーザーが実際の入力するようなキーワードをサジェストすることが出来ます。また、過去1ヶ月間の検索ログなど、集計対象の期間を絞ることで、キーワードのトレンドを反映し、秋ならサンマ、冬に近づくとサバを候補語の上位に表示するなどが実現できるようになります。ただし、検索ログベースのため、検索ログを取得する環境を構築する必要がある、検索にヒットしない候補語を除外する必要がある等、実装のコストは増えてしまいます。

今回は2の検索ログベースでサジェスト機能を実装しました。

schema.xml

先ずはApache Solrのスキーマを定義していきます。
候補語の表示用として name フィールドを定義します。

<fields>
    <field name="_version_" type="long" indexed="true" stored="true"/>
    <field name="id" type="string" multiValued="false" stored="true" indexed="true"/>
    <field name="name" type="string" multiValued="false" stored="true" indexed="true"/>
</fields>

また、検索用に text_ja_romaji の型を定義しています。 ローマ字に変換して検索を行うことで、入力途中のキーワードでもサジェストが行えるようにしています。

<fieldType name="text_ja_romaji" class="solr.TextField" positionIncrementGap="100" autoGeneratePhraseQueries="false">
  <analyzer>
    <tokenizer class="solr.JapaneseTokenizerFactory" mode="normal"/>
    <filter class="solr.CJKWidthFilterFactory"/>
    <filter class="solr.JapaneseReadingFormFilterFactory" useRomaji="true"/>
    <filter class="solr.ShingleFilterFactory" minShingleSize="2" maxShingleSize="99" outputUnigramsIfNoShingles="true" tokenSeparator=""/>
    <filter class="solr.LowerCaseFilterFactory"/>
  </analyzer>
</fieldType>

全体的な流れとしては、 JapaneseTokenizerFactory でKuromojiによる形態素解析を行い、 JapaneseReadingFormFilterFactory のオプションでuseRomajiを指定することで形態素解析で取得した読みをローマ字に変換します。最後に ShingleFilterFactory により形態素解析で分割された文字列を連結します。

ローマ字変換のイメージ>
おいしい健康→おいしい 健康→oishi kenko→oishikenko

solrconfig.xml

次にサジェスト用のsearchComponentとrequestHandlerを定義します。

今回はSolrの solr.SpellCheckComponent を使います。

<!-- Auto Complete component -->
<searchComponent class="solr.SpellCheckComponent" name="autocomplete_ja">
    <lst name="spellchecker">
      <str name="name">autocomplete_ja</str>
      <str name="classname">org.apache.solr.spelling.suggest.Suggester</str>
      <str name="lookupImpl">org.apache.solr.spelling.suggest.fst.AnalyzingLookupFactory</str>
      <str name="buildOnCommit">true</str>
      <str name="comparatorClass">score</str>
      <str name="field">name</str>
      <str name="suggestAnalyzerFieldType">text_ja_romaji</str>
      <bool name="exactMatchFirst">true</bool>
    </lst>
    <str name="queryAnalyzerFieldType">text_ja_romaji</str>
</searchComponent>

suggestAnalyzerFieldTypeとqueryAnalyzerFieldTypeに先程定義したtext_ja_romajiを指定することでローマ字での前方一致検索を行っています。

comparatorClassで候補語のソート順を指定します。score、freq、もしくは独自で作成したcomparatorClassが指定可能です。今回のように検索ログベースで、検索回数順にソートする場合は、通常はcomparatorClassを作成する必要がありますが、今回は前述のnameフィールドに「キーワード:検索回数」のようなフォーマットでインデックスし、アプリ側で取得したキーワードと検索回数をもとに検索回数順にソートすることで、簡易的に検索回数順ソートを実装しました。

最後に、上記のsearchComponentを「/autocomplete_ja」のパスで受けられるように、requestHandlerを定義します。

<!-- Auto Complete handler -->
<requestHandler name="/autocomplete_ja" class="org.apache.solr.handler.component.SearchHandler">
    <lst name="defaults">
      <str name="spellcheck">true</str>
      <str name="spellcheck.dictionary">autocomplete_ja</str>
      <str name="spellcheck.collate">true</str>
      <str name="spellcheck.count">100</str>
      <str name="spellcheck.onlyMorePopular">true</str>
    </lst>
    <arr name="components">
      <str>autocomplete_ja</str>
    </arr>
</requestHandler>

終わりに

今回はApache Solrの定義の部分のみの説明でしたが、実際には、Fluentd、BigQueryによる検索ログ取得、検索にヒットしない候補語の削除等も行いました。Apache SolrのSpellCheckComponentを使うことで比較的簡単に入力サジェストの実装を行うことができました。

MySQLにユーザーと権限を設定する

MySQLにユーザーを追加する機会がありましたので、ユーザー作成と権限追加について復習しました。
MySQL 5.6で動作確認しています。

dockerを使い、動作確認用のコンテナを起動します。 簡単に捨てられる環境が使えるのは便利ですね。

$ docker run -e MYSQL_ROOT_PASSWORD=password -p 13306:3306 --rm mysql:5.6

一通りログが流れきったら使えるようになります。 mysqlコマンドで母艦からmysqlにアクセスします。

$ mysql -u root -p -h 127.0.0.1 -P 13306

まずはユーザー一覧を表示します。

mysql> select Host, User from mysql.user;
+-----------+------+
| Host      | User |
+-----------+------+
| %         | root |
| localhost | root |
+-----------+------+

何も設定していないのでrootしかいない状態です。検証用のユーザーを作成します。

mysql> create user writable identified by 'hogehogehoge';

hogehogehogewritableユーザーのパスワードです。ユーザーを作成したので権限を確認してみます。

mysql> show grants for writable;
+---------------------------------------------------------------------------------------------------------+
| Grants for writable@%                                                                                   |
+---------------------------------------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO 'writable'@'%' IDENTIFIED BY PASSWORD '*E8DD65E018E30F27D962FB9BFA2F4E8206DC3AF8' |
+---------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

USAGEは権限なしを表します。この辺に権限の説明があります。 https://dev.mysql.com/doc/refman/5.6/ja/privileges-provided.html

rootの権限は*.*を対象にALL PRIVILEGESをもっているので、すべての権限をもっています。

mysql> show grants for root;
+--------------------------------------------------------------------------------------------------------------------------------+
| Grants for root@%                                                                                                              |
+--------------------------------------------------------------------------------------------------------------------------------+
| GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY PASSWORD '*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19' WITH GRANT OPTION |
+--------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

権限を付与するためのデータベースを作成します。

mysql> create database hoge;
Query OK, 1 row affected (0.00 sec)

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| hoge               |
| mysql              |
| performance_schema |
+--------------------+
4 rows in set (0.00 sec)

hogeデータベースが作成されました。

この時点でwritableユーザーでログインしてデータベース一覧を表示してもinformation_schemaしか表示されません。

$ mysql -u writable -p -h 127.0.0.1 -P 13306
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
+--------------------+
1 row in set (0.00 sec)

writableユーザーにhogeデータベースの全ての権限を付与します。

mysql> grant all privileges on hoge.* to writable@'%';
Query OK, 0 rows affected (0.00 sec)

mysql> show grants for writable;
+---------------------------------------------------------------------------------------------------------+
| Grants for writable@%                                                                                   |
+---------------------------------------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO 'writable'@'%' IDENTIFIED BY PASSWORD '*E8DD65E018E30F27D962FB9BFA2F4E8206DC3AF8' |
| GRANT ALL PRIVILEGES ON `hoge`.* TO 'writable'@'%'                                                      |
+---------------------------------------------------------------------------------------------------------+
2 rows in set (0.00 sec)

これでwritableユーザーが自由にhogeデータベースを操作できるようになりました。 再びwritableユーザーでログインしてデータベース一覧を見るとhogeデータベースが表示されます。

$ mysql -u writable -p -h 127.0.0.1 -P 13306
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| hoge               |
+--------------------+
2 rows in set (0.00 sec)

テーブルを作り、レコードをいくつか追加します。

mysql> create table users(
    ->   id int auto_increment not null primary key,
    ->   name varchar(32)
    -> );
Query OK, 0 rows affected (0.08 sec)

mysql> insert into users(name) values('Taro'),('Hanako'),('Jiro');
Query OK, 3 rows affected (0.02 sec)
Records: 3  Duplicates: 0  Warnings: 0

mysql> select * from users;
+----+--------+
| id | name   |
+----+--------+
|  1 | Taro   |
|  2 | Hanako |
|  3 | Jiro   |
+----+--------+
3 rows in set (0.00 sec)

再びrootに戻り、今度はreadonlyユーザーを追加します。 読み込みだけできればいいので、SELECT権限だけ付与します。

mysql> create user readonly identified by 'hogehogehoge';
Query OK, 0 rows affected (0.00 sec)

mysql> grant select on hoge.* to readonly@'%';
Query OK, 0 rows affected (0.00 sec)

mysql> show grants for readonly;
+---------------------------------------------------------------------------------------------------------+
| Grants for readonly@%                                                                                   |
+---------------------------------------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO 'readonly'@'%' IDENTIFIED BY PASSWORD '*E8DD65E018E30F27D962FB9BFA2F4E8206DC3AF8' |
| GRANT SELECT ON `hoge`.* TO 'readonly'@'%'                                                              |
+---------------------------------------------------------------------------------------------------------+
2 rows in set (0.00 sec)

ユーザーの作成ができたので、動作確認してみます。

$ mysql -u readonly -p -h 127.0.0.1 -P 13306

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| hoge               |
+--------------------+
2 rows in set (0.00 sec)

mysql> use hoge;
Database changed
mysql> show tables;
+----------------+
| Tables_in_hoge |
+----------------+
| users          |
+----------------+
1 row in set (0.00 sec)

mysql> select * from users;
+----+--------+
| id | name   |
+----+--------+
|  1 | Taro   |
|  2 | Hanako |
|  3 | Jiro   |
+----+--------+
3 rows in set (0.00 sec)

mysql> insert into users(name) values('Saburou');
ERROR 1142 (42000): INSERT command denied to user 'readonly'@'172.17.0.1' for table 'users'

select文で内容を表示することはできましたが、insert文を実行することはできませんでした。

以上です。

esa のトップページを活発にするなら esapad があるじゃない

(\( ⁰⊖⁰)/) < こんばんは!

おいしい健康でいろんな開発を担当しています、関口 id:tanukiti1987 です。

社名やサービス名となっているおいしい「健康」ですが、今日の晩ご飯はカップラーメンを食べてしまいました。

全粒粉入りの麺だということだったので、それでご勘弁願いたいです。

情報共有ツールとしての esa

弊社では、諸々の事情により社内の情報共有ツールに esa を使っています。

社内wiki として非常に整理された機能を持っており、合わせて WIP でモノを書いていこう!荒々のうちにアウトプットしていこうぜ。のコンセプトが非常に気に入っております。

GoogleAnalytics を仕込むこともでき、みんながいつどれくらい活用しているかだって見れちゃいます。 自前のS3バケットに画像などを保存していくこともできます。

できることが結構多い!最高!

esapad is 何

esa はレスポンシブにデザインされていて、ある程度広い画面で見ていれば 「Recently updated」という項目が見れて、最近のみんなの動きが見れるのですが、ノートPCで特に意識せずにブラウジングしているくらいでは、この項目がまー、出てこない。

そこで esapad です。

github.com

READMEとなるページに特定のタグを埋め込んでおき、 この rubyコードを実行すると、指定したページに stock な記事、flow な記事、最近スターされた記事などをリストでリプレイスしてくれます。

弊社ではこんな感じで活用しています。

f:id:oishi-kenko:20170818204523p:plain

全然見せられそうなのがないので、 esapad に対応した!というblog 記事だけモザイクなしです..

この esapad を定期的にジョブ管理システムに実行させるようにして、対応がサクッと完了します。

弊社ではトップページを開いたときに常にこの stock/flow の記事を見られるようにして、動きを少しでも出すようにしています。

動きがでることで更に活発になっていくことを願って、やってみました。

最後に

esapad は id:hogelog さんが作ったものですが、 gem を公開してくださいよ〜。とツンツンしていたら僕 contributor に加えてくださり、僕が少しだけ手を加えたものです。

こんなことやってみたいんだけど。。使ってみたけどバグってる。。などありましたら、お気軽に issue を立てたり、pull request したりしてください :)

それでは、よい週末を〜。

あれっ。 Xcode9(beta5) が遅すぎる... と思ったときにありがちな問題

こんばんは! 関口@tanukiti1987 です。

おいしい健康にも新たな動きがあり、今は iOS 開発に取り組み始めています。

いきなり余談ですが、新卒で入った会社では iPhone OS 3, iOS 4 までは iPhoneアプリの開発に携わっていました。 今はもう Swift でのアプリ開発が主流になってきていますが、当初はまだ Swift の影もなく、 Objective-C が現役を勤めているころでした。

あれから5年。

久しぶりの iPhone アプリ開発に心踊らせ、弊社のプロジェクトは Swift 4.0 と Xcode9 beta5 で開発を進めています。 このまま、開発を続けていってもアプリをリリースするころには、iOS11もXcode9 も出ているだろうし、それを見越した開発です。

余談が長くなりましたが、久々に起動したXcodeは時折鬼のように遅く、マシンをフリーズさせることもしばしば。。 仕事進めさせてくれよ。。と思っていろいろ調べていると、2つ気をつけなければならないところがあったようでした。

1. IBDesignablesAgentCocoaTouch のCPU食い過ぎ問題

Xcode がフリーズしたときにアクティビティモニターを見ていて気になったこのプロセス。

どうやらプロジェクト内で @IBDesignable を定義していると動くプロセスみたいで。

今週から開発し始めた僕は、 @IBDesignable を意気揚々と定義して、ストーリーボードで開発しやすくするぞ!と思っていたのが仇となっていたようです。

PCのスペックの問題もあるかと思いますが、僕のPCでは苦痛なほど作業効率を悪くするものだったので、スッと消して置きました。

ちなみに Editor -> Automatically Refresh Views をオフにすることでこの手の事象を回避することもできるようです。

stackoverflow.com

2. XcodeまわりのプロセスがCPU食いすぎてマシン応答が劣化していく問題

開発を続けていくと、これと言って何か重い処理をしているわけではないのに、Xcode周りのプロセスがどんどんCPUを使っていき、しまいにはフリーズすることもしばしば。

しばらく困り果てていましたが、 Xcode のウィンドウを開いた状態で Product -> Clean またはオプションキーを押しながら Product -> Clean Build Folder をしてみました。

すると、少なくともCPUが張り付くことがなくなり、ビルド途中でフリーズするとこともなくなりました。

この手の不具合は、5年前にもあったようなきがするけど、すっかり忘れていました。

ときどきクリーンしよう。

終わりに

久々に Xcode を開いてみましたが、 beta を使っていることもあってか、それ以外の不具合にも多数遭遇します。 若干チャレンジングだと思ってはいますが、Swiftもだいぶ書きごこちよく、楽しく開発していますヨ :)

おいしい健康の開発者、本格募集をはじめました!

f:id:oishi-kenko:20170630111418j:plain

おいしい健康で開発全般を担当している関口です。 こんにちは!

4本目の Tech Blog で、さっそく求人のことを書くのはどうなのよ?と自問自答を繰り返しましたが、やっぱりみなさんにお知らせしたい!ということで、おいしい健康で開発者(デザイナも!)の本格募集を始めたお話です。

求人内容のご紹介

今回の求人への思い(番外編)

他社のたくさんの原稿があるなかで、少し毛色の異なる求人なのかなと思っています。

おいしい健康では、どんな思いで日々開発を進めているのかを大切に書かせていただきました。 一方で、福利厚生や待遇面については文字数制限の兼ね合いもあり、控えめに書いています。

まだまだ小さい会社です。はたらきやすさ。というのは、全社員で話し合っていく余地が十分にあり、正しく主張していくことでどれほどにでも働きやすくなっていく環境にあります。どうかその点は心配しないで、来ていただきたいと思っています。

働きやすさの一方で、正直、求めているスキルはすこし高いのかもしれません。 それぞれのスキルや軸がしっかりしているからこそ、その信頼の元にはたらきやすい環境を築ける。というのが僕の理解です。

会社が立ち上がってからの、初めての採用活動です。 いただいた書類は、ていねいに見させていただきます!

面白そうだな、なんか違うな、へーッ。と思ったかた、それぞれの感想があるかと思いますが、共感いただけると一人でもおおくお会いできることを楽しみにしています!