Stepwise Word(CODEEVAL)

最大文字数の単語を抽出し、段階的に表示させる問題。
injectwith_index と組み合わせて利用できるというのを知れたのが収穫。
 

CHALLENGE DESCRIPTION:

Print the longest word in a stepwise manner.

INPUT SAMPLE:

The first argument is a path to a file. Each line contains a test case with a list of words that have different or the same length.

cat dog hello
stop football play
music is my life

OUTPUT SAMPLE:

Find the longest word in each line and print it in one line in a stepwise manner. Separate each new step with a space. If there are several words of the same length and they are the longest, then print the first word from the list.

h *e **l ***l ****o
f *o **o ***t ****b *****a ******l *******l
m *u **s ***i ****c

CONSTRAINTS:

  1. The word length is from 1 to 10 characters.
  2. The number of words in a line is from 5 to 15.
  3. If there are several words of the same length and they are the longest, then print the first word from the list.
  4. The number of test cases is 40.

My Code

#!/usr/bin/env ruby -w

def stepwise(word)
  word.each_char.with_index.inject([]) do |result, (char, i)|
    mask = "#{'*' * i}"
    result << "#{mask}#{char}"
  end
end

ARGF.each_line do |line|
  words = line.chomp.split
  max_length_word = words.max_by(&:size)
  puts stepwise(max_length_word).join(" ")
end

WEB+DB PRESS Vol.94 Pumaについて

WEB+DB PRESS Vol.94

WEB+DB PRESS Vol.94

Rails5で標準採用されたPumaについての記事。
Unicornとの対比しつつ、process/thread 、GIL、スロークライアント問題について記載されています。
どれも知っておいて損はない話です。
後述していますが、Puma採用時にライブラリがスレッドセーフかどうかを確認しておかないといけないというのは、結構きついかもしれないですね(実際のところ気にしないといけないケースは少ないのでしょうが)。
 
以下、気になったところ。
 

GIL(Global Interpreter Lock)

プロセスごとにロックがかかる仕組み。

threads = Array.new(3) do
  Thread.new do 
    rand(10)
  end
end
threads.each(&:value)

MRIだとrand(10)のところは、GILでロックされているので、1つのスレッドしか実行できない。

ただし、I/O であればGILのロックは解放される。

require "open-uri"

threads = Array.new(3) do
  Thread.new do 
    open("http://example.com")
  end
end
threads.each(&:value)

MRIは、JRubyやRubiniusに比べてthreadによる効果は小さい。
GILを採用している理由は、MRI及びC拡張ライブラリがスレッドセーフでないこと、MRIをスレッドセーフにするとシングルスレッド利用時の性能が落ちることなど。

Railsでの標準採用の理由

  • Heroku スロークライアント問題の緩和のためPumaが採用された
  • WEBRickはAction Cableで利用するhijack APIに未対応。
    1プロセスでWeb用とWebSocket用のスレッドを取り扱うことができる。

Connection Pool数

RailsのConnection Pool数は、Pumaのスレッド数に合わせる必要があります。
RailsのConnection Poolについてはこちらが詳しいです。
Rails4.2のコネクションプールの実装を理解する - Akatsuki Hackers Lab | 株式会社アカツキ(Akatsuki Inc.)

1スレッドあたり1コネクションしか使いません。
つまり、シングルスレッドのUnicornでは、1ワーカープロセス = 1コネクションとなります。

プロセス数とスレッド数

Pumaのドキュメントによると、ワーカープロセス数は、MRIならCPUコア数の1.5倍。
スレッド数は同時接続数をワーカープロセス数で割った値、Pumaはデフォルト16に設定されていて、まともな数字であると記載されています。
puma/DEPLOYMENT.md at master · puma/puma

Use cluster mode and set the number of workers to 1.5x 
the number of cpu cores in the machine, minimum 2.

Set the number of threads to desired concurrent requests / number of workers. 
Puma defaults to 16 and that's a decent number.

Puma採用時に注意すべき点

どれも使用頻度は低いでしょうが、Rails5で追加された対応方法もあるようです。
でも、ライブラリがスレッドセーフかどうかを見ないといけないんですね。
これはちょっと大変だなぁ。

対応方法

スレッドローカル変数

# before
class Content
  class << self
    attr_accessor :adult
  end
end

# after
class Content
  class << self
    def adult=(boolean)
      Thread.current[:adult] = boolean
    end

    def adult
      Thread.current[:adult]
    end
  end
end

thread_cattr_accessor

Rails5以降で利用可。

# after
class Content
  thread_cattr_accessor :adult
end

WEB+DB PRESS Vol.94 MySQLのバックアップ・リストアが非常に良い

WEB+DB PRESS Vol.94

WEB+DB PRESS Vol.94

メルカリさんの記事。こんなに事細かに記事にしてくれるなんて有難い。

mysqldumpの問題点

–master-data

–master-data オプションではマスタにテーブルロックがかかる。
スレーブに対して実行した場合も同様なので、バックアップ専用のスレーブサーバを作成する。

–dump-slave

–dump-slaveオプションではバックアップ期間中にレプリケーションが停止する。
MySQL :: MySQL 5.6 リファレンスマニュアル :: 4.5.4 mysqldump — データベースバックアッププログラム

ourdump

kazeburo/ourdump: simplified mysql40dump
スレーブに対して実行する mysqldump の wrapper script。
(文字で書くとわかりにくので、是非紙面の図で見てください)

  1. FLUSH TABLES WITH READ LOCK
  2. SHOW MASTER/SLAVE STATUS
  3. 子プロセスでexec: mysql dump –single-transaction
  4. 親プロセスでpipe経由でバックアップ読み込む
  5. CRAETE TABLEの文字列を検出
  6. READ LOCK を解放
  7. CHANGE MASTERステートメントを出力

ロック期間は、1-6 までなので1s以内に解放される。
 
すごいんだけど、、、なんか悲しい。
–single-transaction と –dump-slave が共存できる仕組みがあれば良いのかなぁ。
みんなこんなことやってるんだろうか。

XtraBackup

物理バックアップツール。
トランザクションログを監視し、全て記録。リストア時にバックアップしたデータファイルに対してトランザクションログのバックアップファイルを使うことで不整合を解消。

$ innobackupex --defaults-file=/usr/my.cnf --user=root --password=password --salve-info --no-timestamp backup_dir

Percona XtraBackup – MySQL, MariaDB, and Percona Server Hot Backup Software for InnoDB & XtraDB
 
これはよさそう。

リストア

STRICT_TRANS_TABLES

リストア時にMySQLが起動すればデータの矛盾がないことが保証される。

ただし、STRICT_TRANS_TABLES が含まれていないと、
破損したバックアップデータでも無効な値や適当な値がリストアされます。

へー。こわい。
5.6からはデフォルト。

ourdump

メルカリのリストアでは10〜20時間。

$ zcat dump.sql.gz | mysql -u root db

リストア実行後にポイントインタイムリカバリを手動で行おうとすると時間がかる。
夜間実行後に手動ポイントインタイムリカバリするとロスが発生する。
 

以下のようなshellで一気に実行しているとのこと。

# restore.sh
#!/bin/sh
set -e
echo 'SET session sql_log_bin=0;'
echo 'SET session long_query_time=30;'
echo 'SET global innodb_flush_log_at_trx_commit=0;';
zcat dump.sql.gz
echo 'SET session sql_log_bin=1'
echo 'SET session long_query_time=0.1;'
echo 'SET global innnodb flush log at_trx_commit=1;';
echo "CHANGE MASTER TO MASTER_LOG_FILE='ファイル名', \
MASTER_LOG_POS=ポジション, MASTER_HOST='マスタのIPアドレス', \
MASTER_PORT=3306, MASTER_USER='レプリケーションユーザー', MASTER_PASSWORD='パスワード';"
echo 'start slave'
  • バイナリログ、スローログをoff
  • innodb_flush_log_at_trx_commit=0 でI/Oを減らす
$ sh restore.sh | mysql -uroot db

 
リストア直後はレプリケーションが追いついていない可能性もあるので、
スレーブで SHOW SLAVE STATUS を実行し、 Seconds_Behind_Master の値が0で継続していることを確認。

XtraBackup

メルカリのリストアでは1〜2時間。
新スレーブでMySQL停止後に実施。

$ innobackupex --defaults-file=/usr/my.cnf --apply-log backup_dir

# --move-backで起動時にデータファイルとして扱えるようにMySQLのデータ領域に移動
# --force-non-empty-directories で上書き
$ innobackupex --defaults-file=/usr/my.cnf ---move-back --force-non-empty-directories backup_dir
$ chown -R mysql:mysql mysql_data_dir

あとは、MySQL起動後に start slave; する。レプリケーション設定がmy.cnfでrelay_log_info_repository=tableとなっている必要がある。

See Also

WEB+DB PRESS Vol.93 SQL 高速化ガイド - rochefort’s blog