Importing a MySQL database into Apache Solr Part I

This is a tutorial for getting started with Apache Solr enterprise search with PHP and MySQL on Unix/Linux or Mac OS X. Most of the information I have found is rather incomplete or full of erros so I wanted to share how I have successfully used Solr in a number of different applications. I’m going to break this down into three separate posts/sections.

  • Importing a MySQL database into Apache Solr
  • Using PHP with Apache Solr
  • Using the Suggester feature in the new Apache Solr 3.1

Apaches Solr is an enterprise search application that was developed at CNET and is now part of the Apache Lucene project. It runs in a standalone java servlet contain like Jetty or Tomcat. Since it runs as a webservice, you access it from any application like PHP or in Rails. Results can be returned in XML, JSON, or now CSV format.

There are several advantages to using Solr for your search function in you application. The biggest is speed. In a ~1 gig table Solr will return a result in under 0.2 seconds versus MySql fulltext search which can take up to 10 seconds (or even longer) depending on the length and complexity of the query. End users expect fast search results and Solr can deliver that, while allowing a level of control over search that a person would not be able to achieve using Google, i.e. search for posts by user name or product by manufacturer.

Another big advantage with Solr is that it recognizes plurals and slight misspellings, for instance a query with “Arc’teryx” will return results with both “Acrteryx” and “Arc’teryx”. A description of using other search features like facets can be found at the Solr Homepage.

Where would you use Solr? Any site that has a lot of content will benefit from using enterprise search; large popular blogs, e-commerce sites with a large product database, and forums/message boards.

Server requirements depend on the size of database but are fairly modest while running queries. I have been able to run Solr on a VPS with 300 megs or RAM with laser quick results. However, the database import requires much more RAM, for a 1.5 gig DB it requires at least 2 gigs and preferably 4gig. I don’t believe it’s necessary to run Solr on a separate server or VPS, just making sure that you setup your cron jobs for import to run in the evening when traffic is low.

Let’s get started!

First, all the examples in this tutorial will be run on locally on OS X under the domain solr.local. If you don’t already develop on your machine locally I encourage you to do so. On Mac OS X both MAMP and VirtualHostX are great tools for using a local dev environment.

  1. Start by downloading Solr. The current version is 3.1. Place the directory in an appropriate location, ie User/Sites/Solr31.
  2. In the termimal change into the example directory.
  3. Start the Jetty server up by executing in the terminal “java -jar start.jar.
  4. Get familiar with the terminal output for Jetty as this will help you track down errors later in your configuration.
  5. In your browser go to http://solr.local:8983/solr/admin
  6. Go through the Solr tutorial to get yourself familiar with the feature set.
  7. Stop Jetty with Control-C.

Great. Now we are ready to start getting Solr configured to import your MySQL database. In this example I will be configuring Solr to import a generic message board table.

  1. Download the MySQL JDBC driver from http://www.mysql.com/downloads/connector/j/
  2. Copy the downloaded directory into example/lib
  3. In example/solr/conf create a file called data-config.xml. Change the sql queries to reflect the database you are indexing.
    1. <dataConfig>
    2. <dataSource type="JdbcDataSource"
    3. driver="com.mysql.jdbc.Driver"
    4. url="jdbc:mysql://localhost/ccubbupgrade"
    5. user="root"
    6. password=""/>
    7. <document name="content">
    8. <entity name="id"
    9. query="SELECT
    10. p.post_id,
    11. p.post_posted_time,
    12. FROM_UNIXTIME(p.post_posted_time) AS post_posted_datetime,
    13. p.user_id,
    14. t.forum_id,
    15. p.post_subject,
    16. p.post_default_body
    17. FROM
    18. POSTS AS p
    19. JOIN
    20. TOPICS AS t
    21. ON p.topic_id = t.topic_id
    22. WHERE
    23. p.POST_IS_APPROVED = '1'
    24. AND t.TOPIC_STATUS != 'M'"
    25. deltaImportQuery="SELECT
    26. p.post_id,
    27. p.post_posted_time,
    28. FROM_UNIXTIME(p.post_posted_time) AS post_posted_datetime,
    29. p.user_id,
    30. t.forum_id,
    31. p.post_subject,
    32. p.post_default_body
    33. FROM
    34. POSTS AS p
    35. JOIN
    36. TOPICS AS t
    37. ON p.topic_id = t.topic_id
    38. WHERE
    39. p.post_id = ${dataimporter.delta.post_id}"
    40. deltaQuery="SELECT
    41. post_id
    42. FROM
    43. POSTS
    44. WHERE
    45. post_posted_time > UNIX_TIMESTAMP('${dataimporter.last_index_time}')">
    46. <field column="POST_ID" name="post_id" />
    47. <field column="POST_POSTED_TIME" name="post_posted_time" />
    48. <field column="USER_ID" name="user_id" />
    49. <field column="FORUM_ID" name="forum_id" />
    50. <field column="POST_SUBJECT" name="post_subject" />
    51. <field column="POST_DEFAULT_BODY" name="post_default_body" />
    52. </entity>
    53. </document>
    54. </dataConfig>
  4. We need to tell Solr that we will be using the JDBC driver. Open the solrconfig.xml file in the same directory and insert the following code.
    1. <requestHandler name="/dataimport"
    2. class="org.apache.solr.handler.dataimport.DataImportHandler">
    3. <lst name="defaults">
    4. <str name="config">data-config.xml</str>
    5. </lst>
    6. </requestHandler>
  5. Now we need to add some info into the schema.xml file, located in the same directory, to tell Solr more about our table columns and how to conduct a search.
    1. <!-- Field to use to determine and enforce document uniqueness.
    2. Unless this field is marked with required="false", it will be a required field
    3. -->
    4. <uniqueKey>post_id</uniqueKey>
    5. <field name="post_id" type="string" indexed="true" stored="true" required="true"/>
    6. <field name="post_posted_time" type="tint" indexed="true" stored="true"/>
    7. <field name="user_id" type="int" indexed="true" stored="true" />
    8. <field name="forum_id" type="int" indexed="true" stored="true"/>
    9. <field name="post_subject" type="text" indexed="true" stored="true"/>
    10. <field name="post_default_body" type="text" indexed="true" stored="true"/>
    11. <!-- field for the QueryParser to use when an explicit fieldname is absent -->
    12. <defaultSearchField>fulltext</defaultSearchField>
  6. Go ahead and start Jetty up again with “java -jar start.jar” and check for any errors in the terminal.
  7. Now it’s time to do a full index. In your browser go to http://solr.local:8983/solr/dataimport?command=full-import . This process will take a while depending on the size of your database. Check the status of the import with http://solr.local:8983/solr/dataimport . The response format will look like this for the first little bit “00data-config.xmlidleThis response format is experimental. It is likely to change in the future.” Eventually it will show you the index progress after it has loaded the database.
  8. After the index process is done you should be able to search your data at http://solr.local:8983/solr/admin . If not, look at the errors popping up in the terminal. Results will be in XML format.
  9. To setup delta indexing (ie changes made every hour or interval at you choosing) setup a cronjob to the following url http://solr.local:8983/solr/dataimport?command=delta-import
  10. To run the Solr service in the background or in the background on your server make sure you start it like this “java -jar start.jar &”

In another post I will cover locking down Solr so that it is only accessible by IP address or username/password combo.

 

출처 : http://entropytc.com/importing-a-mysql-database-into-apache-solr/#codesyntax_1


1. 문장 끝에 ; 대신 \G 를 사용하면 결과를 세로로 볼 수 있다.
2. select  문장 앞에 explain  키워드를 사용하면  플랜을 볼 수 있다.
explain
select * from User \G 

I. plan 결과 설명
    ※ http://dev.mysql.com/doc/refman/5.5/en/using-explain.html

    1. select_type 값
        - SIMPLE
           . 평범한 SELECT 문장
        - PRIMARY
           . 하위 쿼리나  UNION을 사용할 때 외부(첫 번째) 쿼리
        - UNION
           . UNION 두 번째나 다음 번 쿼리
        - DEPENDENT UNION
           . UNION에서 첫 번째 쿼리에 따른 두 번째나 다음 번 쿼리
        - UNION RESULT
           . UNION 겨로가
        - SUBQUERY
           . 내부 하위 쿼리
        - DEPENDENT SUBQUERY
           . 첫 번째 쿼리에 따른 내부 하위 쿼리(즉, 상관 하위 쿼리)
        - DERIVED
           . FROM 절에서 사용한 하위 쿼리
        - UNCACHEABLE SUBQUERY
           . 그 결과가 캐쉬될 수 없으며 각 행마다 다시 계산되어야 하는 하위 쿼리
        - UNCACHEABLE UNION
           . UNCACHEABLE SUBQUERY에 속한 UNION에서 두 번째나 그 이후의 SELECT

    2. table
        - 쿼리에 응답하는 테이블 명

    3. type
        - 쿼리에서 테이블이 어떻게 조인되는지 설명
        - const 또는 system
           . 테이블이 한 번만 읽어온다. 테이블에 row가 단 하나만 있을 때 일어날 수 있다.
        - eq_ref
           . 조인하는 테이블에서 row 집합을 읽어올 때마다 이 테이블에서 한 row씩 읽는다.
             테이블의 인덱스를 사용할 때 조인이 사용되며 인덱스는 UNIQUE하거나 기본 키이다.
        - fulltext
           . fulltext 인덱스를 사용하여 조인한다.
        - ref
           . 조인하는 테이블에서 row 집합을 읽어올 때마다 이 테이블에서 대응되는 row 집합을 읽어온다.
             조인 조건에 따라서는 한 행만 선택할 수 없을 때, 조인에서 키의 일부분만이 사용되었을 때,
             혹은 키가 UNIQUE 하지 않거나 기본 키가 아닐 때
        - ref_or_null
           . ref 쿼리와 비슷하지만 NULL 인 row 도 찾아본다. (하위 쿼리에서 가장 많이 사용된다)
        - index_merge
           . Index Merge 라는 특별한 최적화 기법이 사용되었다.
        - unique_subquery
           . 유일한 한 줄이 리턴될 때 IN 하위 쿼리에서 ref를 대신하여 사용할 수 있다.
        - index_subquery
           . unique_subquery 와 비숫하지만 인덱스된 유일하지 않은 하위 쿼리에서 사용한다.
        - range
           . 조인하는 테이블에서 row 집합마다 이 테이블 중 특정 범위에 들어가는 row들을 읽어온다.
        - index
           . 모든 인덱스를 읽는다.
        - ALL
           . 이 테이블의 모든 행을 읽는다.

    4. rows
        - 조인을 실행하기 위해 테이블마다 읽어야 하는 row의 수를 대충 계산
          쿼리가 사용하는 총 row의 수를 얻기 위해서는 값들을 곱해야 한다.

    5. possible_keys
        - 테이블을 조인하기 위해 사용할 수 있는 키
        - PRIMARY 는 테이블마다 일반적으로 있기 때문에 대부분 나온다. 

    6. key
        - row 을 조인할 때 실제로 사용하고 있는 키
        - NULL 은 키를 사용하고 있지 않다는 뜻이다.

    7. key_len
        - 사용된 키의 길이를 나타낸다.

    8. ref
        - 테이블에서 row를 선택할 때 사용한 키를 나타낸다.

    9. Extra
        - 어떻게 조인을 실행하는가에 대한 정보
        - Distinct
           . 처음으로 일치하는 줄이 발견되면 끝난다.
        - Not exists
           . LEFT JOIN 을 사용하여 쿼리가 최적화되어 있다.
        - Range checked for each record
           . 조인하는 테이블에서 row 집합을 읽어올 때마다 사용할 수 있는 인덱스를 찾는다.
        - Using filesort
           . 테이터를 정렬하려면 두 번 읽어야 한다. (따라서 시간도 두 배 걸린다)
        - Using index
           . 테이블에 있는 모든 정보를 인덱스에서 얻어왔다. 따라서 실제 row는 읽지 않았다.
        - Using join buffer
           . 조인 버퍼를 사용하여 테이블을 부분적으로 읽어 들인다. 그 후 쿼리를 수행할 때 버퍼에서 각 행을 추출한다.
        - Using temporary
           . 이 쿼리를 실행하는 동안 임시 테이블을 사용하였다.
        - Using where
           . 행을 선택하는 데 where 절을 사용하였다.

II. sql 쿼리 튜닝
    1. 조인 최적화를 위해 키 분배를 검사
        - myisamchk 유틸리티 사용
            #>myisamchk --analyze <pathtomysqldatabase/table명>
            #>myisamchk --analyze <pathtomysqldatabase/*.MYI>
            #>myisamchk --analyze <pathtomysqldatadirectory/*/*.MYI>
        - Analyze Table 문을 사용
            sql>analyze table customers, orders, order_items, books;

    2. 테이블 최적화
        - 테이블의 단편화를 없앤다.
            sql>optimize table <table명>;
            #>myisamchk -r table
        - 테이블의 인덱스를 정렬하고 인덱스에 맞게 데이터를 재배치 한다.
            #>myisamchk --sort-index --sort-records=1 <pathtomysqldatadirectory/*/*.MYI>

실무에서 자주 사용되는 쉘 스크립트  #2
           (MySQL DB관리 유틸리티)




  지난번, 강좌(apache 자동 재 시작 프로그램)에 이어 실무에서 자주 사용되는 쉘 스크립트에 대한 강좌를 써 내려 가겠다. 물론 최대한 쉽고 최소한의 라인 수로 작성 가능한 쉘 스크립트에 대한 강좌를 쓸 것이다.
   금번 강좌는 MySQL을 쉽게 관리 할 수 있는 툴이다. 서버 관리를 하다 보면, mysql 접속하여 여러가지 sql문으로 서버 상태를 알아 보거나, 모니터링 해야 할 때가 있다. 이럴 때 마다 로그인을 해서, sql문을 얻어내는 것은 아주 귀찮은 일이고, 또 익숙하지 않으면 아주 오랜 시간이 소요되는 작업이다.  내가 만들고자 하는 스크립트는 이런 작업을 쉽게 해 줄 수 있는 스크립트를 만드는 것이다.

먼저, 어떤일을 할 것인지 생각 해 보자...


1) MySQL의 프로세스 리스트를 보려면?
   mysql의 프로세스 리스트는 ps 등의 명령어로 쉽게 볼 수 있다. 난 그런 것을 원하는 것이 아니고, 각 프로세스들이 하는 일들을 보고 싶다. mysql은 다음과 같은 방법으로 이런 일이 가능하게 제공한다.
 
   mysqladmin -u root -p processlist

또는

   mysql 접속한 상태에서
   show processlist;

나는 일괄적으로 다음과 같은 방법으로 mysql 명령을 실행 시킬 것이다.

echo "쿼리;" | mysql -u"root" -p"비밀번호"

위 명령어는 다음과 같다.
 echo "쿼리; " : "쿼리;"를 출력한다.
| : 파이프, 앞 명령어의 내용을 뒷 명령어의 표준 입력으로 넣는다.
mysql -u"root" -p"비밀번호" : mysql 서버에 접속한다. "비밀번호"는 서버의 비밀번호를 넣어준다.

우리가 원하는 일은 다음과 같이 한다.

echo "show processlist;" | mysql -u"root" -p"비밀번호"





 2) root로 그냥 접속만 하려면?
  '1)'의 프로세스 리스트를 보는 것 보다 쉽다. 단순하게..

mysql -u"root" -p"비밀번호"


이렇게만 하면 끝난다.



 3) MySQL에 존재하는 database 들을 보려면?
  '1)'의 프로세스 리스트를 응용하면,

echo "show databases;" | mysql -u"root" -p"비밀번호"

이렇게만 하면 끝난다.


 이정도 했으면, 원하는 쿼리들을 위와 같은 방법으로 자유롭게 추가 할 수 있을 것이다.
내가 원하는 것은 위 명령어들을 모아서 쓰기 편리한 메뉴 방식으로 만드는 것이다.
어떻게 할까???
  나는 키보드로 특정 문자들을 입력받아 변수에 넣을 것이다. 그리고 그 변수에 따라 위 명령어들을 실행 시킬 것이다. 이런 작업들을 특정 문자가 입력될 때 까지 반속 할 것이다.



 4) 쉘 프로그램에서 키보드 입력 받기
 
나는 쉘 프로그램에서 키보드로 입력을 받아서 변수에 넣을 것이다. 어떻게 해야 할까

read <변수>
 
no라는 변수에 입력값을 넣으려면 다음과 같이 한다.
read no

이렇게 하면, '엔터'키를 누를 때 까지 입력을 받는다.

read no
echo "$no"

위와같이 하면 간단하게 입력받은 문자를 출력해서 보여 줄 수 있다.


5) 입력된 문자에 따라서 프로그램 실행 시키기
  '4)'에서 입력받은 문자에 따라서, 프로그램을 실행시키기 위해서는 case문을 사용해야 한다.
다음과 같다.

case <변수> in
   "값" )
        <명령어> ;;
   "값" )
        <명령어> ;;
 esac

위와같다.
위 방법을 우리가 목표 하는 것에 적용 시키면.

먼저 메시지를 뿌려 준다.

echo '
   1. 프로세스 리스트 보기
   2. mysql 접속
   3. database 리스트 보기
'
echo -n "번호 선택 : "
read no

case $no in
   "1" )
       
echo "show processlist;" | mysql -u"root" -p"비밀번호" ;;
   "2" )
       
mysql -u"root" -p"비밀번호" ;;
   "3" )
       
echo "show databases;" | mysql -u"root" -p"비밀번호" ;;
 esac


위와같이 작성하면, 위 쉘 스크립트를 실행 시키면, 리스트가 나오며 입력 대기 할 것이고, 1,2,3 중 하나의 번호를 누르면 지정 명령어가 실행 될 것이다..
  그렇다면, 다른 문자를 입력하면??  조건에 만족하는 문자가 없으면 그냥 지나간다...



6) 실행이 끝나면 다시 메뉴 보여주기
  명령어를 실행 시키고 빠져 나가 버리면, 다른 메뉴를 선택하기 위해서 또 명령어를 실행시켜야 한다. 하나의 명령어 실행 시키고 다시 메뉴 리스트를 반복적으로 보여 주기 위해서는 이전 강좌에서 배웠던, while 문을 사용하여 계속 실행 시키도록 해 보자.

while ( true ) ; do
  <프로그램>
done

위와같은 문법을 우리 스크립트에 적용하면,

while ( true ) ; do
   clean
   echo '
    1. 프로세스 리스트 보기
    2. mysql 접속
    3. database 리스트 보기
'
   echo -n "번호 선택 : "
   read no

  
case $no in
     "1" )
         
echo "show processlist;" | mysql -u"root" -p"비밀번호" ;;
     "2" )
         
mysql -u"root" -p"비밀번호" ;;
     "3" )
         
echo "show databases;" | mysql -u"root" -p"비밀번호" ;;
   esac
done


이렇게 하면, 선택한 명령어 실행이 끝나면, 다시 메뉴를 보여 줄 것이다.
clear 명령어는 화면의 내용을 지우라는 명령어이다. 메뉴가 줄줄줄~~ 내러 가는 일을 방지하기 위함니다.




7) 이 반복 메뉴에서 빠져 나오려면??
  한 명령어 실행이 끝나면, 메뉴가 나올 것이다. Ctrl + C 키를 눌러 빠져 나올 수 있지만, 그건 좀 아닌 것 같은데... 하나의 항목을 추가 했다. 메뉴에서 'q'키를 누르면 빠져 나오게 한다.



while ( true ) ; do
   clean
   echo '
    1. 프로세스 리스트 보기
    2. mysql 접속
    3. database 리스트 보기
    q. 끝내기
'
   echo -n "번호 선택 : "
   read no

  
case $no in
     "1" )
         
echo "show processlist;" | mysql -u"root" -p"비밀번호" ;;
     "2" )
         
mysql -u"root" -p"비밀번호" ;;
     "3" )
         
echo "show databases;" | mysql -u"root" -p"비밀번호" ;;
     "q" )
          exit 0 ;;

   esac
done


위 쉘스크립트에서 'q'키를 누르면 exit 명령어를 실행시켜 빠져 나오게 된다. 물론 정상 종료이기 때문에 0을 리턴한다..




8) "비밀번호"가 바뀌면 다 고쳐 줘야 하나요??
  비밀번호가 바뀌면 위 스크립트에서 "비밀번호" 부분을 모두 수정해야 한다. vi 등의 에디터에 치환 기능이 있어 한번에 치환 한다면 문제가 없다. 하지만, 비밀번호가 'mysql' 이라면, 얘기는 달라진다,.. 명령어인 'mysql'이 함께 바뀌기 때문에.. 하나 하나 수작업으로 고쳐야 한다.
  변수라는 개념을 써서 이 부분을 해결 해 보자.

a="변수"
echo $a

이렇게 하면 출력값은 ? 

변수

위와같이 출력된다. 변수라는 개념을 우리의 쉘스크립트에 적용시키면?

mysqlpw="비밀번호"

while ( true ) ; do
   clean
   echo '
    1. 프로세스 리스트 보기
    2. mysql 접속
    3. database 리스트 보기
    q. 끝내기
'
   echo -n "번호 선택 : "
   read no

  
case $no in
     "1" )
         
echo "show processlist;" | mysql -u"root" -p"$mysqlpw" ;;
     "2" )
         
mysql -u"root" -p"$mysqlpw" ;;
     "3" )
         
echo "show databases;" | mysql -u"root" -p"$mysqlpw" ;;
     "q" )
          exit 0 ;;

   esac
done

위와 같이 보라색 부분을 수정했다. 이렇게 수정하면, 상단의 변수만 수정 하면 다른 부분은 수정할 필요없이 적용되는 것이다.



우리는 이것으로
 쉘 스크립트의 case문과, 변수, 키보드로부터 입력받기 를 알게 되었다.
여러가지 편리한 기능들을 넣어 둔다면, 나만의 관리툴로 만들 수 있을 것이다. [뿌듯]

'OS > LINUX' 카테고리의 다른 글

쉘스크립트 ex5  (0) 2011.05.14
쉘스크립트 Ex4  (0) 2011.05.14
쉘스크립트 example  (0) 2011.05.14
egrep , awk  (0) 2011.05.14
YUM 사용법  (0) 2011.05.13

mysql

SELECT LAST_INSERT_ID();

php

@mysql_insert_id();

mssql

select @@identity 


+ Recent posts