My Sphinx Search replication (master and slaves)

One instance and one storage with indices is neither secure nor effective.  That is why I created my replication for Sphinx Search . To put it shortly, my Sphinx’s architecture constists of tree indices and MVA (multivalue attributes stored in mva_updates_pool) – that is why I cannot use RT index.

My first idea was to create independent Sphix index + searchd(aemon) per machine which would be self-sufficient – but it has tree disadvantagies:
– there is one source (i.e. database :));
– advanced structure in database (as manager function);
– does not conform to KISS;

Therefore,  a known solution from ancient times – one master many slaves – seems to be better.
Master works as usual and its additional requirement is NFS server ON. You should configure your iptables – this is my shot (considering that slave is 192.168.1.51):

ACCEPT     tcp  --  192.168.1.51            0.0.0.0/0           tcp dpt:32803
ACCEPT     udp  --  192.168.1.51            0.0.0.0/0           udp dpt:32769
ACCEPT     tcp  --  192.168.1.51            0.0.0.0/0           tcp dpt:2049
ACCEPT     udp  --  192.168.1.51            0.0.0.0/0           udp dpt:2049
ACCEPT     tcp  --  192.168.1.51            0.0.0.0/0           tcp dpt:892
ACCEPT     udp  --  192.168.1.51            0.0.0.0/0           udp dpt:892
ACCEPT     tcp  --  192.168.1.51            0.0.0.0/0           tcp dpt:875
ACCEPT     udp  --  192.168.1.51            0.0.0.0/0           udp dpt:875
ACCEPT     udp  --  192.168.1.51            0.0.0.0/0           udp dpt:111
ACCEPT     tcp  --  192.168.1.51            0.0.0.0/0           tcp dpt:111

Share master storage giving adequate writing to /etc/exports

/storage/ 192.168.1.51(rw,no_subtree_check,sync,no_root_squash)

Now slave, firstly you should mount master through nfs in yout fstab:

# 192.168.1.50 is master
192.168.1.50:/storage      /storage_master         nfs     ro,rsize=8192,hard,intr,nfsvers=3,tcp,noatime,nodev,async 0 0

Ok, slave sees master storage, now I would like to explain how my script works:
1. Script observes whether master’s index change;
2. In case master’s index has been rebuilt it copies all files conform to sphinx rotate requirements (conforming to Sphinx sources);
3. It executes special stop/start file with option rotate (I present this file as /etc/init.d/sphinx)

/etc/init.d/sphinx

How to install

On redhat/centos Os you execute:

chkconfig --add sphinx

Content

#!/bin/bash

# inits, open order, close order
# chkconfig: 2345 99 10
# description: Sphinx daemon
HOME_DIR="/usr/local/services/sphinx/bin/"
STORAGE_DIR="/storage"
NAME="searchd"
CONFIG="/usr/local/services/sphinx/etc/arch.cnf"
iterator=2
MAX_ATTEMPT=5
aINDEXES=(daily weekly archive)
INDEX_PID_EXT="spl"

if [ ! -r $CONFIG ]; then
    echo "No config file or cannot be read. Script has been stopped"
    exit 0
fi

if [ ! -d $HOME_DIR ]; then
    echo "No home dir. Script has been stopped"
    exit 0
fi
# Function unlinks files containg Sphinxa indices' pids  (they exists when daemon is ugly killed)
del_index_pid(){
    element_count=${#aINDEXES[@]}
    index=0
    while [ "$index" -lt "$element_count" ]
    do
	INDEX_FILE=${aINDEXES[$index]}
	INDEX_FILE_PATH="$STORAGE_DIR/$INDEX_FILE.$INDEX_PID_EXT"
	if [ -f  "$INDEX_FILE_PATH" ]; then
	    rm $INDEX_FILE_PATH
    	    if [ -f  "$INDEX_FILE_PATH" ]; then
		"Error: cannot remove PID index file in localization: $INDEX_FILE_PATH"
	    else
		"Warning: Sphinx was not clearly closed. I removed index PID file: $INDEX_FILE_PATH"
            fi
	    # echo "$INDEX_FILE_PATH does not exists."
	fi
        index+=1
    done
}

check_pid(){
    DAEMON_PID=$(/sbin/pidof -s -o %PPID "$HOME_DIR$NAME")
    DAEMON_PID=$(($DAEMON_PID+0))
    usleep 500000
}

start_process() {
    if [ $DAEMON_PID -gt 1 ]; then
        echo "Process is running ($DAEMON_PID)"
    else
	del_index_pid
	echo "Starting..."
	echo "$HOME_DIR$NAME" --config "$CONFIG"
	"$HOME_DIR$NAME" --config "$CONFIG"
	rebuild;
        RETVAL=$?
    fi
}

kill_process() {
    if [ $DAEMON_PID -lt 1 ]; then
	echo "Process is not running $DAEMON_PID"
    else
        if [ $iterator -gt 1 ]; then
	    echo "Killing $NAME..."
	fi
	killall $NAME
	sleep 1
	check_pid
	if [ ! $DAEMON_PID  ]; then
	    echo "Process killed $DAEMON_PID"
	else
	    if [ $iterator -gt $MAX_ATTEMPT ]; then
		echo "I cannot kill process. I give up after $MAX_ATTEMPT attempts. Sorry, I fucked it."
	    else
	        iterator=$(( $iterator + 1 ))
		kill_process
	    fi
	fi
    fi
}

function rotate {
    if [ $DAEMON_PID -lt 1 ]; then
	echo "Process is not running $DAEMON_PID"
    else
	echo "Process is to be rotated"
	kill -HUP $DAEMON_PID;
    fi
}

check_status() {
    if [ $DAEMON_PID -gt 1 ]; then
        echo "1"
    else
	usleep 500000
	check_pid
	check_status2
    fi
}

check_status2() {
    if [ $DAEMON_PID -gt 1 ]; then
        echo "1"
    else
	echo "-1"
    fi
}

check_pid

case "$1" in
    start)
	start_process
	;;
    stop)
	kill_process
	;;
    restart)
	kill_process
	start_process
    	;;
    status)
	check_status
	;;
    rotate)
	rotate
	;;
    *)
    echo "Usage: $0 (start|stop|restart|status|rotate)"
    ;;
esac

Ok, now the main script self-descripting “sphinx-slave.sh”

#!/bin/bash

MASTER_DIR="/storage_master/arch/";
LOCAL_DIR="/storage/arch/";
LOG_ERROR_DIR="/var/log/sphinx/slave/";
LOG_FILE="/var/log/sphinx/slave.log";
LOCK_FILE="/var/log/sphinx/slave.lock";
UNIQUE_NUMBER="";
# basic file to watch - it is created as last.
FILE_TIMESTAMP="sph"
# Lock file of Sphinx process. File is present while process is working.
FILE_LOCK_SPH="spl"
MAKE_ROTATE=1;
MAKE_RESTART=0;

SPH_INDICES=('daily' 'weekly' 'archive');
# Basic index which requires to stop searchd(aemon).
SPH_MAIN_INDEX="archive";
SPH_INDEX_ERR=();
SPH_INDEX_NEW=();
SPH_INDICES_QTY=${#SPH_INDICES[@]};
SPH_INDEX_NEW_ITEM_ITERATOR=0;

FILES_TO_DETECT=('spd' 'sph' 'spi' 'spp');
FILES_TO_DETECT_QTY=${#FILES_TO_DETECT[@]};
FILES_TO_RENAME=('spl' 'spa' 'spd' 'sph' 'spi' 'spk' 'spm' 'spp');
FILES_TO_RENAME_QTY=${#FILES_TO_RENAME[@]};

# To be set
HOMEDIR="";

# Block functions

function set_homedir {
    dirExec=`pwd`;
    fileExec=`basename $1`;
    fullPath="$dirExec/$fileExec";
    if [ -f $fullPath ]; then
        HOMEDIR=$dirExec;
    else
        HOMEDIR=`dirname $0`;
    fi
}

function check_all {

    if [  -f $LOCK_FILE ]; then
	write_to_log "Lock file exists ($LOCK_FILE)";
	return 0;

    fi

    return 1;
}

function compare_int {
    if [ $1 -gt $2 ]; then
	return 1;
    else
	return 0;
    fi
}

function write_to_log {
    INFO=$1
    TIME=$(date '+%Y.%m.%d|%H:%M');
    echo "[$UNIQUE_NUMBER] $TIME $INFO" >> $LOG_FILE
}

# End block functions

# Detect homedir
set_homedir $0
UNIQUE_NUMBER=$(date '+%s');

check_all
MAY_CONTINUE=$?;
if [ "$MAY_CONTINUE" -eq 0 ]; then
    exit 0;
fi
touch $LOCK_FILE;

for ((INDICES_ITERATOR=0;INDICES_ITERATOR<$SPH_INDICES_QTY;INDICES_ITERATOR++)); do     SPH_INDEX=${SPH_INDICES[${INDICES_ITERATOR}]};     if [ -f $MASTER_DIR$SPH_INDEX.$FILE_TIMESTAMP ] && [ -f $MASTER_DIR$SPH_INDEX.$FILE_LOCK_SPH ]; then 	# Musza istniec obydwa pliki 	MASTER_STAMP=`/usr/bin/stat  -c '%Z' $MASTER_DIR$SPH_INDEX.$FILE_TIMESTAMP`;         MASTER_STAMP=$((MASTER_STAMP + 0));     else 	# No sph|spl files. spl: master does not work, sph: index does not exists 	MASTER_STAMP=0;     fi     if [ -f $LOCAL_DIR$SPH_INDEX.$FILE_TIMESTAMP ]; then 	LOCAL_STAMP=`/usr/bin/stat  -c '%Z' $LOCAL_DIR$SPH_INDEX.$FILE_TIMESTAMP`; 	LOCAL_STAMP=$((LOCAL_STAMP + 0));     else 	LOCAL_STAMP=0;     fi          if [ "$MASTER_STAMP" -eq 0 ]; then 	write_to_log "Brak indeksu master: $SPH_INDEX"     else 	INDEX_ERROR_FILE="$LOG_ERROR_DIR$SPH_INDEX.err"; 	# echo "$MASTER_STAMP for $SPH_INDEX"; 	compare_int $MASTER_STAMP $LOCAL_STAMP 	is_new=$? 	if [ "$is_new" -eq "1" ]; then 	    if [ -f $INDEX_ERROR_FILE ]; then 		rm $INDEX_ERROR_FILE; 	    fi 	    # write logs 	    write_to_log "Index '$SPH_INDEX' is to be renewing"; 	    write_to_log "Stat for index '$SPH_INDEX' MASTER|SLAVE: $MASTER_STAMP|$LOCAL_STAMP"; 	     	    # Sprawdz, czy na pewno nie ma plikow tymczasowych (oznaczajacych, ze indeks jest w trakcie budowy) 	    TMP_FILE_QTY=$(ls -l $MASTER_DIR$SPH_INDEX.*.tmp* 2>/dev/null | wc -l);
	    TMP_FILE_QTY=$(( TMP_FILE_QTY + 0));
	    if [ "$TMP_FILE_QTY" -gt "0" ]; then
		write_to_log "Temporary files are available [$TMP_FILE_QTY]. Omitting index renewal - '$SPH_INDEX'.";
		continue;
	    fi

	    if [ "$SPH_MAIN_INDEX" = "$SPH_INDEX" ]; then
		write_to_log "Stopping service"
		write_to_log "Copying file with stopwords"
		cp $MASTER_DIR../stopwords-pl $LOCAL_DIR../
		/etc/init.d/sphinx stop
		write_to_log "Executing command 'sync'";
		sync;
		write_to_log "Writing command to clear cache";
		echo 3 > /proc/sys/vm/drop_caches;
		sleep 10;

	    fi
    	    # copy files as .new
	    for ((TO_COPY_ITERATOR=0;TO_COPY_ITERATOR<$FILES_TO_RENAME_QTY;TO_COPY_ITERATOR++)); do 		SPH_INDEX_COPY_SRC=$MASTER_DIR$SPH_INDEX"."; 		SPH_INDEX_COPY_DST=$LOCAL_DIR$SPH_INDEX"."; 		FILE_TO_COPY=${FILES_TO_RENAME[${TO_COPY_ITERATOR}]}; 		# echo $SPH_INDEX_COPY$FILE_TO_COPY; 		if [ "$SPH_MAIN_INDEX" = "$SPH_INDEX" ]; then 		    rm_result=`rm -f $SPH_INDEX_COPY_DST$FILE_TO_COPY  2>&1`;
		    res_int=$(( $? + 0 ));
		    rm_result=`echo -n $rm_result | sed 's/\s\n//g'`;
		    rm_no_file=`echo $rm_result | grep -i "No such file"`;
		    # the length of STRING is zero

		    if [ $res_int -gt "0" ] && [ -z "$rm_no_file" ]; then
			echo "Error: -z rm_nofile: $rm_no_file vs. result:  $rm_result vs. $SPH_INDEX_COPY_DST$FILE_TO_COPY"
			SPH_INDEX_ERR[$INDICES_ITERATOR]=1;
			write_to_log "$SPH_INDEX $rm_result";
		    fi
		    cp_result=`cp $SPH_INDEX_COPY_SRC$FILE_TO_COPY $SPH_INDEX_COPY_DST$FILE_TO_COPY 2>&1 `;
		else
		    # add extenstion ".new"
    		    cp_result=`cp $SPH_INDEX_COPY_SRC$FILE_TO_COPY $SPH_INDEX_COPY_DST"new."$FILE_TO_COPY 2>&1 `;
		fi

		cp_result=`echo -n $cp_result | sed 's/\s\n//g'`;
		# echo $cp_result;
		if [ -n "$cp_result" ]; then
		    SPH_INDEX_ERR[$INDICES_ITERATOR]=1;
		    MAKE_ROTATE=0;
		    write_to_log "$SPH_INDEX $FILE_TO_COPY error: $cp_result."
		else
		    write_to_log "$SPH_INDEX $FILE_TO_COPY successfuly copied."
		fi
	    done

	    IS_ERROR=${SPH_INDEX_ERR[${INDICES_ITERATOR}]};
	    if [ -z $IS_ERROR ]; then
		IS_ERROR=0;
		# set index to renew
		SPH_INDEX_NEW[$SPH_INDEX_NEW_ITEM_ITERATOR]=$SPH_INDEX;
		((SPH_INDEX_NEW_ITEM_ITERATOR++));
	    fi
	    if [ "$IS_ERROR" -eq "1" ] ; then
		touch $INDEX_ERROR_FILE;
	    fi
	fi
    fi
done 

SPH_INDEX_NEW_QTY=$(( ${#SPH_INDEX_NEW[@]} + 0 ));
SPH_INDEX_ERR_QTY=$(( ${#SPH_INDEX_ERR[@]} + 0 ));

if [ "$SPH_INDEX_NEW_QTY" -gt "0" ] && [ "$SPH_INDEX_ERR_QTY" -lt "1" ]; then

    write_to_log "Qty of new indices is larger then 0 and error indices is less then 1 [OK].";

    for ((INDICES_ITERATOR2=0;INDICES_ITERATOR2<$SPH_INDEX_NEW_QTY;INDICES_ITERATOR2++)); do
	SPH_INDEX_NAME=${SPH_INDEX_NEW[${INDICES_ITERATOR2}]};
	if [ "$SPH_INDEX_NAME" = $SPH_MAIN_INDEX ]; then
	    MAKE_RESTART=1;
	else
	    continue;
	    # for IDX_FILE_TMP in "$LOCAL_DIR$SPH_INDEX_NAME"*".new"; do
	    #	IDX_FILE=`echo $IDX_FILE_TMP | sed 's/\.tmp$//g'`;
	    #	mv $IDX_FILE_TMP $IDX_FILE;
	    # done
	fi
    done

    if [ "$MAKE_RESTART" -gt "0" ]; then
	write_to_log "Starting service"
	/etc/init.d/sphinx start
    else
	if [ "$MAKE_ROTATE" -gt "0" ]; then
	    write_to_log "Rotating indexes"
	    /etc/init.d/sphinx rotate
	else
	    write_to_log "Not allowed to rotate indices";
	fi
    fi

# else
#    write_to_log "Qty of new indices [$SPH_INDEX_NEW_QTY] is less then 0 OR error indices [$SPH_INDEX_ERR_QTY] is greater then 0.";

fi                    

rm $LOCK_FILE;

exit 0
  1. No comments yet.

  1. No trackbacks yet.