Седьмой принцип, изложенный в Манифесте Redis , действительно совпадает с моими убеждениями и взглядами, и, вероятно, убеждениями всех инженеров (независимо от опыта). В этом мире не так много удовлетворения, как этот порыв волнения, который вы получаете, изобретая умный способ сделать что-то новое и / или лучшее. Эта радостная спешка, в свою очередь, может и часто приводит к развитию серьезной зависимости, как признают Дональд Кнут («преждевременная оптимизация — корень всего зла») и Рэндалл Манро (например, http://xkcd.com/1205/ , http://xkcd.com/1445/ и http://xkcd.com/1319/ ) давно.
Однако я обнаружил, что, используя здравый смысл и холодную логику каждый раз, когда у меня возникает желание начать оптимистический квест, я могу поддерживать свою зависимость удовлетворенной и контролируемой. Этот пост о трех таких побуждениях из прошлой недели.
оптимизация путем обмена знаниями
Как сторонник программного обеспечения с открытым исходным кодом и приличного пользователя сети в целом, я стремлюсь помогать другим во всем, что связано с Redis. Конечно, Redis можно использовать как хранилище тупых значений ключей, но это гораздо больше, чем просто требует разумного использования.
Так что именно во время моего ежедневного патрулирования интернетов я наткнулся на этот ТАК вопрос и начал на него отвечать. В своем ответе я сознательно пропустил подробности о том, как можно было бы сканировать большой список — ОП выглядел как новичок, и я не думал, что он это ухитрится, — но я все еще получал эту радость, потому что я помог кому-то сделать что-то лучше. Надеюсь, я также посоветовал ему изучить и понять команды, которые он использовал, прежде чем погрузиться в кодирование (и, возможно, я также сэкономил миру некоторые ресурсы процессора и сети на этом пути).
оптимизация путем разведки
Хотя этот ТАК вопрос сам по себе не является чем-то особенным — или блогом (хотя я только что сделал) — он заставил меня задуматься об этой недостающей команде LSCAN. Есть SCAN для сканирования пространства ключей , HSCAN для хэшей , SSCAN для наборов и ZSCAN для отсортированных наборов, но ничего для списков. Так как списки Redis’ (и списки быстрого ) являются регулярными списки (или связанные списки ziplists), случайным образом доступ к их элементам делается в O (N) сложности. Наличие эффективной команды LSCAN означает реализацию списков Redis по-разному, и поскольку бесплатных обедов не существует, нужно платить. Списки хороши (то есть O (1)) для всплывающих окон и толчков, и даны принципы # 3 ( «Фундаментальные структуры данных для фундаментального API»)) и № 5 ( «Мы против сложности» ) Манифеста, если вы используете Список и хотите сканировать его с одного конца на другой, то, вероятно, вам не следовало использовать Список в первую очередь.
Но, ради аргумента и просто для хихиканья, можете ли вы как-то сделать то, что сделал бы LSCAN (если бы он был там в первую очередь)? Интересный вопрос … ну, конечно, вы можете. Для начала, если вас не беспокоит согласованность из-за параллелизма, вы можете легко использовать цикл с RPOPLPUSH и вести подсчет для более эффективного обхода списка. Конечно, если параллелизм является проблемой, вам, вероятно, нужно найти способ дублировать список и безопасно пройти копию.
Так как же дублировать список с 10 миллионами элементов? Давайте сначала создадим такой список (все тесты выполняются в ВМ на ноутбуке, поэтому YMMV):
foo@bar:~$ redis-benchmark -r 10000000 -n 10000000 -P 1000 lpush L __rand_int__ ====== lpush L __rand_int__ ====== 10000000 requests completed in 1.30 seconds … 814929.50 requests per second foo@bar:~$ redis-cli llen L (integer) 10030000
Быстрое копирование необходимо будет выполнить на сервере, поэтому наивным способом копирования списка будет написание сценария Lua, который будет извлекать элементы один за другим и возвращать их обратно в оригинал и в место назначения. Я написал такой сценарий и проверил его в этом длинном списке …
-- @desc: copies a list with POP and PUSH -- @usage: redis-cli --eval copy_list_with_popnpush.lua <source> <dest> local s = KEYS[1] local d = KEYS[2] local l = redis.call("LLEN", s) local i = tonumber(l) while i > 0 do local v = redis.call("RPOPLPUSH", s, s) redis.call("LPUSH", d, v) i = i - 1 end return l
Это заняло целую вечность, и в журнале появилось ожидаемое сообщение «Lua медленный скрипт обнаружен» :
foo@bar:~$ time redis-cli --eval copy_list_with_popnpush.lua L T (integer) 10030000 real 0m23.579s user 0m0.000s sys 0m0.006s
23 секунды — это слишком долго, и хотя вы можете выполнять некоторые вызовы LPUSH, возможно, LRANGE — лучший подход:
-- @desc: copies a list with LRANGE -- @usage: redis-cli --eval copy_list_with_lrange.lua <source> <dest> local s = KEYS[1] local d = KEYS[2] local i = tonumber(redis.call("LLEN", s)) local j = 0 while j < i do local l = redis.call("LRANGE", s, j, j+99) redis.call("LPUSH", d, unpack(l)) j = j + 100 end
foo@bar:~$ redis-cli del T (integer) 1 foo@bar:~$ time redis-cli --eval copy_list_with_lrange.lua L T (integer) 10030000 real 0m11.148s user 0m0.000s sys 0m0.004s
11 секунд намного лучше, чем 23, но как насчет сортировки — это будет быстрее? Возможно, давайте проверим это:
foo@bar:~$ redis-cli del T (integer) 1 foo@bar:~$ time redis-cli sort L by nosort store T (integer) 10030000 real 0m2.390s user 0m0.000s sys 0m0.003s
Впечатляющее улучшение — всего 2,248 секунды и примерно в 10 раз быстрее, чем хлопать и толкать, но можем ли мы сделать еще лучше? Позвольте мне проверить, есть ли в Redis какие-либо команды COPY, DUPLICATE или CLONE… Нет, самое близкое, что есть, — MIGRATE , но он принимает только одно имя ключа. Но ждать! [лампочка] А как насчет DUMP и его дополнительного ВОССТАНОВЛЕНИЯ … это может быть?
-- @desc: The fastest, type-agnostic way to copy a Redis key -- @usage: redis-cli --eval copy_key.lua <source> <dest> , [NX] local s = KEYS[1] local d = KEYS[2] if redis.call("EXISTS", d) == 1 then if type(ARGV[1]) == "string" and ARGV[1]:upper() == "NX" then return nil else redis.call("DEL", d) end end redis.call("RESTORE", d, 0, redis.call("DUMP", s)) return "OK"
foo@bar:~$ redis-cli del T (integer) 1 foo@bar:~$ time redis-cli --eval copy_key.lua L T "OK" real 0m1.661s user 0m0.000s sys 0m0.007s
Это действительно хороший трюк — копирование с помощью дампирования и восстановления происходит намного быстрее, потому что вы обходите всю логику управления структурой данных (вроде как хороший старый POKE в BASIC, и самое далекое, что вы можете пройти, пока в Redis не введены указатели на значения: П). Это почти в 20 раз лучше, чем наивный подход, и теоретически он должен работать для любого типа данных — списков, хэшей, наборов и отсортированных наборов. Мне нужно запомнить этот небольшой взлом, когда мне нужно быстро скопировать значения.
оптимизация путем взлома
Но держись! Я могу даже подумать о чем-то более неприятном — как насчет того, чтобы Redis работал быстрее, чем Redis? Что если кто-то напишет фрагмент кода, который может выпустить Redis-совместимые сериализации значений, как это делает DUMP? С этой функциональностью вы можете действительно быстро загружать данные в Redis. Если не пытаться записывать данные непосредственно в пространство памяти процесса Redis, этот подход потенциально может превзойти любой обычный клиент на основе RESP, перенеся часть нагрузки Redis на самого клиента. Захватывающе!
Я знаю, по крайней мере, один такой код, так что теперь я действительно испытываю желание посмотреть на реализацию DUMP [вставить ссылку] и посмотреть, смогу ли я разорвать его и перенести, скажем, для Hashmap. Тогда я мог бы проверить загрузку JSON, предварительно обработав и введя их окончательную сериализацию напрямую вместо использования HMSET. Возможности действительно бесконечны, но столько же нужно усилий, чтобы это произошло. И даже это работает и относительно без ошибок (да, верно), я буду полагаться на внутренний частный API Redis, так что вероятность поломки огромна. И эти проблемы в стороне, это просто кажется неправильным. Возможно, лучшим решением будет встроить Redis в клиентское приложение и реализовать репликацию Master <-> Master для синхронизации между локальным и удаленным экземплярами …
Так что я останавливаюсь здесь, оставляя мой ход мыслей все еще на своем пути, и большая часть моих диких оптимизационных фантазий остается невыполненной. Я сказал вам, что могу контролировать свою склонность к радости, здравый смысл наконец-то потянул аварийный тормоз, и это доказательство. Но, тем не менее, на днях Сальваторе сказал мне (совсем по другому вопросу), что: «ломать вещи — это хорошо, квинтэссенция взлома :-)» , так что, возможно, я должен…
Вопросов? Обратная связь? Не стесняйтесь, чтобы написать или написать мне — я очень доступен ?