이미지 파일에 대한 URL 라우팅 속도비교 Node vs Go vs Nginx

  • 평소에 궁금했던 사항인데요. 정적 이미지 파일을 URL로 나눠서 로딩하는데 걸리는 시간이 언어나 프레임워크 별로 얼마나 차이가 날까였어요.
  • 그래서 한 번 시도해 봤습니다. 먼저 Node 코드 입니다.
var express = require('express');
var app = express();
var fs = require('fs');

app.get("/image1", function (req, res) {
  var img = fs.readFileSync("./image1.jpg");
  res.set({'Content-Type': 'image/jpeg'});
  res.send(img);
});
app.get("/image2", function (req, res) {
  var img = fs.readFileSync("./image2.jpg");
  res.set({'Content-Type': 'image/jpeg'});
  res.send(img);
});

module.exports = app;
  • JMeter로 동접자 100으로 시도해 봤어요. 그러자 첫번째 파일의 LoadTime은 26ms, 두번째 파일은 8ms 가 나왔습니다.
  • 다음은 Go 코드 입니다.
package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
)

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/image1", func(res http.ResponseWriter, req *http.Request) {
		buf, err := ioutil.ReadFile("image1.jpg")
		if err != nil {
			fmt.Printf("error: %s", err.Error())
		}
		res.Header().Set("Content-Type", "image/jpeg")
		res.Write(buf)
	})
	mux.HandleFunc("/image2", func(res http.ResponseWriter, req *http.Request) {
		buf, err := ioutil.ReadFile("image2.jpg")
		if err != nil {
			fmt.Printf("error: %s", err.Error())
		}
		res.Header().Set("Content-Type", "image/jpeg")
		res.Write(buf)
	})
	http.ListenAndServe(":3001", mux)
}
  • 첫번째 파일은 11ms, 두번째 파일은 6ms가 나왔습니다. 싱글쓰레드인 Node와 그렇지 않은 Go의 성능차이는 어느정도 추측되었는데요.
광고를 클릭해주시면 블로그운영에 큰 힘이 됩니다.
  • 그렇다면 Nginx에서 Alias를 이용해서 정적파일을 다른 URL 로딩하면 어떻게 될까요?

user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    server {
        listen 80;
        location /image1/ {
            alias /images1/;
        }
        location /image2/ {
            alias /images2/;
        }
    }
}
  • 첫번째 파일이 35ms, 두번째 파일이 4ms가 나왔습니다. 단순비교상으로 Node > Nginx 인 경우가 있다니 좀 신선하네요.
  • 아무래도 캐시를 사용하면 결과가 달라질 수 있겠지만, 개인화된 이미지나 동영상을 처리해야 할 때는 캐시가 부적절 할 수 있죠.
  • 보너스로 Vue-Router를 이용하면 어떻게 될지 추측해봤습니다. 참고로 Vue는 SPA이고 가상URL을 사용하기 때문에 JMeter로 테스트하기가 어렵더라구요.
  • 단순히 이미지 로딩시간만 보면 첫번째 파일이 53ms, 두번째 파일이 45ms가 소요됩니다.
  • 다만 컴포넌트화 한 페이지 모두가 로딩되려면 각가 280ms, 150ms 정도가 필요하구요.

Flutter 에서 많은 이미지 로딩시 메모리 오버플로우 막는 방법

  • GroupGridView 를 이용해서 한 번에 여러개의 사진을 보여주고자 했습니다.
  • 그런데 이런상황에서 용량이 큰 사진을 한번에 로딩하면 메모리 문제가 발생합니다.
  • 이럴때는 아래와 같이 cacheWidth, cacheHeight 속성을 이용합니다.
      Image.file(File(filepath), 
        cacheWidth: newWidth, 
        cacheHeight: newHeight));
    
광고를 클릭해주시면 블로그운영에 큰 힘이 됩니다.
  • 실제 이미지의 크기와 다르게 위에서 지정한 값으로 로딩하여 레이아웃에 반영하기 때문에 사용하는 메모리를 현격하게 줄일 수 있습니다.
    • 이미지1

Flutter 개발시 IDE에서도 Release 모드로 실행하다 낭패본 사연

  • 제가 개발중인 고양이 집사를 위한 만보기앱에서 발생한 이슈입니다.
  • 이 앱에는 블루투스 기기를 선택하는 페이지가 있습니다.
  • SingleChildScrollView 가 전체 body를 감싸고 있고, 그 안에 LoadingOverlay를 두어서 배타적으로 동작해야 하는 행위들을 막아버립니다. 이때 사용한 LoadingOverlay를 패키지는 아래 링크에 해당합니다.
  • LoadingOverlay 내부에는 복잡한 위젯들의 조합으로 구성되어 있습니다.
  • 그런데 LoadingOverlay가 나타나야 하는 순간 아래와 같은 화면 깨짐이 발생하게 되었습니다.
    • 이미지1
  • 원래 원했던 실행화면은 아래와 같습니다.
    • 이미지2
  • 이러한 화면 깨짐은 “transform layer is constructed with an invalid matrix.” 에러와 함께 발생했는데요.
광고를 클릭해주시면 블로그운영에 큰 힘이 됩니다.
  • 이슈의 실체를 구체적으로 기술하면, LoadingOverlay가 Stack을 이용해서 CircularProgressIndicator를 만들어 삽입 할 때 중앙정렬을 하지 못하는 상황인 것 인데요.
  • 원인은 LoadingOverlay 내부의 Stack 구성시에 자식위젯의 높이가 무한대로 지정되어 버려서 인 것으로 파악되었습니다.
  • 사실 이 이슈는 디버그모드에서 실행했다면 쉽게 찾아낼 수 있는 버그 였는데요, 왜냐하면 디버그모드에서는 해당상황에 대한 경고메시지와 함께 페이지 구성이 아예 되지 않기 때문입니다. 반면 릴리즈 모드에서는 우스꽝스럽게라도 화면이 만들어 집니다. 당연히 원인이 되는 경고메시지도 출력되지 않습니다.
  • 디버그모드를 무겁다는 이유로 간과한 결과, 상당한 기술적부채가 발생한 것이죠. 아울러 이런 상황을 애당초에 방지하기 위해서라도 빌더툴의 사용해야 겠다는 생각이 점점 드네요. flutterflow 같은 툴이 괜히 있는게 아닐듯 합니다. 이런 툴이 있으면 애당초에 하지 말아야 할 위젯의 조합이나 구성을 막아주지 않을까 싶네요.