More Related Content Similar to jjugccc2018 app review postmortem Similar to jjugccc2018 app review postmortem(20) jjugccc2018 app review postmortem3. アジェンダ
• 自己紹介
• アーキテクチャの説明
• レビューについて
– どのレベルで?
– どういう観点で?
– 指摘が多いもの/具体的な内容
• 性能問題
– JVMの話
– JVMの外の話
• 障害例
• ボトルネックの見つけ方
• 負荷試験
• おまけ
3
4. 自己紹介
• Name: Kiyotaka Suzuki
• Twitter: @tamtam180
• Main works
– SquareEnix (5.5Y)
• PlayOnline, FF-XIV, etc.
– SmartNews (4Y〜)
• Cross-functional Expert Team
– Software Engineer
– 広告, 公共
4
16. レビューの観点
• 計算量/メモリ空間
– O(1), O(LogN), O(N), …
– メモリはワーキングメモリ/定常使用 両方
• Objectの生成数
• オンライン処理かバックグラウンド処理か
• スレッドセーフか
• ブロッキング処理になっていないか
• 副作用(他の機能を壊していないか)
• 撤退可能かどうか
• お金でスケールできる設計になっているか
• 意思のあるコードか
– (意味を理解していないコピペかどうか)
16
23. 指摘が多い内容/プログラム編
• Iterator
23
Long2LongMap map1;
for (Map.Entry<Long, Long> entry : map1.entrySet()) {
Long k = entry.getKey();
Long v = entry.getValue();
}
ObjectIterator<Long2LongMap.Entry> itr =
map1.long2LongEntrySet().fastIterator();
while (itr.hasNext()) {
Long2LongMap.Entry e = itr.next();
long k = e.getLongKey();
long v = e.getLongValue();
}
24. 指摘が多い内容/プログラム編
• ラッパークラスを instance で比較する
24
Boolean val = returnBooleanMethod();
Optional<Boolean> optB = hogefuga();
if (optB.orElse(null) == val) {
}
Boolean b1 = new Boolean(true);
Boolean b_box_1 = true;
Boolean b2 = Boolean.TRUE;
Boolean b3 = Boolean.FALSE;
System.out.println(b1 == b2); // false
System.out.println(b1 == b3); // false
System.out.println(b_box_1 == b2); // true
25. 指摘が多い内容/プログラム編
• 古いJavaの知識のまま止まっている
25
Charset UTF8 = Charset.forName("UTF-8");
new InputStreamReader(System.in, UTF8);
new InputStreamReader(System.in, StandardCharsets.UTF_8);
try-with-resource, exception-multi-cache, stream, lambda, etc, etc..
I/F default実装, final化
など
31. 指摘が多い内容/プログラム編
• Enumのvalues()
31
enum Color {
RED(1), YELLOW(2), BLUE(3), BLACK(4), WHITE(5);
private final int value;
private Color(int value) { this.value = value; }
private Color lookup(int value) {
for (Color c : values()) {
if (c.value == value) return c;
}
return null;
}
}
逆引きMapをstatic{}で作ろう
values()は毎回配列が生成される
(配列はImmutableではないため)
32. 指摘が多い内容/プログラム編
• スレッド/ デッドロック
– ワーカースレッドから同じワーカースレッドを使う
32
ExecutorService svc;
svc.submit(() -> {
// 何かの処理
svc.submit(() -> {
// 何かの処理
});
});
ExecutorServiceのRejectポリシー実装によりますが、デッドロックします
33. 指摘が多い内容/プログラム編
• スレッド/ サイレント
– スレッドの中で例外発生に気が付かない
33
ExecutorService svc;
svc.execute(() -> {
something();
raiseException(); // ここで例外発生
veryImportantProcess(); // 実行されない
});
例外が何処にも出ないので気が付かない
35. 指摘が多い内容/プログラム編
• スレッド/ スレッドローカル問題
– JettyのHttpServletRequestを別スレッドから参
照する
35
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
asyncWorkers.execute(() -> {
log.info("real-ip={}", req.getHeader("X-FORWARDED-FOR"));
});
}
JettyはRequestにThreadLocalを使用しているので、
別のスレッドでは値を参照できない
36. 指摘が多い内容/プログラム編
• スレッド/ forkJoinを使って並列処理
36
ForkJoinPool forkJoinPool = new ForkJoinPool(2);
forkJoinPool.execute(() -> {
Arrays.stream(new int[]{ 1,2,3,4,5,6,7,8,9,10 })
.parallel()
.forEach(ii -> {
// process
});
});
1つのTaskが重いと他のTaskを道連れにする
https://blog.orz.at/2017/10/18/forkjoin/
37. 指摘が多い内容/プログラム編
• スレッド/ 無限キューでOOM
37
ExecutorService svc1 = Executors.newFixedThreadPool(10);
svc1.execute(() -> {
// 重い処理
});
ピーク時に処理が追いつかなくてキューが溜まる
メモリ使用量が増える
OutOfMemoryError
new ThreadPoolExecutor(…, new LinkedBlockingQueue<Runnable>());
// 無限キュー
RejectedExecutionHandlerを実装する(CallerRunsPolicyとか)
Reject時のRetry処理を入れるとか
40. 指摘が多い内容/プログラム編
• Lambda + レキシカルスコープ参照
40
Lambda Compilerの問題でもある
Lambdaの中でレキシカルスコープ参照で動的内部クラス生成
(Singleton実装にならない)
JITによりバイトコード変換, Metaspaceへ
使われなく案ってClass unloadedが定常的に発生する
Object obj = new Object();
lambdaCaller.execute(() -> {
obj.toString();
});
41. 指摘が多い内容/プログラム編
• 勝手に依存を追加する
– Jackson, AwsSDK, Logbackの挙動が変わる
41
mvn dependency:tree –Doutput=dependency.txt
勝手にライブラリのバージョンを上げたり、
ライブラリを追加して他の依存バージョンを更新してしまう
依存関係をきちんとgitで管理する
気が付かないまま次の副作用が
Jackson: JSONのデフォルトの変換処理が変わる
AwsSDK: 初期化処理やCredentialの更新周りの変更
Logger: 設定ファイルの変更(<Encode>属性の場所)とか
42. 指摘が多い内容/プログラム編
• Spring FrameworkのProxy化
– Annotationが消える
42
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {}
@Component @MyAnnotation
public void MyClass {
@Transactional public void dbAccess() { … }
}
@Autowired private MyClass myClass;
myClass.getClass().getAnnotation(MyAnnotation.class); // NULL
45. せっかくなので画像処理編
• 画像処理
– お行儀の悪い画像を読み込むと例外が出る
– APP0-JFIFが欠落しているケース
• byte[]を弄ってからImageIOを使う
• APP0-JFIFの情報を無理矢理入れる
45
http://hp.vector.co.jp/authors/VA032610/
byte[0..3] == { 0xff, 0xd8, 0xff, !{0xe0 && 0xe1} }
// 以下を差し込む
{ 0xff, 0xe0, 0x00, 0x10, 'J', 'F', 'I', 'F', 0x00,
0x01, 0x01, 0x01, 0, 72, 0, 72, 0, 0};
46. 指摘が多い内容/プログラム編
• InstanceOf / DownCast
– 正しく設計していれば殆どのケースで使う事は無い
– これが出てくる時点で設計ミスを疑ったほうが良い
• 特にDownCast
• InstanceOf <Interface>のミスマッチは特に処理
コストが高い
46
47. 指摘が多い内容/プログラム編
• String#replaceAll / String#replace
– 正規表現が不要な置換処理でもreplaceAllを使っている
– 単純な置換処理であればreplaceで良い
47
String s = "abracatabra";
s.replaceAll("ra", "&&");
String s = "abracatabra";
s.replace("ra", "&&");
53. 性能問題
• CPU L2キャッシュ
– 要素数が少ない場合はシーケンシャルアクセスの方が速
い事が多い
– Sortも同様
– Mapも同様
• Get: MapはO(1)で ArrayはO(N)だけど、要素数が少な
いとArrayのが速い
• アルゴリズム上の計算量だけではなく
目に見えない定数項を考慮
53
64. JVMの外の話
• ClockSource
– EC2(KVMベースのc5,m5は除く)はxen
– ClockSourceもxen
– getTimeOfDayはvDSOでユーザーランドで動く
• xenだとカーネルランドで動く
• System.currentTimeMillis / System.nanoTime
– gettimeofday, clock_gettime
64
cat /sys/devices/system/clocksource/clocksource0/current_clocksource
xen
echo tsc >/sys/devices/system/clocksource/clocksource0/current_clocksource
危険
66. JVMの外の話
• TCP Offload
– OFFにしないとxenのネットワークドライバの不具合を
踏む事がある
– (最近のでは直っていると思う。たぶん。)
• RabbitMQでMirror設定を入れるとこれを踏んで
Panicを起こす事があった
66
for path in /proc/sys/net/ipv4/conf/*
do
nic=$(basename "$path")
if [[ $nic == eth* ]]; then
/sbin/ifconfig $nic txqueuelen 10000
/sbin/ethtool -K $nic ¥
rx off tx off sg off tso off ufo off gso off gro off lro off
fi
done
68. 障害例
• もぐら叩き問題
• Port番号Conflict
• IOPS不足
• AWSの問題
– リージョン間通信の経路がおかしくなる
– ElasticacheのARPテーブル問題(FIXED)
– DDBが定期的に短時間の間不安定になる
• GC多すぎ問題
• ブロッキング処理多発
• JVMクラッシュ
68
80. おまけ: 仕事の仕方編
• テストを書かない
• 検証をしないでいきなりProductionへ投入する
• 大きな新機能をデプロイ後、誰も反応できる関係者不在
• 既存の文化に合わせないで修正コードを書きまくる
• Etc..
80
😨