KUOLC ISUCON部
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Write
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Help
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Write
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # ISUCON9 予選 [TOC] ## レギュレーション https://isucon.net/archives/53555007.html ## マニュアル https://github.com/isucon/isucon9-qualify/blob/master/docs/manual.md ## 接続情報 ``` Host isubench HostName 13.113.185.7 User ubuntu IdentityFile ~/.ssh/isucon7q-sshkey.pem ServerAliveInterval 60 Host isucon1 HostName 35.72.103.176 User ubuntu IdentityFile ~/.ssh/isucon7q-sshkey.pem ServerAliveInterval 60 Host isucon2 HostName 35.73.89.21 User ubuntu IdentityFile ~/.ssh/isucon7q-sshkey.pem ServerAliveInterval 60 Host isucon3 HostName 35.76.49.129 User ubuntu IdentityFile ~/.ssh/isucon7q-sshkey.pem ServerAliveInterval 60 ``` ## 内部IP - isucon1: 172.31.45.45 - isucon2: 172.31.43.113 - isucon3: 172.31.34.49 ## スプレッドシート https://docs.google.com/spreadsheets/d/1mwPr_hABe4BZlAj6ZXEWs5krmSfZvZrIevQKDNnjnzs/edit?usp=sharing ## 事前 - [ ] Elastic IPを登録しておく - [ ] AWSインスタンス作成時にダウンロードした鍵を共有しておく - isucon-sshkey.pemで~/.sshに保存 - chmod 600 ~/.ssh/isucon-sshkey.pem - [ ] ~/.ssh/configの設定 - [ ] isucon[X]-qualifyレポジトリを空で作成しておく - `git@github.com:isucon-kuolc38th/isucon(X)-qualify.git` <!-- - テンプレート準備 - local - pprof - results - tools - .gitignore - docker-compose.yml - (各自)クローンし、ローカルで諸々のセッティングをしておく - VSCode - Github Desktop - (要検討)これらはサーバー内に用意されている方を踏襲したほうがよさそう? - mysql - nginx - env.sh --> - [ ] isucon[X]-qualify-utilレポジトリを作成し、各自Pullして動作確認を済ませておく - `git@github.com:isucon-kuolc38th/isucon(X)-qualify-util.git` ## 初期構成 - isucon1 - Nginx - App - isucon2 - 何もなし - isucon3 - (Redis) - MySQL ## AWS - [ ] EC2でインスタンス作成→マシンイメージ選択で指定されたAMIを選択 - [ ] インスタンスタイプを選択 - [ ] ボリュームはSSDで16GB(インスタンスタイプによって変わるかも) - [ ] セキュリティグループの設定 - 「すべてのTCP」、ソースに「0.0.0.0/0」を指定 - 「すべてのICMP」、ソースに「0.0.0.0/0」を指定 - セキュリティグループはあらかじめ作成しておいたものを使うのが良い - [ ] キーペアにisucon-sshkeyを指定 - [ ] インスタンスが起動したら、Elastic IPのページからインスタンスに事前に用意したElastic IPを紐付け - [ ] それぞれの名前をつける ## タイムスケジュール |timetable|all|rkoike|cojiro|ryunosuke| |--|--|--|--|--| |00:00 競技開始||||| ||マニュアル確認<br>初期ベンチ回す(レコード数確認)<br>アプリ触ってみる<br>レポジトリPush・各自クローン|||| |00:20 作業開始||||| |||レポジトリPull<br>マシンセットアップ<br>Nginx(ログ含む)<br>MySQL(ログ含む)<br>App (GOインストール)<br>netdata|AppのDB接続設定<br>Appのエラーログ設定<br>measure設定<br>pprof設定|ALPのグルーピング<br>コードリーディング<br>仕様の整理<br>ボトルネックの洗い出し| ||(ここまでの目標)<br>スクリプトからデプロイできるようにする<br>ベンチを回してmeasure/ALPを吐き出せるようにする<br>ローカルでアプリが動くようにする<br>ボトルネックの洗い出し・タスク分割|||| |01:30 改善開始||||| ||仕様・ボトルネックの共有と把握<br>タスクの割り振り|||| |||Nginxの設定チューニング<br>Staticファイルのキャッシュ設定<br>ベンチマーク分析<br>インフラ最適化|MySQLの設定チューニング<br>インデックス作成<br>SQLクエリ改善|メインボトルネック解消<br>影響範囲の広いApp改善<br>変更のないデータのオンメモリ化| |05:00 メインボトルネック解消||||| |||ベンチマーク分析<br>インフラ最適化<br>時間のある時に再起動試験|影響範囲の狭いApp改善|使用頻度の高いデータのオンメモリ化| |07:00 提出準備||||| ||再起動試験|netdata無効化<br>MySQLログ無効化<br>Nginxログ無効化|measure無効化|安全な(?)App改善<br>ボーダーが遠い場合にワンチャンかける| ## 作業マニュアル ### マニュアル確認 ### ベンチマーク ### Git #### SSH Key登録 ``` bash mkdir ~/.ssh cd ~/.ssh && ssh-keygen cat id_rsa.pub ``` - [ ] isucon[X]で名前をつける - [ ] Write権限にチェックを入れること #### 初回Push - [ ] SSH Key登録 ``` bash cd ~/<apphome> git config --global user.name "isucon[1-3]" git init git remote add origin git@github.com:isucon-kuolc38th/isucon[X]-qualify.git # 必要に応じて.gitignoreをいい感じに編集 git add . git commit -m "initial setup" git push origin master ``` #### 初回Pull - [ ] SSH Key登録 ``` bash cd ~ mv <apphome> <apphome>_backup git clone git@github.com:isucon-kuolc38th/isucon[X]-qualify.git git config --global user.name "isucon[1-3]" ``` フォルダをマージする場合 `rsync -av isucari_repo/ isucari/` ### Nginx #### インストール - [ ] `sudo apt install nginx` #### セットアップ - [ ] 設定をgit管理下にコピー ``` bash cd ~/<apphome> sudo mkdir /etc/nginx_backup sudo cp -r /etc/nginx /etc/nginx_backup mkdir -p ./isucon[1-3]/nginx/config sudo cp -r /etc/nginx/* ./isucon[1-3]/nginx/config ``` - [ ] 初期実装サービスの停止 ``` bash # Apache sudo apachectl stop sudo service httpd stop # h2o sudo systemctl stop h2o sudo systemctl disable h2o ``` - [ ] appのIPアドレス設定 ``` http { upstream app { server app:8080; } location / { proxy_set_header Host $http_host; proxy_pass http://app; } } ``` - [ ] ログの設定 ``` nginx.conf http { log_format ltsv "time:$time_local" "\thost:$remote_addr" "\tforwardedfor:$http_x_forwarded_for" "\treq:$request" "\tmethod:$request_method" "\turi:$request_uri" "\tstatus:$status" "\tsize:$body_bytes_sent" "\treferer:$http_referer" "\tua:$http_user_agent" "\treqtime:$request_time" "\truntime:$upstream_http_x_runtime" "\tapptime:$upstream_response_time" "\tcache:$upstream_http_x_cache" "\tvhost:$host"; access_log /var/log/nginx/access.log ltsv; error_log /var/log/nginx/error.log; } ``` - [ ] Nginxサービスの`start`と`enable` #### 設定のチューニング ``` worker_processes auto; # ワーカーの数。基本はautoでOK pid /run/nginx.pid; include /etc/nginx/modules-enabled/*.conf; worker_rlimit_nofile 4096; # nginxの開けるファイルとコネクションを合わせた数。too many file opensになった場合は引き上げる error_log /var/log/nginx/error.log error; events { worker_connections 1024; # nginxの開けるコネクションの数。too many file opensになった場合は引き上げる。worker_rlimit_nofile以上にはできない } http { include /etc/nginx/mime.types; default_type application/octet-stream; server_tokens off; sendfile on; tcp_nopush on; tcp_nodelay on; client_max_body_size 10m; open_file_cache max=100 inactive=65s; # ファイルを開きっぱなしにする。ファイルの読み込みが頻繁に発生する場合に有効 gzip_static on; access_log off; # TLS configuration ssl_protocols TLSv1.2; ssl_prefer_server_ciphers on; ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384'; # リクエストのロードバランシング upstream app { server 127.0.0.1:8000 weight=2; server 172.24.83.44:8080 weight=7; server 172.24.83.45:8080 weight=1; keepalive 10; # バックエンドへの接続を繋ぎっぱなしにできる。接続数が多い場合に有効 } server { listen 443 ssl http2; # http2の有効化 server_name www.example.com; ssl_certificate /etc/nginx/ssl/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/privkey.pem; root /home/isucon/isucari/webapp/public; location /static/ { add_header Cache-Control "public max-age=86400"; # staticファイルのキャッシュ設定 } location / { # keepaliveを有効にする場合に設定? proxy_http_version 1.1; proxy_set_header Connection ""; proxy_pass http://app; proxy_set_header Host $host; } } } ``` ### MySQL #### インストール ``` bash sudo apt install mysql-server mysql-client ``` #### セットアップ - [ ] 設定をgit管理下にコピー ``` bash cd ~/<apphome> sudo mkdir /etc/mysql_backup sudo cp -r /etc/mysql /etc/mysql_backup mkdir -p ./isucon[1-3]/mysql/config sudo cp -r /etc/mysql/* ./isucon[1-3]/mysql/config ``` - [ ] ログの設定 - 設定が効いているか確認: `SHOW VARIABLES LIKE "general_log%";` ``` my.cnf [mysqld] log_output=FILE general_log=1 general_log_file=/var/log/mysql/query.log slow_query_log=1 slow_query_log_file=/var/log/mysql/slow_query.log log_error=/var/log/mysql/error.log long_query_time=1 log_queries_not_using_indexes=1 ``` - [ ] MySQLサービスの`start`と`enable` ``` bash sudo systemctl start mysql [mariadb] sudo systemctl enable mysql [mariadb] ``` - [ ] (要検討)初期実装がmysql以外の時のスキーマやデータのコピー方法 #### データのdump ``` bash mysqldump -u user db > data.sql mysqldump -u user --no-data db > schema.sql sftp isucon[1-3] /home/isucon/data.sql ./ sftp isucon[1-3] /home/isucon/schema.sql ./ ``` #### 設定のチューニング ``` [mysqld_safe] socket = /var/run/mysqld/mysqld.sock nice = 0 [mysqld] user = mysql pid-file = /var/run/mysqld/mysqld.pid socket = /var/run/mysqld/mysqld.sock port = 3306 basedir = /usr datadir = /var/lib/mysql tmpdir = /tmp lc-messages-dir = /usr/share/mysql skip-external-locking bind-address = 0.0.0.0 # 外部から接続可能にする key_buffer_size = 16M max_allowed_packet = 16M thread_stack = 192K thread_cache_size = 8 myisam-recover-options = BACKUP max_connections = 1024 table_open_cache = 64 thread_concurrency = 10 query_cache_limit = 1M query_cache_size = 16M log_error = /var/log/mysql/error.log slow_query_log = 1 slow_query_log_file = /var/log/mysql/mysql-slow.log long_query_time = 0.5 log-queries-not-using-indexes = 1 ``` #### インデックス最適化 - WHERE ORはUNIONにする ```sql SELECT * FROM items WHERE seller_id = ? OR buyer_id = ? -------------------------------------------------- SELECT * FROM items WHERE seller_id = ? UNION SELECT * FROM items WHERE buyer_id = ? ``` ### systemd `/etc/systemd/*`を編集し,appで`<appdir>/isucon[1-3]/env.sh`を読むようにする ### App #### セットアップ - [ ] Go最新版に入れ替え ``` bash cd ~/local wget https://golang.org/dl/go1.16.5.linux-amd64.tar.gz rm -rf go tar -xzf go1.16.5.linux-amd64.tar.gz rm go1.16.5.linux-amd64.tar.gz ``` - [ ] PATHを通す - `export PATH=$PATH:$HOME/local/go/bin` - `vim ~/.bashrc`をして`PATH=`に`$HOME/local/go/bin`を追記 - [ ] `<apphome>/isucon[1-3]/env.sh`を読むように設定 ``` bash cd ~/<apphome> touch ./isucon[1-3]/env.sh sudo vim /etc/systemd/system/<service_name> # [Service] # EnvironmentFile=<apphome>/isucon[1-3]/env.sh ``` - [ ] 実装切り替え - 他の言語実装を`stop`と`disable` - Golang実装を`start`と`enable` ### netdata ``` bash bash <(curl -Ss https://my-netdata.io/kickstart.sh) all --dont-wait ``` ### measure https://github.com/najeira/measure インストール ``` go get github.com/najeira/measure ``` 使い方 ```go import "github.com/najeira/measure" // 測定 func hoge() { defer measure.Start("hoge").Stop() } // リセット measure.Reset() // 結果取得用ハンドラ (echoの場合) e.GET("/stats", echo.WrapHandler(http.HandlerFunc(measure.HandleStats))) ``` `/stats`にアクセスし、結果を右クリック>stats.csvで保存 ### pprof 使い方 https://christina04.hatenablog.com/entry/golang-pprof-basic ```go import "net/http/pprof" // echo e.Group("/debug/pprof").Any("/cmdline", echo.WrapHandler(http.HandlerFunc(pprof.Cmdline))) e.Group("/debug/pprof").Any("/profile", echo.WrapHandler(http.HandlerFunc(pprof.Profile))) e.Group("/debug/pprof").Any("/symbol", echo.WrapHandler(http.HandlerFunc(pprof.Symbol))) e.Group("/debug/pprof").Any("/trace", echo.WrapHandler(http.HandlerFunc(pprof.Trace))) e.Group("/debug/pprof").Any("/*", echo.WrapHandler(http.HandlerFunc(pprof.Index))) // goji mux.HandleFunc(pat.Get("/debug/pprof/cmdline"), pprof.Cmdline) mux.HandleFunc(pat.Get("/debug/pprof/profile"), pprof.Profile) mux.HandleFunc(pat.Get("/debug/pprof/symbol"), pprof.Symbol) mux.HandleFunc(pat.Get("/debug/pprof/trace"), pprof.Trace) mux.HandleFunc(pat.Get("/debug/pprof/*"), pprof.Index) ``` ベンチマーク中に、稼働しているサーバーに対してローカルで次を実行 ``` bash // CPU curl -s http://<ip>/debug/pprof/profile?seconds=60 > cpu.pprof // メモリ curl -s http://<ip>/debug/pprof/heap?seconds=60 > memory.pprof ``` ローカルで結果表示 ``` bash go tool pprof -http=localhost:8080 *.pprof ``` Webでの閲覧にはgraphvizのインストールが必要 ``` bash brew install graphviz ``` ### ローカル環境 #### セットアップ - [ ] isucon-templateのdocker-compose.ymlのコメントアウトを編集 - [ ] appのフォルダ - [ ] DBの環境変数 - [ ] MySQLの変数 - [ ] local/publicにpublicをコピー - [ ] local/nginx/config/nginx.confを編集 - [ ] appのupstream - [ ] staticファイル - [ ] local/mysql/initの各SQLファイルを編集 - [ ] 00_setup.sqlにユーザー作成処理を書く - [ ] 01_schema.sqlにスキーマを書く ### キャッシュ ### ベンチマーク分析 ### 再起動試験対策 #### AppのDB接続 ``` go for { var err error db, err = sql.Open("mysql", dsn) if err == nil && db.Ping() == nil { break } log.Println(err) time.Sleep(time.Second * 3) } ``` #### initialize ``` go func initialize() { ClearCache() // DBロード処理 } func getInitialize() { ... initialize() } func main() { ... initialize() } ``` ### 掃除 - [ ] MySQLログ無効化 - [ ] Nginxログ無効化 - [ ] Appログ無効化 - [ ] Redisログ無効化 - [ ] netdata無効化 - `systemctl stop & disble` - [ ] measure削除 - `github.com/najeira/measure`をインポートから外して関連コードを削除 <!-- ## 以下未整理(項目ごとに上の作業マニュアルに入れていく) - [ ] 与えられたディレクトリ構成に合わせてこの資料を編集 - (要検討)3台で別々のamiインスタンスが渡された場合のテンプレ ## 当日やること(ツール類の整備) ### 計測ツール検討中 - [ ] AWS X-Ray ## その他のツールやコマンド ### alp ``` bash wget https://github.com/tkuchiki/alp/releases/download/v1.0.3/alp_linux_amd64.zip apt install unzip unzip alp_linux_amd64.zip sudo mv alp /usr/local/bin/ rm alp_linux_amd64.zip ``` ### 初期実装切り替え #### init.d ``` bash sudo chkconfig <service> on/off sudo service <service> start/stop ``` #### systemd ``` bash sudo systemctl enable/disable <service> sudo systemctl start/stop <service> ``` #### supervisord vim /etc/supervisord.conf ``` [<service>] ... autostart: true/false ``` ``` supervisorctl restart ``` ### 更新 & ベンチ ``` bash cd <apphome> git pull origin master ps -aux | grep app sudo kill <pid> cd app && make build make run & cd ~/webapp && ./tools/benchmark.sh # Running benchmark... vim results/initial_score # TODO: 自動化 git add * git commit -m "initial_score" git push origin master ``` --> ## Trouble Shooting ### Nginxで権限問題 - 迷ったら`nginx.conf`にはrootユーザーを書くのが無難 - 「13: Permission denied」[参考リンク](https://itips.krsw.biz/nginx-fail-port-forward-13-permission-denied/) ``` bash sudo setsebool httpd_can_network_connect on -P sudo chmod 701 /home/isucon sudo systemctl restart nginx ``` - 「SELinuxの無効化」[参考リンク](https://qiita.com/hanaita0102/items/5d3675e4dc1530b255ba) ``` bash # 一時的に無効化 setenforce 0 # 永続的に無効化(要サーバー再起動) # vi /etc/selinux/config SELINUX=enforcing ``` ### MySQLで権限問題 ``` bash sudo chmod 755 -R /var/log/mysql # git add できない sudo chmod 755 -R isucon3/mysql/config ``` ### MySQLで外部サーバーから接続できない https://ameblo.jp/mojeld/entry-12645669164.html ``` my.cnf # bind-address = 127.0.0.1 bind-address = 0.0.0.0 ``` ### Redisで権限問題 ``` sudo chown redis:adm -R /var/log/redis sudo chmod 755 /var/log/redis ``` ### サーバー間通信でno route to host - pingは通るがcurlは通らない ``` sudo systemctl stop firewalld sudo systemctl disable firewalld ``` ### デプロイコマンドで権限問題 ``` sudo chmod 777 <apphome>/webapp/go sudo chmod 777 <apphome>/isucon[1-3]/restart.sh ``` ### worker_rlimit_nofileをあげてもtoo many open filesが解決しない ``` bash ulimit -Sn 4096 ps aux | grep nginx cat /proc/<pid>/limits ``` /etc/systemd/system/multi.target/nginx.service ``` [Service] LimitNOFILE=65536 ``` ## やる必要があるかもしれないこと - [ ] my.cnfの読み込み優先度が高いものが入ってないか確認 - [ ] ` mysql --help | grep .cnf`の場所にファイル実体があればそれも読み込まれることを意識 - mysqlコマンドで`defaults-extra-file`オプションがあればそれも入る - 候補は以下の通り 1. `/etc/my.cnf` (git管理済み) 1. `/etc/mysql/my.cnf` (git管理済み) 1. `SYSCONFDIR/my.cnf` 1. `$MYSQL_HOME/my.cnf` 1. `--defaults-extra-file=path` 1. `~/.my.cnf` 1. `~/.mylogin.cnf`

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully