Skip to content

Blog

キャッシュフローを考慮して賃貸と分譲を比較する

キャッシュフローを考慮……?

例えばなのだが「1000 万円を〇〇に投資したら、10 年後に 1300 万円になりました! 300 万円、つまり 30%も儲かりました!」というような話をしている人がいる。これは儲かったと言えるのだろうか?これを年利に換算すると、2.6%程度の利益になる。これはインフレ率が 2-3%という前提で考えると、実質的にはほとんど儲からなかったもしくは損をした、 ということになる。S&P500 の平均リターンは 10%程度ということを考えると、ちょっと分が悪い投資だな、という感想になるだろう。これが逆に 1 年で 1300 万円になった場合は、 30%のリターンになるので、これはかなりいい投資だった、ということになる。

このように、投資の成果を評価するときには、単純に金額だけでなく、キャッシュフローを考慮することが重要になる。賃貸と不動産購入を比較するときに、「購入の場合はコレぐらいのコストがかかって、コレぐらいのリターンがあるので、差額でコレぐらい儲かる」というような比較をしている場合がままある。こういった比較だとキャッシュフローが考慮されていない、つまり時間軸を無視しているので、もうちょっとそれっぽい比較を行いたい。

ついでにいうと、比較も大抵の場合、賃貸の場合は家賃を払っただけで、比較対象となる不動産購入のシナリオと比べた場合の余剰資金の運用について考慮されていない。これも含めて考慮したい。

計算する方法

途中まで Google Spreadsheet で計算していたのだが、途中でめんどくさくなってしまったので Python で適当なライブラリを探していたら pyxirrという名前そのまんまなライブラリを見つけたので、それでそのまま計算してみることにする。

MORTGAGE_RATE = 0.005
MORTGAGE_PERIODS = 35

def Y(n: int) -> date:
    return date(2020 + n, 1, 1)

def annual_mortgage_payment(*, buy_price: int, downpayment: int) -> float:
    return -1 * pmt(MORTGAGE_RATE, MORTGAGE_PERIODS, buy_price - downpayment)


def mortgage_left(*, buy_price: int, downpayment: int, years: int) -> int:
    return (
        buy_price
        - downpayment
        + cumprinc(MORTGAGE_RATE, MORTGAGE_PERIODS, buy_price - downpayment, 1, years)
    )

35 年固定ローンで、金利が 0.5%の場合の年間のローン支払いと、ある年におけるローン残高を計算できるようになったので、もろもろのキャッシュフローを出してみる。ここでは次の動画にある設定を使ってみる。

def mortgage_payments(
    *, buy_price: int, downpayment: int, hoa_monthly: int, sell_price: int, years: int
) -> list[tuple[date, float]]:
    payment = annual_mortgage_payment(buy_price=buy_price, downpayment=downpayment)
    ret = [
        (Y(0), -downpayment),
        (Y(0), -0.03 * buy_price),
        (Y(0), -6),
        (Y(0), -0.03 * buy_price * 0.1),
        # 楽天銀行は定額
        # (Y(0), -0.022 * (buy_price - downpayment)),
        (Y(0), -33),
        (Y(0), -40),
        (Y(0), -15),
        (Y(0), -3),
        # ここは変わるけど……
        (Y(0), -40),
        (Y(0), -2),
        (Y(0), -8),
    ]
    for i in range(1, years + 1):
        ret.extend(
            [
                (Y(i), -payment),
                (Y(i), -12 * hoa_monthly),
                (Y(i), -10),
                (Y(i), 15),
            ]
        )
    left = mortgage_left(buy_price=buy_price, downpayment=downpayment, years=years)
    ret.extend(
        [
            (Y(years), -left),
            (Y(years), -7),
            (Y(years), -6),
            (Y(years), -0.03 * sell_price),
            (Y(years), sell_price),
        ]
    )
    return ret

動画にでている設定をほとんど流用している。いくつかの値は物件によって変わるのだが、たぶん大体同じぐらいのレベルの物件だったらあまり変わらないかなと思って、そのまま流用している。本当に本当に正確に計算したいならここらへんも変える必要があるけど、誤差の範囲のレベル(例えば数千万円規模の話の中の数万円程度の誤差)なので、 あまり気にしないことにする。

RENT_INITIAL_COST = 4
RENT_RETURN_COST = 0.5

def rent_payments(*, monthly: int, years: int) -> list[tuple[date, float]]:
    ret = [(Y(0), -monthly * RENT_INITIAL_COST)]
    for i in range(1, years + 1):
        ret.append((Y(i), -12 * monthly))
        if i % 2 == 0 and i != years:
            ret.append((Y(i), -monthly))
    ret.append((Y(years), monthly * RENT_RETURN_COST))
    return ret

賃貸の場合も同様に動画を参考にして値を出している。ここでの初期費用は家賃の 4 ヶ月分で、退去時に家賃の 0.5 ヶ月分が返ってくるという設定になっている。

実際にこの計算がある程度妥当な値を出すかということを確認するために、参考にした動画と同様の設定で N 年目にどれぐらいの値段で売れると、賃貸と購入のどちらが特になるのかという計算をしてみる。

buy_price = 7700
downpayment_ratio = 0
for years in range(1, 11):
    rent_flow = rent_payments(monthly=34, years=years)
    for sell_price in range(1000, 25000, 10):
        buy_flow = mortgage_payments(
            buy_price=buy_price,
            downpayment=buy_price * downpayment_ratio,
            hoa_monthly=3,
            sell_price=sell_price,
            years=years,
        )
        rent_npv = xnpv(0.02, rent_flow)
        buy_npv = xnpv(0.02, buy_flow)

        if buy_npv > rent_npv:
            appreciation = sell_price / buy_price
            print(f"Year {years}: {sell_price} {appreciation:.2f}")
            break

実行結果は次のようになる。

Year 1: 7900 1.03
Year 2: 7560 0.98
Year 3: 7170 0.93
Year 4: 6810 0.88
Year 5: 6420 0.83
Year 6: 6050 0.79
Year 7: 5650 0.73
Year 8: 5280 0.69
Year 9: 4860 0.63
Year 10: 4480 0.58

この値は多少購入の方が有利ではあるものの、概ね動画の結果と一致しているので、このコードである程度妥当な値がでると言えるだろう。これをベースにして、もうちょっと設定を変えて計算してみる。

頭金を払うとどうなるか

「頭金を払うと払う金利が減りますよ」といい感じに話されていることがままあるのだが、これが本当のところどうなのかを計算してみる。

# downpayment_ratio = 0.1 の場合
Year 1: 7920 1.03
Year 2: 7580 0.98
Year 3: 7210 0.94
Year 4: 6860 0.89
Year 5: 6480 0.84
Year 6: 6120 0.79
Year 7: 5730 0.74
Year 8: 5370 0.70
Year 9: 4970 0.65
Year 10: 4600 0.60

# downpayment_ratio = 0.2 の場合
Year 1: 7930 1.03
Year 2: 7600 0.99
Year 3: 7240 0.94
Year 4: 6910 0.90
Year 5: 6540 0.85
Year 6: 6200 0.81
Year 7: 5810 0.75
Year 8: 5460 0.71
Year 9: 5070 0.66
Year 10: 4710 0.61

やはり多少収益性が悪化するものの、そこまで大きく動くことはない。ここから更に現実的な設定を加えていく。

余剰資金を投資するとどうなるか

賃貸と購入の比較をするときに、ざっくりいうとつぎのようなパターンを比較していることが多い。

  • 頭金 2000 万円を払って不動産を購入する
  • おなじ 2000 万円をタンス預金して、家賃を払い続けて、最後の比較ではなぜか 2000 万円 はなかったことになっている。

この比較はおかしい。普通に考えて 2000 万円とかあったら投資する。ということで、賃貸と購入のそれぞれのベースのキャッシュフローを計算した上で、同時期にかかるキャッシュフローを比較、その上で差額部分を S&P500 に投資するという変形を行う。これにより比較シナリオは次のようになる。

  • 頭金 2000 万円を払って不動産を購入する。その後賃貸で家賃を払い続けるよりもラン ニングコストが低い場合は、その差額を S&P500 に投資する。
  • 2000 万円を S&P500 に投資する。その後購入したときと比べてランニングコストが低い 場合は、その差額を S&P500 に投資する。

この比較を行うために、2 つのキャッシュフローを受け取って、余剰資金投資を行った場合の新しいキャッシュフローを返す関数を作成する。

def free_cash_investment(
    flow1: list[tuple[date, float]], flow2: list[tuple[date, float]]
) -> (list[tuple[date, float]], list[tuple[date, float]]):
    agg1 = defaultdict(float)
    agg2 = defaultdict(float)
    for d, amount in flow1:
        agg1[d] += amount
    for d, amount in flow2:
        agg2[d] += amount

    dates = set(agg1.keys()) | set(agg2.keys())
    final_date = max(dates)

    free_cash1 = []
    free_cash2 = []
    ret1 = []
    ret2 = []
    for d in sorted(dates):
        am1 = agg1[d]
        am2 = agg2[d]
        if d == final_date:
            ret1.append((d, am1))
            ret2.append((d, am2))
            continue
        if am1 > am2:
            free_cash1.append((d, abs(am2 - am1)))
            ret1.append((d, am2))
            ret2.append((d, am2))
        else:
            free_cash2.append((d, abs(am2 - am1)))
            ret1.append((d, am1))
            ret2.append((d, am1))
    inv_result1 = 0
    inv_result2 = 0
    for d, amount in free_cash1:
        inv_result1 += amount * pow(1.1, final_date.year - d.year)
    for d, amount in free_cash2:
        inv_result2 += amount * pow(1.1, final_date.year - d.year)
    if inv_result1 == 0:
        inv_result1 = 0.001
    if inv_result2 == 0:
        inv_result2 = 0.001
    ret1.append((final_date, inv_result1))
    ret2.append((final_date, inv_result2))
    return (ret1, ret2)

S&P500 の歴史的な平均的なリターンがインフレ調整前で 10%程度なので、ここでは 10%のリターンを仮定している。NPV を使って計算しているので、ここでインフレ調整をしている。

buy_price = 7700
downpayment_ratio = 0
for years in range(1, 11):
    rent_flow = rent_payments(monthly=34, years=years)
    for sell_price in range(1000, 25000, 10):
        buy_flow = mortgage_payments(
            buy_price=buy_price,
            downpayment=buy_price * downpayment_ratio,
            hoa_monthly=3,
            sell_price=sell_price,
            years=years,
        )
        rent_flow, buy_flow = free_cash_investment(rent_flow, buy_flow)
        rent_npv = xnpv(0.02, rent_flow)
        buy_npv = xnpv(0.02, buy_flow)

        if buy_npv > rent_npv:
            appreciation = sell_price / buy_price
            print(f"Year {years}: {sell_price} {appreciation:.2f}")
            break

実行結果は次のようになる。

# downpayment_ratio = 0 の場合
Year 1: 7930 1.03
Year 2: 7590 0.99
Year 3: 7210 0.94
Year 4: 6840 0.89
Year 5: 6420 0.83
Year 6: 6010 0.78
Year 7: 5550 0.72
Year 8: 5100 0.66
Year 9: 4590 0.60
Year 10: 4080 0.53

# downpayment_ratio = 0.1 の場合
Year 1: 8000 1.04
Year 2: 7750 1.01
Year 3: 7450 0.97
Year 4: 7180 0.93
Year 5: 6860 0.89
Year 6: 6560 0.85
Year 7: 6220 0.81
Year 8: 5890 0.76
Year 9: 5520 0.72
Year 10: 5160 0.67

# downpayment_ratio = 0.2 の場合
Year 1: 8080 1.05
Year 2: 7910 1.03
Year 3: 7690 1.00
Year 4: 7510 0.98
Year 5: 7300 0.95
Year 6: 7110 0.92
Year 7: 6880 0.89
Year 8: 6690 0.87
Year 9: 6450 0.84
Year 10: 6250 0.81

このように頭金を払い、かつ賃貸の場合できちんと余剰資金を投資することを考慮すると 10 年後の損益分岐点がかなり変わることがわかる。参考にしている動画では 40%下落することはまずない、という前提で購入の方が有利だと言っているが、頭金・手付金を 20%払うという前提だと、この損益分岐点が 10 年後で 20%程度の下落率になる。

金利が 1%になった場合

ここまででは住宅ローン金利が 35 年固定で 0.5%だと仮定していたが、最近は金利が上昇する流れになってきているので、仮に 1%になった場合にどうなるかを計算してみる。

# downpayment_ratio = 0 の場合
Year 1: 7970 1.04
Year 2: 7670 1.00
Year 3: 7330 0.95
Year 4: 7010 0.91
Year 5: 6630 0.86
Year 6: 6280 0.82
Year 7: 5860 0.76
Year 8: 5470 0.71
Year 9: 5010 0.65
Year 10: 4570 0.59

# downpayment_ratio = 0.1 の場合
Year 1: 8040 1.04
Year 2: 7820 1.02
Year 3: 7560 0.98
Year 4: 7330 0.95
Year 5: 7050 0.92
Year 6: 6800 0.88
Year 7: 6500 0.84
Year 8: 6230 0.81
Year 9: 5900 0.77
Year 10: 5600 0.73

# downpayment_ratio = 0.2 の場合
Year 1: 8110 1.05
Year 2: 7970 1.04
Year 3: 7790 1.01
Year 4: 7650 0.99
Year 5: 7470 0.97
Year 6: 7320 0.95
Year 7: 7130 0.93
Year 8: 6980 0.91
Year 9: 6790 0.88
Year 10: 6640 0.86

金利が 0.5%から 1%に上昇した場合、損益分岐点がそこそこ上昇してくることがわかる。

購入価格を変えた場合

2024 年 6 月時点で、ドゥ・トゥールの 70 平米の物件価格が 1.25 億円ほど。それに対して家賃が 31 万円ほどに下落している。この価格で購入するとどうなるかを計算してみる。

# downpayment_ratio = 0 の場合
Year 1: 13190 1.06
Year 2: 13030 1.04
Year 3: 12830 1.03
Year 4: 12690 1.02
Year 5: 12520 1.00
Year 6: 12400 0.99
Year 7: 12260 0.98
Year 8: 12170 0.97
Year 9: 12070 0.97
Year 10: 12020 0.96

# downpayment_ratio = 0.1 の場合
Year 1: 13310 1.06
Year 2: 13270 1.06
Year 3: 13210 1.06
Year 4: 13210 1.06
Year 5: 13200 1.06
Year 6: 13240 1.06
Year 7: 13290 1.06
Year 8: 13400 1.07
Year 9: 13510 1.08
Year 10: 13700 1.10

# downpayment_ratio = 0.2 の場合
Year 1: 13430 1.07
Year 2: 13510 1.08
Year 3: 13580 1.09
Year 4: 13730 1.10
Year 5: 13870 1.11
Year 6: 14090 1.13
Year 7: 14320 1.15
Year 8: 14630 1.17
Year 9: 14960 1.20
Year 10: 15380 1.23

このように購入価格が上昇すると、損益分岐点がかなり上昇してくることがわかる。現状の価格で 20%の頭金を払うと、10 年後には 1.23 倍程度の価格で売却できない限りは賃貸の方が有利になる。

家賃固定で 10 年住んだ場合と比較した損益平衡購入価格

35 年固定金利 1%の環境で、家賃を 34 万円で固定して 10 年住んだ場合と比較して、果たしていくらぐらいの物件を買うのであれば賃貸よりも有利になるのかを計算してみる。

rent_flow = rent_payments(monthly=34, years=10)
for downpayment_ratio in (0, 0.05, 0.1, 0.15, 0.2):
    print(f"Downpayment Ratio: {downpayment_ratio}")
    for buy_price in range(20000, 7000, -1000):
        for sell_price in range(1000, 25000, 10):
            buy_flow = mortgage_payments(
                buy_price=buy_price,
                downpayment=buy_price * downpayment_ratio,
                hoa_monthly=3,
                sell_price=sell_price,
                years=10,
            )
            rent_flow, buy_flow = free_cash_investment(rent_flow, buy_flow)
            rent_npv = xnpv(0.02, rent_flow)
            buy_npv = xnpv(0.02, buy_flow)

            if buy_npv > rent_npv:
                appreciation = sell_price / buy_price
                print(f"\t{buy_price} -> {sell_price} {appreciation:.2f}")
                break

計算結果が次のようになる。

Downpayment Ratio: 0
        20000 -> 22020 1.10
        19000 -> 20600 1.08
        18000 -> 19190 1.07
        17000 -> 17770 1.05
        16000 -> 16350 1.02
        15000 -> 14940 1.00
        14000 -> 13520 0.97
        13000 -> 12100 0.93
        12000 -> 10680 0.89
        11000 -> 9260 0.84
        10000 -> 7850 0.79
        9000 -> 6430 0.71
        8000 -> 5010 0.63
Downpayment Ratio: 0.05
        20000 -> 23380 1.17
        19000 -> 21900 1.15
        18000 -> 20410 1.13
        17000 -> 18930 1.11
        16000 -> 17440 1.09
        15000 -> 15960 1.06
        14000 -> 14480 1.03
        13000 -> 12990 1.00
        12000 -> 11510 0.96
        11000 -> 10020 0.91
        10000 -> 8540 0.85
        9000 -> 7050 0.78
        8000 -> 5560 0.69
Downpayment Ratio: 0.1
        20000 -> 24740 1.24
        19000 -> 23190 1.22
        18000 -> 21640 1.20
        17000 -> 20090 1.18
        16000 -> 18540 1.16
        15000 -> 16990 1.13
        14000 -> 15430 1.10
        13000 -> 13880 1.07
        12000 -> 12330 1.03
        11000 -> 10780 0.98
        10000 -> 9230 0.92
        9000 -> 7670 0.85
        8000 -> 6120 0.77
Downpayment Ratio: 0.15
        19000 -> 24490 1.29
        18000 -> 22870 1.27
        17000 -> 21250 1.25
        16000 -> 19630 1.23
        15000 -> 18010 1.20
        14000 -> 16400 1.17
        13000 -> 14780 1.14
        12000 -> 13160 1.10
        11000 -> 11540 1.05
        10000 -> 9920 0.99
        9000 -> 8300 0.92
        8000 -> 6680 0.83
Downpayment Ratio: 0.2
        18000 -> 24100 1.34
        17000 -> 22410 1.32
        16000 -> 20730 1.30
        15000 -> 19040 1.27
        14000 -> 17360 1.24
        13000 -> 15670 1.21
        12000 -> 13980 1.17
        11000 -> 12300 1.12
        10000 -> 10610 1.06
        9000 -> 8920 0.99
        8000 -> 7240 0.91

こうしてみると、頭金が 0 の場合は価格の下落率についてそこそこ余裕ができるが、頭金を払えば払うほど、価格が上昇しない限り S&P500 に投資しながら賃貸に住むほうが有利になってくることわかる。

雑感・その他の要素について

東京のマンション価格が 2021 年ぐらいからかなり上昇しているので、それ以前の価格で買った人はかなり利益が出ているだろう。しかし今回の計算結果を見ると、現在の価格で購入する場合は、フルローンで購入する、もしくは今後 10 年の同様の値上がりが見込めると信じるない限り、(S&P500 に投資する)賃貸よりも儲かるかというとそうでもないようにみえる。

一般的に日本の不動産の価値は下落していくと言われている。流石にここ数年のような価格上昇が同様に続いていくとは考えにくいが、適切な物件を選べば緩やかな価格上昇もしくは以前よりも緩やかな価格下落ぐらいは有り得そうな感じがする。例えば、 このページ によると歴史的には 10 年経過後の価格は 20%程度下落しているが、下がるとしてもこれよりも緩いペースで下がるということが考えられる。この場合、頭金を極力抑えることができれば、現状の価格でもそれなりに有利な購入ができるように思われる。

上で行った計算についての細かい考慮事項としては、以下のようなものがある。

  • 家賃の上昇を考慮していない。日本では 2024 年に 17%ほど上昇しているものの、これも恒久的に 17%上がるとは考えにくい。アメリカでは給料も家賃も不動産価格も年々上昇していくのだが、日本では給与も不動産価格も歴史的にはそこまで上昇していない。このトレンドは変わりつつあると言えるかもしれないが、不動産価格の上昇が一部の都心のマンションに限られていることを考えると、全体的に家賃が毎年上がりつつけるかというと、あまりないように思える。仮に想定インフレ率 2%と同じペースで上昇するとすると、例えば頭金 0 円 1.2 億円の 10 年後損益分岐点が 89%から 85%ぐらいになる。
  • 売却時の各種税金を考慮してない。自分が住んでいる不動産を売却した場合は 3000 万円の控除が発生する。他方で S&P500 も NISA 枠を使えば税金がかからないパターンも考えられる。特に賃貸+S&P500 のパターンは、賃貸を乗り換える場合でも株式は売却しなくても良いため、最後の 10 年後にどういう税金がかかるかどうかは、個々人のライフプランによって変わるだろう。例えば 10 年後に賃貸にするのであれば、不動産の方は税金がかかるとした方が良いだろうし、逆に購入するのであれば、S&P500 の方は税金がかかるとしたほうが良いだろう。
  • 雑多な修繕費などは含んでいない。例えば購入した場合は定期的にエアコンや給湯器などの修繕が必要になる。これは賃貸の場合は大家持ちになるので、その分賃貸のほうがお金がかからない。他方で賃貸も数年に一回引っ越しをするということであれば、引越し費用がかかる。これらの支払いはあまり全体の損益に大きな影響を与えないと思われるが、もし細かく計算したいのであれば入れてみればよいだろう。
  • この記事では NPV を用いて、適当に日本のインフレターゲットの 2%を割引率として比較を行った。自分の理解が正しければ、この賃貸と購入の比較を行うにあたって用いた設定及び比較方法においては、どのような割引率を使っても結果は変わらないはずである。同様に、NPV ではなく IRR を使っても単純に同じ式をどちら側から見ているかだけなので、結果は変わらないはずである。というのも、余剰資金を投資するように補正を行った関係で、最終年度を除いてキャッシュフローがどちらのパターンも同一になっているからである。

このようにいくつかの具体的な数値および仮定をおいて、賃貸と購入の比較を行ってみたが、どちらかというと実際にどちらが得かという結果よりも、このような枠組みを作ることで、様々な仮定を追加、変更することによる結果の変化を見ることができたり、変数を変えていったときにどのような結果の変化が出るのかを自分で組むことができるということが重要だと思われる。また、コスト・リターンの計算に様々な要因があるなかで、 どの要素がどれぐらい効いてくるのかという理解も高められる。適当な数値を入れて計算して遊んでみてほしい。

Google を退職して Launchable に入社しました

新卒で Google に入社して 7 年ちょっと経って、なんか最近社内・社外問わず転職しているひとをチラホラ見るし、ちょっとキャリアパスを真面目に考えたら転職してもいいかなぁと思ったので転職した。ちょうどいい時期にLaunchable にポジションがあったので、コンタクトをとって採用パスにのせてもらったら、採用された感じ。

9 年ぶり 2 回目って書いたんですが、よく考えたらインターン後、日本から US 転籍後に退社しているので 6 年ぶり 3 回目が正しい気がする。

キャリアパスに思うところがあったとは

なんかつらつら書こうと思ったけど、Quora のWhy don't engineers make it further than being senior engineers at Google?の回答が良い感じの説明に見える。なんか Trajectory to L6 は悪くなかったというかマネージャーからは「既に L6 としての仕事してるし、次でプロモできるはずだから、それ待ってから転職したら?」って言われたけど、まぁなんか L6 っぽい仕事はつらい。そもそも L6 っぽい仕事がちゃんとできていた気がしない。この先 L6 になれたとして、あんまりこれは自分のやりたい仕事ではないなぁという感じがして、じゃあもうちょっと違う地平をみてみようという気持ちがあった。

こう書いてみるとネガティブといえばネガティブな理由な気もするけど、まぁ Carrer advancement とも言える。Google が提示する「これが Individual Contributor のキャリアパスだ」っていうのが、今のところ自分にとってはしっくりこない。だからもうちょっと違う環境に身をおいて、違う方向を目指してみたい。それでやっぱり Google の L6 っぽい仕事もいいなって思えてきたら、また Big tech に行くなり Google 戻るなりしてもいいし。

なんか「退職する!」って言ったらマネージャーとかディレクターとか(あと他のチームのマネージャーとか)からミーティングを設定されて、退職理由を聞かれたりするわけなんだけど、この理由を挙げるとみんな Big tech→Start-up→Big tech を繰り返してたりするので「まぁ、Start-up に一回行ってみるっていうのは良いよね。自分もあの経験は忘れられないよ!」って口を揃えていっていたりする。引き留めようとしているのか、 背中を押してくれているのか……

Launchable に入った

これはたまたまタイミングが合ったという感じ。たまたま上記の理由で転職しようかなぁ、と思ってたときにポジションがあって、たまたま繋がりがあって、US でもいいよっていうことになって、自分の興味も一致したし、良さそうだったので。自分としては何らかの DevOps, DevTools, EngProd あたりの分野をやっていきたかった、大企業とかかなり後期の Start-up よりかは、そこそこ初期のスタートアップで、US ベース、知っている人がいるみたいなのを考えていて、追加で Founder が既に Start-up を一回経験してるとか、ある程度の Market fit を確認できていると更に良かった。そこにガッチリ Launchable がはまって、追加で「お、日本にも人がいるってことは、そのうち日本に出張で行くみたいなのもあるかも」みたいなオマケがついてきた感じ。

そういったものの、実際は US に転籍するか日本に残るか、みたいな選択肢が降ってきたときも「うーん。5 年後を考えたときに US に行ったほうが圧倒的に楽しそう」みたいなノリで US に行く選択をしたし、「5 年後を考えたときに Launchable に行ったほうが圧倒的に楽しそう」みたいな気持ちで決めた気もする。

今週の頭から始めたのでまだ二日目が終わったところなんだけど、そういえば仕事中に日本語を喋ったりすることがあるというのがほぼ初めて or 久しぶりな気がするので新鮮な感じがする。Google の東京オフィスにいたときは、自分以外日本語ネイティブな人がいないチームだったので、日本語で仕事をすることはあんまりなかった気もする。 今のところ、日本語だから楽だなぁっていう感覚は思ったよりも無い。逆に言えば 7 年ぐらい英語話してれば、そこまで困らないぐらいには話せるようになるってことなのかも。希望がある話ですね。

ということで、まだどういう感じの仕事をしていくか良くわからないけど「draftcode が転職するっていうのをまだ言わないので、グッてこらえて言っていない」という状況にあるのはつらそうだったので公開情報にしようと思った次第です。

そういえば、自分がオファーレターにサインする前に https://rebuild.fm/309/ で Launchable が紹介されたのだけど、これは Rebuild 転職にあたるのだろうか。

ICFPC2021 感想

Special Weekend ってチームでダッシュボードっぽいやつを書いてました。バックエンドは Go、フロントエンドは React で書いてました。今回は本当に問題にノータッチだった……

仕事では HTML や JS 関係を全く触らないし、もっと言えば Docker も RDB も触らないという環境なので、ICFPC が唯一の触る環境だった感じもあります(社内に Docker じゃないコンテナフォーマットとか、Kubernetes の前身のコンテナオーケストレーションシステムとか、RDB の皮を被っているようでで被っていない DBMS などがある)。チーム変わってから Kubernetes とか AWS とか Azure を使い始めたので、そこらへんはわかってきた。

コンテスト前におさけーさんが React とか Material UI とか使うと適当に UI が作れて便利〜って言っていたので、当日ぶっつけ本番で Create React App ってやつをベースに適当に作りました。また、当日にぶっつけ本番で GKE を初めて使い、ほぼ初めて SQLite と MySQL を使ってバックエンドを作ってました。使った Tech stack の中で、Go と Kubernetes のジェネリックな部分だけがちゃんと知っているところだった気がする。

以下、記憶があるうちに箇条書きでどんなことをやったか:

  • とりあえず問題を解く班と解かない班(解かない班とは)に別れて、解かない班は手動 UI とダッシュボードを作る感じに。自分はダッシュボードをやる感じに。
  • 最近は Kubernetes を触っているし、GKE でいいやろって思って適当に GCP Project を作って GKE Autopilot でクラスタ作成。
  • ご自宅に立ててる Go のサーバー向けに使ってた Dockerfile をコピペして、Go が動くイメージを作ってデプロイ。マネージド Kubernetes 使ってると Service とか Ingress あたりがうまく統合されていて、ロードバランサーとかも適当に作られて便利。
    • あと GKE は Cloud Console から適当に Kubenetes オブジェクト見れることができて便利ですね。
    • でも Network Endpoint Group あたりのアタッチが遅い気がする……
    • ここらへんは Prober を適切に設定することで、改善した雰囲気もある。
  • 作らない班でデータどうしようか話す。みんなして「データベースは持ちたくない……」って話をする。これが仇になってあとで MySQL へのマイグレーションをすることになる。
    • 話の中では Git をデータベースとして使えばいいんでは、ってなって最初はそういうコードを書こうと思ったけど、途中でやっぱそれも面倒くさくなって Persistent Volume + SQLite という構成にした。
    • Git サーバーをやっていた身としては Git をデータベースとして使うのは万死に値する行為だけど、使う側からすれば便利っぽく見えるよね……
      • Git サーバー側からすれば、たかだか一秒に N 回(しかしフルで clone される場合もある)みたいな要件と、一秒に 1000×N 回(ただし 1 ファイルしか読まない)みたいなのが混在したトラフィックは超厳しいです……まぁ現実には 1000×N 回フル clone が一秒間に同時に来て死ぬんですが。
  • 雑に SQLite でデータを保存するやつを、マニュアル見ながらせこせこ書く。DB ファイルと問題と回答のファイルは適当に PV へ。
  • git push したあとに git fetch したら Go のコードはきれいになっていて、React のコードは TypeScript になっていた。すごい。
  • git push したあとに git fetch したらビジュアライザが勝手についていた。すごい。
  • Kubernetes は Secret あたりの管理が楽でいいなぁと思う
  • JavaScript よくわからないなぁって話をした気がする。Java とか Go はなんだかんだ言って仕様を読むと良いんだけど、JavaScript は ES の仕様を読むといいんだろうか、みたいな話をした。
  • 回答のバリデーションとか Dislike の計算をサーバーサイドでしたいけど、そのコードは Rust で書かれている…… 妥当なのは Dockerfile で Rust のバイナリをビルドするんだけど、どうせそんなに更新されないしな、と思ってバイナリをレポジトリに突っ込む。ギルティ。
  • 公式サイトから Minimal dislike をとってくるコードとかすでに submit された回答を scrape するコードをサーバーサイドに移植して定期的に走らせる。
    • ここらへんが API としてアクセス可能ではなくて厳しい。去年は一応すべてが API でアクセス可能だったので非常に良かった気がする。
  • ダッシュボードから公式に submit できるようにする
  • なんかデプロイするとダッシュボード(API)サーバーが一時的に落ちたりとかするのが厳しいという思いが出てくる。しかしデータベースが SQLite だったり、PV が ReadWriteOnce だったりしてレプリカ作るのは厳しい。そこにちょうどタスクキューシステムを Cloud SQL で動かすということになったので、データをすべて Cloud SQL の MySQL にマイグレーションするってことをやる。
    • 泣きながら MySQL のドキュメントを読んで SQL を書く。
    • 特に問題なくマイグレーション完了。ダウンタイム 20 分ぐらい。
  • ついでにフロントエンドのデプロイとバックエンドのデプロイを分けたい、という思いから分ける。バックエンドはフロントエンドの asset サーバーのプロキシとして動作するようにして、フロントエンドの asset サーバー単体で更新できるようにする。
    • まぁこれは API サーバーを最初から別ドメインで運用しておけばよかった気がする。
  • ダッシュボードからタスクキューに投げるボタンをつける
  • ダッシュボードが重すぎる
    • なんか Chrome のプロファイラすら動かない……と思ったら useEffect の部分があれで無限ループになってる部分があった。
    • それでも重いと思ったけど、ちゃんと Warning みて key をつけたら軽くなった。なんか想像するに React の仮想 DOM 計算時に key をベースにして計算を省いている部分がある?
    • あと、ダッシュボードが実質全回答をサーバーから取得するようになっていて厳しい。が、これは最後まで手をつけられず。まぁ数人しか使っていないサーバーだし……

以下、事後の感想:

  • なんか今年は FP な雰囲気が弱かった……気もする。まぁ毎年問題は FP と関係ないようになってる気がするけど、どこかしらに FP っぽいなにかが散らばってたような気もする。
  • JSON とかテキストで表現された問題郡に対して、色々JSON とかテキストで回答を提示し、それに対するメタデータ(点数とか)をつけてランキングをつくったりタグ付けしたり、というダッシュボード要件は変わらないんだから、パッとできるようになっておきたい。なっていたい……
    • まぁそれはタスクキューもそうだね……
    • もしかしたら雑に Mongo とか使うとこういうときは楽なのかもしれない
  • GKE と Cloud SQL はよかった
  • React 便利だったけど、当日ぶっつけでコピペしながら使ったので、微妙にこれ使い方違ったなってところがあったりする。あと Redux とかでグローバルな状態をそっちに押し込んでおけば良かったみたいな気持ちもある。
    • まぁでも 1 分ぐらいしか触ってない人でも、とりあえず見れるダッシュボードが作れる仕組みは良いですね。
  • RDB を脳死で使えるようになったほうがいいなぁ、と思いつつ、仕事で使わないと微妙にモチベーションがわかんのだよな…… Spanner で Query 書くのそこまで推奨されてないし、Query 書くのは Dremel 向けだし……
    • あと Spanner はスキーマ定義だけすれば、あとは差分が自動で適用されていいなぁと思いました。まぁ Spanner は Spanner で(というか地域分散する都合で)主キーの設計あたりで微妙に別プラクティスがある気がする(e.g. 番号が連続したキーを作らないとか)。
    • まぁ ICFPC で普段使わないツールを使ったり、チーム変わって違うツール使ったりするけど、なんだかんだ言ってすぐ使えるようになれてるので、必要になったら学ぶでも普段は問題ない気がする。こういうコンテストみたいに「今すぐ必要」みたいなときに、やっときゃよかったって思う。

こうやって見返してみると、本当に問題にノータッチだったな。まぁ問題ノータッチでも参加して楽しめるってことで。

2021 年仕事メモ

2017 年にキャリアパスについて適当に思ったことを書いてから 4 年、相変わらずキャリアパスよくわからんなぁ、と思うけど、最近さらによくわからない感じがある。

Google ではエンジニアレベルというのが決められていて、L3 か L4 ぐらいでスタートして上は L9 ぐらいまであるんだっけかな。なんかこういう説明だと「じゃあ中堅どころが L7 ぐらい?」みたいに見えるけど、L7 は普通に 7000 万とか 8000 万ぐらい貰えるっぽい人たちです。L5 とか L6 あたりが中堅と呼ばれるところじゃないだろうか。

各レベルごとに「こういうことができて欲しい」という期待値が設定されている。Ln のときには L(n+1)における期待値というのがよくわからない感じだったけれども、あとから L(n+1)になると「あー、なるほど。このレベルの期待値っていうのは、こういうことを言いたかったんだなぁ」と思うことがある。別に昇進が全てではないから、それに従う必要はないのだけれども、まぁわかりやすいし、先に言ったように期待値の設定自体にそこそこ妥当性を感じたりするので、それをベースに身の振る舞いを考えている。

現状 L5 になって数年経って、評価が Superb まで上がって、一回 L6 への昇進に挑戦してみて、まぁ駄目だった、というふうになっている。

昇進に挑戦してみる前から「まぁ駄目だろうなぁ」とは思っていたけど、そもそも「複数チームやプロダクトに渡った戦略に影響を与えられる」みたいなのがどういうことなのかを、そのときは具体的に想像できていなかった感じがする。失敗してから、一応意識して L6 とは、というのを見ているのだけど、最近なんとなく意図することがわかってきた気がする。

L5 まででは、まぁ複数チームや複数プロダクトにまたがった仕事をするし、何ならそれが長期に渡るようなことも多いのだけど、なんだかんだ言って結構 Tactic な仕事という感じが、今となってはする。長期に渡るのに Tactic とはって感じだけど、やっている仕事が「ある大きい Strategy を達成するための一部品」という感覚に近い。何が Tactic で何が Strategic かは、多分相対的なものだけれども、端的に言ってしまえば「より大きいレベルで仕事ができること」って感じになっている。

というのが最近わかってきたことで、次のレベルまでの距離がわかった(ような気がする)のはいいけれども、実際周りにいる L6+な人がやってる仕事を見ると、本当に L5 以前とはやってることが違って厳しい感じがある。

ちょっと前にデザインドキュメントの話が盛り上がって、みんな色々言っててなるほどと思ったのだけれども、自分はデザインドキュメントを書きまくることで、リアルタイムで英語で応答する機会をできるだけ下げる、みたいな使い方をしている側面がある。英語で話すのもう慣れたけど、でも話さなくてもいいんだったら、そっちのほうがいいかもって思ってしまう。書面だったら他の人にも回しやすいし。でも L6 以降は対話というのがかなり積極的に求められている感じがする。周りのチームやプロダクトが、どういう長期的視野に基づいているのかについて、より深く、よりリアルタイムに知る必要があるし、自分のチームがどういう方向に向かっているのかについても積極的に発信する必要がある。日本語でもそういうのできなさそうなのに、英語でやるのかーと思うと辛い。でも、こういうのも、出来るようになってしまえばどうということはない、というものなのかもしれない。

うーん世の中は厳しい。

Ray Tracing in One Weekend やった

Ray Tracing in One Weekendというものが Twitter で流れてきたので土日でやった。やるだけだと半日でできる。今までこういう画像をどうこうする系は、単純にメタデータを取り出すとか変換する以外だとやったことなかったので楽しい。

Ray tracing rendering result

このチュートリアルでは C++を使ってやっているのだけど、とりあえず一通りやって、最後の画像を生成するところでめちゃくちゃ時間がかかったので、並列化しようと思った。C++でマルチスレッドのプログラム書くのは面倒くさいなぁと思ったので、Go で書き直した。多分書き直すほうが面倒くさい。Go で書き直したら今度は Rust でも書き直してみたくなったので、Rust でも書いてみた。Go に書き直すときは全くリファレンス見ずにかけたけど、Rust は全然覚えてなくて笑った。Rust 書くたびに Trait object あたりの扱いと、Iterator あたり(というか Java で言う Collection のようなもの)をどうやって扱うのか良くわからないので、誰かに教えてほしい。

最後の部分で被写界深度を考慮してボケさせる、っていうところで、やってる操作はわかるけど、なぜそれでボケるのかがよくわからない。そもそもボケるっていうのがどういう現象なのかが分かってない気がする。物理のお勉強が必要。

3D レンダリングをリアルタイムでやろうと思うと多分違う方法でやってると思うので、そっちのほうもやってみたい。

物欲

実在物欲

12U ぐらいのサーバーラック

最近は無駄にサーバーラックが欲しくなってきた気がする。今使ってるサーバーが 2015 年に買った Intel NUC で、普段は問題がないんだけどたまに良いマシン欲しいなぁと思うことがある。これを 4U ぐらいの ATX が普通にはいるエンクロージャー買って、ルーターとかコミコミで 12U ぐらいのラックに収めたい気持ちがある。

Wi-Fi 壁 AP

新しい家では宅内 LAN がいい位置についていて、Chromebox を有線でつなぐことができた。有線にしてみるとなかなか速度も出て安定していて良いなと思ったので、UniFi In-Wall HD Access Pointみたいな壁につけると 4 ポートぐらい生えるやつ+Wi-Fi AP がついてるやつほしいなぁと思っている。これを生やすと Windows Desktop も有線にできる。

ボチボチ Wi-Fi 6 なやつが出てきて、そっちにしようかなぁと思ってたら、6GHz 対応の Wi-Fi 6E がそのうち出そうって感じなので、どうせならってことで待機になってる。

ネコ用監視カメラ

家の中に 3 台ぐらい置いて、自分が寝ている間にネコが何してるのかを見てみたいというお気持ちがある。

Chromebook / Chromebox

Chromebook が 2016 年購入、Chromebox が 2019 年購入。まぁ Chromebox はまだ良いとして、Chromebook をそろそろ買い替えたいお気持ちがある。候補としてはPixelbook Goなのだが、2FA キーが USB A ポートを必要としているので USB Type-C にする必要がある。また、Wi-Fi 6E 待ちたいということであればもう一年待たなければならない。

Chromebox は買い換えるとしたら最近出たASUS Fanless Chromeboxが気になる。これに机の天板裏につける VESA マウントをつけて固定したい。

スマホ

Pixel を 2 年おきで買おうと思っていたのだけど、現状 Pixel 3XL で Pixel5 買おうかと思ってたら時期を逃した感じがある。これも Wi-Fi 6E が。

エスプレッソマシンの横に取り付ける牛乳プシューってするやつ

こういうやつ。ブログとか書いてなかったけど、二年弱前にLa Marzocco Linea Miniというエスプレッソマシンを買って、自分でエスプレッソ毎日作ってるけど、これがあると便利。会社には一部あった。(ただ、会社だと Bottomless portafilter というトレーニング用のツールがないので、家で練習するしかなかった。最近毎日家でやってるので、めちゃくちゃ上手くなった。)

非実在物欲

太らない体

流石に 32 歳になると厳しい体になってきた。

日本酒やワインが勝手に出てくる冷蔵庫

あったら欲しい。

draftcode.osak.jp を Netlify でホストするようにした

draftcode.osak.jp は適当に Go で書いた静的ファイルを生成する謎スクリプトで構成されていて、これがターミナルで文章を書く前提になっていた。5 年ぐらい前に ChromeOS に移行してから、ターミナルで日本語が入力できなくなったので、サイトの更新もほぼ止まっていた。

ちょっと前に「へー Jamstack ってやつがあるんだ。なんかアニソン歌ってる人たちでそんなのなかったっけ」って思ってスルーしていたけど、見てみると CMS によっては適当にウェブサイト上で文章を書くと、それを GitHub に Markdown として push してくれる仕組みになっているっぽかったので、それを利用してブラウザ上で更新できるようにしてみた。

利用しているのは Netlify と Netlify CMS で、適当に設定したらなんかできるようになった。ちょっと前に静的ファイル生成部分だけなんか変えるか、と思って Bazel ベースにしたので、それを拡張して自前で Markdown をレンダリング、ファイルの再配置などを行うようにした。会社だとこういう Bazel で何でもかんでもファイルを生成させるみたいなのをよくやってるんだけど、あんまり社外では見ない。やっぱり Bazel は導入障壁が高いんだろうか。(Bazel で Go をビルドするの面倒くさいけど)。Bazel でビルドパイプラインをすべて完結させると、Bazel をインストールすればすべて終了して便利。あと、複数言語でビルドパイプライン組みたい、みたいなときでもこれで完結する。しかし Bazel を使ってビルドするとどうしても 1 分ちょいかかる。(追記: Bazel の共有キャッシュではなく、--output_user_rootまるごとキャッシュすることで 1 分切るようになった)

https://github.com/draftcode/draftcode.github.com/tree/draftcode.osak.jp に色々全部入っている。サイトのデザインは r7kamura.com を丸パクリさせてもらった。読みやすいので。