perl DBI 를 사용하여 multi row Insert 하는 부분 질문입니다.

jip102의 이미지

안녕하세요. 여러분
Perl DBI를 사용하여 Mulirow를 Insert 를 하려 합니다.
Insert 하는 방식은 데이터 파일(test.csv)을 읽어서 file Open 후 테이블에 insert 를 하려는데 잘 안되는군요
데이터 파일(test.csv)의 내용은 아래와 같습니다.(약 70만건 됩니다.)

21,spderp01,CPU Usage,2013-10-01 10:09:03,8.48
22,spderp02,CPU Usage,2013-10-01 10:09:05,.48
.
.
22,spderp02,CPU Usage,2013-10-01 10:09:05,.48

위의 데이터 파일을 읽어서 테이블에 insert를 하는데 bind 관련 에러가 발생해서, Insert 의 Multi Row 도 select 처럼
while 로 fetch를 해서 실행해야 하나요? 제가 테스트 해본 소스는 아래와 같은데 쉽제 않군요... 좀 도와 주세요

my $textfile = "test.csv";

open (TEXT, "$textfile") or die "Couldn't open textfile: $!";
{
local $/ = undef;
$dbh->do("INSERT INTO SUM_CPU values (?,?,?,?,?)", undef, );
}

jip102의 이미지

소스코드를 다시 올립니다.

undef 다음에
,); 빠졌네여..

chanik의 이미지

질문에 올리신 코드를 보면 아래와 같이 DB에 insert문의 문자열을 바로 던지는 식인데요. DBI 사용법에 맞는지 아닌지를 떠나서, 이런 형태로 작성하시면 DB에서 매번 insert문을 분석하는 과정을 거치게 되므로 다수의 row를 입력하는데 매우 오랜 시간이 걸리게 됩니다.

    $dbh->do("INSERT INTO SUM_CPU values (?,?,?,?,?)", undef, );

[1] 아래와 같이 우선 insert statement를 prepare해두고 이후엔 이것을 재활용하여 입력데이터만 바꿔가면서 execute()를 반복하는 것이 빠릅니다.

    my $dbh = DBI->connect( ... )
    my $inserted = 0;
    $sth = $dbh->prepare("INSERT INTO SUM_CPU VALUES(?, ?, ?, ?, ?)");
    open my $fh, '<', 'test.csv';
    while (<$fh>) {
        my @arr = split(/,/);
        $inserted += $sth->execute(@arr);
    }
    $sth->finish();
    $dbh->disconnect();

[2] 그리고, 매 insert 문마다 autocommit이 일어나게 방치하지 말고, 아래 코드와 같이 일정횟수마다 한 번씩 몰아서 transaction을 commit해주면 훨씬 더 (수십배쯤?) 빨라집니다. 저는 5000건씩 묶어서 commit했습니다 (1000건씩 commit하게 하면 시간이 약간 더 걸리더군요. 적절한 갯수는 자신의 환경에서 테스트로 정하면 되겠죠).

참고로, $dbh->begin_work; 문이 등장하면 autocommit이 일시적으로 꺼지고, $dbh->commit; 이나 $dbh->roolback; 이 등장하면 autocommit이 다시 켜지게 됩니다. DB connect할때나 그 직후에 autocommit을 꺼둘수도 있지만 굳이 그렇게 하지 않아도 transaction 관리가 필요할 때 위의 명령을 적당히 끼워넣어 일시적으로 통제하는 것만으로도 성능개선에는 충분한 것 같습니다.

    my $dbh = DBI->connect( ... )
    $dbh->begin_work;
    my $inserted = 0;
    $sth = $dbh->prepare("INSERT INTO SUM_CPU VALUES(?, ?, ?, ?, ?)");
    open my $fh, '<', 'test.csv';
    while (<$fh>) {
        my @arr = split(/,/);
        $inserted += $sth->execute(@arr);
        if(($inserted % 5000) == 0) {
            $dbh->commit;
            $dbh->begin_work;
        }
    }
    $dbh->commit;
    $sth->finish();
    $dbh->disconnect();

8만건짜리 가짜데이터를 만들어 SQLite에서 실험해보니 [1]은 2분 좀 넘게, [2]는 1.3초 걸렸습니다.
80만건으로 늘려보니 [2]가 12.5초 걸리네요. [1]은 안해봤습니다. 아마 20분쯤 걸리겠죠.

PostgreSQL-8.4에서 해보니 8만개 insert에 [2]방식이 6.7초 정도였습니다. 기본설치상태에서 별 노력없이 돌려본 것이니까 최적화같은 것을 하면 달라질 수 있는 부분도 있을 것입니다. 어떤 DB를 쓰시는지 모르겠지만 이런 두 방식간의 성능차는 그 정도는 달라질 수 있어도 어느 DB에든 적용될 것입니다.

참고자료 : How do I improve the performance of SQLite?
http://stackoverflow.com/questions/1711631/how-do-i-improve-the-performance-of-sqlite
C로 작성된 샘플과 함께 성능개선에 대해 설명하고 있는 자료인데 perl은 아니지만 기초가 되는 내용이므로 유용할 것입니다.

TODO: DB에 따라서는 bulk insert같은 것을 지원하는 경우도 있습니다. 한 예로, oracle이 있는데요. 이 경우 DB를 오가는 횟수가 획기적으로 줄어들게 됩니다. [2]의 예에서 DB를 5000번 오갈때마다 transaction을 마무리하는 식으로도 큰 성능개선이 이뤄졌는데요, bulk insert를 쓰게 되면 5000개의 데이터를 DB에 한 번에 넘겨주고 transaction을 마무리하는 셈이 될 것입니다. 이렇게 해서 추가로 얻어지는 개선폭이 어느정도인지는 모르겠습니다만 데이터가 극히 많은 상황에서는 이런 고려가 필요하기도 할 겁니다. 단점은, DB마다 나름대로 SQL문을 확장하여 제공하는 방식이므로 DB별로 문장형태가 조금씩 다를 것이라는 점입니다.

chanik의 이미지

아래와 같이 입력방식을 달리했을때,

[1] autocommit : commit per 1 insert
[2] 트랜잭션관리 : commit per 5000 inserts
[3] bulk insert : 5000 rows per insert (execute_array() 이용)

Oracle 10gR2-XE 에서 80만건 insert 실험해보니 아래와 같이 나옵니다.
(DB는 같은PC에 있었지만 접속은 Listener를 통해서 했음)

[1] 4분30초
[2] 1분50초
[3] 17초

PostgreSQL-8.4는,

[1] 4분50초
[2] 1분12초
[3] [2]와 비슷

SQLite-3.3.6-7은,

[1] 21분17초
[2] 12.5초
[3] 18초 (?)

실험은 많이 반복한 것은 아니고 그냥 몇 번 정도씩만 해본 것입니다.

Oracle에서 꽤 효과를 보인 bulk insert는 말은 그렇게 붙였지만 차이는 그냥 execute_array()를 실행한 것 뿐입니다. 각 DBD별로 하부가 얼마나 잘 구현되어 있는가에 따라 execute_array()가 상당한 개선효과를 보이기도 하고 오히려 역효과를 보이기도 하는 것이 아닐까 추측합니다.

chanik의 이미지

파일 정리하다가 위의 [3]방식인 insert_array()를 써서 작성해둔 코드가 눈에 띄어 올려둡니다.
오라클에서는 꽤 큰 성능향상이 가능했으니, 필요하신 분 참고하시기 바랍니다.



우선 실험용 데이터 생성하는 코드입니다.
#!/usr/bin/env python
 
# 01,spderp01,CPU Usage,2013-10-01 10:09:03,8.48
from datetime import datetime
from datetime import timedelta
import random
 
diff = timedelta(seconds=1)
when = datetime.now() - timedelta(days=100)
for i in xrange(20,800020):
    when += diff
    a = (str(i), 'spderp%02d' % (i%8+1), 'CPU Usage', when.strftime('%Y-%m-%d %H:%M:%S'), "%.02f" % (random.random() * 10))
    print ','.join(a)

실행하면 80만줄을 표준출력으로 뿜어내므로 아래와 같이 쓰면 됩니다.

$ ./gen_data.py > multiline.txt

다음으로, 오라클 DB에 insert_array()를 써서 데이터를 insert하는 코드입니다.

DB에 SUM_CPU 테이블이 있는지 확인해보고,
없으면 생성하고 multiline.txt 의 데이터를 읽어 모두 insert 한 다음 테이블 내용물을 select 합니다.
테이블이 이미 있으면 insert 생략하고 그냥 select가 실행됩니다.

http://gentooboy.tistory.com/136 에 있던 python sqlite 샘플을 가져다 perl oracle 로 고쳐쓴 것입니다.

#!/usr/bin/env perl
 
use strict;
use warnings;
use DBI;
 
sub fill_tbl {
    my ($dbh, $txtfile) = @_;
    my $sth = $dbh->prepare( "SELECT tname FROM TAB"
                           . " WHERE tname = 'SUM_CPU'");
    $sth->execute();
    # fetch하기 전에는 항상 $sth->rows() == 0 이 나와서 판별이 안됨
    while (my @row = $sth->fetchrow_array) {}
    if ($sth->rows() == 0) {
        $dbh->do("CREATE TABLE SUM_CPU (col1 integer primary key, col2 varchar2(20), col3 varchar2(20), col4 date, col5 number(10,2))");
        my $inserted = 0;
        my $transize = 5000;
        my $rows = 0;
        my $i = 0;
        my (@c1, @c2, @c3, @c4, @c5);
        # URL: stackoverflow.com/q/3864406
        # (($var-1) x 5) == (map $var-1, 1..5) == ($var-1) foreach (1..5) == ($var-1) for (1..5)
        ($#c1, $#c2, $#c3, $#c4, $#c5) = (($transize-1) x 5);  # pre-allocate array
#        $dbh->begin_work;
        #$sth = $dbh->prepare("INSERT INTO SUM_CPU VALUES(?, ?, ?, ?, ?)");
        $sth = $dbh->prepare("INSERT INTO SUM_CPU VALUES(?, ?, ?, TO_DATE(?,'YYYY-MM-DD HH24:MI:SS'), ?)");
        open my $fh, '<', $txtfile;
        while (<$fh>) {
            #next if $. == 1;   # 첫줄은 건너뜀
            my @arr = split(/,/);
            $i = $rows++ % $transize;    # ($i: zero-based index) vs. ($rows: one-based count)
            ($c1[$i], $c2[$i], $c3[$i], $c4[$i], $c5[$i]) = (@arr[0..3], $arr[4]+0.0);
            if(($rows % $transize) == 0) {
                # - URL: backpan.perl.org/authors/id/T/TI/TIMB/DBI_AdvancedTalk_200608.pdf
                #   caution: returns count of executions (even ones that failed) and not rows-affected
                # - URL: perlmonks.org/?node_id=743082
                #$inserted += $sth->execute_array({ArrayTupleStatus=>\my @tuple_status}, \@c1, \@c2, \@c3, \@c4, \@c5);
                $inserted += $sth->execute_array({}, \@c1, \@c2, \@c3, \@c4, \@c5);
                ($#c1, $#c2, $#c3, $#c4, $#c5) = ((-1) x 5);             # initialize all array
                ($#c1, $#c2, $#c3, $#c4, $#c5) = (($transize - 1) x 5);  # pre-allocate array again
#               $dbh->commit;
#               $dbh->begin_work;
            }
        }
        # $transize에는 미치지 못했지만 남은 레코드가 있으면 마저 입력
        if($rows % $transize) {
            ($#c1, $#c2, $#c3, $#c4, $#c5) = (($rows % $transize - 1) x 5);  # adjust array size
            $inserted += $sth->execute_array({}, \@c1, \@c2, \@c3, \@c4, \@c5);
        }
#        $dbh->commit;
    }
    $sth->finish();
}
 
sub fill_and_select {
    my ($txtfile) = @_;
    my $dbh = DBI->connect(
        "dbi:Oracle:host=192.168.0.101;port=1521;sid=ora10",
        "db_username",
        "db_password",
        { RaiseError => 1 },
    ) or die $DBI::errstr;
 
    #printf("AutoCommit == %d\n", $dbh->{AutoCommit});
    #$dbh->{AutoCommit} = 0;
 
    # 테이블이 존재하지 않으면 테이블을 만들고 데이터를 읽어들임
    # 이미 테이블이 존재하면 그냥 리턴
    fill_tbl($dbh, $txtfile);
 
    my $sth = $dbh->prepare( "SELECT col1, col2, col3, col4, col5"
                           . "  FROM SUM_CPU" );
    $sth->execute();
    while (my @row = $sth->fetchrow_array) {
        print "$row[0] $row[1] $row[2] $row[3] $row[4]\n";
    }
 
    $sth->finish();
    $dbh->disconnect();
}
 
fill_and_select("multirow.txt");
jip102의 이미지

정말 자세한 답변 감사합니다.
이런 내공은 언제쯤 가지게 될까여...
잘 참조하여 쓰도록 하겠습니다.
감사합니다.

jip102의 이미지

1건은 읽어서 insert 처리를 하는데
1건이상의 데이터를 읽어서 처리를 하니
oracle 에러가 나네여(ORA-01722 : invalid number)
insert 되어지는 컬럼은 number 형식으로 맞게 되어 있는데 파일을 읽으면서
형변환이 일어나는 듯 합니다.
해결책이 없을까요?

chanik의 이미지

오류번호 하나만으로는 제3자가 문제를 추적하기 어렵습니다. 오류 재현에 필요한 정보를 올려주시면 테스트해보겠습니다. 아래 내용 정도가 되겠죠. 노출이 곤란한 민감한 정보는 적당히 지우시고요.

- 테이블 만드는 create table 문
- 오류재현이 가능한 perl 샘플 코드
- 오류재현에 필요한 text 데이터 몇 줄

jip102의 이미지

안녕하세요. 우선 상세한 답변 너무 감사드립니다.
이소스의 목적은 월별로 호스트 name별 CPU 사용량(%)이 파일로 생성되면
생성된 파일을 읽어서 테이블에 insert하는 작업입니다.Insert 전 데이터를 Truncate 합니다.
관련된 정보를 파일로 첨부하여 올려 봅니다.

- 테이블 만드는 create table 문
--> crt_table.sql

- 오류재현이 가능한 perl 샘플 코드
--> data_insert.pl

- 오류재현에 필요한 text 데이터 몇 줄
--> cpu_usage_201310.csv

댓글 첨부 파일: 
첨부파일 크기
Package icon test_file.zip1.49 KB
chanik의 이미지

아래와 같이 고쳐보니 잘 되는군요.

    my $inserted = 0;
    #$sth = $dbh->prepare("INSERT INTO SUM_CPU VALUES(?, ?, ?, ?, ?)");
    $sth = $dbh->prepare("INSERT INTO SUM_CPU VALUES(?, ?, ?, TO_DATE(?,'YYYY-MM-DD HH24-MI'), ?)");
    open my $fh, '<', 'cpu_usage_$GET_DATE.csv';
    while (<$fh>) {
          chomp $fh;
        my @arr = split(/,/);
        #$inserted += $sth->execute(@arr);
        $inserted += $sth->execute($arr[0], $arr[1], $arr[2], $arr[3], $arr[4]+0.0);
    }

문제되는 부분이 두 군데였습니다. date 타입의 컬럼과 NUMBER 타입의 컬럼인데, date 타입은 prepare()할때 TO_DATE 함수를 끼워서 해결했고, NUMBER 컬럼은 execute()할때 $arr[4] 문자열을 슬쩍 실수 타입으로 변환해서 넘기는 식으로 해결했습니다.

NUMBER 타입의 문제만 말씀하신 것을 보니 DATE 타입의 문제는 겪지 않으신 것 같은데요. jip102님은 DBD-ODBC를 쓰셨고, 저는 DBD-Oracle 드라이버를 썼는데 그 때문에 생긴 차이인지도 모르겠습니다.

jip102의 이미지

우선 답변 감사드립니다.
아. 저런식으로 배열에 실수 타입을 cast시킬수 있군요.
저도 비슷한 방법으로 배열에 형변환 되는 함수를 씌우면 되는줄 줄 알았는데 찾아보니 없더라구요
예를 들면 뭐 이런식으로 INT($arr[0])
여하튼 한수 배웠습니다. 다시한번 감사드립니다.

jip102의 이미지

chanik 님 Oracle 의 Hint를 써볼려고 하는데

$sth = $dbh->prepare("INSERT INTO \/\*\+ PARALLEL(SUM_CPU 4) \*\/ INTO SUM_CPU VALUES(?, ?, ?, ?, ?)")
혹은
$sth = $dbh->prepare( q{INSERT INTO /*+ PARALLEL(SUM_CPU 4) */ INTO SUM_CPU VALUES(?, ?, ?, ?, ?)})
해보았는데
ORA-01861: literal does not match format string 에러가 나는군요
Perl 에서도 Oracle Hint가 가능하나요?

chanik의 이미지

아래와 같이 INTO 키워드가 중복되어 있던 것을 제거하니 잘 되네요. 오류를 내지는 않았지만 SUM_CPU와 4 사이에 ','도 추가했습니다. 백슬래시를 이용한 이스케이핑은 하든 안하든 오류는 안 나네요.

$sth = $dbh->prepare( q{INSERT /*+ PARALLEL(SUM_CPU,4) */ INTO SUM_CPU VALUES(?, ?, ?, ?, ?)});
$sth = $dbh->prepare("INSERT \/\*\+ PARALLEL(SUM_CPU,4) \*\/ INTO SUM_CPU VALUES(?, ?, ?, ?, ?)");
$sth = $dbh->prepare("INSERT /*+ PARALLEL(SUM_CPU,4) */ INTO SUM_CPU VALUES(?, ?, ?, ?, ?)");

저는 DBD-ODBC가 아닌 DBD-Oracle 드라이버를 썼습니다. 만약 위의 prepare문이 실패한다면, 혹시 ODBC 계층에서 명령 문자열을 변조하는 것은 아닌지 의심됩니다. http://support.microsoft.com/kb/274551 을 보니 ODBC를 거쳐가는 SQL문을 추적하는 기능이 ODBC 관리자에 있는것 같으니 참고하시고요..

jip102의 이미지

문법 에러이네여. ㅋㅋ 죄송~
테스트 해보니까 어차피 While Loop로 뺑뺑이 돌면서 하는거라 Hint 자체 의미가 없군요..
ㅎㅎ

chanik의 이미지

Oracle에서 데이터 입력 시간을 줄이려면, execute() 대신 execute_array()를 쓰십시오.
http://kldp.org/node/140697#comment-600165 에 좀 엉성하지만 동작시간을 올려뒀습니다.

jip102의 이미지

정말 감사합니다.

jip102의 이미지

예전에 올렸던 질문에서 답변을 받았던 내용이긴 합니다만, cpu_usage.txt 라는 파일에 아래와 같은 내용이 있습니다.

select * from HISTORY1 a,host b where a.param_name='CPU Usage' union all
select * from HISTORY2 a,host b where a.param_name='CPU Usage' union all
select * from HISTORY3 a,host b where a.param_name='CPU Usage' union all
select * from HISTORY4 a,host b where a.param_name='CPU Usage' union all
select * from HISTORY5 a,host b where a.param_name='CPU Usage' union all

그때 질문 드렸던건 마지막 라인의 union all 만 ';'으로 바꾸는 방법이었는데 위의 파일을 cat으로 읽어서 다시 가공하는 방법이었습니다.
cat cpu_usage.txt | perl -ne print $last; $last = $_; END { $last =~ s/\s*union all/;/i; print $last}'

위의 부분을 File Open 문을 이용하여 할 수는 없을까요?
제가 테스트를 해본결과 if문을 써서 union all 매칭되는 키워드를 ';' 로 바꾸기는 하는데 전부 바껴 버려서..(아직 내공이 부족하군요)
Open 문을 사용하여 마지막 라인만 ';' 바꾸는게 가능할까요?

chanik의 이미지

http://kldp.org/node/140639#comment-600196 에 답글 달았습니다.

주제와 직접 관련되지 않은 질문을 글타래에 섞으면 다른 분들이 참고하기가 어려워집니다.
이 질문에 대한 내용은 원래의 질문글에 모으는 것이 나을 것 같아 그 글에 달아뒀습니다.

jip102의 이미지

네 생각이 좀 짧았습니다.
감사합니다.

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.