s o m e t h i n g a b o u t m e

Solr problems identification

Here you can find a quick-recognize list that can help you to identify Solr error that hiding behind your application business logic.

Symptoms and possible causes.

There is a few symptoms that can help you to identify a problem:

  • Searching is fine, indexing is not working. When you change your data and no change appears in index and you cannot search by the new data.
    Possible causes:

    • Your application don’t add’n’commit data into Solr. Solr need to commit changes to see changed data in index.
      Check errors/exceptions in your application server log files
    • Solr index deadlocks.
      Try to push info to Solr index manually using magic command. If no result appears after command execution and execution just freezes — it is a lock. You have to check sudo kill -3 SOLRPID to be sure.
  • Searching returns wrong (maybe not unique) results.
    There can be a few possible causes:

    • Wrong search query.
      You have to check your query. Is it created right?
    • Wrong index data.
      You have to check an appropriate indexer to clarify index data constructing process. If there are no errors you can try to push some entry data into Solr index. Use magic command to add data into index (sometimes it is useful to specify all document fields in command to check if there are any problem with specific field).
    • Solr startup exception
      If search returns an empty result it can be caused by Solr startup error. You have to check Solr log files to identify it.
    • Index corruption.
      After searching query identification you have to try to search the query in Solr admin console. If wrong results appears — it is index corruption. You have to fire index rebuild (old index deletion is not necessary but preferable).
  • Search don’t work.
    • Exception during searching.
      Check log files for exceptions and errors. Connection refused (Solr master/backup server is down)? Are there any exceptions in your application during index data gathering?
    • Solr startup exception
      If search returns an empty result or returns an error it can be caused by Solr startup error. You have to check Solr log files to identify it.

Magic command

SolrJ client version 1.2 cannot show errors returned from server during indexing process. So we need to manually execute index add/commit operations to see server return message.

user@server$ curl http://solrserver.com/update -H "Content-Type: text/xml" --data-binary '<add><doc><field name="id">999999</field></doc></add>'
user@server$ curl http://solrserver.com/update -H "Content-Type: text/xml" --data-binary '<commit/>'

a good response is:

<result status="0"></result>

There can be any other errors and if you cannot identify some with this quick-recognizing list you need to keep digging inside your log files and stack traces :)


dancing around RMI

How to create a simple client-server application using RMI interface and run it on linux?

  1. We need to create an interface that extends java.rmi.Remote interface:
    public interface IFacade extends Remote {
    
            String sayHello() throws RemoteException;   // its important exception - its required for object exporting
    
    }
    
  2. Then we are implementing this interface. In main method we need to get|create a registry object, export our object marked as Remote, and bind it to some name (and actually start the server). Here we can specify the port number for binding.
    public class Facade implements IFacade {
    
            public String sayHello() {
                    return "hello";
            }
    
            public static void main(String[] args) {
    		int port = 7001;
                    String name = "Facade";
    		try {
    			IFacade facade = new Facade();
                            Registry registry = LocateRegistry.createRegistry(port);
                            IFacade stub = (IFacade) UnicastRemoteObject.exportObject(facade, port);
                            registry.bind(name, stub);
               		System.out.println("Server is up and running");
    
    			BufferedReader rdr = new BufferedReader(
    					new InputStreamReader(System.in));
    			while (true) {
    				System.out.println("Type EXIT to shutdown the server.");
    				if ("EXIT".equals(rdr.readLine())) {
    					break;
    				}
    			}
    			registry.unbind(name);
    			UnicastRemoteObject.unexportObject(facade, true);
                    } catch (Exception e) {
    			e.printStackTrace();
    		}
             }
    }
    
  3. We need to start rmiregistry service:
    traut@traut-laptop:~$ rmiregistry
    Bad port number - using default
    

    default port number is OK

  4. About client:
    public class Client extends Thread {
    
            @Override
    	public void run() {
    		super.run();
    		System.out.println("Client started");
    		try {
    			Registry registry = LocateRegistry.getRegistry(host, port);
    			IFacade facade = (IFacade) registry.lookup("Facade");
                            System.out.println(facade.sayHello());
    		} catch (Exception e) {
    			e.printStackTrace();
    			return;
    		}
          }
    
            public static void main(String[] args) {
    		int port = 7001;
    		String host = "traut-laptop";
    
    		new Client(host, port).start();
    		new Client(host, port).start();
    	}
    }
    
  5. Fin

A few Interesting things:

This release adds support for the dynamic generation of stub classes at runtime, obviating the need to use the Java(tm) Remote Method Invocation (Java RMI) stub compiler, rmic, to pregenerate stub classes for remote objects. Note that rmic must still be used to pregenerate stub classes for remote objects that need to support clients running on earlier versions.

Java RMI Release Notes

So, we don’t need to create stub files with rmic program. It must be processed silently undercover from 1.5

I’m not created and used here SecurityManager, policy files, and other boring stuff that documentation advice to use :)

RMI is still easy-to-understand interface but horrible to implement and to use


how to remove chunks from PNG

From PNG specification: The PNG datastream consists of a PNG signature followed by a sequence of chunks. Each chunk has a chunk type which specifies its function.

There are different types of PNG chunks:

  1. four of them are termed critical chunks: IHDR (first chunk, header), PLTE (for indexed PNG images), IDAT(image data chunks), IEND (last chunk, bottom line)
  2. The remaining 14 chunk types are termed ancillary chunk types, which encoders may generate and decoders may interpret.
    1. Transparency information: tRNS (transparency information).
    2. Colour space information: cHRM, gAMA, iCCP, sBIT, sRGB (colour space information).
    3. Textual information: iTXt, tEXt, zTXt (textual information).
    4. Miscellaneous information: bKGD, hIST, pHYs, sPLT (miscellaneous information).
    5. Time information: tIME (time stamp information).

By the way, each chunk has a 4 character identifier as you see and upper case means it’s required, lower case means it’s optional.

Chunks follow a format that goes like this: 4 bytes that store the length of the data block of the chunk, 4 bytes for the identifier, then the chunk data, then 4 bytes for the CRC. So chunk is [length.id.data.crc] Additionally every chunk type has its position boundaries in PNG data stream.

My issue was to remove bKGD chunk from PNG file (some mobile phones doesn’t like bKGD chunk in PNG file) so we will look closely at bKGD chunk.

bKGD can be placed between IHDR (always first) and first IDAT chunk so we need to find bKGD identifier or identifier of first IDAT chunk and then break the loop. So code is:


public static InputStream removebKGDChunk(InputStream is) {
        try {
            byte[] data = IOUtils.toByteArray(is);

            if (data.length == 0) {
                return new ByteArrayInputStream(data);
            }

            int chunkDataLenght = 0;
            int chunkPosition = 0;
            for (int i = 0; i < data.length; i++) {
                if (data[i + 4] == 'b' && data[i + 5] == 'K'
                    && data[i + 6] == 'G' && data[i + 7] == 'D') {
                    chunkPosition = i;
                    chunkDataLenght = (data[i++] << 4) | (data[i++] << 4)
                                            | (data[i++] << 4) | data[i];
                    break;
                }
                if (data[i + 4] == 'I' && data[i + 5] == 'D'
                    && data[i + 6] == 'A' && data[i + 7] == 'T') {
                    break;
                }
            }
            if (chunkPosition != 0) {
                int fullChunkLenght = MIN_CHUNK_LENGHT + chunkDataLenght;

                // MIN_CHUNK_LENGHT = lengthField + idField + CRC = 12

                System.arraycopy(data, chunkPosition + fullChunkLenght,
                        data, chunkPosition,
                        data.length - chunkPosition - fullChunkLenght);
                return new ByteArrayInputStream(
                        ArrayUtils.subarray(data, 0, data.length - fullChunkLenght));
            }
            return new ByteArrayInputStream(data);
        } catch (IOException e) {
            logger.warn("Cannot remove bKGD chunk ", e);
        } finally {
            IOUtils.closeQuietly(is);
        }
        return null;
    }

I am working with InputStream directly to make this functionality easy to plug/unplug.

Any questions? :)


interfaces

воочию убеждаюсь в необходимости универсальных интерфейсов и обязательности их использования. в ключевых узлах особенно.

в контексте последних постов, о mogilefs: если все классы используют универсальный интерфейс IStorage с методами get/save/delete/rename/etc, то подмена хранилищ - с обычного локального диска на mogilefs через клиента - происходит сменой маппинга бина в spring. И все счастливы и начинают ловить уже баги клиента mogilefs, а не вычищать хлам :)

но по опыту, в большинстве случаев народу (и я так поступаю, и мой код грязный :( ) проще сделать

new File(someFileName).exists()

чем прописывать бин и делать что-то наподобие

abstractStorage.isFileExists(fileName)

solrj client

also you can read solr problems identification


mogilefs. installation and configuration

MogileFS is open source distributed filesystem. Its properties and features include:

  • Application level — no special kernel modules required.
  • No single point of failure — all three components of a MogileFS setup (storage nodes, trackers, and the tracker’s database(s)) can be run on multiple machines, so there’s no single point of failure. (you can run trackers on the same machines as storage nodes, too, so you don’t need 4 machines…) A minimum of 2 machines is recommended.
  • Automatic file replication — files, based on their “class”, are automatically replicated between enough different storage nodes as to satisfy the minimum replica count as requested by their class. For instance, for a photo hosting site you can make original JPEGs have a minimum replica count of 3, but thumbnails and scaled versions only have a replica count of 1 or 2. If you lose the only copy of a thumbnail, the application can just rebuild it. In this way, MogileFS (without RAID) can save money on disks that would otherwise be storing multiple copies of data unnecessarily.
  • “Better than RAID” — in a non-SAN RAID setup, the disks are redundant, but the host isn’t. If you lose the entire machine, the files are inaccessible. MogileFS replicates the files between devices which are on different hosts, so files are always available.
  • Flat Namespace — Files are identified by named keys in a flat, global namespace. You can create as many namespaces as you’d like, so multiple applications with potentially conflicting keys can run on the same MogileFS installation.
  • Shared-Nothing — MogileFS doesn’t depend on a pricey SAN with shared disks. Every machine maintains its own local disks.
  • No RAID required — Local disks on MogileFS storage nodes can be in a RAID, or not. It’s cheaper not to, as RAID doesn’t buy you any safety that MogileFS doesn’t already provide.
  • Local filesystem agnostic — Local disks on MogileFS storage nodes can be formatted with your filesystem of choice (ext3, XFS, etc..). MogileFS does its own internal directory hashing so it doesn’t hit filesystem limits such as “max files per directory” or “max directories per directory”. Use what you’re comfortable with.

installation and configuration:

  1. svn checkout http://code.sixapart.com/svn/mogilefs/trunk/
    дальше ставим trunk/server по шагам:

    $ make Makefile.PL
    $ make
    $ make test
    $ make install

    на шаге “make test” может оказаться, что не хватает перловских модулей. берем CPAN и ставим все, что нужно. CPAN в виде интерактивной оболочки запускаем как

    $ sudo perl -MCPAN -e shell
  2. делаем схему в базе данных, делаем отдельного юзверя mogile, даем ему полные права на базу. Все необходимые таблицы сделает mogdbsetup с параметрами хоста, базы, пользователя, пароля в коммандной строке.
  3. для запуска tracker-серверов и storage-серверов сделаем пользователя mogile
  4. настраиваем конфиги tracker-серверов и storage-серверов: /etc/mogilefs/mogilefsd.conf и /etc/mogilefs/mogstored.conf на соответствующих серверах (на каждом storage- и tracker- сервере запускается по демону, которому нужен конфиг)
    $ cat /etc/mogilefs/mogilefsd.conf
    #daemonize = 1
    db_dsn = DBI:mysql:mogilefs:host=DBhost.host.com
    db_user = mogile
    db_pass = mogile
    listen = 0.0.0.0:7001
    conf_port = 7001
    listener_jobs = 5
    delete_jobs = 1
    replicate_jobs = 5
    mog_root = /mnt/mogilefs
    reaper_jobs = 1
    $ cat /etc/mogilefs/mogstored.conf
    httplisten=0.0.0.0:7500
    docroot=/var/mogdata
    mgmtlisten=0.0.0.0:7501

    нужно сделать /var/mogdata где и будет хранится контент FS

  5. запускаем tracker-сервера (нужны для запуска и конфигурирования storage-серверов) как
    # su mogile
    $ mogilefsd -c /etc/mogilefs/mogilefsd.conf --daemon
    $ exit
  6. конфигурим хранилища:
    $ mogadm --lib=/usr/local/share/perl5/5.8.5 --trackers=trackerhost.host.com:7001 host add mogilestorageN
    --ip=STORAGEIP --port=7500 --status=alive

    где mogilestorageN - имя хранилища (любое), STORAGEIP - ip-шник хранилища. trackers-хосты с портами можно перечислять через запятую.

    проверить факт добаления можно так:

    $ mogadm --lib=/usr/local/share/perl5/5.8.5 --trackers=trackerhost.host.com:7001 host list

    теперь нужно добавить devices (папки, в которых будет хранится контент)

    $ mogadm --lib=/usr/local/share/perl5/5.8.5 --trackers=trackerhost.host.com:7001 device add mogilestorageN Y

    где mogilestorageN - хранилище, на котором создаем device, YY - порядковый номер device-а.
    Device-ы создаются с именами dev1, dev2, dev3 … (в лучших традициях :) ) и олицетворяют папки /var/mogdata/devN

    список девайсов смотрим так:

    $ mogadm --lib=/usr/local/share/perl5/5.8.5 --trackers=trackerhost.host.com:7001 device list

    и стартуем хранилища:

    $ su mogile
    $ mogstored --daemon
  7. окончательная проверка:
    $ mogadm --lib=/usr/local/share/perl5/5.8.5 --trackers=trackerhost1.host.com:7001,trackerhost2.host.com:7001 check
    Checking trackers...
      trackerhost1.host.com:7001 ... OK
      trackerhost2.host.com:7001 ... OK
    
    Checking hosts...
      [ 1] mogilestorage ... OK
      [ 2] mogilestorage2 ... OK
    
    Checking devices...
      host device         size(G)    used(G)    free(G)   use%   ob state   I/O%
      ---- ------------ ---------- ---------- ---------- ------ ---------- -----
      [ 1] dev1             6.736      2.942      3.794  43.68%  writeable   0.0
      [ 2] dev2             6.736      5.242      1.494  77.82%  writeable   0.0
      ---- ------------ ---------- ---------- ---------- ------
                 total:    13.473      8.185      5.288  60.75%

    Не доверяя автоматическим тестам можем проверить работу хранилищ вручную:

    $ telnet storagehost.host.com 7500
    Trying 10.10.10.10...
    Connected to storagehost.host.com (10.10.10.10).
    Escape character is '^]'.
    PUT /dev1/test HTTP/1.0
    Content-length: 4
    
    test
    HTTP/1.0 200 OK
    Content-Type: text/html
    Content-Length: 18
    Server: Perlbal
    Connection: close
    <h1>200 - OK</h1>
    Connection closed by foreign host.
    $

    и смотрим, сохранились ли данные:

    $ ll /var/mogdata/dev1/
    total 324
    -rw-r--r--  1 mogile mogile      4 Sep  5 02:33 test
    drwxr-xr-x  2 mogile mogile 319488 Sep  5 02:30 test-write
    -rw-rw-rw-  1 mogile mogile    140 Sep  5 02:33 usage

    папка test есть - все ok.

пара слов, чтобы утрясти в памяти и оставить steps-list на будущее.

поиск и борьба с java клиентом к этому цирку - в следующей серии :) стей ин тач


Lucene/Solr Overview Tech Talk

Первый опыт теч-токов. Если есть вопросы - обращайтесь

Lucene/Solr Overview Tech Talk

Lucene/Solr Overview Tech Talk


enter your password

появилась потребность реализовать ввод логина/пароля в рамках лабораторной работы по защите информации. т.к. преподу абсолютно все равно - интегрирована защита в ОС (как я пытался изначально сделать, юзая корявые shell скрипты) или сделана в отдельном приложении (!), решил писать все лабораторные на java, эмулируя командную строку (изначально пугали необходимостью резидентов и проч. в лабе, а все сошлось к банальным приложениям на делфе. как обычно).

Начав делать ввод пароля, натолкнулся на интересные вещи - по запросу преподавателя нужны были “*” вместо пароля (плевать ему, что это не секьюрно и лучше вообще не светить количество символов в пароле). Если б без звездочек - пересобрал бы shadow или использовал бы кривой Console.getPassword(), но тут, бля, звездочки.

Тут оказалось, что в джаве нет поддержки по-символьного ввода и нет поддержки режима ввода без echo (под посимвольным вводом я понимаю реакцию метода read на нажатие кнопки в консоли. в джаве в стандартном режиме считать из консоли можно лишь по return). Гугл подсказал несколько workaround-ов и указал на этот баг 9-летней (!) давности (продолжил свою жизнь тут). Частично проблема исправлена в 1.6 классом Console, но криво (например, очень мне нравится код наподобие try {bla-bla} catch (IOException e) {}) и то создан только метод getPassword(), причем без поддержки echo/-echo режимов, а только полная аналогия никсового getpass().

Пришлось выкручиватся, т.к. лезть в swing принципиально не хотелось :)

Варианты решения проблемы:

  • забить на звездочки и использовать
    System.console().getPassword();
  • играться с /dev/tty через exec.
  • использовать, например, jcurses

Решил попробовать второй вариан - и он все-таки заработал, не очень красиво, но хитрость забавляет :) Вот код:


p = Runtime.getRuntime().exec(new String[] {
                                                             "sh", "-c", "stty" +
                                                             (on ? " echo -cbreak" : " -echo cbreak") +
                                                             " < /dev/tty"});
p.waitFor();

т.е. на on == true включает echo и отключает cbreak, на false - наоборот.

screen

Теперь осталось дописать подобие интерактивной консоли и можно парить сразу 3 лабы :)


quartz enterprise job scheduler

краткие вкусовпечатления, так сказать :) понравилась узкая специализированность и продуманность фреймворка - все, для решения необходимой задачи. Каждая задача характеризуется JobDetails - тут ее можно отнести к некоторой группе задач, дать этой задаче имя, и, собственно указать, что есть задача - указать на класс (что примечательно, т.к. экземпляр класа задачи создается именно в момент выполнения и раньше не засоряет систему. + определение задачи классом прочищает мозг и мотивирует делать правильную архитектуру :) ).


JobDetail jobDetail = new JobDetail("myJob",                      // job name
                     sched.DEFAULT_GROUP, // job group ('null' to use the default group)
                     DumbJob.class);           // the java class to execute

Еще интересная штука - система календарей. Т.е. мы можем запустить scheduler (главный экземпляр механизма так сказать) с разными календарями - календарь с вырезанными выходными днями (Sun/Sat), с вырезанными праздниками, и собственный календарь, с вырезанными чем-угодно даже до минут :) (Calendar - an interface to be implemented by objects that define spaces of time that should be included or excluded from a Trigger’s normal ‘firing’ schedule). Да, триггеры тоже интересная вещь - они задают время выполениния джоба (т.е. сам джоб и расписание его выполнения - совершенно разные штуки), причем триггеры весьма гибкие - есть даже подобие cron-a с сохранением синтаксиса (например “0 30 10-13 ? * WED,FRI”), но тут плюс - выполнение до секунд, в отличии от поминутного никсового крона. А, еще джобы интересно интерраптятся - прям по правилам threads - поток должен завершить работу сам, потому джоб (если может быть прерван по запросу) должен реализовать спец. интерфейс InterruptableJob - а там есть метод interrupt, который и нужно реализовать :) красиво

плюс к этому всему - сохраниение джобов (в памяти, в базе, где-настроишь), листенеры на события - запуск джоба, пред-запуск, завершение работы и т.д.

т.е. довольно таки гибкий и удобный специализированный инструмент. мог в деталях ошибиться :) tutorial тут


Solr server digging

Такс. Пара слов о Solr - enterprise level search server based on Lucene search mechanism. На данном эпате копания, 3 дня всего, выяснились такие плюсы/минусы:

”+”:

  • взаимодействие по http протоколу через POST/GET запросы с передачей XML/JSON данных. Удобно, просто, довольно быстро. Главное - позволяет обслуживать без проблем до 100 запросов в секунду (по тестам людей из mailing-list)
  • впечатляющая гибкость настройки и плагинообразность. файлы schema.xml и solrconfig.xml управляют такими вещами как
    1. настраиваемые поля (shema.xml):
      • возможность указания флагов indexed/stored вообще для поля или типа поля;
      • возможность сделать свой тип поля, подключив свой jar-ник как плагин;
      • возможность повесить на текстовое поле сколько угодно tokenizers && filters (lowercase, stop-words) factories, причем разделив анализаторы для index и search механизмов;
      • возможность задания жесткого набора полей по именам или указание динамических полей с именами-масками (!) с привязанными свойствами
    2. Использование и настройка 3х типов кэша (query, filters, docs) (solrconfig.xml)
    3. Возможность подключения своих request/response handler-ов, опять же через jar-ник плагин;
  • Десяток shell-скриптов out-of-the-box для бекапирования, создания snapshot-ов индекса и еще всякой фигни.
  • Использование lucene-based языка запросов. Удобно и понятно.

“-“:

  • Взаимодействие по http протоколу через POST/GET запросы с передачей XML/JSON данных. Необходимость написания парсеров для приема/отсылки информации. Формирование (имхо) кучи лишней информации из-за xml-формата передачи. Клиент в зачаточном состоянии, но быстро правится и быстро собирается вручную :) удобно, дешево и сердито
  • Использование lucene-based языка запросов. Для быстрого формирования запросов типа “искать это1 в полях поле1, поле2, поле3 И это2 в полях поле4, поле5” и т.д. нужно держать на клиенте (по отношению к серверу Solr) lucene классы - там все эти запросы делаются одной левой посредством BooleanQuery etc. и легким движение toString переводятся в корректный запрос к Solr. Т.е. если хочется задать поля для поиска - только в запросе, явно указав. Иначе никак. Имхо - недоработка.
  • Что за формат даты такой корявый? 1995-12-31T23:59:59Z
  • Некрасивый формат сообщений об ошибках сервера - вернее, вообще нету формата. Вроде бы в jira для solr есть такой таск, может поправят.

продолжаем копать. на очереди - запуск пары экземпляров solr на одном app. server и креш-нагрузки


thumbnails sux

хрень какая-то с этими image processing tools. кратко

  • awt toolkit - работает, причем проблем с форматами замечено небыло. довольно-таки удобный API. Из минусов - среднее качество и отвратительная скорость, ограниченный набор функций.
  • Java Advanced Imaging (JAI) - знатная вещь. я не знаю, что курили разработчики, но такое хитро вывернутое api нужно еще суметь придумать. Документации - жалкие крохи, отсутствие исходников усложняет интеграцию на порядок. Плюсы - скорость работы получше, куча функций. Минусы - не удалось добиться сколь-нибудь приемлимого качества. Как говорит один из комментаторов тут, нужны пляски с бубном, чтоб сделать good-looking thumbnails.
  • ImageJ - мегамощная вещь, но как гранатомет - стрелять ею по быстрому созданию превьюшек - как по воробьям. Очень научная и заточенная под глубокий анализ вещица, именно потому довольно сложная в обращении.
  • JMagick (ImageMagick java API) - сейчас в тестировании, как наиболее перспективная тулза. Из ожидаемого: хотя скорость на win32 машинке не впечатлила, есть надежда, что с увеличением версии (на вин. машинке currently стоит 5.5.7, учитывая, что последняя 6.2.6) и переносом на nix-овую машинку скорость увеличится. Огромный потенциал, который еще предстоит раскопать (mail-list уже мой друг): например, при ресайзинге gif-ов можно попасть впросак с fixed-colors системой, получить полосы при кропе не выставив page, etc.

как же меня заибал этот ресайзинг :) копаем дальше